F15 waits for protocol 13 (D176); F9 and F10 open that bump on edge in all three repos (D177); a reload fails on the log's evidence under a 30 s ceiling with a loaded check before any restore (D178); the configuration page polls the write until it settles (D179). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
33 KiB
module-rust — plan fixes
Status: plan, approved 2026-09-26; its open questions were answered the same day (D169–D175), and step 1's
shape was settled as work began (D176–D179). Everything in it — fixes and redesigns alike — lands before Module-Rust's cutover
(phase 19, PLAN.md §34, D145; D169). Everything here comes from the first walk of
PLAYER_WALK.md with a real player in the game — the Oxide pass, walked by
the org lead on the rust-oxide rig with every frame checked on the console, the sidecar and the site's
database. The Carbon pass has not been walked yet and may add to this document.
This is a companion to PLAN.md, not a replacement: its decisions continue PLAN.md's numbering
(D159 onward), and where the two disagree this one is later and wins. It has three kinds of content, and
all of it lands before the cutover (D169, §6):
- Fixes (§2) — the plugin, the sidecar or the site does something other than what the plan and the protocol say. Fifteen, F1–F15.
- Interface and walk-doc corrections (§3) — what works but misleads, and where the walk doc itself is wrong.
- Changes the org lead decided during the walk (§4) — new behaviour, each with its decision of record.
0. What the walk was
| Date | 2026-09-26, 16:40–21:05 UTC |
| Rig | Pterodactyl server 17 rust-oxide, Oxide, world 3000 / seed 1234 |
| Plugin | Rust-Plugins v0.1.1, the released file, byte-identical |
| Sidecar | the phase-17 build, protocol 12 (pre-D155) |
| Site | the phase-18 walk core on :3270, Module-Rust at phase 17 |
| Extra plugins | Kits 4.4.9, ZoneManager 3.1.14, BetterChat 5.2.15, PopupNotifications 0.2.1, and — added during the walk — PermissionsManager 2.1.2 and ZoneDomes 2.0.2 |
Scorecard. Player walk: 1–7, 9, 10 pass (4 and 9 with findings), 8 needs a second player. Identity: 1–5 pass, 6 behaves as written but that behaviour is F5. Permissions: all seven pass, three with findings. Configuration: 1, 3, 4, 6, 7 pass; 2 failed as written (F9) and passed on a retry; 5 not applicable; 8 proven at the site's guard only. Events: 1–6 pass in the game, 6's frame is F13; 7 needs several players. Rewards: 1, 3, 4, 6 pass (6's text is F15); 2 is covered by 3; 5 deferred. Chat titles: 1 authored, not earned; 2–5 not walked. Map and phone walks: not walked (two accounts, the app).
1. Decisions of record
Taken by the org lead during and straight after the walk.
| # | Decision |
|---|---|
| D159 | "Gathered" is everything a player harvests — swings, the bonus on the final hit, and what is picked up off the ground. Rejected: swings only (the leaderboard would disagree with the inventory). F1. |
| D160 | The site is the source of truth for all permissions and groups — those that were on the server before the site, those added in the game, and those added on the site. Everything flows back to the site. This reverses D31, under which a permission the site had not authored was "somebody else's business" and never enumerated. |
| D161 | Each server has a policy for a change made in the game: revoke, adopt, or auto-adopt. The default is auto-adopt. |
| D162 | The permission screen follows uMod PermissionsManager's flow (the reference is PermissionsManager 2.1.2 on the rig, screenshots in the workspace's perms-screenshots/): all players ⇄ all groups → a player or group → its plugins → that plugin's permissions with Granted / Revoked, Grant all / Revoke all → a player's groups, a group's players, Remove all. |
| D163 | Every subject on that screen shows the linked website account, if any, and the in-game name — or the Steam id when the server has no cached name. |
| D164 | A "kit open for everyone" event is a lease paired with a use credit. core.lease on the kit's permission plus rust.kit.entitle (one use per recipient, D103, withdrawn on revert), offered as a template — and the lease form warns when the kit's own limits mean players have already used it up. Rejected: a new lease that rewrites Kits' usage data, which D103 deliberately avoided. |
| D165 | The live map's monument markers get a per-type toggle, and the default shows the major monuments only. Substations (18 on the rig's map), caves and similar small places are off unless staff turn them on. |
| D166 | The zone step offers ZoneManager's own flags and settings, read from the installed ZoneManager, not a hard-coded list. |
| D167 | ZoneDomes is an optional dependency. The zone step gains a "show a dome" option; the bridge adds the dome and removes it with the zone. |
| D168 | When a plugin we depend on does not expose what we need, we may write a small helper plugin for it. It ships from Rust-Plugins beside the bridge, is optional, and is detected at hello like the other integrations. The bridge keeps calling only public APIs (R2); the helper is where a gap in someone else's plugin gets bridged. |
| D169 | Every fix and every redesign in this document lands before Module-Rust's cutover (phase 19). Rejected: cutting over after the fixes and following with the redesigns. |
| D170 | An expired zone is marked expired on the site. Once F13 makes world.expired recognisable, ingest marks the run's resource row expired. This amends D96's "the website maps it to nothing". F14. |
| D171 | Steam sign-in belongs to the website's own plan and is part of the base website, exposed to modules however they need it. It leaves this document (§4.8). |
| D172 | Chat titles use all twenty-three conditions the org lead listed, under new names (§4.6). Rejected: a first subset. |
| D173 | "Quests completed" counts Rust's own missions — the ones the game's NPCs already hand out on the map. No quest plugin. How the bridge detects a completed mission is settled by a spike on the rig (§4.6), because uMod's catalogue names no mission hook and CARBON.md lists none among Carbon's own. |
| D174 | The title rules §4.6 recommended are adopted: the two kill-distance titles are best columns (the longest single kill), not sums; a weapon-class kill counts any kill the player is credited with (players, NPCs, animals), from weapon lists kept in one place; the APC and the helicopter credit the killing blow; healing counts only other players. |
| D175 | The titles in §4.6 are the module's defaults, not fixed names. An admin can write their own title for any category, and it supersedes the default everywhere that category is used; clearing it brings the default back. A rule may still carry its own text, which wins over both. |
| D176 | F15 is held for protocol 13 and releases with it, not ahead of it as a patch. Rejected: a Rust-Plugins v0.1.2 on protocol 12. |
| D177 | F9 and F10 open protocol 13. They are built first, on edge in Rust-Plugins, Rust-Link and Module-Rust; the rest of §6 step 2 joins the same bump, and all of it releases together. Rejected: F9 alone as protocol 13 with the rest as 14; and a wider window now as a stop-gap. |
| D178 | A reload fails on evidence, not on a clock. While a configuration reload is pending the plugin reads the framework's new log lines each second and rolls back the moment they show the target plugin failing to compile or initialise. The ceiling is 30 s; when it passes, the plugin checks whether the target is loaded (a hook it missed) before restoring anything, and reports what actually loaded (F10). A framework hook for a failed load, if the rig shows one, replaces the log read. Rejected: the clock alone with a longer ceiling, which leaves a broken plugin down for the whole ceiling. |
| D179 | The configuration page polls the write. The save is recorded as reloading; ingest settles that row from the outcome frame, and the page polls it every couple of seconds until it does. Rejected: pushing the outcome over the admin event stream. |
2. Fixes
Grouped by severity. Each says what the walk saw, why, and the fix. Line numbers are against Rust-Plugins v0.1.1 and Module-Rust at phase 17.
2.1 High
F15 — every non-ASCII character the site sends to the game is corrupted. (Rust-Plugins)
An event announcement's em dash arrived as â in chat and in the console. RunicGateway.cs:787 reads the
socket byte by byte and appends (char)buffer[i] — UTF-8 decoded as Latin-1. The write side (line 756) is
correct, so only site→game text breaks: announcements, news in chat and popups, chat titles and styles,
kit and zone names — and configuration saves: a file saved from the website that holds any accented
letter, dash or emoji is written back to disk mangled. Configuration step 3 passed only because the file
was ASCII.
Fix: collect bytes per line and decode the finished line with Encoding.UTF8; a multi-byte sequence split
across two reads must survive. Test: é, — and an emoji through an announcement and a config save, on both
frameworks. Add the same line to the walk.
F9 — a valid configuration edit is rolled back because the reload window is shorter than a cold compile.
(Rust-Plugins, Module-Rust)
Kits' chat command changed from the site → "The plugin did not come back, so the old file was put back
automatically". Oxide's log: the compiler had idled out (it stops after 60 s, oxide.config.json), restarted,
compiled Kits in 2.85 s, and loaded it — just after ConfigReloadWindowSeconds = 4f (line 4087). The retry,
needing no compile, passed. The window races work whose length the plugin does not control: host speed,
plugin size, Carbon's own compiler. Operators must not have to tune Oxide to make the site work.
Fix: answer the save at once ("saved, reloading…") and deliver the outcome as a frame when it arrives, under a
long ceiling (30 s or more). Roll back on a real failure — Oxide logs "Failed to initialize plugin" the moment
it happens (configuration step 4 showed it) — not on a clock. Protocol-visible (§5). Settled by D177–D179: the
plugin reads the log for that failure, the ceiling is 30 s with a loaded check before any restore, and the site
records the write as reloading and polls it.
F10 — the rollback can say "did not come back" about a plugin that did, and can race it. (Rust-Plugins) After F9's timeout the plugin restored the file and fired a second reload, but the log shows one compile, so which file the running plugin read depends on timing. Fix: never restore while a compile of that plugin is in flight, and report what actually loaded. F9's fix removes the cause.
F12 — a player already inside a zone when it is created or restored is not "in" it. (Rust-Plugins)
After a restart the bridge re-created the event zone correctly (same id, same place, row still confirmed);
the org lead woke up 18 m inside it and ZoneManager's IsPlayerInZone said nobody. The same happened when a
zone opened around a player. ZoneManager counts a player only on trigger enter, and the participation tally
asks it every five seconds (§29.7) — so after any restart mid-event, everybody already in the arena scores
nothing until they walk out and back in.
Fix: after creating or re-creating a zone, have ZoneManager re-evaluate who is inside; if its API has no way
to, that is D168's case (a helper), or the tally measures distance itself for zones the bridge made. Walk it on
Carbon too.
F13 — the world.expired frame leaves with its kind overwritten. (Rust-Plugins, protocol)
Three zones expired in the game exactly on time; all three frames were filed as kind: "zone". Line ~7310
builds Frame("world.expired", "event") and then sets frame["kind"] = e.Kind. PROTOCOL.md
§15's example has no field for the entity's kind, so the collision never showed in the spec. The walk's own
check (/events?kind=world.expired) cannot pass. Present since protocol 9.
Fix: send the entity kind as what, the name the site's resource rows already use; amend §15. Protocol bump.
2.2 Medium
F1 — gathered counts swings only. (Rust-Plugins) 825 wood in the inventory, 587 tallied; 20 cloth from
hemp, none. The tally's shape was right (60-second deltas that sum). Fix (D159): also count
OnDispenserBonus and OnCollectiblePickup, and OnGrowableGathered for farmed plants; add all three to
ExpectedHooks so rg.hooks shows them. PROTOCOL.md §8.6 names the hooks that feed the counter.
F3 — destroying your own building counts toward the raid stat. (Rust-Plugins) The org lead broke their own
wall and the next tally carried structures: 1. The entity.destroyed frame is right, and the site already
skips the raid alert for an attacker authorised on the cupboard (D59) — only the counter is wrong.
Fix: skip Structures++ (~line 1700) when the attacker owns the piece or is authorised on its cupboard;
still emit the frame.
F5 — one dead server makes every wrong link code say "your code is still good". (Module-Rust) With five of
seven enabled servers offline, a spent code and a made-up ZZZZZZ both answered "One of the servers could not
be reached… Your code is still good", and will until an admin disables the dead server.
Fix: ingest already sees every account.link.requested with its server and TTL. Ask only the servers that
issued a code in the last five minutes, and answer "unsure" only when one of them is unreachable
(links.model.js:181).
F6 — redeeming a code waits on every dead server in turn. (Module-Rust) About 21 seconds, four per dead server; the rig sorts last, so a successful link waited too. F5's fix usually leaves one server to ask; failing that, ask in parallel with a short timeout.
F8 — loading a missing plugin does not re-sync permissions. (Rust-Plugins, Module-Rust) PopupNotifications
came back at about 18:44; the grant that had been unresolved landed at 18:57, on the fifteen-minute audit. The
page promises "It will land by itself when the plugin is back" — true, thirteen minutes late.
Fix: emit a frame from OnPluginLoaded / OnPluginUnloaded when that plugin registers permissions; ingest
marks the server dirty the way perm.drift does. §4.1 needs the same signal.
F11 — the lease target picker was designed and never built. (website) core.lease's own comments say the
authoring form reads the chosen lease's target source; EventEditor.jsx only ever renders param.source
(line 299), so Module-Rust's rust.options.grouppermissions is never offered and the target is typed by hand.
The walk's first attempt put it in the wrong field. Built as part of §4.3.
2.3 Low
F7 — the restart re-sync fires before the game has loaded. (Module-Rust) With Oxide's permission files
deliberately wiped, the site restored its group, membership and grant — R2's central promise, proven. But its
restart sync ran 35 s before "Server startup complete", timed out with no warning in the log (the titles push
at the same moment did log one), and the retry 2.5 minutes later recorded "0 applied", so nothing says what the
restart restored. Fix: hold the restart sync until the hello says worldReady: true; log a failed sync.
F2 — NPC killers are named by prefab. (Rust-Plugins or Module-Rust) "killed by wolf2". Send a display name,
or map prefab to label on the site — whichever the killfeed page already expects.
F4 — a death of a player made by another plugin carries steamId: null. (Rust-Plugins) UserIDString is
empty for players other plugins spawn; the plugin already avoids it for entity.destroyed. Use
((ulong)player.userID).ToString() at all 13 sites. Real connected players are unaffected.
F14 — the site never learns that a zone expired. (Module-Rust) Expired zones stayed confirmed on the run
console until the runs were cancelled, when teardown found them "already gone" and counted that a success. This
was the design — PROTOCOL.md §15: "The website maps it to nothing. Core learns about it through reconcile and
revert" (D96) — and D170 changes it. Fix: after F13, ingest maps world.expired to the run's resource row
and marks it expired; teardown treats expired as already reverted; amend PROTOCOL.md §15.
3. Interface and walk-doc corrections
U-1 — "unresolved" is a sentence in the server block, not on the grant. The org lead granted a permission for an unloaded plugin, looked, and saw nothing; the warning sits under the server's row next to "in sync". State belongs on each toggle (§4.1).
U-2 — a configuration conflict offers nothing. "That file changed on the server since you opened it", with no way to load the server's version or compare. Offer the server's version beside the operator's edit.
U-3, U-4 — the event step editor is shaped for developers. Type tags on every field (LEASE * · STRING), the
lease is a text box with a separate "Pick from…" menu, the examples are Ultima Online values shown to a Rust
admin, and a yes/no value is typed. The org lead: "it should be a dropdown of possible values created
dynamically". §4.3.
U-5 — Start runs the published version and says nothing about an unpublished edit. The org lead fixed a step, saved, pressed Start, and ran the broken version 1. Ask "Publish and start, or start version 1?".
U-6 — a lease's "10 minutes" lasted one second. A phase with no advance rule ends as soon as its steps finish, and teardown takes the lease back. Nothing in the editor says so. §4.3.
Walk-doc corrections (PLAYER_WALK.md):
- D-1 — permission step 4 names
zonemanager.admin, which ZoneManager does not register, and never says the permission must already be granted on the site to someone (true today; D160 retires the condition). - D-2 — configuration step 2 names a ZoneManager setting ("Auto Show Zones") that 3.1.14 does not have. Use Kits' chat command, which changes something every player can see.
- Events step 6's check (
kind=world.expired) cannot pass until F13. - Configuration step 8 through the site is refused by the site first; the plugin's own guard needs a direct sidecar request with the rig's token.
- Record that Kits hides a kit the player lacks permission for (
Show kits … without the permission: false), so "locked" in the events and rewards walks means "not listed".
4. Changes decided during the walk
4.1 The permission manager, rebuilt around the game's real state (D160–D163)
What exists: a desired-state mirror. The site pushes the set it authored, the plugin reports foreign holders
only for names the site claims (buildDesired → managed), and rust_perm_catalogue holds just
(server_id, permission, seen_at) — no owner, no holders, no groups.
What it becomes:
- An inventory read — a new plugin verb returning every registered permission with the plugin that
registered it, every group with its title, rank, parent and members, and what each player holds directly
and through groups. The walk showed why the owner must come from the registration, not the name:
PermissionsManager files
zonemanager.ignoreflag.nokitsunder Kits. - The first import — on first contact with a server, everything the inventory reports becomes site-owned (D160), so a server brought under the site keeps what it had.
- In-game changes — per D161: auto-adopt by default, or revoke, or adopt by hand. The existing Revoke / Adopt answers stay for the two manual policies.
- The screen (D162, D163, U-1) — players ⇄ groups → subject → plugin → permissions with Granted / Revoked; each toggle shows its state on each server (granted · waiting for a first connection · unresolved · not landed); subjects named by website account and in-game name or Steam id.
- The plugin-load signal (F8), so the inventory and unresolved grants move when a plugin loads.
- Open for the plan: ZoneManager's
zonemanager.ignoreflag.*permissions exempt players from zone flags (§4.4). Under D160 the site owns those too — say how the screen presents them.
4.2 The kit-weekend template (D164)
core.lease on <server>/default/kits.<kit> plus rust.kit.entitle to everyone, authored together. The lease
form reads the kit's MaximumUses and Cooldown and warns: "N linked players have already used this kit".
4.3 The event step editor (U-3–U-6, F11)
Pick an action or a lease by its label, and the step becomes that thing's own form: pickers for server, group,
permission, monument, kit; toggles for yes/no; the limit shown beside minutes. No type tags, no ids, examples
from the chosen lease. A phase that holds a timed step defaults to lasting that long, or says plainly that it
won't. Start warns about unpublished changes. rust.announce already takes delivery: popup (phase 17) —
show it as a Chat / Popup choice, and consider popups for the other player-facing event messages (a zone
entered, a reward earned).
4.4 Zones: ZoneManager's options, and a dome (D166, D167)
- Options. ZoneManager 3.1.14 takes key/value arguments to
CreateOrUpdateZone: settings (name, radius, size, rotation, enter and leave messages, radiation, comfort, temperature, safe zone, permission, parent, eject spawns) and 64 flags — vehicles (NoVehicleMounting,NoVehicleDismounting,KeepVehiclesIn,KeepVehiclesOut), combat (PvpGod,PveGod,NoPve,NoFallDamage), building (NoBuild,NoDecay,UnDestr,NoUpgrade), loot, NPCs (NoNPCSpawns,NpcFreeze), comms (NoChat,NoVoice) and more. The plugin reports the installed ZoneManager's flag list at hello; the form groups them and offers presets ("Arena"). There is no flying flag — "allow flying in the zone" needs another plugin or a D168 helper. - Domes. ZoneDomes exposes
AddNewDome(player, zoneId, type, stack)andRemoveExistingDome(player, zoneId). Three things the walk established: its default sphere (type 0, one layer) is invisible at noon, and red at three layers showed only a band near the ground — choose a default by looking at each type on the rig; its domes are saved to its own data file and outlive the zone unless the bridge removes them at teardown and at expiry; and after a restart it loads before the bridge re-creates the zone, so the bridge must re-add the dome. Check that a nullplayeris safe (it is used for messages only). - F12 and F13 land in the same code.
4.5 The live map (D165)
A per-type marker setting for staff, defaulting to the major monuments. Decide in the plan whether the plugin's
monument sweep or the site does the filtering; the type (monument_substation, cave, …) is already in the
monument data.
4.6 Chat titles: twenty-three conditions (D172)
All of the conditions the org lead listed, each with a new default title. The conditions are decided; the titles
are defaults an admin can replace (D175). Every hook below is in uMod's catalogue (HOOKS.md); each is declared void, as the read
path requires (PROTOCOL.md §8.7), and each must be checked on Carbon, whose catalogue renames some.
| Condition | Title | Where the count comes from |
|---|---|---|
| Animal kills | Beastbane | OnEntityDeath, victim a BaseAnimalNPC — today folded into npcKills; split it |
| NPC kills | Exterminator | OnEntityDeath, victim a human NPC (scientists, bandits) — the other half of that split |
| Players killed | Reaper | the existing kills column (player.death with a player attacker) |
| Bow kills | Fletcher | a kill whose weapon is a bow or crossbow — the weapon shortname the killfeed already carries |
| Melee kills | Brawler | a kill with any BaseMelee weapon |
| Blade kills | Duelist | a kill with a bladed melee weapon (machete, swords, knives) — a named list |
| Revolver kills | Outlaw | a kill with the revolver or the python |
| Headshots | Crownbreaker | a killing blow with HitInfo.isHeadshot |
| PvP kill distance | Longshot | the longest single player kill — the distance the killfeed already carries |
| PvE kill distance | Marksman | the longest single NPC or animal kill |
| APC kills | Tankbreaker | OnEntityDeath, victim a BradleyAPC, credited to the killing player |
| Helicopter kills | Rotorbane | OnEntityDeath, victim a patrol helicopter (and the CH47), credited to the killing player |
| Wood gathered | Timberwright | gathered.wood, all three sources (D159) |
| Ore gathered | Prospector | gathered metal, sulfur and high-quality ore, all three sources |
| Plants gathered | Greenhand | OnGrowableGathered plus picked-up plants (hemp, berries, mushrooms, corn, potatoes, pumpkins) |
| Clothes crafted | Seamster | OnItemCraftFinished, item category attire |
| Weapons crafted | Armorer | OnItemCraftFinished, item category weapon |
| Structures built | Mason | OnEntityBuilt placing a building block |
| Structures repaired | Tinkerer | OnStructureRepair |
| Players healed | Mender | healing another player — OnHealingItemUse on someone else, and OnPlayerRevive |
| Rockets fired | Artillerist | OnRocketLaunched |
| Explosives thrown | Demolitionist | OnExplosiveThrown and OnExplosiveDropped |
| Quests completed | Wayfarer | Rust's own NPC missions (D173) — detection settled by the spike below |
Defaults, and the admin's own (D175). Phase 17 already has the machinery: a title rule, per server, ranks
one stat for the current wipe and gives its top N a title (rust_title_rules: stat, top_n, text of up to 24
characters with [ ] < > { } stripped, color, position). Today stat is one of kills, npckills and
playtime; the twenty-three categories become more values of it. What changes is where a rule's text comes from,
in three layers, the first that is set winning:
- The rule's own text, if the admin typed one for that rule.
- The admin's title for the category — set once for the site, on the titles screen, and used by every rule of that category on every server that has not typed its own.
- The module's default from the table above, shipped with the module.
So an admin who renames Beastbane to "Hunter" does it once, and every animal-kill rule shows "Hunter" from the next push; clearing it brings Beastbane back. The same 24-character limit and markup stripping apply to an admin's title as to a rule's. New rules open with the category's current title filled in, so what the admin sees is what the players will see. Nothing is stored per player — titles are still computed from the standings at each push (D135), so a rename is never stale.
The rules, adopted (D174):
- Two kinds of column. Every existing leaderboard column is a sum; the two distance titles are a best (the largest single value). The tally carries the interval's maximum, and the site keeps the running best.
- A weapon-class kill counts any kill the player is credited with — players, NPCs and animals — so Fletcher rewards a hunter as well as a raider. The weapon lists (bow, blade, revolver) are named sets in one place, so a Rust update adds a weapon by editing a list.
- A vehicle kill goes to the killing blow. The APC and the helicopter are usually killed by several people;
credit the player who dealt the last hit (
HitInfo.InitiatorPlayer) — the rule the killfeed already uses. - Healing counts only other players, never yourself — otherwise Mender is earned by bandaging after every fight.
- Counters are aggregates (rule 2): all of these ride on
player.tally, never one frame per shot, craft or swing.
Wayfarer: Rust's own missions (D173). The game already has quests — the missions its NPCs hand out at
monuments — so no quest plugin is installed and none is needed. What is not known is how to see one finish:
uMod's hook catalogue (HOOKS.md) names no mission hook, and CARBON.md lists none
among Carbon's own, so the detailed plan
starts with a spike on the rig, trying in this order and keeping the first that works on both frameworks:
- A framework hook the catalogues missed — check Oxide's and Carbon's current hook lists at build time, since both grow with Rust updates.
- Read the game's own record. The player object keeps its missions and their status; at each tally flush the plugin counts the ones that became completed since the last flush. No hook, main thread, public fields only (R2). The spike confirms the field names and statuses against the live server's assembly.
- A D168 helper that patches mission completion (both frameworks load Harmony) and raises a hook the bridge subscribes to — the last resort, because a patch breaks when Facepunch changes the method.
The spike's pass condition is a person on the rig taking a mission from an NPC, finishing it, and seeing Wayfarer's count move by exactly one — and not moving for a mission abandoned or failed.
4.7 NPCs
Research before deciding: which free uMod NPC plugins are maintained and expose spawning with a loadout, a
name and a behaviour, against extending rust.npc.place to dress the stock scientist from a Kits kit and give it
a display name. The goal the org lead named is server customisation — NPCs with different kits and names.
4.8 Steam sign-in (moved to the website's plan, D171)
Steam as a sign-in provider beside Google and Discord is part of the base website and is planned there, not here. What this module needs from it: a way to learn a signed-in account's verified Steam id, so linking by Steam can stand beside the in-game code (R1). Steam is OpenID 2.0, not OAuth2/OIDC, so the website needs its own adapter.
5. Protocol
One bump, protocol 13, carries every wire change here, so the plugin, sidecar, module and PROTOCOL.md move once:
world.expiredcarrieswhat(F13).- The configuration reload outcome as an asynchronous frame (F9).
- A plugin-loaded / unloaded frame (F8).
- The permission inventory verb and its reply (§4.1).
- ZoneManager's flags at hello, zone options on
world.zone, the dome option (§4.4). - New tally fields: D159's gathering sources, the animal / NPC split, weapon-class kills, headshots, crafting, building, repairs, heals, rockets, explosives, and the two per-interval maxima for kill distance (§4.6).
world.expiredhandled by the site (F14, D170) — no wire change beyond F13's.
F15 changes no message shape, but it is held for the bump and releases with it (D176). The work lands on edge
in Rust-Plugins, Rust-Link and Module-Rust, and the three cut over to main together once §6 step 2 is done
(D177).
6. Order, and what gates the cutover
All of it lands before Module-Rust's cutover (D169). The order inside that:
- First: F15 (data corruption), and F9 + F10 (they throw away edits) — the opening of protocol 13, on
edge(D176, D177). - Protocol 13 with the remaining fixes: F13, F14, F12, F1, F3, F8, F7, F2, F4 in the bridge and the module, and F5/F6 in the module.
- The redesigns, each planned in detail before code and walked on both frameworks: the permission manager (§4.1), the event step editor with the kit-weekend template (§4.2, §4.3), zones and domes (§4.4), the map's marker types (§4.5), the chat title conditions (§4.6), and the NPC research (§4.7).
- The cutover, once the re-walk (§8) passes on Oxide and Carbon.
7. Helper plugins (D168)
Where the walk already points at one:
- ZoneManager — re-evaluating who is inside a zone the bridge just created or restored (F12), if it has no API for it; and a flying permission per zone (§4.4).
- ZoneDomes — only if a null
playerturns out to be unsafe in its API. - Kits — none needed: D164 uses the existing credit path.
A helper is optional, versioned and released with the bridge, detected at hello, and does one job. The bridge never depends on one to load.
8. The re-walk
After step 2 of §6, on both frameworks:
- The whole player walk again, adding: a line with é, — and an emoji through announce and a config save (F15); a configuration save that needs a cold compile (F9); wood from a felled tree and picked-up hemp (F1); your own wall (F3); a code redeemed with a server down (F5, F6).
- A zone restarted with a player standing in it, and a zone opened around a player (F12).
- A zone left to expire, checked by
kind=world.expired(F13). - A plugin unloaded, a grant made, the plugin loaded again — the grant lands within a minute (F8).
- An expired zone reads
expiredon the run console (F14). - Each redesign's own walk, and every title condition earned at least once.
- Then the steps still owed: the Carbon subset, player step 8 and the map walk with a second player, events step 7 with several players, the phone walks.
9. Answered, and what stays open
The four questions this plan first asked were answered on 2026-09-26: the gate (D169), F14 (D170), Steam sign-in (D171) and the title conditions (D172) — then quests (D173), the title rules (D174) and admin-owned title names (D175).
Left for the detailed plans of §6 step 3, none of them blocking the fixes:
- How a completed mission is detected — the spike in §4.6 (D173).
- Whether the plugin or the site filters monument types (§4.5).
- The ZoneDomes default type and stack, chosen by looking at each on the rig (§4.4).
- The NPC route — a plugin, or extending
rust.npc.place(§4.7).