fix(rust): protocol 13 step 2 — expiry, plugin loads, the loading hold, NPC names, the link fleet (F2 F5 F6 F7 F8 F13 F14)
Some checks failed
PR Checks / client-build (pull_request) Successful in 18s
PR Checks / frozen-manifest (pull_request) Failing after 56s
PR Checks / server-tests (pull_request) Successful in 7m45s

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
This commit is contained in:
2026-09-26 21:35:46 -05:00
parent 80c05a3c1e
commit b10f11b057
23 changed files with 611 additions and 44 deletions

View File

@@ -61,6 +61,22 @@ const ZONES = (() => {
})()
/** An ISO instant as the value a `datetime-local` input takes, in the browser's clock. */
/**
* What the missing ZoneManager helper costs this server (PLAN_FIXES F12, D182), or
* null when there is nothing to say — the helper patched, or a plugin that
* reported no ZoneManager state at all. Scoring is never what is lost: the bridge
* falls back to the zone's shape. ZoneManager's own flags are.
*/
export function zoneHelperNote(helper) {
if (!helper || helper.state === 'patched' || helper.state === 'no-zonemanager') return null
const cost =
'Event zones still score everybody inside, but ZoneManager’s own flags and messages miss a player who was already standing in a zone when it opened or came back after a restart.'
if (helper.state === 'missing') {
return `The ZoneManager helper (RunicGatewayZones.cs) is not installed. ${cost} Reinstall or update the plugin to put it back.`
}
return `The ZoneManager helper could not patch this ZoneManager${helper.reason ? ` (${helper.reason})` : ''}. ${cost}`
}
function toLocalInput(iso) {
if (!iso) return ''
const d = new Date(iso)
@@ -235,6 +251,9 @@ export default function ServerSettings() {
: 'No wipe schedule set.'}
</div>
<div className="dim" style={{ fontSize: '0.76rem', marginTop: 2 }}>{titlesSummary(s.titles, s.titlePush)}</div>
{zoneHelperNote(s.zoneHelper) && (
<div style={{ fontSize: '0.76rem', marginTop: 2, color: 'var(--warn, #d9c184)' }}>{zoneHelperNote(s.zoneHelper)}</div>
)}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 8 }}>
<button type="button" className="btn" onClick={() => { setError(''); setTitling(null); setForm(formFrom(s)) }}>Edit</button>
<button type="button" className="btn" onClick={() => test(s)}>Test the sidecar</button>