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 bridge names a kill's weapon by the held entity's short prefab name
(ak47u.entity), and the feed printed it with the dots turned to spaces:
"with ak47u entity". Every event read now gives the frame a weaponName
(server/model/titles/weaponNames.js): the game's own English name for the
53 ranged weapons (guns, bows, crossbows, launchers, flamethrowers), read
off the Oxide rig's item list on Rust 26139947, and for anything else the
prefab name as words with its .entity/.weapon ending dropped.
The site prints weaponName and falls back to the same stripping for an
older server. The prefab name stays in the frame: the title lists count by
it. Added at read, so stored rows are renamed too. No protocol change; the
events route's Swagger description names the new field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Twenty-four strings an administrator reads carried an internal decision
number - "(D189)", "(D198)", "(D257, D270)" and the like: the permission
screen's group Servers tab and a chip tooltip, the event editor's step
field and budget descriptions, two option sources, a command reward's
skipped note, and five NPC profile validation errors. A D-number means
something to the plan, never to an operator. Each is removed, and the
sentence around it left as it was. The developer-facing swagger
descriptions and code comments keep theirs.
The servers page's chat-title line read "Last pushed: 1 player hold
one." It now says "holds" for one.
Two tests pinned the old wording and now assert the text instead.
Found while writing the runicgateway.com Rust docs (its PLAN.md §16,
phase 15).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
website#220 cut website edge over to main as 6890a74. The pin pointed at
9a1654f, the sha website#218 merged into edge as, which its own note said
moves to main when the cutover lands. The new tree adds the last fixes'
core half (website#219, no MODULE_API bump), whose route changes are only
core's own event routes, so routes.manifest.json does not change. The
frozen-manifest job, replayed locally at this ref, passes (89 routes, all
documented).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 4 of docs/modules/rust/PLAN_LAST_FIXES.md, the module's half of W1.
A placed zone now reports `until`, reckoned from the answer as now plus
holdMs, so it lands a round trip after the game's own deadline and never
before it. Core's reconcile then files a zone gone past that deadline as
expired, where a reconnect used to log resource.orphaned five seconds
before the game's world.expired arrived. Crates and NPCs send no
holdMs, so they carry no until.
No core pin change: `until` has been in the resource contract all along,
and a core without the reconcile half only shows it on the console.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 2 of PLAN_LAST_FIXES (docs#366).
- W7/W11: an "is it loaded" refusal is confirmed live before it is given.
New liveStatus.js asks the sidecar once (boot.refreshOne, as a poll tick
would), at most once per server per 5 s and waiting at most 3 s, then
reads the stored status again. Used by Economics (step and staff page),
Backpacks, Dangerous Treasures, Guarded Crate, RaidableBases, ZoneDomes,
TruePVE and RunicNPC.
- W3: a world call answered `already-out` (the bridge half comes in stage
3) succeeds as called 0, saying a Travelling Vendor is already out.
- W6: "A locked crate dropped by a Chinook is being hacked."
- W8: a RaidableBases profile's audit row names RaidableBases, not "@data".
- W10: the backpack sizes picker lists each size once, naming its servers.
- W13: a RaidableBases name both servers describe differently shows each
server's own description.
- W14: each RaidableBases note after the first starts its own sentence.
- W15: a staff deposit refused at Economics' limit answers 409.
- W16: a payment's failedNote explains only the reasons that happened.
- W17: the sync state counts player grants that did not land
(waitingToLand), and the Permissions page says "in sync · 1 waits for
their player to join" instead of plain "in sync".
- W18: Guarded Crate's kept note drops the "30 minutes" it could not know.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
W21: the permission push named its fields one by one and left out
`notices`, so no kit reward, lend or give ever told its player
anything (D333). The body is now built by `pushBody`, which carries
every field the payload has; a test fails if a payload field is
dropped again.
W4: a downed patrol helicopter, Chinook or Bradley is stored with the
credit rule in force when it arrived, and the feed reads that stamp.
Before, the line re-credited past rows with the server's current rule
while the Rotorbane/Tankbreaker counts kept the old one. Rows stored
before this change keep today's reading.
PLAN_LAST_FIXES.md stage 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
#218 (Steam sign-in, MODULE_API 1.17.0) merged into website edge as
9a1654f. The pin pointed at the PR's branch head, 5e398d8, while it was
open. The two trees differ by one core test file, so routes.manifest.json
does not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Registers core's MODULE_API 1.17.0 identity hooks (coreApi ^1.17.0):
onIdentityLinking refuses a Steam id that is another website account's Rust
link (D396); onIdentityLinked writes the Rust link with the PRIMARY KEY as the
race guard, reconciles Teams and raises the linked notice, as /link does.
Unlinking Steam leaves the Rust link (D393); the Rust account page says so.
ci/core-ref.json still pins the 1.15.0 core and must move to the website
branch head carrying 1.17.0 before CI can pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
PLAN_REDESIGNS §11.4 (D202-D208, D386-D391), protocol 14. Spec: docs
rust-link/PROTOCOL.md §20.12.
- raid.started, raid.completed, raid.purchased and raid.locked public,
raid.ended staff's; raidableBases.js reads each row per viewer (raiders'
and a buyer's names only for the presence audience, D376/D390; Steam ids,
position and a run's key only for staff). Feed lines in the World filter.
- The step rust.raidbase.spawn, "Spawn a raid base" (ledgered, budget
rust.raids): difficulty, profile and base from option sources listing each
server's own names (D386), a spot as a world call's location with a height
adjustment or an exact height, and PvP/NPCs/lock as on/off/blank. A base
the raids helper made is a world resource, so eventWorld's revert and
reconcile serve it and world.expired marks it expired; one it did not make
records nothing and the run log says it despawns on its own timer. Refused
at verify for a server that said it cannot; a dry run with a spot returns
the spot check as detail.warnings (MODULE_API 1.16.0, D391). rb-not-ready
retries in ten minutes.
- Warlord: raids_completed, title category raidscompleted, the leaderboard's
Raid bases column (sort=raidsCompleted).
- The map's raids layer (D114), public by default with its own audience; the
run's key is dropped before any viewer gets it.
- Server settings' line for RaidableBases; its profiles in Mod configuration
under a virtual path (D205).
- Swagger fragment, bundle and README.
Tests: server 684/684 (raidableBases.test.js new, every name in it invented);
client 99/99, build and externals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Two event steps in server/eventCommands.js, on one new budget,
`rust.commands` (D382-D385):
- rust.command.run: up to 10 typed commands once on one server, with
optional teardown commands. irreversible / ledger: the ledger row keeps
the teardown as it was, and core reverts it when the run ends or is
cancelled. Priced one unit per command, teardown included.
- rust.command.reward: the kit reward's recipient picker (tally modes and
linked), by Steam id, so no website account is needed. The bridge fills
{steamid} and {name}; offline recipients are skipped and named (D383).
irreversible / none. Priced commands x people, the bound for a mode that
is not a count.
Both take core's `multiple` string field as a free text box (no
allowlist, D384) and a required "I am sure" toggle. {steamid}/{name} in a
run step are refused. unknown-command, malformed, events-disabled,
interrupted are permanent; the error says how many ran before it.
D385: the run also hands its teardown to the bridge, which keeps it with
the key. A revert with only the key (the step failed, or its answer was
lost, so core recorded nothing) asks every enabled server to run what it
kept; a server the step never reached runs nothing.
sidecarClient.commandsRun -> POST /commands/run; ci/bundle.json lists the
new file; entry.test.js pins the new ids; 21 tests in
test/commandSteps.test.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 7 of the §14.3 integration plan (D374): Server settings' plugin
line says whether ZLevels is loaded and that an event may lend its XP
boosts as permissions. The integrations schema gains zLevels; the
swagger fragment is regenerated. No leaderboard change, nothing stored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 6 of the §14.3 integration plan, on protocol 14. Spec: docs
rust-link/PROTOCOL.md §20.9.
- gc.started and gc.ended public, gc.failed staff's; guardedCrate.js reads
each row per viewer (the clearer's name for the presence audience, Steam id,
position and key for staff, `won` for everyone).
- Triggers rust.gc.started and rust.gc.ended (a crate a player cleared only).
- The step rust.gc.place: an event from rust.options.gc_events and a world
call's location; refused at verify for Guarded Crate absent, NpcSpawn missing
(D364), console unavailable, or an unknown event. Kept (D373); the run log's
success line says so (D380). Budget rust.gc.crates.
- Warden (D375): guarded_won, title category guardedwon, Crates cleared column.
- Map marker, server settings line, swagger, engagement manifest, bundle, README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 5 of the §14.3 integration plan, on protocol 14 (docs
rust-link/PROTOCOL.md §20.8).
- dt.started and dt.won are public kinds, dt.ended a staff kind. The new
dangerousTreasures.js reads each row per viewer: grid and invaderCount for
everyone, the winner's name for the presence audience (D376), the position,
the step's key, the winner's Steam id and the invaders for staff.
- Feed lines in the World events filter: "A Dangerous Treasures event has
started · K12", "Alice won the treasure · K12 · 3 players fought for it".
- Triggers rust.dt.started (with byEvent) and rust.dt.won, public, cooldown
subject the server, names only.
- eventDangerousTreasures.js: rust.dt.start, "Start a Dangerous Treasures
event" (risk change, reversible none, budget rust.dt.events), server and
difficulty. Refused at verify where the server said DT is not loaded or
console.plugins is false (Rust-Plugins#34). dt-not-ready answers "Dangerous
Treasures is not ready yet" with retryAfterMs 600000 (D365, D371);
dt-no-spot retries on core's delay; DT's own refusals are permanent.
- Treasure Hunter (D375): treasuresWon -> treasures_won, the title category
treasureswon, and a sortable Treasures column shown where the server
reports DT or a row counts one.
- The map draws a running chest on the world layer; the server settings'
plugin line names DT and whether a step can start one, from the
integrations read's new `console`.
- coreApi ^1.15.0, ci/core-ref.json pinned to website main at #215's merge.
ci/bundle.json ships the two new files; swagger fragment and engagement
manifest regenerated; README lists DT.
Server 627/627 (16 new in test/dangerousTreasures.test.js) and every check;
client 92/92, build and externals. Booted against the pinned core: 45
triggers, 13 actions, 11 budgets, 21 option sources; 89 routes unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 4 of the §14.3 integration plan, on protocol 14 (docs
rust-link/PROTOCOL.md §20.7).
- pvp.zone.created is a public kind, pvp.zone.deleted a staff kind. Each row
is read per viewer by the new dynamicPvp.js: grid for everyone, the event's
name only for Dynamic PVP's own vanilla events, position and zone id for
staff only.
- The feed's world filter carries the opening: "A PvP zone opened around the
downed patrol helicopter · G7 · for 10 min". It names nobody.
- The map gains a fifth layer, `pvp` (PvP zones), public by default with its
own audience in Admin → Rust visibility, drawn as a red ring with its
event's name.
- Server settings' plugin line names Dynamic PVP; the swagger schemas and
route descriptions carry the layer and the integration (fragment
regenerated); ci/bundle.json ships dynamicPvp.js; README lists it.
Tests: server 611/611 (dynamicPvp.test.js new, catalogue and map extended)
and every check; client 89/89 (feed and map tooltip), build and externals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 3 of the §14.3 integration plan (D362, D368), on protocol 14 (docs
rust-link/PROTOCOL.md §20.6).
- rust.zone.open gains noPvpProtection (boolean) and pvpRuleset (string),
sent as world.zone's truePve { exclude: true } or { ruleset }.
- Both set, a name over 64 characters, or a server whose hello says TruePVE
is not loaded is refused for good, at verify too. A server that has not
said is left to the plugin, as a dome is.
- truepve-missing and truepve-ruleset are permanent failures, with the
plugin's words.
- The zone servers list carries each server's truePve from hello; Server
settings' plugin line says whether TruePVE is loaded; the integrations
schema documents it (swagger fragment regenerated).
- README lists TruePVE among the optional plugins.
Tests: five new in zones.test.js; server 603/603 and all checks, client
84/84 and the build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 3 of the §14.3 integration plan (D362, D368): world.zone's optional
truePve ({exclude: true} or {ruleset}), all or nothing, unmapped on every
path that erases one of our zones, with truepve.json for erases while
TruePVE is away. Docs and PRs still to come.
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
website#213 (MODULE_API 1.14.0, the `multiple` param) merged as a981b2b.
Its tree is identical to the branch head 743f1bc this was pinned to, so
routes.manifest.json is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
D342-D346 (docs#342). Two steps over the permission mirror's own tables, so
no plugin, sidecar or protocol change:
- rust.perm.lend, "Permissions until the event ends": a kit reward's lease,
one rust_perm_run_grants row per recipient per permission, the message on
one row each; change/ledger with rust.kit.entitle's revert and reconcile;
budget rust.permissions.
- rust.perm.give, "Permissions for good": a standing rust_perm_grants row per
website user per permission, scoped to the step's server, source `event`,
note "event run <run>, step <step>"; a grant already there is left alone,
and a retry's own grants are counted as given; the message rides a
notice-only run-grant row; irreversible/none; budget rust.permissions.kept.
- Both: several permissions through core's `multiple` param (MODULE_API
1.14.0, at most 10); a required "I am sure" toggle refused unticked in the
dry run and at run time, naming the permissions (D343); a permission the
server's catalogue lacks refused by name; a blank message tells nobody.
- rust.options.permissions: the enabled servers' catalogues, grouped by the
registering plugin (by prefix when none), sorted plugin then permission
without case, a row noted "Only on X" when not on every server.
- Permission manager: an event-given grant shows "given by event run N,
step M"; staff turn it off like any grant.
- coreApi ^1.14.0; ci/core-ref.json pinned to website#213's head until it
merges; engagement-triggers.json regenerated; routes.manifest.json
unchanged (no new routes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The site half of §11.3.2, on protocol 14:
- `rust.backpack.size` ("Bigger backpack"): a kit reward with a different
permission (D337) — one rust_perm_run_grants row per website user with
backpacks.size.N, no kit, no credit; the perm mirror pushes it and the run's
revert deletes it. risk change, reversible ledger, a new `rust.backpacks`
budget, rust.kit.entitle's recipient modes including `linked`. Refused with
"Backpacks is not installed on this server" in the dry run too (D199), and a
size the server does not offer is refused by name. Its message is never
silent: empty is the take-back warning (D338).
- `rust.options.backpacksizes`: each server's sizes above its smallest, from
the last hello, grouped by server.
- GET /player/rust/backpacks (own links only, 20 reads a minute) and
GET /admin/users/:id/rust/backpacks (that user's links only). Live, never
stored; a stranger's id is a 404 and never reaches the game.
- Client: a shared Backpack panel (pick server and account, open, read once)
on the account page and the admin user page; the grid draws pages of
pageSize, each slot named by the game (D340, no icons), blueprints by what
they make (D341), overflow marked. Backpacks joins the server's mod line.
Server 548/548 (13 new in test/backpacks.test.js), client 79/79 (5 new),
routes.manifest regenerated against the pinned core (89 routes), swagger
fragment regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The site half of §11.2.2, on protocol 14:
- `rust.coins.pay` ("Pay coins"): irreversible, reversible none (D335). The
recipient modes of rust.kit.entitle including `linked`, one payment per
website user (D103's rule), an optional message with {amount}, a new
`rust.coins` budget. Refused with "Economics is not installed on this
server" in the dry run too (D199). Core's key goes to the plugin, so a
retried step answers `repeat` and pays nobody twice.
- `rust_player_wipe_stats.balance`: the tally's balance, written as a value.
A nameless balance frame (an offline player) moves nobody's last-seen.
`economics.wiped` clears the wipe's balances, except at a new save.
- The richest: a Coins leaderboard column on one wipe of a server that
reports Economics, `sort=balance`, and a `richest` title category
(Magnate); both rank only above the starting balance (D336).
- GET /player/rust/economics (live, falling back to the last known), and
GET/POST /admin/users/:id/rust/economics for staff — scoped to the user's
own links, keyed per submission, an activity row with before and after.
- Client: the account page's Coins, the admin user page's Coins panel with
its form, the leaderboard column, and Economics in the server's mod line
(saying when its config could not be read).
Server 535/535 (14 new in test/economics.test.js), client 74/74,
routes.manifest regenerated against the pinned core (87 routes), swagger
fragment regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
website#212 (MODULE_API 1.13.0) merged as ca7ec53, whose tree is the
same as the branch head e58857c this repo was pinned to while it was
open. Only the pin moves; routes.manifest.json is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
PLAN_REDESIGNS §2, the Rust half (D164, D192, D332, D333):
- rust.template.kit_weekend (core registerEventTemplates, MODULE_API 1.13.0):
inputs kit/minutes/message/delivery; one phase advance after the weekend,
core.lease rust.group.permission default/<kit permission> + rust.kit.entitle
linked. Steps only where the kit can use them; validated by the real core.
- rust.kit.entitle: recipients 'linked' (every linked player who has played
on the server, no tally, refused past the bound), optional message ({kit})
and delivery, stored per row (rust_perm_run_grants.notice*).
- perm.sync notices: the untold set to the picked account, keyed
run:step:user, in the digest; the report's noticesTold marks them told.
- rust.options.grouppermissions notes a kit's permission rows via kits.uses
(cached a minute per server; a failure is no note).
- duration: 'minutes' on rust.zone.open and rust.participation.open; the
group-permission lease's value example is true.
- protocol 13 -> 14, coreApi ^1.13.0; ci/core-ref.json pinned to website#212's
head while it is open; engagement-triggers.json regenerated (coreApi line).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY