The site half of stage 1, on protocol 14:
- server/worldEvents.js: the 24 types, D372's four credit rules, the grid
from the map's geometry (none off the map), and the per-viewer reading.
- The feed serves `world.event` (a public kind): an unknown type is staff's;
the event and its grid are public; names only for the presence audience
(D376); Steam ids and positions only for staff (D353). A downed one carries
`credited`/`creditedCount` by the server's rule. Client lines for all 24
types and a World events filter.
- rust_servers.credit_rule (blow, last, most, all; default blow), set on the
server's settings. Ingest counts heli_kills (patrol helicopters and
Chinooks, D379) and apc_kills from `world.event` by that rule, so the line
and Rotorbane / Tankbreaker agree. A protocol-13 tally's heliKills/apcKills
still add as before.
- The tally's cratesHacked / dropsLooted → crates_hacked / drops_looted;
title categories Safecracker and Scavenger (D356); leaderboard columns
Crates hacked and Drops looted, sortable (D377).
- 24 engagement triggers rust.world.<event> (D355): ceiling everyone,
cooldown subject the server and event (crates add where), names only.
- server/eventWorldCall.js: rust.world.call, "Call a world event"
(risk change, reversible none, budget rust.world.calls). A position for an
airdrop or a patrol helicopter only; the Chinook takes none (D378).
Server 585/585 (22 new in test/worldEvents.test.js), client 84/84, every
check; booted against the pinned core (43 triggers, 12 actions, 10 budgets,
20 option sources), routes.manifest unchanged at 89. Engagement manifest and
swagger fragment regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
A Place NPCs step on a server without RunicNPC, or with one older than the
bridge needs, is refused before it is sent: "<server> needs RunicNPC to place
NPCs, Rust's own scientists included: <why>". Rust's own scientists were the
fallback until now (D243); they need RunicNPC too. Crates are unaffected.
- npcs.model: API_NEEDED rises from 4 to 6, the bridge's RunicNpcApiNeeded, so a
server the site calls ready is one the bridge will not refuse as runicnpc-old.
The profile push follows the same floor.
- eventWorld: profileMissing becomes npcMissing, asked for every NPC step.
- Admin → Rust → Servers: each server carries `runicNpc` ({ready, absence,
version, api}) and a server without one that will do reads "Incomplete" with
the reason. Swagger documents the field.
The event picker is unchanged in this commit (an open question on the PR).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
- Schema: fourteen title columns on rust_player_wipe_stats (the two
best_* distances move by GREATEST, the rest are sums), rust_weapon_kills
(kills by weapon prefab name) and rust_title_categories (the admin's
title per category). purge.sql drops the two tables.
- Ingest: player.tally's new fields, in one statement per frame, none
for an older plugin's frame.
- Titles: 23 categories plus playtime, each naming where it is read
from (a sum, a MAX, npc_kills - animal_kills in signed arithmetic, or
a named list). The bow/melee/blade/revolver/wood/ore/plants lists are
one file, applied when a title is read. "NPC kills" ranks human NPCs
only (D209).
- A rule's text may be empty, meaning the category's title: the rule's
own, then the admin's, then the default. Typed-only-markup is still
refused. A rename forgets every server's cached answer.
- Admin API: GET /admin/rust/title-categories and
PUT /admin/rust/title-categories/:stat (empty text resets); the server
list returns them too. Swagger fragment and routes.manifest.json
regenerated (63 routes, against the pinned core).
- Screen: every category in the rule form, the category's title as a
placeholder, and a Category titles section with Save and Reset.
The weapon lists are unverified until the rig probe runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The module's half of PLAN_FIXES §6 step 2 (decisions D181-D185, docs#288).
- F13/F14 (D170, D183): `world.expired`, recognisable from protocol 13 by its
`what`, is handed to core as the resource the zone step ledgered
(`world`, `<serverId>:<id>`) through ctx.events.expired, which records it
`expired`. coreApi moves to ^1.11.0 (website#209).
- F8 (D184): `plugin.loaded` / `plugin.unloaded` mark the permission sync dirty
when the plugin added or removed permissions, so an unresolved grant lands on
the next tick instead of the fifteen-minute audit.
- Catalogue: plugin.loaded/unloaded, world.expired and lease.expired are staff
kinds. The last two were never classified (default deny kept them off public
pages); the test now covers every event kind through protocol 13.
- F7: permission and title pushes hold while the stored hello says
`worldReady: false` (a human's "sync now" does not); a failed or refused
permission sync now logs at warn.
- F2 (D185): the killfeed names an NPC attacker — a family (Scientist, Bandit
guard, Bradley APC…) or the prefab without its variant digits (wolf2 → Wolf).
- F5/F6: a link code is asked of the servers that minted one in the last six
minutes first, then of the rest, each group in parallel; "unsure" only when
one of the minting servers is unreachable.
- D182: the admin server list carries the ZoneManager helper's state from the
hello, and the servers page says what a missing or failed helper costs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
PLAN.md §33, D134-D143. Protocol 12.
- Chat titles (D135-D137): per-server rules (stat, top N, text, colour)
that rank the current wipe, and a mode (first | all | up to N). Worked
out once in model/titles and read three ways: pushed whole to the game by
a new titleSync loop (on change, restart or wipe), and on every
leaderboard row as `titles`. Admin: PUT /servers/:id/titles.
- Group styles (D138, D139): a site group may carry all twelve BetterChat
fields (rust_perm_group_chat). They ride perm.sync with `expect` from the
pushed ledger, which gains a value column; a field changed in game is a
`chat-field` drift row with the game's value, adopted into the style or
put back. A withdrawn style is one `chat-group` retirement, never for
`default`, cleared from the ledger only once BetterChat removed it.
- The voice (D140): one fleet setting naming a styled group; news and
rust.announce chat lines carry its format and the plugin says them with
no sender. Admin: GET/PUT /voice.
- Popups (D141, D142): rust.announce gains `delivery` (still version 1,
from rust.options.delivery); each server gains news_delivery beside the
news switch; `popup-unavailable` is not retried.
- GET /servers/:id/integrations reads, live, which optional mods a server
has loaded. README lists BetterChat and PopupNotifications as optional.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The module had no page for its own server rows: they were written only
through PUT /admin/rust/servers/:id, and the README described an
"Admin → Rust" server form that did not exist. D133 (org lead) builds it:
add, edit, test and delete a server, with the D130 wipe schedule in the
same form. The token stays write-only and an edit sends the stored
protocol back rather than re-stamping the row.
The next wipe shows on the server list and in the server page's header,
in the reader's own clock, marked "rescheduled" for a one-off date.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY