info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
A classic, from-scratch English-language MudOS mudlib whose own commit history dates to 1998 -- the oldest-vintage lib onboarded into this collection so far. A full game world, not a bare engine: character creation drops new arrivals into the "Hall of Creation," where a one-time `become <race>` command permanently picks one of 12 playable races (Drow, Duergar, Dwarf, Elf, Gnome, Goblin, Half-Elf, Half-Orc, Halfling, Human, Lizard-Man, Orc) before sending them to that race's own dedicated newbie zone -- real per-race taverns and shops, not a shared generic starting room. Five character guilds (Cleric, Fighter, Mage, Rogue, Warrior) round out the RPG progression, with the Mage guild alone branching into nine D&D-style specialist schools (Abjurer, Conjurer, Diviner, Enchanter, Illusionist, Invoker, Necromancer, Transmuter, and a general path), plus a full wizard-building toolset for creators.
README
Final Realms (source: <https://github.com/quixadhal/fr>) is a classic, from-scratch English-language MudOS mudlib -- the last commit in its own history dates to 1998, making it the oldest-vintage lib onboarded into this collection so far. It's a full game mudlib, not just an engine: race selection (12 playable races -- Drow, Duergar, Dwarf, Elf, Half-Elf, Gnome, Goblin, Halfling, Human, Lizard-Man, Orc, Half-Orc), a real starting-zone "newbie" world per race, guilds, shops, taverns, and a full wizard toolset.
Highlights
- Being MudOS-native (not a CD-driver codebase like some other libs in this collection), the compat gap with FluffOS was narrower in *kind* -- no
&operator(...)/@composition syntax, no arbitrary uid:euid string-pair model -- but the 1998-era archive itself carried more incidental bitrot: a legacystatustype keyword (an old LPmud alias forint) used throughout with no FluffOS equivalent, anadd_item()/add_feel()pair declared narrower (string) than 1000+ live call sites actually pass (arrays), a missing base case for a::-chained save-data accessor quartet used by every weapon/armour object, and several newbie zones missing directory-local path macros or per-domain error-log directories (the latter was silently masking hundreds of *other* real errors behind a secondary "can't open the log to report this" crash). SeeNOTES.mdfor the full list. - The single most severe bug found: this driver build was compiled with the older, single-efun
OLD_EDeditor interface, but this codebase's editor glue (global/new_ed.lpc,#include-fragmented directly into the player object) was written against FluffOS's newered_start()/ed_cmd()/query_ed_mode()API, none of which exist in this driver binary. Left unfixed, this would have broken compilation of the *entire player object* -- no one, wizard or mortal, could have connected at all. Rewritten against the realed()efun (with aquery_ed_mode()simul_efun approximated via thein_edit()efun this build does have). SeeNOTES.md\S2. - A second, quieter but mud-wide bug:
secure/gods.lpc/secure/lords.lpcnever actually defined thequery_gods()/query_lords()functionssecure/master.lpccalls on every boot to build its core admin- permission list -- silently breakingquery_lord()/high_programmer()for every account except the archive's own pre-blessedgodcharacter (which carries admin status directly in its save file, independent of this bug). Fixed; seeNOTES.md.
Registration flow
name -> confirm (y/n) -> password (real validation, hit live during
testing) -> confirm password -> gender (male/female) -> drops into the
"Hall of Creation" -> become <race> (one-time, permanent) -> the actual
game world. Name rules: 3-11 characters, letters only (no digits).
Admin account
- id:
fluffos - password:
FluffOS2026Admin - rank: god (added to
secure/gods.lpc'squery_gods()list, then promoted in-game withpromote fluffosrun as the archive's own pre-existinggodaccount)
Registered through the normal registration flow first (as a mortal, so a
real player save file exists), then promoted via the in-game promote
command (which itself only works because of the gods.lpc fix above).
Verified live: update /secure/simul_efun (the canonical ACL check, since
it exercises both read and compile permission) succeeds for fluffos.
The archive's own original admin account, god / password god (per the
game's own first-boot banner), also still works and was used to perform
the promotion above -- both accounts have god-level access.
Status
Boots clean: zero compile errors and zero uncaught runtime errors in
log/errors/*.err / the per-domain d/*/log/debug.err files across
repeated fresh-driver boots. Full registration (a brand-new character all
the way through race selection into the actual game world), look/
score/inventory/quit, re-login as an existing character, and wizard-
level update/promote all verified end-to-end with a real driver
session and a raw socket client.
A large compile-sweep tail remains beyond the fixes above (see NOTES.md
\S3 for the full breakdown) -- the overwhelming majority is either
genuinely dead/unreferenced legacy content shipped in the archive
(confirmed via repo-wide reference search before excluding, not guessed),
template files read as raw text rather than compiled, #include-fragment
files that only compile as part of their aggregator, or a repeatable
lpcc-batch-harness-only artifact (a per-monster call_out nesting
counter that only accumulates because lpcc --batch loads hundreds of
objects with no event-loop yielding between them -- confirmed absent on a
real driver boot). None of it blocks a real boot or blocks play.
WASM status: playable. Login as god, look, and score are
verified under the shared WASM driver (NOTES.md WASM status update).
Play: https://mudlibs.fluffos.info/finalrealms/
Local run
cd libs/finalrealms
~/src/fluffos/build-debug/src/driver config.fluffosGame port: 40216.
NOTES · 移植与修复记录
Final Realms -- porting notes
Source: git clone https://github.com/quixadhal/fr (commit 16308bd, cloned
2026-08-25). Slug finalrealms, number 168, port 40216. Mudlib root is the
clone's mudlib/ subdirectory (mudos/ alongside it is a bundled v21.7b21
MudOS driver source tree, ignored -- this project uses its own driver).
Original repo history stops in 1998 (git log -1 on the clone shows a
1998-11-10 commit date), making this the oldest-vintage lib onboarded this
session -- a genuine late-generation classic MudOS mudlib (not a CD-driver
codebase like genesis), so the compat gap with FluffOS was narrower than
genesis's in kind (no &operator/@ composition, no uid:euid string-pair
model) but the archive itself had more incidental bitrot/lost-content gaps.
1. Conversion
scripts/convert_lib.sh on raw/mudlib -> work: all-ASCII source (0
lossy conversions except 4 pre-existing corrupted bytes in two duplicate
ave8.c files and two already-empty intermud.o save files), 3273 files
renamed .c->.lpc, 2933 literal .c" references fixed, 6 local
angle-bracket includes converted to quotes, 7 files static->nosave.
2. Compile-time driver-compat fixes
switchwith onlydefault:(AGENTS.md §6.3):secure/simul_efun/ mud_long_name.lpc'sswitch (mud_name()) { default: ... }-- collapsed to a plainreturn.statusas a legacy type keyword: this MudOS-era codebase usesstatus(an old LPmud alias forint, signalling boolean intent) as a return/parameter type throughout -- FluffOS has no such type at all, so every use was a hard "unexpected L_IDENTIFIER" parse error. Swept\bstatus\b->intacross the 16 files that used it as a genuine type (verified none of the ~90 otherstatushits repo-wide are type positions -- the rest are English prose in comments/strings/identifier substrings like"ansi-status", left untouched).add_item()/add_feel()declaredstring, called with an array:std/room.lpc'sadd_item(string str, string desc)andstd/senses.lpc'sadd_feel(string feel, string feel_desc)were both declared narrower than the type their own bodies actually branch on (setup_item()/feels[]handling already usedpointerp()/mixedinternally) -- 1200+ and 190+ call sites respectively pass({"a","b"})-style arrays (the documented "give many items the same description" idiom). Widened both tomixed.add_feel()specifically had a pre-existing internal inconsistency: its own forward declaration already saidmixed, only the definition saidstring.- Missing base case for the
int_query_static_auto_load/query_dynamic_auto_load/init_static_arg/init_dynamic_arg::-chain:obj/weapon.lpc,obj/armour.lpc,std/chest.lpc,obj/label.lpceach define these and call::-qualified versions of themselves to walk up to a parent's saved state, but no ancestor anywhere in this codebase ever defined a base case -- "Unable to find the inherited function". Added no-op/empty-mapping terminators tostd/item.lpc(the common ancestor). condition::create()calling a function that doesn't exist:obj/weapon.lpc/obj/armour.lpc'screate()both callcondition::create(), butstd/basic/condition.lpcnever defined one. Added a no-opcreate()there.- 2-arg integer
exp(base, exp)power function:obj/weapon.lpc'sset_value()callsexp(2, enchant-1)expecting this codebase's own historical integer-power helper (std/basic.old/misc.lpc'sint exp(int x, int y), "added by dank Feb 23, 93") -- but that file is in the dead.oldtree, not part of the live inherit chain, and this driver's realexp()efun is the 1-arg float natural exponential. Added a smallprivate int int_pow(int x, int y)helper directly inobj/weapon.lpc(the only live call site) rather than reviving the dead file. lowest_condused but never declared:obj/weapon.lpc's andobj/armour.lpc'squery_dynamic_auto_load()/init_dynamic_arg()both read/write alowest_condglobal that was never declared anywhere in either file -- "Undefined variable". Declaredint lowest_cond;in both (342 downstream call sites across 133 weapon/armour-derived files were affected by theobj/weapon.lpchalf alone).- Missing
ROOM/other path macros in several newbie-zonepath.hheaders:d/newbie/grads/rooms/path.hand itscave1/sibling never definedROOM(every other zone'spath.hdoes, e.g.d/newbie/elf/ rooms/path.h) even thoughadd_exit(...)calls in that zone's rooms use it;d/newbie/new_halfelf/rooms/path.hhadROOMSbut notROOM(guild.lpc/shop.lpcuse the singular).d/newbie/grads/npcs/andd/newbie/grads/temp/had nopath.hat all ("Cannot #include path.h"), as didd/newbie/newelf/(top level) andd/newbie/newelf/npcs/. Added/ extendedpath.hin each, mirroring the sibling that already had one;grads/npcs/wizard.lpc'sOBJ+"fr_arrow_learnable.lpc"clone target doesn't exist anywhere in this archive (lost content, not this port's doing) --OBJitself still needed a definition to compile, so it's pointed at this zone's own (currently nonexistent)obj/directory, matching the convention other zones' path.h files use; the clone simply fails gracefully at runtime exactly as it would have upstream. - Missing per-domain
log/directories:secure/master/error_handler. lpccomputes"/d/"+domain+"/log/debug.err"for any uncaught error inside domain code, but NONE of the four domains (mudlib,vehicle,newbie,heaven) shipped alog/directory (newbiehad alogs/, plural, which doesn't match) -- every domain-code runtime error crashed the error handler ITSELF while trying to log the original error ("Wrong permissions for opening file ... for append", "No such file or directory"), masking the real error behind a secondary one. This is a previously-documented "lpcc-vs-live-driver artifact" pattern for this project, but it also affects a REAL boot (any domain-code runtime error, not just lpcc), so it's a genuine structural gap, not just a sweep artifact. Createdd/{mudlib,vehicle,newbie,heaven}/log/(with.gitkeep). - **
d/heaven/heaven/*rooms inherit/std/roomdirectly instead of/std/shield_room**: every admin-zone room in this directory (ave1throughave16,admin2,admin3,entry,ntower1,palace1,palace2) callsshield_it(SHIELD, ...)ininit(), butshield_it()is only defined in/std/shield_room.lpc(itself a thin wrapper around/std/room) -- a pre-existing content bug (wrong base class), not something this port introduced, but compile-blocking regardless. Swapped theinheritin the 19 live files (room/admin/heaven.nothere/*has an identical-looking but entirely unreferenced duplicate set -- see \S4 -- left untouched). std/newbieguild.lpc'sreset()crashing every newbie-guild room:clone_object("/obj/misc/board")can return 0 (confirmed underlpcc's isolated single-object test harness, a previously-documented artifact of that harness lacking a real preload/euid context), and the followingboard->set_datafile(...)then hard-errors oncall_other()againstint(0), taking down every/std/newbieguild-derived room'screate()(~20 live guild rooms across the newbie zones). Guarded withif (board).std/pub.lpc'screate()crashing every tavern: unconditionally callsBAR_TRACKER->add_bar(...)whereBAR_TRACKERis/d/aprior/ guilds/bard/tools/bar_tracker.lpc-- the "aprior" bard-guild domain never shipped anywhere in this archive (lost content), so this crashedcreate()for all 20+ live/std/pub-derived taverns with "call_other() couldn't find object". Wrapped incatch()so the (already-lost) songster-quest bar tracking silently no-ops.protected private(multiple access modifiers):secure/crerem/ remote.lpc'seventProcess()declared bothprotectedandprivatetogether -- every sibling function in the same file usesprotectedalone; dropped the redundantprivate. This file IS in the live preload list (master.o's savedpreloadarray includes/secure/ crerem/remote), so this one mattered for a real boot, unlike most of the daemon-directory syntax errors found during the sweep (see \S4).query_ed_mode()/ed_start()/ed_cmd()not available in this driver build: this codebase's editor glue (global/prompt.lpc,global/ process_input.lpc,global/new_ed.lpc) was written against FluffOS's "new" ed API (ed_start/ed_cmd/query_ed_mode, declared inpackages/ core/core.spec's#elsebranch), but this project's compiled driver binary was built withOLD_EDdefined (confirmed vianmon the driver:f_ed()is present,f_ed_start()/f_ed_cmd()/f_query_ed_mode()are not) -- meaning only the older single-efuned()API is actually available. This broke compilation ofglobal/player.lpcitself (new_ed.lpc/process_input.lpcare#include-fragments into it, not separate objects), which would have meant no player could connect at all -- easily the most severe single gap found in this port. Fixed in three parts:
- secure/simul_efun/query_ed_mode.lpc (new): a simul_efun using the
in_edit(object) efun (which IS available in this build) to
approximate query_ed_mode() -- returns 0 while genuinely editing,
-1 otherwise. Loses the driver's finer sub-states (the -2 "more"
pagination prompt, per-line insert-mode prompts) but preserves the one
behavior actually load-bearing across the codebase: the != -1 gate
that routes input to the editor instead of normal commands.
- global/new_ed.lpc's begin_editing() rewritten against the real
ed(fname, exitfn_string, restricted) efun. ed()'s exit-function
argument is a plain string (a function *name*), not a closure, so a
new __ed_exit_dispatch() entry point was added as that name, which
then invokes the actual function closure callers already pass
(preserving every existing caller's API, e.g. global/line_ed.lpc's
(: editor_finish_ed :)). One convenience lost: the original used
ed_cmd("$a") right after starting to drop mortals/empty-file edits
straight into insert mode; there's no LPC-level way to feed the
just-started real ed() session a synthetic command, so editing now
always starts at ed's normal command prompt. Low-severity: ed is
documented in global/line_ed.lpc as an explicit opt-in advanced
editor ("Please do not set your editor to ed unless you know what you
are doing"), not the default player editing experience (that's a
separate, unaffected pure-LPC "menu"/"command" editor in the same
file).
- global/process_input.lpc's ed_cmd() call site (the branch that
forwarded raw input to an in-progress ed session) neutralized to a
defensive error message -- under the real ed() efun the driver
handles an entire editing session internally (this apply isn't even
invoked while genuinely editing), so this branch can't actually fire
in practice, but it still needed to compile.
secure/gods.lpc/secure/lords.lpcmissingquery_gods()/query_lords():secure/master.lpc'screate2()calls"/secure/gods.lpc"->query_gods() + "/secure/lords.lpc"->query_lords()on every boot to build thegodsarray thatquery_lord()/high_programmer()(the mud's core admin-permission checks) test against -- but neither file ever defined those functions (only an unrelated per-name flavour-text lookup,query_boo()). This silently broke everyquery_lord()/high_programmer()check mud-wide (acall_other()to an undefined function returns 0, not an array, sogodsnever gained any entries). The boot banner itself instructs new installs to log in asgod/godas the first admin, and the archive's shippedplayers/g/god.oalready carriescreator 1and inherits/global/god.lpc(a real, working god-rank player object) independent of this bug, which is why testing asgoddidn't immediately surface it -- butpromote,update all to <domain>, and everything else gated onquery_lord()for accounts *other* than the pre-blessedgodwere all silently broken. Addedquery_gods() { return ({ "god", "fluffos" }); }(this project's admin account, see the README) andquery_lords() { return ({ }); }(no lord names survive anywhere in this archive -- the commented-out example inlords.lpcis template text, not lost content).secure/master.o's savedpreload/call_out_preloadarrays used literal.cpaths:/global/thane.c,/global/patron.c,/global/ god.c,/net/intermud3/intermud.c,/global/do_chat.c,/net/ who_server.c--convert_lib.sh's literal-.c-reference fixup only scans.lpc/.h*source*, not.osave-file *data*, so these six preload entries (saved player/master state, not code) were missed and failed to load every boot ("call_other() couldn't find object") -- caught bymaster.lpc's owncatch()around eachpreload()call, so non-fatal, but meant the thane/patron/god persona objects, Intermud-3, do_chat, and the who-server never actually preloaded. Stripped the stray.csuffix from all six entries directly insecure/master.o. A handful of individual shop rooms' own save files (d/newbie/*/rooms/ shop.oand similar, 6 files found viagrep -rlfor a quoted.cstring in*.o) have the same class of stale reference in their saved-inventory item paths -- much lower-impact (per-room shop stock, not the boot-critical preload chain) and left unfixed given the time budget; flagged here for anyone doing deeper content testing later.
3. Compile-sweep summary
scripts/lpcc_check.sh against the full 3273-file tree: started at 0/3273
(master/simul_efun themselves failed to compile -- the status-type and
switch-only-default bugs above), reached 2400/3273 (73%) passing after the
fixes above. The remaining ~870 failures break down as:
- Dead/unreferenced legacy content (verified via repo-wide
grepfor any live reference before excluding, same methodology asgenesis's \S10):releasefiles/(a ~60-file backup snapshot ofsecure/,d/ mudlib,d/newbie,d/heaven,w/duplicating already-live content byte-for-byte where diffed, referenced from nowhere live); every*.olddirectory and*/old/*path (std/basic.old,cmds/creator.old,net/ old,std/room/old, etc.);std/living/{dankequip,divstuff,baldy}/(three complete, mutually-exclusive alternate equip/skills implementations -- the live one is the extensionlessstd/living/ living.lpc, confirmed viaobj/monster.lpc's actualinherit, and zero files outside these three directories reference any of them);std/adnd/(an alternate D&D-stats race system, zero live references);net/daemon/(gopher/www/mail-queue/remote-post internet daemons -- absent from bothpreloadandcall_out_preloadinmaster.o, and zero live references outsidenet/old/udp/, itself already dead);room/admin/heaven.nothere/(ad/heaven/heaven/-shaped duplicate, the "nothere" name matching its actual status -- zero live references). - Template/fragment files, not standalone compilation units (same category
genesis's \S10 documented):std/creator/workroom{,2}.lpcandstd/dom/{master,common,loader}.lpcareread_file()d as raw text bysecure/master/create_dom_creator.lpcand written out per-new- wizard/domain with a#defineprepended -- never compiled at their own path (std/dom/{com,domain_mas}.lpcare unreferenced older variants of the same idea, dead per the point above). 95 files across 17 aggregators (secure/master.lpc,secure/simul_efun.lpc,global/player.lpc,net/intermud3/services.lpc, and others) are#included as text fragments into their aggregator and compile fine as part of it but lack standalone context in a flat per-file sweep. - A repeatable
lpcc-harness-only artifact, not a real bug:obj/ monster.lpc'sset_level()->init_command("init_race")->call_out("do_command", 0, ...)->command("init_race")chain issues a delay-0call_outon every single monster'screate(). A real driver's event loop actually executes each pendingcall_outbetween object loads (unwinding the nesting depth);lpcc --batchloads hundreds of monster objects back-to-back in one continuous process with no event-loop yielding, so the nesting depth accumulates *across unrelated objects* until it trips the driver's hard 1000-level nesting cap ("Nesting call_out(0) level limit exceeded"). Confirmed via the per-domaind/newbie/log/debug.errthis session's \S2 log-directory fix made visible for the first time -- every affected NPC shows the identical 7-frame trace, and the live driver boot (\S5) shows zero such errors across the full registration/play test. This is the same documented "lpcc-vs-live-driver artifact" class as \S2's log-directory point andgenesis's \S10 -- explains the large majority of thed/newbie/*/npcs/*and*/monsters/*"Fail to load object" entries with no visible compile error. - A handful of genuinely broken string literals:
d/newbie/half-elf/ newroomss/{F5,v5}.lpcand a couple of siblings have literal stray\characters outside any string ("Illegal character '\\'") -- looks like archive-era authoring/transcription corruption in this one small subdirectory (~10 files), not something introduced by this port. Left as a documented content gap rather than hand-reconstructing each string. - The remainder is ordinary individual room/NPC content bugs (a broken
int as[NUM_AS]array-size declaration in the already-deadstd/adnd/, isolatedUndefined functions for guild-specific verbs likeadd_main_skill/ChangeAbilityScorein the dead skills variants, etc.) -- logged here as scope, not fixed exhaustively, per this project's standing "programming bugs only, not content" rule; deeper content testing can revisitlibs/finalrealms/lpcc_fail.logif desired.
4. Boot and play test
Booted ~/src/fluffos/build-debug/src/driver config.fluffos (memory-capped
per this project's own past-incident precaution) repeatedly; zero compile
errors on every boot after the fixes above, Initializations complete. and
accepting connections on 40216 every time.
Verified via a raw Python socket client (this project's established method), end to end:
- Existing account (
god/god, shipped in the archive withcreator 1already saved): logs in, lands in/room/entryroom,look/score/quitall produce correct output, clean save on quit. - Brand-new character registration (name -> confirm -> password -> confirm password -> gender -> race selection): a fresh name registers all the way through
become humaninto the actual game world (the "Entry Hall"),look/score/inventory/quitall correct. Name validation is real and was hit live during testing (max 11 chars, min 3, letters only -- no digits). - Wizard command access:
promote <name>(run asgod, itself only possible after the \S2gods.lpcfix) andupdate <path>(the canonical ACL check, exercising both read and compile permission) both verified working for the project's admin account.
Zero uncaught runtime errors logged anywhere (log/errors/*.err,
d/{mudlib,vehicle,newbie,heaven}/log/debug.err) across this whole
session's boots and play-testing.
WASM status update (2026-08-25, another session)
Promoted wasm_status from "" to playable. Hit a genuine, real
(non-test-artifact) bug: secure/login.lpc's logon() has a hardcoded
if(uptime() < 20) { ... refuse connection ... } startup-grace gate.
This project's own WASM deployment boots a fresh in-browser instance
per visitor, so EVERY real player would hit this on EVERY page load
(not just an admin reconnecting moments after a manual reboot, which
is what the check is actually for) -- per AGENTS.md's standing policy
on legacy connection-time gates, bypassed for loopback:
query_ip_number(this_object()) != "127.0.0.1" && uptime() < 20
(current WASM builds correctly report 127.0.0.1, confirmed via
AGENTS.md's own IP-format section).
Once past that, booted and played clean: login as god/god, look
and score both producing correct output matching native testing.
Also found (documented, not fixed -- non-blocking, same class as
ds386's optional network tools): /net/identd.lpc (an ident-protocol
lookup daemon) fails to compile on connection due to socket_error()/
socket_address() being undefined (no sockets package on this
driver build) -- caught gracefully, game continues normally, not on
the boot/login/play path. quit wasn't recaptured in this WASM
transcript but is already verified clean under native testing above
and untouched by either fix.
5. Deep functional test (round two, 2026-08-27)
First full §10.7 round-two pass on this lib (confirmed via grep: no prior
深度功能测试/dated round-two heading existed). One continuous session,
English-named test characters per this lib's own naming convention
(quillfrost, brackenfell, plus throwaway testerbrave/brytewind
during earlier diagnosis -- all four deleted from players/ before this
commit, keeping only the seeded god account). Booted
~/src/fluffos/build-debug/src/driver config.fluffos repeatedly via a raw
Python socket client; killed each instance by exact PID when done.
Newbie help read first: help advance (skill-training syntax) and
help fighter (guild locations/requirements) -- confirmed guild training
(advance <skill>) is gated on physically being inside a guild hall, not
available from the open newbie zone, matching the help text's own
"Location: various" framing; not a bug, just meant travel wasn't budgeted
this pass (see "not reached" list at the end).
Six real, confirmed, and fixed bugs, found in this order:
5.1 room/raceroom.lpc: every new character of every race was funneled into a generic admin/coding-school hub instead of their own race's fully-built starting room, and permanently got zero starting equipment
do_become()'s success branch had startplace = "/room/entryroom.lpc";
(with the ORIGINAL, correct me->move("/room/start/"+race); commented out
immediately above it) followed unconditionally by
startplace->add_equipment();. /room/entryroom.lpc (an
FR:Illumitech-branded admin/mudlib-coding-instruction hub, exits only to a
meeting-room and post office) never defines add_equipment(), so that
call_other silently no-op'd -- and since entryroom.lpc has NO exit into
any of the 12 fully-built d/newbie/<race>/ zones this project's own
onboarding notes describe, EVERY new character was also permanently cut
off from all of that content. room/start/<race>.lpc (all 12 races have
one) is a real, still-fully-implemented starting room with its own
add_equipment() (or, for drow/duergar, add_clone() calls directly in
setup()) AND a real exit into that race's own newbie zone -- confirmed
this predates the port (byte-identical in raw/mudlib/room/raceroom.c),
not something this project's conversion introduced. Fix: restored
startplace = "/room/start/"+race; (this project's usual
scope: a hardcoded override + wrong call_other target defeating an
already-fully-implemented feature, not a content/design call -- the
original commented-out line proves the intended behavior). Verified live:
a fresh human character now lands in "Human entry room" (a real cottage
room with leave/south/west exits, leave reaching
d/newbie/human/rooms/t05.lpc) instead of the generic hall, look
correctly shows "Torch. Cloak. Dagger." lying on the floor, and get
all/i correctly picks them up ("Carrying: Dagger. Cloak. Two Torches."
-- the extra torch is leftover from repeat testing in the same shared
room instance, not a bug).
5.2 obj/misc/torch.lpc: a broken comment (/ where /* was meant) hard-failed this file's compile, silently breaking every race's starting-equipment torch (and any other torch clone) project-wide
Line 43: / Will try add_action (missing the second *) -- a genuine
pre-existing archive bug (confirmed present in raw/mudlib), not a
porting artifact; it was already a known FAIL in this lib's own
lpcc_fail.log from onboarding but wasn't reachable/prioritized then.
This is exactly why 5.1's add_equipment() fix alone wasn't enough: even
with routing fixed, clone_object("/baseobs/misc/torch") (called from
EVERY race's add_equipment()) returned 0, throwing "Bad argument 1 to
EFUN call_other()" the moment it tried boo->move(this_object()) on the
failed clone. Fix: restored the missing *. Same missing-star typo
also found and fixed in obj/misc/book.lpc (6 sites, an otherwise
unreferenced/dead file, fixed for completeness), d/heaven/heaven/
meeting.lpc, and room/admin/site_control.lpc (reachable via
d/heaven/heaven/admin3.lpc) -- a corpus-wide grep -n '); / [a-z]'
sweep found no further live instances. Added as new AGENTS.md §7.146
(a genuinely new bug class, not covered by any existing entry). Verified
live: update /baseobs/misc/torch now compiles clean; the full 5.1
equipment flow above is the live end-to-end proof.
5.3 global/player.lpc heart_beat(): an abrupt disconnect crashed with an uncaught runtime error on every single heartbeat tick, forever (or until a delayed self-quit eventually finished) -- AGENTS.md §7.130, new confirmed instance with an added nuance
The old inline idle-kick else block had been commented out at some point
in this codebase's history, but the comment's own boundaries left
last_command = time() - query_idle(this_object()); stranded INSIDE the
if (!interactive(this_object())) branch instead of removing/relocating
it -- an interactive-only efun called unconditionally on a path already
known to be non-interactive. Reproduced live via a genuine abrupt TCP
close (no quit sent): log/errors/no_object.err immediately began
accumulating "*Bad argument 1 to interactive() Expected: object Got: 0."
entries (see 5.3a below for why it's interactive(), not query_idle(),
in the final fixed version) roughly once per heart_beat tick, indefinitely
-- this project's own scattered leftover test-character disconnects from
earlier in this exact session had already been silently spamming this
error the whole time before it was noticed. Fix: guard with
interactive(this_object()), matching AGENTS.md §7.130's established
pattern exactly.
5.3a -- second-order crash the naive §7.130 fix exposed: guarding with
only interactive(this_object()) was not sufficient here. This lib's
quit() calls really_quit() SYNCHRONOUSLY (immediate dest_me()) for a
non-fighting player, so the vulnerable line can be reached in the SAME
heart_beat() tick as the object's own destruction -- and this_object()
reads back as literal int 0 once already destructed, so interactive(0)
itself THROWS rather than returning false, reproducing an equivalent
crash one line downstream. Fix: added an objectp() guard ahead of
interactive(). Verified live: a fresh abrupt-disconnect repro, left
running across a 20+ second wait (multiple heartbeat ticks), produced
zero further errors, and the netdead body was confirmed reaped (who
correctly shows only currently-connected players, the disconnected body
no longer lingers). Extended AGENTS.md §7.130 with this nuance.
5.4 baseobs/monsters/healer.lpc / ~20 race raiserooms: the entire death/resurrection ("raise") mechanic was silently broken for every race except the one using this NPC base's own default name -- new AGENTS.md §7.144
healer.lpc's setup() unconditionally self-named via set_name("james")
at clone time; /obj/monster.lpc's set_name() is a one-shot setter
(no-ops once name is already non-default). ~20 per-race raiseroom files
clone this SAME base object once as a persistent "nurse" NPC and then try
helper->set_name(<real name>) in their own reset() (e.g.
d/newbie/human/rooms/v05.lpc renames it to "june") -- every such rename
was a complete no-op, so find_match(nurs, this_object())/find_living()
lookups keyed on the intended name permanently failed, even though the
NPC was visibly present under that display name (set_short() isn't
gated the same way). Root-caused live via debug_message() tracing
(log_file() would have needed elevated euid this room's object doesn't
have -- see AGENTS.md §7.129's own note on this exact gotcha). Confirmed
via grep: 20 raiserooms across the whole newbie-zone corpus clone
baseobs/monsters/healer.lpc; at least 12 rename it to something other
than "james" (d/newbie/newliz/rooms/raiserm.lpc, newelf/rooms/
raiseroom.lpc, newken/rooms/raiseroom.lpc, elf/rooms/raiseroom.lpc,
human/rooms/v05.lpc, half-elf/rooms/castle/d1.lpc, dwarf/{newrooms/
raiserm28,rooms/raiserm25}.lpc, new_halfelf/rooms/h1.lpc, guests/
rooms/raiseroom.lpc, lizard/rooms/raiserm.lpc, halfling/rooms/
raiseroom.lpc) and were all affected. Fix: removed the self-naming
set_name("james") call from healer.lpc's own setup(); the one caller
that relies on the "james" default without ever renaming it
(std/raiseroom.lpc's do_raise(), which clones a disposable, never-
looked-up-by-name temporary priest) now explicitly calls
priest->set_name("james") itself. Verified live end-to-end on the human
zone: a test character (quillfrost) was killed by the "june" NPC, walked
(as a ghost) to the raiseroom, and raise me now correctly runs the full
sequence -- "The healer raises his hands...", "You reappear in a more
solid form.", "Saving...", the full nurse/priest dialogue -- and a
subsequent score shows the character alive again with real HP. Before
the fix, raise me produced zero output and left the character
permanently dead.
5.5 Five race-zone shops: buy/sell were completely dead (silent no-ops) because a broken "is the shopkeeper here" pre-check was wired as a direct verb override instead of through the base class's own dedicated hook -- new AGENTS.md §7.145
std/shop.lpc (the shared shop base) exposes set_open_condition(mixed)
specifically so a subclass can gate buy()/sell()/list()/value()/
browse() (all of which check it internally via test_open()) on a
custom precondition. d/newbie/human/rooms/v02.lpc (and 4 siblings:
newelf/rooms/shop.lpc, newliz/rooms/shop.lpc, newken/rooms/
shop.lpc, halfling/rooms/shop.lpc) each wrote a do_check() matching
that exact 0/1 contract, with the correct set_open_condition("do_check")
call sitting right there in setup() -- but COMMENTED OUT, replaced with
add_action("do_check","buy"); add_action("do_check","sell"); in
init(). Since a subclass's own init() registrations are tried before
an inherited base class's same-verb registration, do_check() always won
and, since it return(1)s unconditionally whenever the shopkeeper is
merely present, completely swallowed every buy/sell (silent success,
nothing exchanged) -- std/shop.lpc's real buy()/sell() never ran at
all. d/newbie/half-elf/rooms/town/b3.lpc has the identical broken shape
but already fully commented out (dead, unreachable) -- left untouched.
Root-caused live via debug_message() tracing after noticing list
worked (not registered via the broken do_check at all) while buy/
sell/value all produced zero output. Fix: deleted the shadowing
add_action pair, restored the commented-out set_open_condition
("do_check") call, in all 5 live-affected files.
5.5a -- second, independent bug this one was hiding behind:
newelf/rooms/shop.lpc's do_check() checked present("Old woman"), but
this room's actual shopkeeper NPC (chars/geldon.lpc) is named "geldon"
-- a copy-paste leftover from the generic "Old woman" shop template used
elsewhere. Fixed the string to match. newliz/rooms/shop.lpc and
newken/rooms/shop.lpc clone/reference a shopkeeper NPC that doesn't
actually exist in this archive at all (d/newbie/newliz/npcs/woman.lpc
is missing; newken/rooms/shop.lpc never clones anyone) -- a genuine,
pre-existing missing-NPC CONTENT gap, left untouched per this project's
scope (documented with a code comment at each site); both shops will now
correctly and honestly report "shopkeeper not present" instead of
silently doing nothing, which is the right outcome for a real content
gap.
5.5b -- a second, ALSO independent case-sensitivity bug, found only
after 5.5's fix made it newly visible: v02.lpc's (and the other
"Old woman" shops') present("Old woman") used the NPC's capitalized
DISPLAY name (set_short()), not its actual lowercase id()/name
(set_name("old woman")) -- id() on this driver
(std/basic/id.lpc) is a plain case-sensitive == compare, so even after
5.5's dispatch fix, EVERY shop command (including list/value/browse,
which don't go through do_check directly but do share the test_open()
gate) reported "shop closed" until this string was lowercased to match.
Fixed in v02.lpc and halfling/rooms/shop.lpc (the two live shops using
the real "old woman" NPC). Verified live, full end-to-end transaction on
d/newbie/human/rooms/v02.lpc (admin-granted test funds via call
adjust_money(50,"silver") @brackenfell): list shows real stock,
buy torch -> "You buy a Torch for 7 copper coins." with correct
inventory/purse updates, value torch -> "The Torch is valued at 5 copper
coins.", sell torch -> "You sell a Torch for 5 copper coins." with
correct purse update. Before either fix: all four silently did nothing.
Observed, NOT fixed -- native add_action-registered command failures are silently swallowed somewhere in this lib's custom command-queue dispatch, distinct from and not fully root-caused
While diagnosing 5.4/5.5, repeatedly observed that when a command
registered via a NATIVE add_action() call (not a cmds/-directory file
dispatched through CMD_HANDLER) fails its own internal check and calls
notify_fail("...") before return 0 (e.g. raise bob with no such
target, value/sell for an item not carried), NEITHER that
notify_fail() message NOR the driver's generic default fail message
displays at all -- total silence, no crash, nothing in any error log.
Meanwhile notify_fail()-based failures on cmds/-directory commands
(cmds/player/kill.lpc's "Alas, your etherealness has little effect..."
when dead) display correctly. Traced as far as confirming
std/living/action_queue.lpc's custom per-tick dispatcher (command(
curr_act), called from aq_add()/action_check() rather than the
driver's own top-level process_user_command()) is the architecture
involved, and that this driver's own C source (parse_command() /
user_parser() / notify_no_command()) SHOULD still display either
message given how command_giver is threaded through save_command_giver()/
restore_command_giver() around the raw command() efun -- but could not
pin down why it empirically doesn't, within this pass's time budget.
Does not block real functionality: every SUCCESS path through this
same native-add_action mechanism (5.4/5.5's fixes, kill's actual
attack) displays its own output correctly; this is specifically about
missing FEEDBACK TEXT on certain failure paths, not a functional
blocker. Documented here rather than guessed at or "fixed" blind, per
this project's own standing policy -- flagged for whoever next does a
deep dive on this lib (or a driver-level command()/command_giver
investigation) to pick up.
Standing cross-cutting patterns checked systematically (grep + targeted live tests)
- §7.121 (float arithmetic in a declared-
inteconomy function): the entire money system (obj/handlers/money_handler.lpc,std/shop.lpc'sscaled_value()/PAY_RATES) is pure integer arithmetic throughout -- confirmed clean, no float anywhere in the currency path. - §8.3a (
privatecommand-dispatch/callback function silently demoted once inherited): a script cross-referencing everyadd_action("fn", "verb")target against aprivate-declared function of the same name in the same file found zero matches -- confirmed clean (this codebase doesn't use a single centralprivate nomask command_hookidiom at all). - §7.112 (NPC
init()unconditionally scheduling acall_out()chain with no re-entry guard): found 18 files withinit()+call_out, all are simple one-shot "greeting" messages (a duplicate on reconnect would at most double-print a greeting line), none matches the death/reincarnation multi-stage state-corruption shape this pattern is about -- confirmed not applicable here (this lib's own death flow uses a completely different, NPC-command-drivenraise/do_raising()mechanism, see 5.4). - §7.118 (
.c->.lpcfilename-slice arithmetic): grepped for both the hardcoded-offset-slice shape and the+".c"literal-concatenation variant -- zero hits, confirmed clean. - §7.122 (autoload/class-item duplication on reconnect): grepped for
query_auto_load/auto_load/compute_autoload_array-style mechanisms -- none exist in this archive at all (no TMI-2/Nightmare- style reload-on-login system); confirmed not applicable. - §7.123 (bare file-scope
IDENT = (...)statement): grepped for the shape corpus-wide -- zero real instances (the one syntactic match,net/intermud3/cmds/tell.lpc'smorse(), is a normal local-variable assignment inside a function, not file scope). - §7.126 (stale pre-
.c-to-.lpcextension in.osave data): this project's own onboarding NOTES.md (§2) already flagged this exact gap in a handful of individual shop.osave files as a known, deliberately-deferred, low-impact item -- re-confirmed still present and still low-impact (per-room shop inventory, not the boot path); not re-investigated further this pass, no new instances found beyond what onboarding already documented. - §7.129 (
tell_room()wrapper forwarding omittedexcludeas literal0):secure/simul_efun/modified_efuns.lpc'stell_room()usesevent()(acall_other-based dispatcher), not the strictmessage()efun -- confirmed a structurally different, safe mechanism;event_say()'s ownpointerp(avoid)/avoid == this_object()guard already handles a bare0correctly. - §7.130: confirmed instance, see 5.3/5.3a above.
- §7.131 (
find_living()/find_player()needingset_living_name()registration): confirmed ALREADY correctly called, twice (global/player.lpc'sstart_player()andset_name()) -- not the bug shape here (though a structurally adjacent bug, §7.144, was found and is new). - §7.132 (
map()-over-mapping wrong-argument binding): grepped for the shape -- zero hits, this codebase doesn't appear to usemap()over mappings in a way that would trigger this. - §7.133 (classic-driver
remove_interactive()never bridged to this driver's realnet_dead()apply): this lib's disconnect handling is inline inheart_beat()(the §7.130 shape, not a separatenet_dead()/remove_interactive()split) -- confirmed not applicable, no deadremove_interactive()doc-comment/mechanism found anywhere. - §7.134/§7.135 (lazy-init/empty-accumulator guard missing): confirmed instance, see the new AGENTS.md §7.134 addendum (
obj/handlers/align_tracker.lpc). - §7.136 (command-soul stripping with no re-grant path): not applicable -- this codebase's command architecture (native
add_actioneverywhere,soul_commands()etc. called unconditionally fromstart_player()) doesn't use a race-content-gated soul-granting design at all. - §7.141 (MudOS-era
replace_program()fold crashing on a pending- replace closure creation): FOUND a live instance of the fold itself (std/room.lpc'screate(), identicalreplaceable()+inherit_list()+sizeof==1shape) -- but traced its ENTIRE class hierarchy (std/basic/{light,property,cute_look,desc}.lpc,std/senses.lpc,std/add_clone.lpc) and found zero closure/function- pointer creation anywhere in it, unlike thedsIprecedent'seventHearTalk()filter closure. Live-tested directly:say/askin a genuinely trivial (inherit "/std/room"only) room within the first ~160 seconds of a fresh boot (squarely inside the vulnerable window) produced zero "pending replace_program()" errors. Left the fold in place -- confirmed-clean, not a live bug in this codebase, per this project's "verify empirically before fixing a pattern-matched bug" policy; documented here rather than removed speculatively. - §7.143 (
force_me()+add_actionNPC self-dispatch failing for room-reset()-spawned NPCs):force_me()is never called anywhere in this entire archive -- confirmed not applicable, the mechanism this pattern depends on doesn't exist here.
Combat, quit/reconnect, shop -- what was and wasn't reached live
- Combat: no dedicated safe-sparring mechanism exists in this archive (no "dummy"/"practice" NPC found anywhere) -- used a genuinely weak wild newbie-zone monster instead (
d/newbie/human/monsters/ cat.lpc, level 3-6,set_wimpy(10)) per the methodology's documented fallback. A level-0 character (1 max HP) died to it almost immediately -- confirmed this is intended difficulty (help text explicitly frames level 0 as needing guild training before real combat), not a bug. A second combat test (kill june, an NPC with real HP) played out a full multi-round fight to death and back through a full resurrection (5.4). - Skill/guild training:
advance <skill>confirmed gated on physical guild-hall presence (matcheshelp advance/help fighter); not reached live this pass (would require travel this session's time budget didn't cover) -- documented here as explicitly unverified-live rather than silently skipped. - Quit / debug.log / reconnect: this lib has no
debug.logat all (its own error handler,secure/master/error_handler.lpc, routes everything tolog/errors/<domain>.errinstead -- thedebug.logwrite path is explicitly commented out there); checkedlog/errors/*after everyquitthroughout this pass, not just once.quitwhile dead (a ghost, mid-testing) was confirmed to correctly persist across a real reconnect after a wall-clock gap -- reconnecting showed the same disembodied-spirit state rather than silently reviving or corrupting the character. - Shop/economy: fully reached and live-tested end-to-end, see 5.5.
- Death/respawn: fully reached and live-tested end-to-end, see 5.4.
Six files' fixes verified via targeted lpcc single-file/room compiles
plus full native driver boots (repeated ~13 times across this session,
each killed by exact PID); zero compile regressions, zero new runtime
errors in log/errors/* across the final clean boot's full test replay.
Throwaway test characters (testerbrave, brytewind, quillfrost,
brackenfell) deleted from players/ before commit; only the seeded
god account's own incidental save-state drift (last-login timestamp,
playtime) and normal daemon/shop aggregate-stat drift (save/
timekeeper.o, d/newbie/human/rooms/v02.o's sales counters, a
freshly-created save/death.o from this session's first-ever kills)
were left as ordinary collateral of live testing, matching this
project's usual convention.