Skip to content

History / Persistence and Durability

Revisions

  • Round 45: launch hardening — TLS-port reject, ref-length lint, transfer-audit detectability (#486/#483/#443) - #486 TLS-only port plaintext reject: Sysadmin-Reference (the "no login prompt" section now describes the ACTIVE reject, not the old silent hang — with a note that a still-hanging connection means a stale gate binary) + Edge-and-Protocol (the first-byte sniff: 0x16 replays via peekConn into tls.Server, else a one-line "use TLS" + close; the sniff runs off the accept loop, and the handshake is driven under an explicit HandshakeContext deadline, closing the resource-exhaustion vector a deadline-less lazy handshake left open). - #483 identity-token LENGTH bound: Content-Loading (ref length added to the lint list) + Pack-Authoring (a ref is a store btree PRIMARY KEY, ~2704-byte page ceiling; RefMaxLen=256; also covers the two content-authored store keys the charset lint skips — pack name and formula-override name — since an over-long value fails the import transaction identically; boot warn + reload hard-reject). - #443 cross-character transfer audit: Persistence-and-Durability — new item_transferred event (a transient Released marker records the releaser; a DIFFERENT saved player's pickup emits the row) reusing the #350 audit trail; covers drop->get and put->get-from-container; self-pickup/floor-item/mob-move record nothing; marker cleared on pickup; bound items never reach it. DETECTION ONLY (no prevention, no conservation invariant). Threaded into the #432 owner_epoch residual note as the first half ("we cannot know" -> "we can know") of the cross-row dupe close-out deferred to Launch.

    @yfronto yfronto committed Jul 24, 2026
  • Round 43: the three review-bundle follow-ups (#397/#399/#401) Refinements to systems already documented (spawn-protection, comms history, the audit trail); each thread lands in its existing home. - Abilities-and-Effects (#397): the sourceless-ambient exemption. A room field with no applier ticks at actor==target, which guardHarmful treats as an exempt self-effect, so it kept damaging a just-respawned occupant. A sourcelessAmbient flag fires ENFORCEMENT even at actor==target while CANCELLATION stays actor!=target-gated, so the field never drops the occupant's own shield; threaded into all four such harm sites. - Combat-System (#397): a swing at a spawn-protected target is short-circuited BEFORE the pipeline, not zeroed inside it. Narration ran before dealDamage, so the target saw a phantom hit each round; the pre-gate also drops a still- protected attacker's own shield. - Builder-Commands + Pack-Entity-Reference (#401): a sent tell is recorded at the CONFIRMED-echo point, so no phantom entry on a resolve-miss or publish-failure; history channel appends a shard-local partial-view footer on a multi-shard fleet; the channel history ring is reaped on channel removal, and the schema now warns that loosening hear_access retroactively exposes buffered lines. - Persistence-and-Durability (#399): the async auditor now coalesces up to 64 events into one pgx.Batch round-trip, with per-row fallback under the same bounded ctx; newest-first reads order by at DESC, seq DESC for true insertion order incl. within a shared-now batch; and staff audit-by-name now folds in the target account tier_changed rows, scoped to the owning account.

    @yfronto yfronto committed Jul 24, 2026
  • Round 38 milestone: owner_epoch — ownership as a durable fence (#432 + #413/#379/#384) CORRECTS A FALSE SAFETY CLAIM. Three pages asserted that state_version "fences stale writes, so a mis-fired handoff or a zombie owner can never clobber a newer record". It never did: state_version is contention control, and every caller answered a CAS loss by rebasing, so a stale writer rebased exactly as a legitimate one did. - Persistence-and-Durability: "The state_version guard" reworked into "Two guards" — state_version (contention) vs owner_epoch (ownership, at the SINK). Documents the rollback primitive it closed (a stale shard 60s-old logout snapshot force-writing over the live owner, repeatable), why the fence had to be a SECOND column (a rebase moves state_version and cannot reach a separate conjunct), why the mint is atomic (a read-then-bump would look fixed), the three-outcome SaveResult, terminal epoch loss on both save paths, the max(directory,row) floor, fail-closed login, the checkpoint tier ownership guard (it had NO CAS and was a full bypass one rung up; detects a double-own ~6x sooner), the (owner_epoch, state_version) load ordering, and the honest residual (an epoch on row X cannot fence a write to row Y). - Distributed-Systems-Model + Overview: same correction; adds a "do not confuse the two epochs" invariant (directory epoch fences ROUTING and is evictable; owner_epoch fences WRITES and is durable). - Cross-Shard-Handoff: handoff mints the epoch AFTER destination resolution (minting first evicted legitimate players mid-move); an unreadable store now fails the handoff closed; concurrent claims derive distinct epochs hence distinct handoff tokens, closing the shared-pending race structurally. - Zone-Runtime: #413 extends the resolve-then-deliver claim to login attach and Prepare (incl. why neither may reuse claimTransferTarget, and Prepare needing a release on post failure); #379 refuses a reconnect racing an in-flight transfer in TWO layers, since a probe reproduced a dupe the resolve-time check alone cannot see. - #384: documents why the drain-reservation margin is not bound by step 1 duration (the hold is rebased per accumulate and anchored to the redirect it covers) and the WARN added to falsify it; corrects the Round 31 "stamped once and never refreshed" wording on three pages.

    @yfronto yfronto committed Jul 19, 2026
  • Round 36 milestone: instanced zones (#72) New deep-dive page Instanced-Zones, plus corrections to two pages that asserted "instancing is deferred / not implemented". - NEW Instanced-Zones: template-vs-id identity and the ownsZoneRef locality chokepoint (with the build-failing AST lint); why the id is <template>#<128-bit random> (# outside the ref charset, unguessable so it is not a farming oracle); unleased by construction; async 3-hop mint + quiescence reaper; the instanceable content opt-in; the isolation exclusions (persistent resets fail closed vs a durable item dupe, per-mint RNG salting, withheld director schedule events, refused signal_region/world, drain/reload/off-box-ingress); THE ANCHOR (template projection was killed because placement is the gate ROUTING key); exit paths; and the security posture. - Distributed-Systems-Model + Running-at-Scale (x2): instancing now exists but is ISOLATION, not transparent load-sharding — the public hot-zone ceiling stands. - Zone-Runtime: quiescence gains the third `incoming` counter for in-flight intra-shard transfers, claimed under the same mutex teardown checks. - Cross-Shard-Handoff + RPC-and-Protobuf: signed-snapshot optionals now written UNCONDITIONALLY — a second append-if-non-empty optional made digest(tier="",account=X) == digest(tier=X,account="") (length prefixes solve boundaries, not presence); instances rejected at every off-box ingress. - Persistence: an instance is never a durable location; the anchor + COALESCE. - Pack-Entity-Reference: zone `instanceable` flag, start_room respawn semantics. - Pack-Lua-Scripting: mud.zone(), mud.send_to_instance (self-only). - Orchestration: instances withheld from reserved schedule fan-out. - Sidebar + Engine-Developer-Reference index entries.

    @yfronto yfronto committed Jul 19, 2026
  • Round 34 milestone: spawn-protection window, audit trail, comms history - Combat-System + Abilities-and-Effects: the actor-agnostic post-respawn spawn-protection window shipped (#394) — updates the #318 "deferred" note. respawnPlayer opens a pulse-deadline protectedUntil; guardHarmful refuses harmful ops aimed at a protected player AHEAD of the !isPlayer no-op so a MOB attacker is covered (safe-room cannot be); drops when the player acts. - Persistence-and-Durability (new section) + Accounts-and-Auth-Internals: the append-only character_audit trail (#350) — world emits died/attr/track via an async per-shard auditor (saver twin, drains on shutdown), account emits create/tier_changed in-transaction; idempotent on (subject,kind,dedup_key) with a DURABLY-UNIQUE key (died = per-death UUID, not the transient deaths counter that collided across relogs); audit self-view scoped by stable pid, audit <name> staff-only. - Builder-Commands + Player-Reference: tells/replay session tell history (#349, structural pair-privacy, skips ignored authors, resets on relog) and history <channel> scrollback (#348, fetch-time canHear gate, shard-local partial view); Pack-Entity-Reference channel_defs.history row enriched.

    @yfronto yfronto committed Jul 18, 2026
  • Round 31 milestone: handoff phase-2 auth, checkpoint tie-break, drain-reservation TTL - Cross-Shard-Handoff + RPC-and-Protobuf: document that Handoff.Commit/Abort are now Ed25519-signed under the cluster keypair with the digest bound to the destination shard id (#314) — the deterministic handoff_token over public inputs was forgeable, and destination binding stops a captured signed Abort from being replayed against a second destination in a split-brain race. - Persistence-and-Durability + Distributed-Systems-Model: the load freshness check breaks a state_version tie toward the Redis checkpoint (#322); a checkpoint is dumped at the pre-CAS version so it ties the row while carrying newer content — the strict > left the tier inert, so the tie-break is what makes the crash window the ~10s cadence and not the ~60s flush. - Cross-Shard-Handoff + Running-at-Scale + Distributed-Systems-Model: the drain-target reservation TTL now outlasts the whole drain (deadline + one presence-reflect window) so a slow-but-alive drain keeps its hold while players migrate; only a crashed drainer lingers the full TTL (#334).

    @yfronto yfronto committed Jul 13, 2026
  • Round 30 milestone: reconnect/placement/drain correctness (#321/#324/#329/#331/#336) + RID re-home (#373) Round did not self-document. Closed: - Persistence-and-Durability: a live reconnect routes by an in-memory residency index BEFORE the lagging durable zone_ref (prevents a double-owned character when a link-dead resume beats the async flush of an intra-shard walk) (#321); the placement writer gets the saver's shutdown-barrier treatment so a logout tombstone isn't lost on graceful shutdown (else the tell/mail oracle reports a player on a dead shard) (#331) - Distributed-Systems-Model: the placement tombstone now carries a per-session nonce, fencing a same-shard/same-epoch relog that shard+epoch alone couldn't distinguish (#329) - Edge-and-Protocol: a fresh login bounced by a draining shard (Unavailable) is re-resolved + re-dialed to the peer instead of dropped; token-bearing re-dials never retried (extends the zero-drop drain to ARRIVING players) (#324) - Cross-Shard-Handoff: a gate-wedged player is skipped by the drain and reclaimed (client-fault straggler), not miscounted zero-drop Redirected (#336) - Entity-Component-Model: an intra-shard transfer re-homes the entity + carried subtree to fresh per-zone RIDs (the overworld 'map on some rooms' bug) (#373)

    @yfronto yfronto committed Jul 12, 2026
  • Round 26: the placement record becomes the reconnect-routing spine Track-6 residuals. Round self-documented only #276 (catch-up metrics); the rest of the engine/ops surface was stale. - Distributed-Systems-Model: NEW 'The placement record' section — zone is the routing key (ShardForZone resolves the current owner, so an offline rebalance is transparent), epoch is the fence AND the tell/mail existence key, shard routes nothing. Every residency registers a placement (incl. the intra-shard zone walk); registerPlacement accepts an equal epoch where the handoff CAS demands a strictly greater one. Clean logout writes a fenced compare-and-delete tombstone, not a delete (#320 slice 2, #70). Also: lease renewal restarts on re-adoption, closing an A->B->A split-brain (#288). - Persistence-and-Durability: close the shard-not-zone 'Known gap'; add the saver shutdown sentinel barrier (#282). - Edge-and-Protocol: world-side Send watchdog — keepalive can't see a gate whose transport acks PINGs while its application stopped reading (#274). - Orchestration-and-Directors: a runtime-adopted zone's scope replica is seeded before the zone is exposed (a full-map replace would clobber newer state) (#280). - Cross-Shard-Handoff / Running-at-Scale / DistSys: drain-target reservations now carry per-field, server-timed expiry (#284). - Running-at-Scale: drop the self-contradicting 'deliver_lag_ms silent' gap; the gate row now describes zone-keyed reconnect routing.

    @yfronto yfronto committed Jul 10, 2026
  • Round 25: durable-zone login fix + engine-side notes for #69/#262/#266 The round self-documented #69 (Abilities), #262 and #266 (Sysadmin). Closing the engine-page gaps and the one undocumented item: - Persistence-and-Durability: NEW 'Where a returning player lands' — characters.zone_ref is the login routing key (#320). Documents the data-loss bug it fixed (an intra-shard walk + logout silently reset you to the home zone's start room), the link-dead reconnect side-fix, the room_ref warn, the checkpoint drift guard, and the remaining shard-not-zone placement gap. - Combat-System: the death generation (Living.deaths) + die() entry re-entrancy latch (an item dupe) + why position/location can't carry the 'did it die' signal (#69). - Scoped-Event-Bus: paced durable NAK redelivery + the tri-state ack decision (#266). - RPC-and-Protobuf: AdoptZone is signed on keyed shards (#262). - Edge-and-Protocol: the connect diagram binds the durable zone.

    @yfronto yfronto committed Jul 10, 2026
  • Engine Dev: persistence/durability + content loading/hot reload - Persistence-and-Durability: the memory->Redis->Postgres ladder, state_version CAS + reconcile paths, flyweight item delta, ephemeral resets, handoff bypass - Content-Loading-and-Hot-Reload: boot load, validate-before-broadcast, the applier + zone reconcile version guard, content_version authority, reconcile-on-join

    @yfronto yfronto committed Jul 9, 2026
  • Scaffold wiki + author About and Getting Started - 48-page skeleton across all audiences (flat files + grouped _Sidebar) - Every page carries an Audience + Status banner (Ready/Draft/Placeholder) - About: Overview, History of MUDs (Ready) - Getting Started: Getting-Started, Running-Locally, First-Admin-Setup, Content-Packs-Intro (Ready) — dev bypass, local GitHub OAuth, bootstrap admin, promote/demote, content-pack model, all grounded in code - SRD5/WoW/D6 pack pages are honest placeholders (packs not yet built)

    @yfronto yfronto committed Jul 9, 2026