Skip to content

History

Revisions

  • Round 49: NATS subject authorization (#554 + #552 slices 1-3 + infra #35) Closes the unauthenticated-NATS residual the Round 39 (#424) and Round 41 (#355/#474) notes pointed at: a bare TCP connection to :4222 no longer has full publish rights on every subject. - Deployment: rewrote the Round-39 "restrict it ... until you have" content-bus callout into the shipped per-identity authorization matrix (world/gate/ director/seed via TELOS_NATS_USER/PASSWORD). Captures the load-bearing lessons: gate = deny=[">"] (a denylist leaves $JS.EVENT.ADVISORY.* forgeable); world AND director both publish content.invalidate (a naive matrix breaks admin reload); an empty allow=[] is ALLOW-ALL in NATS; NATS doesn't hot-reload (k8s ships a content-hashed configMapGenerator); the four passwords must be stable + all set (NATS refuses an unset $VAR). Folded in #554 (the in-process handle ACL: the gate is denied publish on the whole telos.comms. root). Updated the NATS ports row + softened the dev-loopback "unauthenticated" wording (the matrix now defends wrong-identity; dev passwords are shared). - Content-Loading-and-Hot-Reload: the #424 residual's "deployment-side fix" now exists — a forgery requires already holding a trusted identity. - Scoped-Event-Bus: narrowed the #355 forged-down-broadcast residual — the matrix denies the untrusted gate outright, leaving only a world/director credential (still needs the up/down subject split to fully close). - Sysadmin-Reference: the four required-stable NATS passwords in the production checklist.

    @yfronto yfronto committed Jul 26, 2026
    0316c36
  • Round 48: SRD items/chargen/plumbing — the last engine wave of #505 (#512/#514-#519/#548) Eight primitives closing the SRD engine register. Two stale claims corrected (chargen array/roll "not implemented"; the Wearer "affix sums" only). - Pack-Entity-Reference: chargen array_assign + roll step kinds now implemented (roll is server-authoritative, forged client values ignored); wearable gains static `modifiers` (#514) and `equip_affects` (#515, on-equip magic + OnHit procs, item-keyed derived state). - Abilities-and-Effects: worn static modifiers compose through the Wearer modSource (mulMod was hardwired to identity); teleport/push same-zone relocation ops (#516, forced-move harm-gated, same-zone structural, instance- entrance refused); the polymorphic dice_num + one-of conflict rejection (#517) and the `fixed` op flag (#515). - Combat-System: item procs = an equip-affect subscribing OnHit, with the OnHit self-loop guard + `fixed` rider; the prevents:move WALK fix (#548, a confirmed bug — attemptMove never consulted prevents:move, voiding the whole immobilize system). - Builder-Commands + Player-Reference: carrying capacity (carry_capacity derived attribute, enforced at acquisition, get-all-takes-what-fits); the `credits` verb (#519); rest short/long. - Pack-MUD-Settings: manifest license/attribution (accumulate per pack; a later pack can't erase an earlier notice). - Pack-Lua-Hooks: OnRest now live + OnShortRest/OnLongRest (distinct kinds, NOT the mag argument which would double a handler's heal). - Pack-Lua-Scripting: h:move now harm-gates forcing another player (was an ungated grief hole) — the safe co-move primitive for mounts/grapple-drag. Store field-drop trap again: #519 pack license needed BOTH fold loops (mergePacks file->Pack AND Merge Pack->LoadedContent) plus the pack_meta round-trip; #514/#515 ride existing wholesale-marshaled bodies.

    @yfronto yfronto committed Jul 26, 2026
    7b79d4c
  • Round 47: SRD engine primitives batch 2 (#536/#539/#541-#547) Nine composable SRD primitives, threaded into the combat/effect/schema pages. - Combat-System: #547 OnEnterCombat (the initiative checkpoint, seeded-rng, per entry); #544 crit dice-doubling (crit_dice doubles the dice term vs crit_mult the whole roll; reaches swing + check-band crits; set-not-multiply, no leak); #543 heterogeneous multiattack routine (replaces attacks, shares one to_hit, same dealDamage path per swing, capped); #536 absorb buffer in the funnel (after the OnDamageTaken reaction, before the vital; instance-set capacity, no max_attr, set_resource op). - Abilities-and-Effects: #541 highest-wins stacking + graded rungs, and the HIGH harm-classifier fix (affectIsDetrimental / respawn-strip now fold in RUNG modifiers/prevents — the 6th blind spot; decrement_rung gated cross-player); #545 duration_kind: indefinite + level/dispel-by-level (indefinite room-field lease kept finite); #546 prevents_source source-relative CC (a targeting gate, not a can't-harm firewall; fail-open across load); #539 concentration single slot (the cross-zone single-writer fix: expireConcentration gated on holder.zone==z + break-before-detach on transfer/quit); #542 richer if predicate (comparators + formula RHS; indeterminate operand fails to FALSE, not 0; unknown comparator rejected at parse); the set_resource / increment_rung / decrement_rung ops; the absorb resource fields. - Pack-Entity-Reference: resource absorb/fronts; affect level/concentration/ duration_kind/rungs/prevents_source + stacking: highest; combat_profile multiattack. - Pack-Lua-Hooks: OnEnterCombat + OnAffectBlocked added to the live event enum. Store field-drop trap hit TWICE this round (absorb/fronts on resource_defs; multiattack on combat_profiles) — both round-tripped through Postgres by the reflection test; noted as JSONB-body fields needing no migration.

    @yfronto yfronto committed Jul 26, 2026
    f082e90
  • Round 46: SRD engine primitives (#511/#513/#537/#538/#540/#535 + #557/#562) The capstone SRD-primitive round. Six composable primitives plus a fold-clamp fix and the check subject selector, threaded into the effect/combat pages. - Check pipeline (Combat-System + the check op in Abilities-and-Effects): * #511 boon/bane — a net-sign channel selecting among boon_dice/bane_dice/ neutral dice (NOT keep-high synthesis, which inverts the demo's roll-UNDER ladder); 5e cancellation falls out of sign-netting; composes through ordinary attribute modifiers, no new state/DTO. * #513 band `when` — a forcing fifth band axis (auto-crit / auto-fail-save) with no engine crit/fail vocabulary; truthiness is non-zero AND finite (NaN != 0 in Go); replaces an affine sentinel hack that broke on stacking. * #562 subject selector — subject: target is the saving-throw idiom (target rolls, narrates to the saver, OnCheck subject = saver, caster rides as other); does NOT rebind band-op target vocabulary (authoring trap noted). - Damage/death (Combat-System): * #537 per-target damage_taken_mult — an affect-granted map, product-composed like prevents (absence = identity-1 structurally), consulted after soak; per-FACTOR normalization at composition closes the "two {fire:-3} buffs compose to a 9x amplifier" cross-player harm-gate bypass. * #535 downed/dying — a third onPoolDepleted disposition: an active suspends_death affect holds the victim at 0 (no die/corpse/respawn); regen pauses, auto-swings stop (swingGatesPass now refuses a prevents:[act] attacker); the resolution is a content affect. die() keeps its one call site. - Reactions/affects (Combat-System + Abilities-and-Effects): * #540 incapacitation gate — canReact = canAct && !preventsTag("react") on every reaction checkpoint; gates reactions only, never the plain bus (passive on_event still fires while stunned). * #538 incoming-affect trait immunity — grants_immunity vetoes an incoming affect BEFORE attach on {ref}u{category}u tags; a separate multiset from prevents; the veto sits after the reattach branch so a load is never vetoed. * #557 attribute-fold clamp — the (base+flat)*mul fold saturates (+/-Inf -> +/-1e12, NaN -> pre-mod base); screen runs BEFORE min/max since NaN passes every range; the reachable consumers read attr() directly (resourceMax -> an unkillable bottomless pool, the OPPOSITE of the panel's claimed one-shot). - Pack-Entity-Reference affect schema: tags, grants_immunity, suspends_death, damage_taken_mult rows + the conventional act/react/cast prevents tags.

    @yfronto yfronto committed Jul 24, 2026
    293ed9f
  • 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
    98e539e
  • Round 44: player aliases, named exits + traverse hook, overworld mob markers (#353/#370/#363) Three player-facing features; each lands in its existing home (plus the traverse hook added to the room-trigger catalog). - #353 player aliases: Builder-Commands (new "aliases expand at the split step" subsection + the alias/unalias command entries) + Player-Reference + the handoff snapshot field list (Cross-Shard-Handoff). Expansion runs at the split step BEFORE verb resolution, so an alias can target any verb and grants no privilege (MinRank runs on the resolved verb); cycle/depth-bounded and re-capped to the ingress byte cap. Distinguished from an engine command-alias (n->north). Durable + rides a signature-bound handoff field, re-sanitized on arrival. - #370 named exits + traverse hook: Builder-Commands (named-exit fall-through, consulted last, exits map only never entrances) + Player-Reference (any exit keyword is typeable) + Pack-Lua-Hooks (the cancellable traverse room trigger: block/return-false/redirect, FAILS OPEN so it is not a hard security boundary, redirect is budget-bounded and can name only an exits key so it cannot reach an instance entrance — the depth-0-typed-move invariant) + the demo Warded Sanctum example. - #363 overworld nearby-mob markers: Pack-Lua-Scripting (has_visible_creature() presence-only disclosure primitive: display-render only, both rooms open_sight, canSee-filtered, mobs-only, never re-opening the foreign-room anti-scry) + Demo-Pack (the minimap's ! marker + the open_sight flag on plains rooms; mutual opt-in).

    @yfronto yfronto committed Jul 24, 2026
    ec0cd25
  • 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
    b3de536
  • Round 42: close the gaps left by a9f0cfe (#408 exemplar + engine-side vital semantics) Round 42 was already partly self-documented in a9f0cfe, which covered #405/#406/ #407 on the builder-facing pages (Pack-Entity-Reference + Combat-System). This fills what that pass did not reach. - Abilities-and-Effects (the ENGINE-INTERNALS view, untouched by a9f0cfe) still carried the pre-Round-42 framing: it called onDepleted "the cancellable death hook" and described routing as op-level-only. Corrected: every pool runs its own on_depleted (on a vital pool it is ALSO the death hook), so a non-vital pool is a full second track — damageable, fires its hook at 0, never lethal; and damage reaches a pool through the three-tier precedence (explicit op `resource` > the damage type s `target_resource` > the `primary` vital). Reframed the capacity discard as a STRUCTURAL immunity a pack can rely on. - Demo-Pack: document #408, the Sanity/horror secondary-vital exemplar — the acceptance artifact proving the three primitives compose. Covers the no-per-op-resource routing, why the pool must NOT be marked vital, why it is inert for existing characters by construction (a 0-base capping attribute, not a check), the two authoring rules the level-triggered hook forces, and why it ships in the existing pack rather than a new one (the pack-set consistency check fatally refuses a process whose TELOS_CONTENT_PACKS disagrees with the published manifest). - GMCP-Reference: the #408 engine rider — Char.Vitals now skips a pool the character has no capacity in, so an opt-in pool is ABSENT rather than reported as 0/0 to every client on the shard. - Pack-Lua-Scripting: on_depleted is a pool-depletion hook, not only a death hook.

    @yfronto yfronto committed Jul 23, 2026
    9e103fd
  • Round 42: depletion hooks on any pool, damage-type pool routing, the depletion arithmetic Updates the builder-facing reference for #405/#406/#407: - resources: `vital` now means only "emptying this causes death"; every pool runs its own on_depleted. Documents the level-trigger and the two authoring rules it forces (make the hook idempotent; never reward from one), and the $depletion.overflow/.applied/.amount scalars with the carry-over idiom. - damage_types: target_resource, the three-tier routing precedence, why a capacity-less target is immune by construction (and how to use a 0-base capping attribute as an opt-in lever), and the never-retrofit rule. - Combat System: the same two corrections where it described the seam as vital-only and the routing as op-level-only.

    @MrG0rd0x MrG0rd0x committed Jul 23, 2026
    a9f0cfe
  • Infra: retarget the reference deployment from Oracle k3s to AWS EKS (infra #27-#33) The infra repo migrated off single-node Oracle Always-Free k3s onto AWS EKS, which invalidated several things the wiki asserted — including two dead doc links and one piece of load-bearing REASONING. - Deployment: the IaC cross-link now describes AWS EKS and points at the single README; DEPLOYMENT.md/RUNBOOK.md/PLAN.md were collapsed and DELETED upstream, so both links were dead. Added a migration note listing what moved (local-path->gp3, Traefik->ingress-nginx, gate via AWS NLB with externalTrafficPolicy: Local to preserve client source IP, OCI Object Storage->S3, TF state on S3 native lockfile, CI via GitHub OIDC->IAM role). - Observability: the retention rationale CHANGED, not just the platform. The old argument was node-level (local-path PVCs are directories on the node root FS and do not enforce size, so a runaway backend takes the game down); on EKS the gp3 PVCs ARE size-bounded, so the blast radius is now the backend itself — worse observability, not a downed game. Policy values are unchanged (Loki 7d, Prometheus 7d+2GB, ~7GiB) and NodeDiskFillingUp now guards emptyDir/image churn. Grafana basic-auth moved Traefik->ingress-nginx (missing Secret => 503, fail-closed); NetworkPolicy enforcement is the VPC CNI and the ingress source is the ingress-nginx namespace, NOT kube-system (a silent breakage if ported across unchanged). Added the cert split (HTTP-01 for web/Grafana, DNS-01 for the gate — its own NLB is unreachable to HTTP-01) and the one-click lifecycle (external-dns + cert-manager via IRSA, per-env delegated Route53 subzone so a compromised staging pod cannot touch production names). - Sysadmin-Reference: the data-loss exposure changed — gp3 EBS survives node replacement (retiring the user_data-forces-replace hazard), but the one-click `down` drains EBS before destroy, so teardown is the real destructive path. pg-backup now targets S3 into a Terraform-provisioned versioned bucket; restore docs moved to the README.

    @yfronto yfronto committed Jul 23, 2026
    c3ac6fd
  • Docs: make up now brings up the full stack incl. Grafana LGTM; up-base is the lean path The Makefile default was inverted so the human-facing 'make up' brings up everything (base + the LGTM overlay, Grafana on :3000) and 'make up-base' is the lean no-observability path CI/smoke use. Update Observability, Running-Locally, and Deployment to match (was: up-obs/down-obs for the overlay).

    @MrG0rd0x MrG0rd0x committed Jul 21, 2026
    d6ce380
  • Observability 3: distributed tracing (gomud #464-#470) The metric->trace->log pivot is real. Fills out the Observability page Traces section (previously "none yet") and corrects the now-stale "no tracing" claims in Running-at-Scale. - Observability (Traces rewrite + intro + diagram): the tracing foundation (TracerProvider on the same OTLP env gate, W3C propagator set unconditionally, head sampling with a 100% carve-out); what is traced — handoff/AdoptZone at 100% (root spans, per-hop children, error-distinguishing failures), session- attach (bounded to the handshake; the Play stream is a context anchor not a span), and the comms bus / zone mailbox (traceparent in the ENVELOPE not NATS headers so MemBus works too; span LINK not parent-child because JetStream is at-least-once; queue_wait_ms at dequeue); the deliberate no-per-command-span decision; the metric->trace exemplar pivot on busLag (and why tick_lag_ms has none); the trace->log traceHandler; and the cardinality-is-a-security-boundary discipline (metricZone template-only, no per-player subjects, gate host not host:port, baggage-free producer links). Documents the dev-vs-staging split: the compose overlay renders the full pivot via otel-lgtm bundled Tempo, but the reference k8s deployment still defers the Tempo backend. - Running-at-Scale: corrected "traces not wired yet"; reframed the occupancy template-label as the #470 security boundary (regression-pinned). - Cross-Shard-Handoff: noted this is the one path traced at 100%.

    @yfronto yfronto committed Jul 21, 2026
    97b571e
  • Sysadmin: connecting to a TLS-only gate + backups/DR operations From the production bring-up: document the 'no login prompt' symptom (a plain telnet client against a TLS-only gate gets nothing — the top false 'prod is down' alarm) with how to verify via openssl s_client and connect via a TLS MUD client, and where the login flow's failure modes actually live (transport vs OAuth config vs gate->account wiring). Add a Backups & DR section: the single- node data-loss exposure (incl. user_data-forces-replace), the reference pg-backup CronJob + dedicated-key guidance, the ~60s RPO, and the load-bearing restore caveat (plain-SQL replay into the populated DB is a silent no-op; use pg_restore --clean with the app scaled to zero, or a selective player-state restore).

    @MrG0rd0x MrG0rd0x committed Jul 21, 2026
    6b375c3
  • Observability 2: the LGTM stack (gomud #458/#459/#481 + infra #13-#20) New Observability page consolidating the telemetry pipeline now that it is real and deployed on staging; the wiki previously had no Loki/Grafana content beyond the forward-references from Observability 1, which are now made concrete. - NEW Observability.md (Sysadmin): OTLP -> collector -> Grafana LGTM pipeline with a flow diagram; metrics (incl. the URL-scheme gotcha #458 that silently dropped all export); logs via the slog OTLP bridge TELOS_OTEL_LOGS (#459), mutually exclusive with the k8s filelog path so nothing double-ships; traces deferred (Tempo empty until tracing ships); the make up-obs dev overlay; and the telosMUD-infra reference k8s deployment (#13-#20) — retention as node protection (Loki 7d, Prometheus 7d+2GB), the collector DaemonSet, guarded public Grafana (own hostname + Traefik basicAuth in front of Grafana login), default-deny NetworkPolicy, and the validate-on-PR + manual-approval-prod CI. Registered in the sidebar and the Sysadmin index. - Running-at-Scale: corrected now-stale claims — "only metrics are wired / no tracing exporter" and "no file sink or log shipper in code" — since logs now ride OTLP into Loki; refreshed the honest-gaps note (account now exports; no director service in dev compose); added the endpoint-scheme warning; made the Observability-1 "planned LGTM" forward-reference concrete. - Content-Loading-and-Hot-Reload + Lua-Sandbox-Internals: the content-DTO log cap (#481) — builder-controlled field values (a 200KB dice/formula field or a reference target) were echoed verbatim into problem strings; now clamped to ~1KB at the source and at the capProblems funnel, via a shared internal/logcap leaf byte-identical to the Lua CapLogMsg. Notes the #454 key-guard does not catch these (keys are area/event/op/value). - Deployment: cross-linked the infra observability layer and the obs overlay.

    @yfronto yfronto committed Jul 21, 2026
    8e0a354
  • Observability 1 milestone: log + port hardening (prelude to the LGTM stack) A security milestone that hardens what the logs and the dev stack expose, ahead of Observability 2 shipping container stdout into Loki — which turns an ephemeral scroll into a durable, indexed archive and makes these exposures real rather than theoretical. - Running-at-Scale (Logging): player input is no longer logged verbatim (#454). Several sites logged the full input line — a tell/say body, a channel post, or a mistyped link code / /login URL — at Debug, and the panic line at Error. Now dropped by default (player+seq remain), re-attached only under a separate TELOS_LOG_RAW_INPUT opt-in deliberately decoupled from DEBUG, with a single- token paste logging only its length. A check-log-keys.sh guard in make verify fails on a sensitive value used as a log key — a backstop for the mechanical case, not a complete invariant. - Lua-Sandbox-Internals (new section) + Pack-Lua-Scripting + Running-at-Scale metric: builder Lua logging is bounded (#456). print/mud.log/director.log had no length cap or rate limit — an always-on write primitive once stdout hits Loki. Length cap (~1KB), a per-CALL line budget reset per FRAME (charges the flooder, not a co-firing victim) that trips the breaker, and a per-runtime wall-clock token bucket (50 lines/s) that bounds a self-rescheduling timer; labeled source=builder_lua; drops on builder_logs_dropped. Notes the two bypasses closed at the source — err.Error() and compile-error echo. - Deployment: the dev Compose stacks now bind every host port to 127.0.0.1 (#455/#479), not 0.0.0.0 — NATS 4222 is a full unauthenticated data plane, plus 8222/4317/8889, redis, postgres default creds, the OAuth bridge and the plaintext-telnet gates. Container-to-container over the compose bridge is unaffected; framed explicitly as dev-stack hardening, not production policy.

    @yfronto yfronto committed Jul 20, 2026
    81ee9e9
  • Round 41 milestone: director & durable-bus reliability (#354/#355/#356/#390 + the forged-event fix) Corrects a stale claim: Orchestration said "No region directors are deployed, so signal_region currently has no consuming director". #356 shipped them. - Orchestration-and-Directors (new section) + Pack-Entity-Reference: per-region director scripts (#356). Region scope had been WRITE-DEAD — content published durable region signals and zones applied region deltas, but nothing consumed the subject or wrote region state. Records the three hazards found building it (an omitted ref minted a PHANTOM WORLD director sharing the world lease, instance id and consumer name; the consumer id was not injective across . and :; the tier hardcoded the demo pack) and the disproved premise that a director losing leadership leaks its VM — the proposed fix would have crashed the process on a late signal. - Orchestration-and-Directors: a signal is NAK d when its scope-state write never landed (#354). The ack consumed the event FLEET-WIDE because every director binds the same durable consumer name. Widened to any store error, recorded at the single write chokepoint (a handler return is defeated by pcall, by closure composition, and by the schedule-state path). Includes the prerequisite defect that inverts intuition: a restarted director CASed on a cold cache version of 0, so ErrCASLost meant "cold cache" far more often than "concurrent writer" and NAKing on it would have requeued on every restart. - Orchestration-and-Directors: the down-broadcast carries the store version and replicas fence on it (#355) — with the issue s framing corrected in two directions (churn does not reproduce; and there is NO re-read, so a stale replica stays wrong until the next write or a reseed). Why the version must be the store s, and the three easily-inverted rules (0 = unversioned, unknown key applies, deletes record too). - Scoped-Event-Bus + Pack-Lua-Scripting: a zone script could forge a director-only event, because signal-up and broadcast-down share one subject and shards core-subscribe it. #355 made it worse — a forged high version permanently freezes a key fleet-wide. Notes why boss.died is deliberately NOT in the shared reserved list, and the residual (a NATS-credentialed publisher still reaches it; needs the subjects split). - Running-at-Scale + Sysadmin-Reference + Scoped-Event-Bus: durable_stalled_total (#390) — the recoverable window before a park. Explains why it is an orchestration outage rather than a delayed message (MaxAckPending=1 blocks the whole per-scope consumer) and carries the known miss: a wedged handler never reaches the counter, so the park alarm can fire with no early warning.

    @yfronto yfronto committed Jul 20, 2026
    ba7a6ac
  • Round 40 milestone: instance residuals + operator tunables (#436/#438/#435/#429/#425) Corrects claims from the Round 37 sync: "nothing caps memory" (three places) and the #340 eviction guidance, whose stated prerequisite now exists. - Lua-Sandbox-Internals (new section) + Sysadmin-Reference + Pack-Lua-Scripting: the per-call string-allocation budget (#438). Documents why the filed premise was impossible — bytes-per-instruction spans FIVE orders of magnitude (25 instructions, 63MB), so no instruction budget bounds memory, and heap sampling cannot work because a neighbour goroutine s noise floor would let a player quarantine content they do not own. The real vector is the `..` OPCODE (every string BUILTIN was already capped; an opcode has no wrapper): at shipping defaults `s = s .. s` reaches 64 GB. Charged in the fork BEFORE the join. Also the misclassification fix — a memory bomb used to land in AbortDeadline (weight 0.1, "transient host load"); new AbortAlloc weighs with AbortBudget. Keeps the review lesson that "does not amplify" != "does not allocate". - Sysadmin-Reference + Deployment: redis.directory_addr splits coordination from cache (#429). The eviction check is now shape-dependent — SHARED stays warn-only, DEDICATED is FATAL — which supplies the prerequisite the previous guidance said was missing, so the advice changes from "flip the policy" to "declare the split". Periodic re-check stays warn-only on both shapes (exiting over a live CONFIG SET would cause the outage it prevents), and a configured-but-unreachable coordination Redis is fatal rather than falling back onto the evicting instance. - Instanced-Zones + Sysadmin-Reference: the instance caps are operator-tunable (#436), with the two ENFORCED cross-field invariants that disproved the issue s "no ordering invariant" premise (instances_per_shard vs the drain eject barrier; burst validated with its window as a rate), plus the corrected claim that per-account bounds churn — it does not. - Instanced-Zones + Building-Instanced-Zones + Pack-Entity-Reference + Pack-Lua-Scripting: the declared instance entrance (#435). Documents why option 2 (relaxing self-only) was rejected — standing in a room is not consent, greet/enter share an actor, and the mint bills the VICTIM s account — and why entrances live in their own map so no push-the-player path can traverse a door it cannot see.

    @yfronto yfronto committed Jul 20, 2026
    fa6d8e3
  • Round 39 milestone: content-pipeline trust (#423/#424/#427/#366) - Content-Loading-and-Hot-Reload: the post-boot content refresh is now VALIDATED before it publishes (#423). Previously it published with nothing checking it while reload's identical publish was gated — so content an operator watched a reload REJECT went live at the next unrelated invalidation, and "nothing propagated" stopped meaning "nothing applied". Documents why the refresh gate is deliberately NARROWER than reload's (rejectable iff a finding makes the snapshot unsafe to BUILD A ZONE FROM), why it freezes the whole snapshot instead of dropping the offending pack (dropping removes populated zones with players in them), the rejected-version cooldown, the problem-set-keyed memo, and why boot reports but does not refuse (no fallback snapshot; the asymmetry is the alertable "fleet split by uptime" signal). Keeps the honest residual: validatePacks is a HEALTH gate, not a PROVENANCE one — the deploy log makes cross-pack deploys legible, and its fingerprint includes `instanceable`. - Content-Loading-and-Hot-Reload + Deployment: the content-bus pack filter now fails CLOSED (#424). An invalidation naming no pack was accepted by every shard, and since the re-read matches on (kind, ref, pack) an empty pack resolves nothing -> Found:false -> the DELETION path: a fleet-wide eviction primitive. Documents the three-part accepts predicate and states the scope honestly — blast-radius reduction, NOT authentication. The real remedy is deployment-side NATS subject permissions, now written up in Deployment. - Content-Pack-Operations + Builder-Commands: `pull <version> force` (#427). The guard still runs and still reports; admin-only on top of builder; a stale override downgrades. Leads with the corrected remedy — REDIRECT CHARACTERS FIRST, because the reboot IS the harm event (a peer cannot build a stripped zone, so the handover fails and players are reclaimed to their home room). - Content-Pack-Operations: the seed->pull ref collision (#366). Definition refs are GLOBAL, not per-pack, and telos-seed never registers — so seeded rows collide with the first pulled pack sharing a ref. Detected in-transaction after the prune; notes that PurgePack must share the LOCK, not just the tx, and that the old message asserted a registry fact it never checked (a dead-end remedy is worse than none).

    @yfronto yfronto committed Jul 20, 2026
    9a9e13b
  • 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
    703e1bd
  • Round 37: operator-tunable Lua caps (#368) + runtime zones from live content (#418) Round 37 was largely self-documenting: #340 landed in 133d670 and #422 (the "document instanced zones" issue) in cf8a00d, which also covered #416, #419, #421 and the mint half of #418. Verified those against the merged commits; this fills the two remaining gaps. - Lua-Sandbox-Internals (new section) + Pack-Lua-Scripting + Sysadmin-Reference (new operator section): the instruction budget and call deadline are now per-deployment tunables, and are a PAIR — at the 5ms default the budget stops firing near 850k instructions, so raising it alone disables the primary bound and silently weakens the circuit breaker (an instruction abort weighs 0.5, a deadline abort 0.1, so every runaway gets reclassified as transient and a script failing 4-in-5 stops tripping). ValidateCaps rejects such a pair; New clamps it structurally; validation sits at the injection point to keep config a leaf; a malformed TELOS_LUA_* refuses boot. InstrPerMS corrected 100k -> 20k after measurement showed real throughput (~90k/ms arithmetic, ~37k/ms allocating) was at or BELOW the old constant — an error in the unsafe direction that let mis-paired configs pass. Also: a deadline may not reach a pulse, and memory is uncapped (~355MB at the ceiling = OOM-kill risk). - Content-Loading-and-Hot-Reload (new subsection): zones built after boot now use live content. The shard snapshot was written once at construction, so a post-boot HostZone or instance mint combined boot-version content with live-cache prototypes; a deleted template still minted and a withdrawn instanceable never took effect. Now an atomic.Pointer refreshed by the reloader, one snapshot threaded through validate+build. Documents the two states this exposed: an incomplete runtime build now refuses to publish (a roomless HostZone would adopt, renew, and drop drained players while the directory reported it healthy), and region membership resolved once.

    @yfronto yfronto committed Jul 19, 2026
    f48a6ed
  • Sysadmin: the directory Redis must not evict coordination state (#340) Replaces the earlier guidance that allowed a volatile-* policy. That was right about the zone hash (PERSISTed, so volatile eviction cannot reach it) and wrong about the directory as a whole: the TTL'd keys each turn a single-writer guard OFF when evicted — the director's leader-election lease (two directors both believing they lead), the shard registration (two processes sharing a shard id both registering), and the instance-template claim, which carries the shortest TTL in the directory and is therefore what volatile-ttl takes first. Adds the shared-instance caveat an operator needs before acting on the warning: this Redis also carries the checkpoint tier, so noeviction without a memory ceiling risks an OOM-kill that wipes the whole directory — worse than the problem. Plus the effects-replication requirement and a runbook line for the post-wipe stuck-drain tail.

    @MrG0rd0x MrG0rd0x committed Jul 19, 2026
    133d670
  • Document instanced zones across all three audiences (#422) Round 36 shipped instanced zones (#72) and Round 37 changed four things about them; only the engine-developer page existed, and it predated the Round 37 work. Builder — new page, Building Instanced Zones: - the `instanceable: true` opt-in and why it is opt-in (a mint runs the zone's boot resets, so every copy is a generation faucet that also routes around every in-world gate: a private copy has no doorman) - the start_room requirement, enforced at mint, and both reasons for it - mud.send_to_instance is self-only, and what "the invoking actor" actually resolves to per attach point: a custom command or an ability acts as the PLAYER, but on("enter")/on("greet") act as the room/mob, so an entity trigger cannot send the entrant in. The dungeon-door idiom is a verb the player types - mud.zone() and why `ev.zone ~= "darkwood"` matches inside every copy - the exhaustive does-not-work list with the why for each: persistent resets (durable item dupe), timed repop (farmable), signal_region / signal_world (the envelope drops source), reserved director schedule events, nesting, the reload freeze - the engine-owned player-facing lines, and the caps to design around Sysadmin — a new section in Running at Scale plus the prune-guard remedy in Content Pack Operations: - the caps ARE PER PROCESS, so an account gets perAccount x shards; and they are not config knobs today (compile-time defaults + a Go option, no TELOS_INSTANCE_* env var wires them) - shard-local and unleased: not in the placement pool, never rebalanced, never a handoff destination, and they die with the shard - metrics label by template because an instance id is unbounded, player-driven cardinality; logs go the other way and keep the id. Adds the missing telos.zone.instances gauge to the metrics table - drain step 0 ejects occupants to their exit anchor - the reload freeze and the pinned-instance advisory - Round 37: a mint builds from the live content snapshot and refuses an incomplete build (#418); the TTL'd dir:tmplinuse claim, so a content pull can be REFUSED while parties are inside copies and clears ~45s after the last one (#416); the concurrent bounded reaper sweep (#419); and the up-to-3-minute rebalance deferral with its "rebalance deferred" log lines (#421) Mudlib — Instanced Zones: - the routing-vs-isolation rule spelled out (the room map is the isolation boundary; the zone-segment test is only a pre-filter), and what the build-failing lint actually covers - the "never mint on a zone goroutine" contract with the reason, the three-hop entry, why the request carries the origin zone POINTER, and why abandonInstance is a flag rather than a record deletion - minting from the live snapshot, and the incomplete-build refusal - the concurrent reaper: the detached per-teardown context, and why the bound is a smoothing bound rather than a correctness one - the dir:tmplinuse claim as the cheapest possible analogue of a lease, its own goroutine, and the mint kick - the anchor's invariant DECAYS, the pre-handoverZoneTo guard and why the placement of that guard is the load-bearing property, why the anchors are queried rather than maintained, and why it fails open

    @MrG0rd0x MrG0rd0x committed Jul 19, 2026
    cf8a00d
  • 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
    4488e23
  • Round 35 milestone: multi-vital support (#71) Corrects a now-stale claim: Abilities-and-Effects asserted "multi-vital is unsupported (only the lowest-ref pool damages and kills)". - Abilities-and-Effects: multiple independently-lethal vital pools, each with its own on_depleted; deal_damage routes via `resource` (empty = the new `primary` vital, so existing content is unchanged); `primary` replaces the implicit lowest-ref rule; max<=0 is natural immunity; non-vital pools are damageable but never lethal. - Combat-System: damage now subtracts from the ROUTED pool; documents the per-pool vitalDepleted predicate (def.vital && max>0 && cur<=0) and why the max>0 term is load-bearing (a zero-max pool would read as already-depleted and instantly kill); the death-generation guard is ordered BEFORE the pool-local cancel re-check so a cross-pool cascade yields exactly one die(). - Pack-Entity-Reference: vital row updated, new `primary` row (rides the resource JSONB body, no migration), per-pool on_depleted, and the LintVitalResources / LintDealDamageResources lints. - Pack-Lua-Scripting: h:damage gains `resource=` for the routed pool.

    @yfronto yfronto committed Jul 18, 2026
    2034b98
  • 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
    09280da
  • Round 33 milestone: staff visibility pair + respawn hostile-affect strip - Pack-Entity-Reference: help_defs gains an optional per-topic min_rank; a gated topic is invisible in both the index and a direct lookup (falls through to "no help on ..." so existence never leaks), see_also is filtered per-viewer, and the gate fails open (a dropped field un-gates text, never a capability) (#351). - Combat-System: document the "no hostile effect survives respawn" invariant at the respawnPlayer chokepoint — whitelist inversion (provably-benign is closed/checkable, unknown ops fail toward stripping), hard removal with no on_expire hooks, and the tickOnce re-entrancy guard for a lethal DoT (#318). - Builder-Commands + Accounts-and-Auth-Internals + RPC-and-Protobuf + Trust-Tier-Model: promote/demote now have the wiz-command posture for non-staff (plain Huh?, no refusal, no account-service round-trip) via a manage_tiers visibility bit on IssueSessionAssertion; the service stays authoritative, the bit is defense-in-depth visibility only (#369).

    @yfronto yfronto committed Jul 17, 2026
    247bca1
  • Round 32 milestone: comms & JetStream durability residuals - Scoped-Event-Bus: MaxAckPending is now a per-consumer option (default 1, double-clamped vs NATS 0==unlimited) rather than a hardcoded transport constant; both ordered consumers keep the serializing posture, and >1 is the lever a future seen-set reorder-tolerant consumer would pull (#312). - Running-at-Scale + Sysadmin-Reference: durable_parked_total is now counted off the broker MAX_DELIVERIES advisory via a per-stream queue group (once cluster-wide), so it also catches an ack-wait-expiry or across-restart park, not just an in-process final delivery — an alert signal, not an exact ledger (the advisory is ephemeral) (#311). - Distributed-Systems-Model: placement is an existence/routing oracle, not a liveness one; a "currently connected?" decision (e.g. the new-mail ping) reads the presence roster and only to gate, never to route (#325). - Content-Loading-and-Hot-Reload: the comms hear-set republish retry and the zone-shape reconcile retry run on separate goroutine budgets at parity, so a reconcile storm cannot starve the security-relevant republish class (#345). (#371 async-who prompt ordering is an internal fix with no documented surface.)

    @yfronto yfronto committed Jul 13, 2026
    a36052d
  • 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
    ce5566c
  • Add tt++ ASCII vitals-in-split + Mudlet chat-window samples - TinTin-Samples: real #split + #draw bar recipe for HP/MP ASCII bars in a reserved bottom status region, refreshed on the Char.Vitals GMCP event. - Mudlet-Samples: chat-window recipe — gag channel text from the main window client-side while rendering the GMCP Comm.Channel.Text mirror into a side miniconsole; documents client-side hide/show (stays subscribed) vs the server channels off <chan> (fully leaves, drops GMCP too).

    @yfronto yfronto committed Jul 12, 2026
    47023ba
  • TinTin++: correct GMCP handling from the tt++ source Verified against scandum/tintin: GMCP arrives as %1 already in native table format (not raw JSON), stored per-module via #event {IAC SB GMCP} {#var {%0} {%1}} and indexed as ${Char.Vitals[hp]} — replaced the earlier speculative #json parse across all three examples. Added the manual / GMCP-protocol / source reference links.

    @yfronto yfronto committed Jul 12, 2026
    0a7145b