1 Commits

Author SHA1 Message Date
e7551cbf8f docs(rust): plan fixes — D181-D185, how step 2 is built
Step 2 of PLAN_FIXES §6 had four open choices; the org lead answered them
before any code:

- D181/D182: F12 is a ZoneManager helper that Harmony-patches
  Zone.InitializeZone, ships in the overlay, and the bridge falls back to
  IsPositionInZone for scoring without it.
- D183: F14's expiry is core's: an `expired` ledger status and
  ctx.events.expired (MODULE_API 1.11.0), without disturbing Module-uo.
- D184: F8 is plugin.loaded / plugin.unloaded with the permission diff.
- D185: F2 is the site's labels; no wire change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 20:41:08 -05:00

View File

@@ -1,7 +1,8 @@
# `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), and its rig walk's fixes by D180. **Everything in it — fixes and redesigns alike — lands before Module-Rust's cutover
shape was settled as work began (D176–D179), and its rig walk's fixes by D180; step 2's four open choices were
answered before its code (D181–D185). **Everything in it — fixes and redesigns alike — lands before Module-Rust's cutover
(phase 19, [`PLAN.md`](PLAN.md) §34, D145; D169).** Everything here comes from the first walk of
[`PLAYER_WALK.md`](../../rust-link/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
@@ -64,6 +65,11 @@ Taken by the org lead during and straight after the walk.
| **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. |
| **D180** | **The step-1 rig walk's fixes stay inside the bridge, and the console is a fair way in.** The walk (2026-09-26) found Carbon's Oxide-compatible `ReloadPlugin` does not reload (W3), Carbon's log is `Carbon.Core.log` (W4), Carbon loads a plugin whose config threw and raises `Loaded` anyway (W5), Oxide's "Could not initialize plugin" line was missed (W2), and Oxide's idle compiler drops a reload with "Timed out waiting for plugin to be compiled" (W1). All are fixed in `RunicGateway.cs` (PROTOCOL.md §19.1), never by changing a third-party plugin. Running server console commands (`c.reload`) is approved, and so is a helper plugin that hooks and runs console commands where one is needed (D168). |
| **D181** | **F12 is fixed by a ZoneManager helper that patches ZoneManager with Harmony** (D168). The patch follows `Zone.InitializeZone` — the one method a zone passes through when it is created, updated or loaded from ZoneManager's data file — and enters every connected player already inside it through ZoneManager's own entry path, so its flags, messages and `OnEnterZone` apply as if they had walked in. It fixes **every** zone on the server, not only the bridge's. Rejected: calling ZoneManager's private `UpdatePlayerZones` by reflection for our zones only; and measuring distance in the bridge with no helper at all. |
| **D182** | **The helper ships in the overlay beside the bridge and is installed by default** (ZoneManager is already a required plugin). An operator may delete it: the bridge still loads, reports the helper missing at hello, and **scores the zones it made by ZoneManager's public `IsPositionInZone`** — so a missing helper costs the zone's flags for somebody already inside, never their score. The site says so. The same fallback applies when the helper loads but cannot find what it patches. |
| **D183** | **F14's expiry is core's to record: the resource ledger gains an `expired` status, and a module reports one with `ctx.events.expired({ kind, ref })`.** `expired` is terminal like `reverted` — teardown never tries to take it back — and the run log says `resource.expired`. It is a MODULE_API minor (1.10.0 → 1.11.0), and it must not disturb Module-uo, which is already built against 1.10.0: additions only, no existing status changes meaning. Rejected: asking core to reconcile, which files an expected expiry as `orphaned` (a fault, and still claimable for a revert); and core marking rows expired from `lease_until` on its own clock. |
| **D184** | **F8's signal is `plugin.loaded` / `plugin.unloaded`, carrying the permissions that plugin added or removed.** The plugin diffs the registered permission list around each load and unload, emits only once the world is ready (a boot is the restart sync's), and the site marks the permission sync dirty when the list is not empty. The same list is §4.1's first evidence of which plugin owns a permission. Rejected: every load, names only. |
| **D185** | **F2 is the site's: NPC attackers are labelled by the killfeed**, which trims a prefab's variant digits (`wolf2` → wolf) and names the NPC families that read badly otherwise (scientists, bandits, animals). No wire change. Rejected: the plugin sending a display name Rust may not have. |
## 2. Fixes
@@ -109,7 +115,9 @@ asks it every five seconds (§29.7) — **so after any restart mid-event, everyb
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.
Carbon too. Settled by D181 and D182: ZoneManager has no public way (its `UpdatePlayerZones` is private and
runs only for a connecting player), so a helper patches `Zone.InitializeZone` with Harmony; without the helper the
bridge scores its own zones by `IsPositionInZone`.
**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
@@ -146,7 +154,7 @@ that, ask in parallel with a short timeout.
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.
marks the server dirty the way `perm.drift` does. §4.1 needs the same signal. Shape settled by D184.
**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`
@@ -162,7 +170,7 @@ at the same moment did log one), and the retry 2.5 minutes later recorded "0 app
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.
or map prefab to label on the site — whichever the killfeed page already expects. The site's, by D185.
**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
@@ -172,7 +180,8 @@ empty for players other plugins spawn; the plugin already avoids it for `entity.
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.
and marks it `expired`; teardown treats `expired` as already reverted; amend PROTOCOL.md §15. The row is core's
(`event_run_resources`), so core gains the status and the call (D183, MODULE_API 1.11.0).
## 3. Interface and walk-doc corrections
@@ -366,7 +375,8 @@ once:
- `world.expired` carries `what` (F13).
- The configuration reload outcome as an asynchronous frame (F9).
- A plugin-loaded / unloaded frame (F8).
- A plugin-loaded / unloaded frame (F8), with the permissions it added or removed (D184).
- The ZoneManager helper's state at hello (D182).
- 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,
@@ -394,8 +404,9 @@ in Rust-Plugins, Rust-Link and Module-Rust, and the three cut over to `main` tog
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).
- **ZoneManager** — re-evaluating who is inside a zone when it is created, updated or loaded (F12). It has no API
for it, so this is the first helper, built in step 2 (D181, D182). A flying permission per zone (§4.4) may join
it.
- **ZoneDomes** — only if a null `player` turns out to be unsafe in its API.
- **Kits** — none needed: D164 uses the existing credit path.