Skip to content

History / Pack Lua Scripting

Revisions

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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 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
  • Overworld map, content-defined toggles, and the demo-strip release model Cross-repo changes (gomud engine+demo, telosMUD-content, telosMUD-infra): Overworld / minimap (gomud #358/#360/#361/#367): - Pack-Entity-Reference: new toggle_defs def table (on/off player preference, verb-flipped, delta-from-default override, never a trust signal); the room display surface (return nil to fall back → own the render for some rooms only) - Pack-Lua-Scripting: coord(), has_room_flag (room content flags, vs has_flag = Living flags), long(), toggle() handle reads - Builder-Commands + Player-Reference: enter/exit/out named-exit verbs + the Exits: line order; toggle verbs - Demo-Pack: the 4th zone (overworld 'The Open Plains', 6x20 grid) + the opt-in minimap (overworld toggle → room display template); the plains route vs the kept-for-parity direct market->grove exit Demo-strip release model (gomud release-images + telosMUD-content/-infra): - Deployment: published images built with nofixture ship core-only; the world is pulled from the external content store; telos-pull added to the published matrix (telos-seed keeps the demo embed for local stacks) - Content-Packs-Intro + Content-Pack-Operations: the demo is a dev fixture, not a shipped world; a deployed fleet (e.g. live staging) pulls the reference pack

    @yfronto yfronto committed Jul 11, 2026
  • Builder guide: guard top-level state.x initializers against hot reload (#67)

    @MrG0rd0x MrG0rd0x committed Jul 9, 2026
  • Builder Reference: pack format, entities, and Lua - Pack-Authoring: directory-tree format, merge semantics, ingestion paths - Pack-MUD-Settings: pack.yaml manifest + pack_meta scalars - Pack-Entity-Reference: exhaustive world-entity + 23 def-table field reference - Pack-Lua-Scripting: sandbox model, curated handle/global API, self.state - Pack-Lua-Hooks: full lifecycle/event/reaction/trigger + EventKind catalog All grounded in code (content loader, world Lua runtime); reserved/unused surface flagged honestly (class_defs, unwired formulas, stub globals).

    @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