Genesis mudlib (CDlib)

✅ 可玩

Genesis

genesis

更新 fdf581a 2026-09-12 源码 下载 ZIP 上游 genesismud/mudlib

▶ 开始游玩 · Play Now

The official mudlib of Genesis, one of the oldest and most influential LPMuds still running (source: <https://github.com/genesismud/mudlib>). This is the CD gamedriver-native mudlib itself, not a snapshot of the live game world -- per the repo's own README: "This repo does not contain a standard domain which will need to be implemented to use the mud." There is no `/d/Genesis` domain here: no rooms, no NPCs, no items, no race std files, no wizard guild content. What ships is the engine: master object, login/ character-creation infrastructure, the std/ base classes (living, player, room, container, weapon, armour, ...), the command souls, the board/mail/ bank/donation/report subsystems, and the full wizard toolset.

README

Highlights

Registration flow

new -> character name -> confirm (y/n/q) -> password (min. 8 chars, 2+ non-letter characters) -> confirm password -> in. No race/gender/stat selection screen (that content is missing, see above) -- new characters start as a plain human by default.

Admin account

Registered through the normal registration flow first (as a mortal), then granted rank by adding an entry to config/sys/local.h's DEFAULT_WIZARDS map -- normal new-character registration refuses any name already present there (by design, to stop name-squatting on a wizard's identity), so the name has to exist as a real character first. Verified live: wizard-only soul commands work (update <path> -- the canonical ACL check, since it exercises both read and compile permissions -- successfully recompiled /secure/simul_efun.lpc end-to-end).

Status

Boots clean: zero compile errors and zero uncaught runtime errors in log/debug.log across repeated fresh-driver boots. Full registration, soul-command play (smile, wave, ...), quit/save, and re-login as an existing (including wizard) character all verified end-to-end with a real driver session and a raw socket client. Since no domain ships, a mortal's very first login runs straight into the mudlib's own graceful "your starting locations are corrupt" fallback and disconnects with an explanatory message -- expected, not a bug (see NOTES.md); wizards are exempt from that disconnect and can play normally in the bare bootstrap landing room this port added specifically so login/testing wasn't blocked entirely (secure/login/bootstrap_room.lpc).

A few residual gaps (documented, not blocking): a small number of files are intentionally not standalone-compilable (#include-fragment files that only compile as part of their aggregator, and two read_file()-as-text room-generator templates); one non-fatal "Denied write permission" logged during some wizard-login save/restore-privilege sequences that doesn't block the actual save (both attempts and the resulting file are confirmed correct); lib/link_room.lpc is dead code (never inherited anywhere in this repo). See NOTES.md for the full list.

WASM status: playable. Login as fluffos, soul commands, and quit are verified under the shared WASM driver (NOTES.md WASM status). Play: https://mudlibs.fluffos.info/genesis/

Local run

cd libs/genesis
~/src/fluffos/build-debug/src/driver config.fluffos

Game port: 40213.

NOTES · 移植与修复记录

Genesis -- porting notes

Source: git clone https://github.com/genesismud/mudlib (commit 92ce6844, cloned 2026-08-25). Slug genesis, number 166, port 40213.

1. What this repo actually is

Per the repo's own README: "This repo does not contain a standard domain which will need to be implemented to use the mud." This is the engine (master, login, std/ base classes, command souls, board/mail/bank/donation/ report subsystems, the wizard toolset) with no /d/Genesis domain at all -- no rooms, no NPCs, no items, no race std files, no wizard guild rooms, no docs directory content that the code references. Every gap documented below as "missing domain content" traces back to this one fact, not to anything this port broke.

2. Compile-sweep: CD-driver -> FluffOS dialect differences

This is a large (225 .lpc/.h files), mature CD gamedriver codebase, so essentially every category of CD-vs-FluffOS incompatibility this project has previously catalogued showed up here too:

- doc/man/efun/m_indexes: "if an integer is given instead of a mapping, 0 is returned" -- FluffOS's keys() hard-errors instead. Fixed in the m_indices() simul_efun (hit for real: secure/master/player.lpc's query_seconds() calls m_indices(m_seconds[first]), legitimately 0 for anyone with no seconds on file, checked on every single login). - doc/man/efun/member_array: "if an integer is given instead of an array, -1 is returned" -- FluffOS's real member_array() efun hard-errors. Shadowed with a simul_efun of the same name (bypassing to the real efun via efun::member_array() for the normal case). Hit for real: std/player/cmd_sec.lpc's check_valid_startloc() calls IN_ARRAY(env, SECURITY->query_list_def_start()) where that function doesn't even exist (a separate, genuine pre-existing bug -- the real functions are FPATH_FILENAME's query_def_start_locations()/ query_temp_start_locations(), not SECURITY's query_list_def_start/ query_list_temp_start, which exist nowhere in this codebase) -- every mortal's very first quit hit this. - doc/man/efun/filter: mapping-filter value-only-callback semantics, see above.

3. add_action() bare function-value bug (the headline bug)

add_action(FUNCTION_VALUE, "verb") -- e.g. add_action(quit, "quit"), using the bare function name as a value, the standard idiom throughout this codebase -- compiles cleanly, and the resulting sentence shows up in commands(), but is never actually matched against typed player input on this driver. Confirmed via lpcc/live-driver testing: switching a handful of calls to the string form (add_action("quit", "quit")) made them work immediately, with no other change.

This wasn't a narrow issue: 151 of 158 add_action() call sites in the whole codebase used the bare-value form, including std/living/cmdhooks.lpc's add_action(my_commands, "", 1) -- the catch-all verb ("") that is the entry point into the entire soul-command dispatch system. With that one call broken, no soul command (the emote soul, wizard souls, everything) worked for anyone, wizard or mortal -- while it *looked* like a soul-loading problem (the "Yikes, baaad soul: /d/Genesis/cmd/misc" message, from the genuinely-missing NPC_SOULS second entry, printed on every keystroke) the actual root cause was upstream of the soul system entirely.

Fixed with a tree-wide mechanical sweep (145 more sites, on top of 7 manually fixed earlier while chasing the initial symptom) converting add_action(IDENT, ...) -> add_action("IDENT", ...). One site was not converted: std/room/exits.lpc's ugly_update_action(object player, string cmd, function fun) { add_action(fun, cmd); } takes a genuine runtime function parameter, not a compile-time-known name -- there's no way to recover a string name from an arbitrary function value on this driver (function_name()/function_object() don't exist here either, a previously-documented gap) -- left as-is, already flagged by an existing comment in that file as a pre-existing "de-dup regression" from an earlier, unrelated fix.

4. set_auth-based uid/euid model: "#" placeholder bug

secure/simul_efun.lpc implements set_auth()/query_auth() (and the getuid/geteuid/seteuid/setuid/export_uid efuns built on top of them) as a from-scratch simul_efun shim, because the CD driver's uid model is an arbitrary, freely-settable "uid:euid" string pair per object (via set_auth(ob, "uid:euid")), which doesn't map onto FluffOS's simpler native single-string-uid/single-string-euid model at all.

setuid(), seteuid(), and export_uid() all used a literal "#" string as a placeholder for "the other half of the pair, don't care right now" -- e.g. setuid() did set_auth(previous_object(), CREATOR + ":#") (uid set correctly, euid set to the placeholder). The bug: getuid()/geteuid() never actually resolved "#" back to anything (they only special-case the string "0" as "unset"). So the very next call in this codebase's own standard idiom, setuid(); seteuid(getuid(this_object()));, would read back the *other* field's real value correctly, then immediately overwrite it with "#" when setting the field it was updating -- seteuid(str) did set_auth(ob, "#:" + str), permanently stomping the uid field it should have left alone.

Net effect: every single object that went through this standard idiom (used throughout std/living.lpc's create_container(), and elsewhere) ended up with a uid of the literal string "#" after its very first seteuid() call -- silently breaking any *later* getuid()-based permission check on that object. This is exactly why the new-player bootstrap (\S5) failed with "Denied write permission in save_object()" on its first save attempt: export_uid(), called from secure/master.lpc's save_player() wrapper, read the corrupted "#" uid and propagated it into the object being saved.

Fixed at the source: export_uid(), seteuid(), and setuid() in secure/simul_efun.lpc now always preserve the field they aren't updating (read it first, write it back unchanged), and setuid()'s euid default is "0" (which geteuid() *does* resolve, to unset/none) instead of "#". One direct call site outside the simul_efun file had the exact same bug and needed the same fix: secure/master/fob.lpc's reset_wiz_uid() (called from std/player.lpc's reset_userids(), i.e. every wizard login) did set_auth(wiz, name + ":#") directly.

Every other set_auth(this_object(), "#:root")-style call throughout secure/master.lpc (15+ occurrences) is not the same bug -- those are the master object's own well-understood, intentional self-referential placeholder (master's own uid is never actually checked anywhere in this codebase, only its euid), left untouched.

Residual, non-blocking: one "Denied write permission in save_object()" is still logged during some wizard-login save/quit euid-transition sequences (traced to std/player/cmd_sec.lpc's save_me(), which explicitly does seteuid(0) immediately before calling SECURITY->save_player() as part of a "drop privileges, let master re-grant them" pattern) -- but the actual save consistently succeeds regardless (the resulting playerfile is correct both times it was checked, and the user-visible "Saving <name>." + clean disconnect always appears). Not fully root-caused given the time budget for this pass; flagged for anyone doing deeper testing on this lib later.

5. Missing character-creation bootstrap (secure/login/ghost_player.lpc)

config/sys/local.h's LOGIN_NEW_PLAYER/LOGIN_TEST_PLAYER macros point at /d/Genesis/login/ghost_player -- unlike most of this repo's other missing-domain-content gaps (narrative/world content with a graceful fallback already built into the surrounding code, left undefined/empty and documented, see \S6), this file never existed anywhere in the upstream repo at all, and it's genuinely load-bearing *infrastructure*: secure/login.lpc clones it and calls open_player()/enter_new_player() on it to create every single new character, and secure/master.lpc's load_player()/save_player() gate every player save/load on LOGIN_NEW_PLAYER->legal_player(). Without some real implementation here, character creation -- and therefore save/load entirely -- was impossible.

Added a minimal, functional bootstrap implementation at secure/login/ghost_player.lpc (inheriting /std/player, config updated to point at it) rather than a recreation of whatever richer race/stat- selection wizard the original surely had (that content is lost along with the rest of the missing domain). It:

A related, previously-dead piece of functionality got wired up in the same pass: std/living/cmdhooks.lpc's load_wiz_souls() (loads a wizard's rank-appropriate command souls, e.g. update, into wiz_souls) was never called from anywhere in this codebase -- genuinely disconnected functionality as shipped, not something this port broke, presumably originally wired into the same missing character-creation/promotion flow. The soul *files* themselves (cmd/wiz/apprentice.lpc, cmd/wiz/wizard.lpc, etc.) are all present and compile cleanly in this repo, so this was a "reconnect existing working code" fix, not content invention: called from std/player.lpc's setup_player(), in the existing if (query_wiz_level()) branch.

6. Missing domain-content symbols (compile-blocking, left content-neutral)

A large number of symbols are referenced throughout std/, cmd/, and secure/ but were never actually #defined anywhere in this repo -- they are normally domain content. Left undefined, each is a hard "Undefined variable/function" compile error (not just a missing-content gap), so a real definition was needed just to get the mudlib to compile at all. Defined in secure/auto.h (this project's chosen "global include file") or the relevant local file, each with a FluffOS PORT NOTE explaining the choice:

7. ARMAGEDDON stale path + bootstrap landing room

Unlike the missing-content symbols in \S6, ARMAGEDDON (the world-shutdown-orchestrator singleton, used pervasively -- checked unconditionally on every single login attempt by secure/login.lpc) is not missing content: its real implementation ships in this repo at secure/armageddon.lpc, exactly like SECURITY/DOCMAKER/MAP_CENTRAL/ etc. (all defined in sys/files.h pointing at their real /secure/... locations) -- but the ARMAGEDDON macro itself, in config/sys/local.h, was still pointing at the original domain path /d/Genesis/obj/armageddon, never updated to match. This turned every login into a hard, uncaught call_other() couldn't find object crash. Fixed by pointing the macro at /secure/armageddon instead.

Separately, secure/login/bootstrap_room.lpc was added: a deliberately bare, purely-functional landing room (std/room base, no exits, no decoration -- explicitly documented in its own header as infrastructure, not game content) used as WIZ_ROOM and every RACESTART entry (\S6). Without *any* real room anywhere in this repo, character registration could never complete at all (see \S5/\S6's circular-dependency discussion) -- this unblocks login/testing while making zero claim to represent anything in an actual game world.

A handful of OWN_STATUE (/d/Genesis/obj/statue, another missing-content path, used for a "linkdead players become a statue" feature) call sites that skipped the objectp(find_object(...)) guard most other call sites in this codebase already use were fixed the same way (secure/login.lpc, std/player.lpc x4, secure/armageddon.lpc) -- this crashed a wizard's very first quit/password-check flow uncaught.

8. std/container.lpc's add_subloc() foreach-on-int crash

add_subloc(string sloc, mixed resp, mixed ids) -- ids is varargs; every 2-arg call site (the overwhelming majority, including std/living/inventory.lpc's wear_reset()/wield_reset()/hold_reset(), called unconditionally during every single living's creation, players and NPCs alike) leaves it at its default mixed value, 0. The subsequent foreach (string aid in ids) hard-errors on this driver ("Bad argument 2 to foreach: Expected array Got: 0") -- the CD driver evidently tolerated a 0-iteration loop over a non-array. This crashed every single living object's create(), silently truncating it partway through (before enable_commands()/cmdhooks_reset() ever ran) -- the underlying cause of "no commands work" being observable well before the \S3 add_action() bug was even found. Fixed with a pointerp(ids) guard defaulting to ({ }).

9. Miscellaneous fixes

10. Fragment/non-standalone files (false positives in a naive lpcc

sweep)

~45 files fail a flat, one-file-at-a-time lpcc --batch sweep but are not bugs -- they are intentionally not standalone compilation units:

A handful of additional files (std/corpse, std/workroom, std/herb, std/board, doc/workroom, secure/armageddon, secure/mbs_central) show as lpcc failures purely from a create()-time runtime crash inside lpcc's isolated single-object test harness (e.g. a restore_object() permission error in a context with no real filesystem/euid setup) with zero actual compile errors -- a previously-documented "lpcc-vs-live- driver artifact" pattern for this project; the real driver boot (which exercises the actual preload chain in the actual runtime environment) shows these compiling and loading cleanly.

WASM status (2026-08-25, another session)

Promoted wasm_status from "" to playable. pack_lib_for_web.sh initially failed outright (not a driver/compile issue): doc/examples and doc/man/Genesis were dangling symlinks pointing at ../d/Genesis/doc/... -- the very game-world domain this archive never shipped (see above) -- and emscripten's file_packager errors out trying to stat() a broken symlink's target. Removed both (pure documentation symlinks, irrelevant to boot/play) rather than fixing their targets, since the domain they point at doesn't exist in this repo at all. Once packed, the driver booted clean under WASM with no further fixes needed. Verified with a real scripted WASM session: login as fluffos/Mud@2026, the documented start-location fallback firing correctly (no /d/Genesis domain to land in), a soul command (smile) dispatching correctly (confirming the add_action() bare- function-value fix carries over to the WASM build), and a clean quit ("Saving Fluffos. Goodbye. Until next time."). One caught-and-logged (non-fatal) error during quit -- call_other() couldn't find object '/d/Web/stats/webstats' inside a catch() -- is the same class of graceful missing-domain-content fallback as the start-location one, not a new bug.

Two sibling bugs ported over verbatim from arkadia's §10.7 pass (2026-08-27)

arkadia (libs/arkadia) is a direct Polish-localization fork of this exact engine, and its own round-two deep functional test (a real, played session, unlike this lib's own short WASM smoke test above) found two live crashes in files byte-identical between the two ports. Both are confirmed present here via direct code reading and a real boot-and-kill compile check (no domain content exists in this repo to drive a full interactive repro the way arkadia's own NOTES.md does):

1. secure/master/fob.lpc's do_decay() was declared to take a single mixed *darr parameter, but decay_exp() (called periodically from check_memory(), itself on a ~900-second reset_master() alarm) invokes it via map(m_domains, do_decay) -- this driver's map() over a MAPPING always calls its function with (key, value), not value alone, so the one declared parameter silently bound to the domain NAME (a string) instead of its data array, corrupting the domain name character-by-character on every decay tick and eventually crashing with "*String index out of bounds" once the walk ran past the string's own length. Fixed by adding the missing leading string dname parameter, exactly as in arkadia. See libs/arkadia/NOTES.md and AGENTS.md §7.132 for the full live repro (reproduced there against real bootstrap domain data, since this repo's own m_domains never gets populated with anything given it ships no domain content at all). 2. std/room/description.lpc's room_descs global was declared nosave mixed room_descs; with no initializer (defaulting to 0), but long() unconditionally calls member_array(0, room_descs) on every single room look/glance -- for any room that never called add_my_desc() (the vast majority), this threw "*Bad argument 2 to member_array() Expected: string or array Got: 0" uncaught, silently truncating that room's entire description. Fixed with a = ({}) initializer, identical to arkadia's fix. See AGENTS.md §7.133.

Neither fix was exercised through a live interactive session on THIS repo specifically (there is no real room/domain content here to walk into), only via a real driver boot-and-kill compile check confirming both files still compile clean; a future deep-functional pass on any lib that eventually builds real content atop this engine (or a WASM re-test of this repo itself) should re-verify live.

Deep functional test (round two), 2026-08-27

One continuous session per AGENTS.md §10.7. This lib had never had a full round-two playthrough before (confirmed via this file having no "深度功能测试"/dated round-two heading), only the original onboarding verification plus the two sibling-fix ports above (immediately re-verified live, see below). Registered real English-named characters (Tamsworth, Zebulon, Wendlebury, each deleted before committing) through the full new -> name -> confirm -> password -> confirm flow, exercised look/stats/vitals/health/i/smile at every state change, tested quit with a debug.log grep before and after every session, and reconnected both immediately and after a real wall-clock gap. Admin login re-verified: fluffos/Mud@2026 (no x-suffix quirk needed here, unlike arkadia) still authenticates and shows WIZ_KEEPER rank. No newbie-help file exists to read first (this repo's own doc/man/general/INTRO is a wizard LPC-coding tutorial, not player-facing help — expected, given no domain content ships at all); the intended "test path" is simply the bootstrap landing room itself.

Six new, previously-undetected, live-reproduced bugs found and fixed this pass, plus the two already-ported sibling fixes re-confirmed live where this repo's content-free nature allows it, plus one genuinely unresolved observation documented honestly rather than guessed at.

Re-verification of the two already-ported sibling fixes

1. check_valid_startloc() calling two SECURITY functions that don't exist anywhere in this codebase — every mortal quit silently treated as "not a recoverable location" (AGENTS.md §7.135's write-up, different architecture than the arkadia original)

std/player/cmd_sec.lpc:134 (quit()'s own inventory-drop gate) called IN_ARRAY(env, SECURITY->query_list_def_start()) / SECURITY->query_list_temp_start()) — functions that exist NOWHERE in this codebase (already flagged, but left unfixed, in this file's own §2 "documented CD-driver graceful degradation" section, because the member_array() leniency shim happens to absorb the resulting undefined-function-call fallout as a graceful -1 rather than a crash). Fix: swapped to the real, already-existing VALID_DEF_START_LOCATION()/VALID_TEMP_START_LOCATION() macros (sys/files.h), which wrap sys/global/filepath.lpc's real valid_def_start_location()/valid_temp_start_location() functions. This is the closest genesis analogue to arkadia's own §7.135 finding (both are about quit()'s inventory-drop gate silently misclassifying a legitimate location) — but the ROOT CAUSE differs: arkadia's was a missing lazy-init guard on one sibling accessor out of several; genesis's sys/global/filepath.lpc initializes its equivalent globals eagerly in create() (no lazy-guard shape to be missing at all) and the actual bug was a wrong callee entirely. Verified live: standing in the bootstrap room and quitting no longer silently treats it as non-recoverable.

2. DEF_STARTING_PLACES never updated to include the one real starting room this port has — "SERIOUS PROBLEM with your start location" printed on literally every mortal login

config/sys/local.h's DEF_STARTING_PLACES (used by sys/global/filepath.lpc to validate a mortal's default_start_location) still lists only the five original /d/Genesis/start/<race>/... paths — none of which exist in this repo. std/player/savevars_sec.lpc's set_default_start_location() requires VALID_DEF_START_LOCATION() for any non-wizard, so it silently rejected the only real starting room this port added (/secure/login/bootstrap_room, per RACESTART) on every single mortal login — enter_game()'s first try_start_location() attempt always failed as a result (the accessor never actually got updated), printing "SERIOUS PROBLEM with your start location. You revert to your default racial start location." before falling through to the racial-default fallback (query_def_start(), called directly, bypassing the broken accessor), which happened to still succeed. Fix: added /secure/login/bootstrap_room to DEF_STARTING_PLACES, exactly the same "let login work" rationale already used for WIZ_ROOM/RACESTART in the same file. Verified live: a fresh mortal character's login no longer shows the SERIOUS PROBLEM message at all.

Residual, wizard-only: wizards bypass set_default_start_location()'s validation entirely (query_wiz_level() is checked first), so this specific fix does not change their path — see the unresolved observation below for why the seeded admin account still shows this message every login despite having a correctly-saved default_start_location on disk.

3. Missing basic player command souls — no mortal OR wizard could look, get, drop, say, or use ANY ordinary verb (AGENTS.md §7.136, new entry)

The headline bug this pass. See AGENTS.md §7.136 for the full technical write-up (root cause, the raw/secure/proto_char.o evidence trail, and the fix). Summary: setup_player() strips a mortal's entire cmdsoul_list on every login relying on the (missing) race content to grant the real set back; nothing does, so load_command_souls() fell back to the NPC-shaped NPC_SOULS default, which is missing every ordinary player verb soul. Fixed with a new DEFAULT_PLAYER_SOULS constant (secure/auto.h), sourced directly from this repo's own shipped raw/secure/proto_char.o, reseeded for both mortals and wizards. Verified live: a freshly registered mortal (Tamsworth/Zebulon/Wendlebury, all three tested) and the previously-broken seeded admin account (fluffos) both now have working look/i/stats/smile/every ordinary verb.

4. command("$...") — CD-driver quicktyper-bypass convention with no equivalent on this driver, 13 silently-dead call sites (AGENTS.md §7.137, new entry)

See AGENTS.md §7.137 for the full write-up. Summary: doc/man/efun/command documents a leading "$" as "bypass the quicktyper" — this driver's real command() has no such handling at all, so every command("$verb ...") call site silently dispatched a literal, nonexistent "$verb" and always failed. Found because the very first look after every login (start_player()'s command("$look")) always silently failed. Swept all 13 call sites (std/player.lpc, std/living/possess.lpc, std/living/move.lpc, std/player/cmd_sec.lpc's quit() drop calls, std/launch_weapon.lpc, cmd/live/things.lpc, cmd/std/tracer_tool.lpc, cmd/live/thief.lpc x5, cmd/wiz/arch.lpc), stripping the leading "$" — confirmed safe since this codebase's own quicktyper's alias-interception hook, modify_command(), is itself dead code on this driver (never called, zero references in driver source), so there was no alias-interference risk to preserve. Verified live: the login auto-look now correctly shows the room description immediately, with no manual look needed.

5. move_living()'s magic-map notification: unguarded call_other() on a 0 object on EVERY successful move, project-wide

std/player.lpc's move_living() override notifies a "magic map" item of every successful move (!result means success, per this function's own return-value convention) via magic_map-> notify_new_room(to_dest), where magic_map is set from present(MAGIC_MAP_ID) — but never falls back to anything if no such item is present (true for every move in this content-free repo, since no magic map item ships anywhere). This driver's call_other() hard- errors on a plain int(0) target ("Bad argument 1 to EFUN call_other()") rather than degrading gracefully — unlike several other missing-item call sites elsewhere in this port, this one had no objectp() guard. Confirmed live: logged in debug.log on literally the first move of a brand new character's registration, and every move after, project-wide (the existing catch() suppressed the crash but not the log noise). Fix: added an objectp(magic_map) guard before the call, matching the same pattern already used pervasively elsewhere in this port for other missing-item call sites.

6. quit()'s own skill-decay alarm outliving the just-destructed player object — a guaranteed dangling-alarm error on every mortal quit

std/player/cmd_sec.lpc's save_me() (called from quit() right before the player object is destructed) schedules set_alarm(1.0, 0.0, decay_skills) whenever query_skill_decay() is true — which it always is after the very first login, since setup_player()'s setup_skill_decay() unconditionally sets do_skill_decay = 1 for every mortal. quit() then immediately calls this_object()->remove_object(), destructing the player one second before that alarm fires — producing "*Owner (.../ghost_player#N) of function pointer is destructed", uncaught, in debug.log, on literally every single mortal quit. Same underlying shape as arkadia's own AGENTS.md-documented dangling-alarm finding (§8.5 there), but on the universal player-quit path instead of a peripheral login helper, and reproducible on every quit rather than an edge case — not yet in AGENTS.md as its own numbered entry, since it's a single narrow instance so far, not (yet) confirmed on a second lib; worth promoting to a numbered cross-cutting entry if it recurs. Fix: added skill_decay_alarm (with query_skill_decay_alarm()/ set_skill_decay_alarm() accessors, needed because std/player/cmd_sec.lpc is a text-#included fragment positioned ABOVE this variable's own declaration in std/player.lpc, so a bare reference to it is "Undefined variable" at compile time) to track the outstanding alarm id, and a remove_object() override that cancels it before deferring to ::remove_object() — a single destruction choke point that also covers any OTHER removal path, not just quit(). Verified live: a full driver restart, fresh mortal registration, quit(), and debug.log grep immediately after shows ZERO dangling- alarm errors (only the two already-documented, benign missing-content catch()-wrapped errors remain: /d/Genesis/cmd/misc at object creation time, /d/Web/stats/webstats on quit).

Unresolved observation: login_time/login_from/(for wizards only) default_start_location never actually restore from a correctly-saved value, despite restore_object() reporting success

Confirmed live, root cause NOT confirmed within this pass's time budget — documented honestly per this project's own testing standard rather than guessed at. Symptom: every single login (fresh or returning, mortal or wizard) prints "Last login at: Wed Dec 31 16:00:00 1969" / "Last login from: 0" — the Unix epoch defaults — even immediately after a session that demonstrably set_login_time()/ set_login_from()'d real values and saved cleanly (confirmed via direct inspection of the .o file on disk: login_time 1787852608/login_from "localhost" are genuinely present and correct). Added temporary write() debug instrumentation directly after load_player()'s own ret = restore_object(PLAYER_FILE(pl_name)) call (removed before committing) and confirmed: restore_object() returns 1 (success) but query_default_start_location()/ query_login_from()/query_login_time() are ALREADY empty/0 immediately afterward, on the SAME line, before any other code has a chance to touch them.

This is NOT visible on mortal accounts for default_start_location specifically, because enter_game()'s own logic unconditionally resets that ONE field for non-wizards regardless of what restore produced (masking the failure) — it stays visible for login_time/ login_from on EVERY account (nothing resets those) and for default_start_location on wizard accounts specifically (the reset path is !query_wiz_level()-gated). This is why the seeded admin account (fluffos) still shows the "SERIOUS PROBLEM with your start location" message on every single login even after fix #2 above — the .o file's default_start_location is correct, but never actually gets restored into the live object before the check runs.

Best working hypothesis (NOT confirmed as the actual mechanism): std/player/savevars_sec.lpc declares default_start_location as the 7th name in a single long comma-separated private string a, b, c, ...; statement, following several fields (player_file, path, mailaddr, adj_desc) that are never set for a plain bootstrapped character and therefore never appear as lines in the saved file at all (this driver's save_object() omits fields still at their compile-time default) — a "sparse" middle of that declaration list. login_from/login_time sit in similarly-shaped later declarations. Every field BEFORE the first gap (name, password) restores correctly; every field tested AFTER a gap does not. This is consistent with (but not proven to be) a #pragma save_binary restore-side quirk on this driver where a sparse/gapped save file causes later name-keyed lines to misalign against the variable table — as opposed to a bug in this port's own code, since the .o file's own content is unremarkable, well-formed, and byte-for-byte what a human would expect to see.

Why this is flagged as an observation rather than "fixed": (a) no gameplay-relevant data loss was ever observed — race_name, cmdsoul_list, learn_pref, acc_exp (all declared in a DIFFERENT file, std/living/savevars.lpc, the base class rather than the player-specific fragment) restore correctly and consistently across every tested session; (b) the affected fields are cosmetic/display-only (a "Last login" banner) except for the wizard-only SERIOUS PROBLEM message, which is itself harmless and self-healing via the existing fallback chain; (c) arkadia (the sibling engine, byte-identical savevars_sec.lpc) does not report this same symptom in its own NOTES.md after its own §7.135 fix, suggesting this may be specific to some other, not-yet-identified difference in genesis's own file layout or save history rather than a generic engine/driver bug — guessing at a fix here risks a worse, harder-to-diagnose regression than leaving it alone. Worth a focused follow-up pass specifically tracing restore_object()'s behavior against a deliberately constructed minimal test object with the same "sparse gap in a multi-variable declaration" shape.

Standing cross-cutting patterns checked systematically

Shop/economy/combat/guild — unreachable, unchanged from onboarding

Same as arkadia's own honest disclosure: this repo ships no domain content at all, so shop/economy, combat, and guild/skill acquisition remain explicitly UNVERIFIED this pass — not something this pass's driver-bugs-only scope permits fixing, and there is no real content anywhere in this repo to reach them through. The bootstrap landing room (secure/login/bootstrap_room.lpc) is the only room; no NPCs, no items, no shops exist to test against.

Verification summary