website/DEMO_MODE.md §3.2, §3.4, §3.5, §7.3 and §7.5 (step 2 of §10).
Nothing changes off the demo; everything below sits behind ctx.demoMode.
- demoLocks in module.json: PUT and DELETE /admin/rust/servers/:id.
- The console-command deny list (DM11): quit, restart, oxide.unload,
oxide.reload, rcon.*, fps.limit and server.tickrate, by first word,
with global. stripped and o. read as oxide. Refused at save through
core's demoRefuses (MODULE_API 1.19.0, DM18) and again at run, with
retry: false, before the rig is asked. Teardown commands too.
- The config editor (DM14): the bridge's files are left out of the
list and refused for reading, writing and in the write history, by
name (canonicalised) and by the catalogue's self. Other plugins'
credential fields are redacted in the text and the form and made
read-only; raw-JSON saves are refused; a file that does not parse is
withheld. The admin server list leaves out the sidecar URL.
- onDemoSeed (DM15): upserts the rig's server row with an encrypted
token and demo-player's stand-in Steam link.
- The release's install manifest carries protocol, read from
PROTOCOL_VERSION, for the wipe's bundle match (DM12).
coreApi is ^1.19.0; ci/core-ref.json pins website#224's head.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
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