The org lead chose 3 by eye at noon in RunicNPC's 9d player session. The
zone step sends it when its stack is left blank, and the field's hint says
so. The wire is unchanged: the module always sends a stack, and the bridge's
own default for a dome without one stays 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
RunicNPC stage 9 (#34, D310) added a runicNpc object to the server status
schema in server/swagger/doc.js but did not regenerate swagger-fragment.json,
so check:swagger turned server-tests red on run 38 and edge has been red
since the merge. This is the output of `npm run swagger`, nothing else.
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
- rust.npc.place's progress() (MODULE_API 1.12.0, D304). It reports the
step's NPCs still standing, by its key, of how many it placed, with
the profile's label: "Bandit: 2 of 3 left". A boss step is its
health, "Walk Juggernaut: 59%", and a boss's adds are not counted.
The data comes from the map's live answer, so it costs the game no
extra ask. A reader who may not see the map's events layer gets no
line.
- The map (D302, D303):
- an NPC's tooltip is its name and its event's title, read from
core's public calendar, which lists only announced events;
- a boss is bigger and in its own colour, and an add says so;
- a boss outside any event is in the world layer;
- a boss's health is removed from what a viewer is sent.
- The world layer's visibility hint mentions those bosses.
coreApi stays ^1.11.0: an older core ignores the optional member, and
the action works without a progress line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The profile form's Loot section (D290-D301):
- what the corpse starts with (Rust's scientist loot, the kit, or
nothing);
- rows that always roll, each with its chance;
- a pool capped at its picks, with a "nothing" weight;
- which crate the table goes into, and a locked crate's hack time;
- how long the corpse stays.
The site checks the block as RunicNPC does, in its words. Whether an
item exists on a server is RunicNPC's to say on the push.
The Place NPCs step's dropLoot switch (D296), on by default and sent
only when off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The profile form's boss box (D273-D277): the bar's distance, where its
lines are said, the spawn and death lines, and phases at health
percentages, each with optional damage, aim, speed, ranges, a kit swap,
adds of another profile and a line. The passive role (D278, D279,
D288): press E answers with a chat line or a window, it moves as its
profile says (now including `stand`), and it starts unhurtable.
The site checks both as RunicNPC does, in its words; a phase's kit is
checked on each server like the profile's own, and its adds must name a
site profile that is not a boss.
Public triggers rust.boss.spawned, rust.boss.phase and rust.boss.killed
(D277) from the bridge's new frames. In the feed, a boss appearing is
public; its death names the killer, so it sits behind the presence
setting; its phases are staff's, as the game tells them only to players
near it (D286). engagement-triggers.json regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
RunicNPC stage 5 on the site (docs runicnpc/PLAN.md, D253-D272):
- profiles gain the guard role, faction, relations (its own exceptions),
alertRadius, turrets, hurtByPlayers, hurtsPlayers and kitUse, checked in
RunicNPC's order and words, and defaulting to "players only" (D255);
- the faction table: one site-wide table, one row per pair and both ways
(D254, D268), stored in rust_npc_factions, edited on the NPC profiles page
(PUT /admin/rust/npcs/factions), pushed to every server with its
profiles and hashed with them, and a standalone server's own pairs
adopted at its first push with the site's winning (D244, D251);
- the Place NPCs step takes escort (a Steam id or a {placeholder}), an ally
(a clan from the new rust.options.clans source, or the team of a player)
and tether (the zone this event made), for a RunicNPC profile only
(D269, D270, D272);
- a placement may be held inside a zone (tether, D272);
- the bridge's RunicNPC API floor is 4.
Tests: 488 server, 66 client. routes.manifest.json regenerated against the
pinned core (one route added).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Admin: Rust NPC profiles (the form RunicNPC reads, per server, shared or
fleet, each server's push state and refusals, replaced profiles with
Restore) and Rust NPC placements (the live map: click to place, pins for
every placement; edit, rename, respawn, remove). The live map takes a pick
handler and pins, unchanged for the public page.
Public: the leaderboard ranks by a profile's kills (D250), and opening a
row shows that player's kills by profile (D252); the killfeed names a
RunicNPC NPC by its own name. Player: your own kills by profile. Titles: a
rule on a profile's kills picks the profile. npcs.test.js covers the
profile checks, adoption, the push, the picker and verb, the triggers, kill
crediting, titles and placements (481 server tests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Admin: /admin/rust/npcs for profiles (create, change, delete, restore a
replaced one, push now) and each server's placements (list, add from a map
point, change, remove, rename, respawn). Public: the profiles a leaderboard
ranks by, one profile's ranking counted as the profile says (D247, D250), and
one player's kills by profile (D252). Player: your own kills by profile.
The Place NPCs step offers the site's profiles first, then Rust's own
(D243). rust.npc.died and rust.npc.health are triggers a phase can wait on.
A title rule can rank a profile's kills. Swagger fragment, engagement and
route manifests regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Schema for site NPC profiles (per server, shared or fleet), the per-server
push record, and kills by profile. The push adopts a server's own profiles
before its first push (D244), keeping one whose name a site profile already
has as replaced (D251). The tally's npcProfileKills are stored per profile
and credited to the site profile pushed under that name (D247).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Read from the Carbon rig regenerated at world 6000, seed 981448696, whose map
carries every built-in label: the three that were unverified (Oxum's Gas
Station, Mining Outpost, Ranch) are spelled as written. Adds Canyon B/C,
Lake A, Oasis A/C, Mountain, Train Tunnel Link and Abandoned Cabins, and a
test over that map's 51 real labels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
A switch per monument label, a fleet default and a per-server override, on
Admin -> Rust visibility's live map card. Substations, caves, train tunnels,
wells and the other minor labels start hidden; every other label is drawn,
so a monument a game update adds appears. GET /map leaves hidden labels out
of the answer, so the website and the app both lose them.
No schema change: rows are map.marker.<label key> in rust_settings and
rust_map_overrides. No wire change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Two conflicts, both additive: schema.sql and purge.sql keep both phases'
tables. Swagger fragment (61 paths) and routes.manifest (67 routes)
regenerated against the pinned core; 455 server and 58 client tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Admin → Rust zone presets: named sets of ZoneManager flags and settings for
one server, several, or every server (D195), ticked in groups read from each
server's own ZoneManager list in its last hello (D211). A flag not on a
covered server is refused on save with the server named; two presets of one
name may not share a server.
rust.zone.open gains options (one line, NoBuild, radiation=10), enterMessage,
leaveMessage, delivery, dome and domeStack. The options field's dropdown is
rust.options.zone_presets, whose row VALUE is the preset's line, so picking
one copies it into the step (D210) and no published event changes when a
preset does. The line and the dome are checked against the server's hello
on save and in a dry run; bad-option and dome-unavailable are permanent.
Schema: rust_zone_presets, rust_zone_preset_servers. Swagger fragment and
routes.manifest regenerated against the pinned core f0e7d2a.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
A read-only probe walked ItemManager.itemList on rust-oxide (2026-09-29). Revolvers are pistol_revolver, python and hc_revolver; bows add the legacy bow, the mini crossbow and the bowless crossbow and leave out the speargun; melee is every BaseMelee except the pies; blades add the obsidian, sunken and skinning knives and the chainsword; plants add wheat and the two wild berries.
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
PLAN_REDESIGNS section 1.
- Every sync reads the store (perm.inventory), reconciles it against the
site's record and its ledger, and pushes. A change made in the game is
settled by the server's policy (D161): auto-adopt (default), adopt, or
revoke. The first read of a server imports everything (D198).
- Groups belong to one server unless an admin shares them (D189), in new
id-keyed tables; the old ones are copied once at boot and left unread.
Holders may be a Steam account nobody linked (D188).
- An in-game change affects that server only (D190): a grant that reaches
further gains an exception, a shared group is split.
- Never judged: a permission the server does not register right now (an
unloaded plugin is not a revocation), and a pair an event lease holds.
- A new admin API (server view, grant/revoke with everywhere-or-here,
groups by id, share/split, members, drift answers) and a screen on
PermissionsManager's flow with a state on every toggle (D162, D163, U-1).
- The announcement voice names a group by id; old name settings still read.
Walked on both rigs against the walk core: import on an existing install,
auto-adopt of a grant and a revoke, a fleet grant's exception, Kits
unloaded without loss, a shared group split, adopt and revoke policies.
Server 420/420, client 58/58, swagger, imports and route manifest current.
Refs #21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Two findings of the step-2 player walk (2026-09-27, both rigs).
F6, option (b) of the org lead (D186): a code no recent issuer holds -
every made-up one - was still asked of every other enabled server, and
while any of them was down the redeem waited out its whole timeout
(12 s on both rigs). The second pass now skips the servers the board
poll last saw without a connected game; they count as offline without
the wait. Issuers are still asked whatever their state, so a good code
on a down server stays "unsure". Live on the walk core: 338 ms with five
servers down, 360 ms with a rig stopped as well.
F7 (D187): on Carbon a due audit sync went out the moment the sidecar
reconnected, 80 s before "Server startup complete". The worldReady hold
reads the stored hello, which is the OLD boot's until the poll reads the
new one. reasonToSync now also holds while the stored state says the
game is not connected (online 0), which the poll writes the moment the
server goes away. titleSync already held on it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The job cloned a MODULE_API 1.10.0 main and the loader refused the module
("needs core API ^1.11.0, this core is 1.10.0"), so it added no routes and
failed by construction. Pinned to #209's head (cc1f49a), where the job's own
steps pass: core alone 280 routes up to date, with this module 49 routes all
documented. Re-pin to #209's main merge sha once it lands.
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
The new GET /admin/rust/config/:serverId/writes/:writeId was documented in
swagger-fragment.json but not in the committed manifest, so the frozen-
manifest job and frozenManifest.test.js both failed. Regenerated against
core at the pinned ref (efa9db7), exactly as the job does: one route added,
nothing of core's moved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The plugin now answers a save once the files are written, with pending and
a writeId, and reports the reload later as a config.outcome event. The save
is recorded as reloading with a settle_by of two plugin ceilings plus slack
on the database's clock; ingest settles the row by (server, writeId), only
while it is still reloading, so a replay moves nothing and a late outcome
still lands. A row past settle_by reads as lost.
GET /admin/rust/config/:serverId/writes/:writeId serves the poll; the page
polls it every two seconds, holds the Save button while it waits, and says
whether a rolled-back plugin came back on its old file. config.outcome is
a staff kind: it carries the server's log tail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The router's own isIn check answered a mode it did not know with
express-validator's "Invalid value" before titles.validateSettings could
say which words are allowed. Found on the walk; the router now checks shape
only, as every other phase-17 route does.
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