Sundering Shadows

✅ 可玩

sunshadow

更新 7c14580 2026-10-28 源码 下载 ZIP 上游 MudRen/SunderingShadows

▶ 开始游玩 · Play Now

A long-running, still-live English-language fantasy LPMud built on a heavily customized Nightmare/TMI-lineage mudlib base. Source cloned from `git clone https://github.com/MudRen/SunderingShadows` (a fork of `SuS-Tsera/SunderingShadows`). Slug `sunshadow`, number 930, port 40232.

English

A long-running, still-live English-language fantasy LPMud built on a heavily customized Nightmare/TMI-lineage mudlib base -- 43,147 files (~47MB) that the live game auto-commits every 30 minutes. New characters emerge through a full command-driven, 15-stage creation wizard (class, gender, race, subrace, template, age, point-buy stat allocation, body type, hair/eye color, bonus languages, alignment, deity, class-special) before a scripted basic-commands tutorial drops them into the world proper. The std/races/ library lists 43 playable strains: the familiar human, elf, dwarf, gnome, halfling, half-elf, half-orc, and drow sit alongside centaurs, minotaurs, satyrs, dragonkin, wemics, kitsune, saurians, yuan-ti, firbolgs, gnolls, bugbears, and troll-kin, with a further tier -- devils, imps, devas, dragonkin, soulforged, strix -- locked behind account-level approval rather than open to every newcomer. Base classes offered at creation (fighter, thief, mage, cleric, druid, ranger, paladin, bard, barbarian, sorcerer, monk, warlock, psion, inquisitor, magus, cavalier, and more) are only the entry point: dozens of prestige classes are earned in play, from archmage, assassin, eldritch knight, and shadowdancer to hexblade, warmind, hierophant, and arcane trickster. The setting runs its own homegrown ten-deity pantheon -- Jarmila, Callamir, Kreysneothosies, Ryorik, Nilith, Lord Shadow, The Faceless One, Kismet, Lysara, and Nimnavanon, each with a distinct portfolio and alignment restrictions that gate which deities and class-special options a given character can take. The persistent world spans dozens of named zones built up over decades of individual-wizard development: the newbie cities of Tabor and Muuldaan, the city of Shadow with its own coliseum sparring grounds, an Underdark drow-and-derro tunnel network, a Ravenloft-flavored gothic-horror domain, a Mesoamerican-styled Atoyatl/Tecqumin jungle region with its own tabaxi denizens, the Dagger forest and marsh territories, plus dedicated guild halls, a magic college with its own demiplane, and player-run housing and shops.

README

The world

A real, substantial archive: 43,147 files (~47MB) that the live game auto-commits every 30 minutes, per the repo's own README. The std/races/ library lists 43 playable races: humans, elves, dwarves, gnomes, halflings, half-elves, half-orcs, and drow sit alongside centaurs, minotaurs, satyrs, dragonkin, wemics, kitsune, saurians, yuan-ti, firbolgs, gnolls, bugbears, and troll-kin, with a further tier (devils, imps, devas, soulforged, strix) locked behind account-level approval rather than open to every newcomer. Base classes offered at creation (fighter, thief, mage, cleric, druid, ranger, paladin, bard, barbarian, sorcerer, monk, warlock, psion, inquisitor, magus, cavalier, and more) are only the entry point -- dozens of prestige classes are earned in play, from archmage, assassin, eldritch knight, and shadowdancer to hexblade, warmind, hierophant, and arcane trickster. The setting runs its own homegrown ten-deity pantheon -- Jarmila, Callamir, Kreysneothosies, Ryorik, Nilith, Lord Shadow, The Faceless One, Kismet, Lysara, and Nimnavanon -- each with a distinct portfolio and alignment restrictions that gate which deities and class-special options a given character can take. The persistent world spans dozens of named zones built up over decades of individual-wizard development: the newbie cities of Tabor and Muuldaan, the city of Shadow with its own coliseum sparring grounds, an Underdark drow-and-derro tunnel network, a Ravenloft-flavored gothic-horror domain, a Mesoamerican-styled Atoyatl/Tecqumin jungle region with its own tabaxi denizens, the Dagger forest and marsh territories, plus dedicated guild halls, a magic college with its own demiplane, and player-run housing and shops.

Registration flow

name -> "Do you really wish NAME to be your name? (y/n)" -> password -> confirm password -> ANSI color test (y/n) -> screen reader (y/n) -> email -> lands in a void room; press button spawns the character-creation object and starts the 15-stage wizard (select OPTION / select random per stage -- except the stats stage, which instead uses recommended/add N to STAT/reroll/done; sheet to review, reset STAGE to redo a stage, finalize once all stages are answered) -> a short scripted tutorial (skip to bypass) -> the real game world.

Status

Boots clean on both the native FluffOS driver and under WASM. Verified end-to-end with a raw socket client (native) and wasm_client.js (WASM): full registration, the complete 15-stage creation wizard, finalize, tutorial skip, and look/score/who/say/quit all producing correct output post-login on both, with no mudlib-side changes needed for the WASM pass. A character genuinely persists to disk only once creation completes (query_level() >= 1), matching this codebase's own intended save-gating, not a bug.

Known gaps (documented, not invented): /cmds/soul/atmos.lpc (the emote/soul-verb fallback daemon) is referenced but entirely missing from the archive, so emote-only input that doesn't match any real command will fail. A handful of content files reached only via non-core background daemons (a mage spellbook's magic-school helper, two tower/marsh-zone NPCs) still fail to compile and were left for a future pass -- none of them sit on the registration or basic-play path. adm/daemon/ipc.lpc (an unreferenced admin chat-relay daemon using raw sockets) fails to compile under WASM, where the sockets package doesn't exist -- harmless, since nothing else in the archive calls it. See NOTES.md for the full porting and deep-functional-test log.

Local run

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

Game port: 40232.

NOTES · 移植与修复记录

Sundering Shadows -- porting notes

Source: git clone https://github.com/MudRen/SunderingShadows (a fork of SuS-Tsera/SunderingShadows, commit 0fb3102f9058f7d756be6c81f88a6bee04a5c8ff, cloned 2026-08-26). The repo's own README says it best: "This repository is synced with the mud every 30 minutes, so any changes made in game will be reflected here." Real, currently-live English-language content: 43,147 files, ~47MB, a Nightmare/TMI-lineage mudlib heavily customized over what looks like decades of active development. Slug sunshadow, number 930, port 40232.

1. Conversion

Pure ASCII archive -- no encoding-normalization work needed. convert_lib.sh renamed every .c to .lpc (roughly 41,000 files) and ran the usual static->nosave/protected sweep with no collisions found. All headers resolve through /adm/include/ (a handful of files also use local #include "dieties.h"-style relative includes, which resolved fine as-is).

2. The pervasive same-file forward-reference gap

This driver compiles each file in a single top-to-bottom pass, unlike the two-pass compiler the original archive was clearly written against: calling a function defined *later* in the same file, with no forward declaration, is a hard compile error here. A corpus-wide scan (scan_forward_calls.py) found 3,212 files with at least one such call (8,453 sites total); a de-duplication pass found the archive's own authors had already added the *correct* forward declaration in ~65% of cases (5,465 of ~8,438 inserted lines were exact duplicates of an existing declaration elsewhere in the same file) -- removed those duplicates, leaving ~2,973 genuine fixes applied mechanically.

3. map_delete() dialect: void/in-place here, not return-the-mapping

Classic MudOS/LDMud-vintage code assumes map_delete() returns the resulting mapping; on this driver it's void and mutates in place. Found live via daemon/multi_d.lpc:169's whoWhen = map_delete(whoWhen,who); breaking that whole file's compile -- which mattered immediately, because login.lpc's get_name() calls MULTI_D->is_allowed(...) uncaught, so a broken multi_d.lpc silently aborted every new registration mid-flow with no visible symptom. Swept the whole corpus: 245 files renamed off the m_delete() MudOS alias, plus 9 files (20 sites) assigning map_delete()'s result to a variable.

4. The set_short()/set_long() closure gap -- blocked EVERY new registration

The single most impactful bug in this port, and the reason no new character could ever get past the very first room. std/Object.lpc's set_short(string str)/set_long(string str) only accepted plain strings, but 291 files (set_long) and 20 files (set_short) call them with old-style closures for dynamic descriptions, e.g. set_long((:TO, "my_long":));. That exact call is in d/shadowgate/setter.lpc -- ROOM_SETTER, the literal first room every brand-new character is moved into by std/user/setup(). Since the room object simply failed to compile, move() into it silently failed with zero driver-level crash, leaving new connections commandless with no error anywhere obvious.

Fixed by widening both setters to mixed and teaching query_short()/ query_long() to evaluate a functionp() case:

if (tmd = query_property("function_long")) {
    if (!tmd = call_other(TO, tmd)) {
        tmd = "A function messed up in query_long() in Object.lpc";
    }
}else if (functionp(ob_data["long"])) {
    tmd = (string)(*ob_data["long"])();
}else {
    tmd = ob_data["long"];
}

This exact pattern already existed elsewhere in the same file (the "cap_name" derivation), confirming it's the established calling convention for this codebase, not a new invention. Also had to widen four dependent override files that had their own stale string-typed set_short/ set_long wrappers: std/room.lpc, std/tobj.lpc, cmds/mortal/room.lpc, and three duplicate Object.lpc copies under dead/failsafe test paths.

5. The command-dispatch filename-slice bug -- blocked EVERY ordinary command

The second silent, total blocker, and the hardest to find: after fixing \S4, new characters could finish the full 15-stage creation wizard and land in the world, but look, score, say, inventory -- every single ordinary player command -- failed with the driver's generic default fail message ("Why?") and *zero* log signature. Verb-specific add_action calls (character-creation's select/press/finalize, room-specific quit/bypass) kept working throughout, which is what made this so confusing: the wildcard catch-all itself (std/living.lpc's add_action("cmd_hook", "", 1)) was clearly registering and running (added debug_message()/tell_object() instrumentation to confirm), but every lookup through it came back empty.

Root cause: daemon/command.lpc (a Nightmare-style command-dispatch daemon) scans each command directory for _*.lpc files and derives the verb name with:

choses[j] = choses[j][1..strlen(choses[j])-3];

This strips a leading _ (1 char) and a trailing extension of exactly 2 characters -- correct for the archive's *original* _look.c naming, where strlen("_look.c")-3 lands exactly on "look". This port's mechanical .c->.lpc rename lengthened every extension by two characters, so the same arithmetic on _look.lpc (a 4-character extension) sliced off "look.l" instead of "look" -- silently corrupting the command-name key for literally every command in every scanned directory, while the *directory* itself still got recorded as "already processed" so it was never retried. Fixed with an extension-agnostic replacement:

choses[j] = explode(choses[j][1..], ".")[0];

This is the same *class* of bug already seen in another lib onboarded this session (a fixed-width filename-slice tied to the .c/.lpc rename), but a distinct instance/formula, found independently here.

6. Missing runtime directories silently aborting callers

Same write_file()/save_object()-into-a-missing-directory pattern documented elsewhere in this project's AGENTS.md: a failing write throws an error that, if the caller isn't wrapped in catch(), silently aborts the *entire* calling function with no driver-level crash. Found via the mudlib's own separate error log (work/log/debug.log/debug.heavy, written by adm/daemon/error_handler_d.lpc, distinct from the driver's native log). Created:

daemon/treesave_d.lpc, daemon/letter.lpc, daemon/localpost.lpc, and daemon/yuck_d.lpc all self-heal their own save directories with mkdir()/dir_exists() checks already, so those were left alone.

7. Missing ansi_str() simul_efun

Referenced by cmds/mortal/_look.lpc, _glance.lpc, _spy.lpc, and one NPC file, but implemented nowhere in the entire 43,000-file archive -- a genuine gap, not something the port broke. This driver already auto-translates this mudlib's %^COLOR%^ markup to real ANSI escapes at the write()/tell_object()/more() layer (confirmed: plenty of other output renders color correctly with no explicit ansi_str() call), so added a minimal passthrough simul_efun (adm/simul_efun/ansi_str.lpc) rather than inventing real behavior for a function whose original implementation is simply lost.

8. Smaller one-off compile bugs

All the usual "stray or missing type" classes seen elsewhere in this session's work, collected here rather than given their own section:

9. Verification

Booted the real FluffOS driver (~/src/fluffos/build-debug/src/driver) against config.fluffos and drove it end-to-end with a raw Python socket client (no mudclient.py/tmux_mud.sh shortcuts): fresh registration (name/password/ANSI test/screen-reader/email) through the full 15-stage ROLL_CHAIN creation wizard (class, gender, race, subrace, template, age, stat rolling via recommended+done, body type, hair/eye color, language, alignment, deity), finalize, the scripted basic-commands tutorial (skip), and then look, score, who, say hello world, and a clean quit -- all producing correct, fully-rendered output. Confirmed the character actually persists (adm/save/users/<letter>/<name>.o written only once query_level() >= 1, i.e. only after creation genuinely completes -- intentional, not a bug) and that daemon/save/saved_descriptions saves cleanly post-\S6 fix. debug.log is clean of errors on this full path after all fixes above; a background random-quest daemon and the bonus_d.lpc/filters_d.lpc heartbeat spam are the only things that appeared during testing, both fixed.

10. Known gaps -- documented, not invented or exhaustively chased

深度功能测试 / Deep functional test (round two, AGENTS.md §10.7)

First genuine hands-on *playthrough* pass on the real driver (the earlier "Verification" section above only exercised registration through the 15-stage creation wizard, tutorial-skip, and look/score/who/say/ quit -- never a continuous session with real movement, combat, skill acquisition, or economy). One continuous session on ~/src/fluffos/build-debug/src/driver, a raw Python socket client throughout (a persistent background process reading commands from a named pipe, since a single Python invocation per command would have lost in-progress creation-wizard/input_to() state across reconnects), plus a second connection for a fluffos/Mud@2026 admin account (registered fresh through the normal flow -- this lib had never been seeded before; granted via directly editing the freshly-registered save file's position field to "Admin", matching the exact string std/user.lpc's enable_wizard() gate checks, since the in-game set_position() path to grant a non-mortal position itself requires calling from an existing wizard's own _xmote/_avmaker command -- a chicken-and-egg the data-edit sidesteps, per AGENTS.md §1.5's own guidance to prefer editing the save data over the code).

Newbie path: help newbie and the community-authored doc/help/user/newbie_tutorial (a step-by-step guide to a wood-elf fighter, explicitly recommending sweepingblow/impale as the first feats to buy) gave a clear, well-signposted test path: register, land in the Offestry newbie tutorial, skip it, reach the town square, then travel west/northwest to "the keep" to fight xvarts for early XP.

Test character: Testwind, a wood-elf fighter (soldier style), password Test@2026 -- kept as a representative playthrough character. A second throwaway character (Feattest, a random saurian fighter) was registered specifically to verify the feat-acquisition fix on a clean slate, uncontaminated by the counter-desync the live bug left on Testwind's save (see Bug 3 below); both are left in place as evidence.

Bug 1: a bare top-level IDENT = (mapping-or-array-literal); statement outside any function is not a valid initializer on this driver -- broke look in every outdoor room, game-wide, plus two spells and the rumors command (new AGENTS.md §7.123)

adm/daemon/astronomy_d.lpc declared nosave mapping moons; (and siblings moonstate/moonphase/moonorbit, plus two globals never even declared at all, moonvisibility/moonillumination) and then assigned their real values via ordinary-looking top-level statements later in the same file, outside create() -- a MudOS/LDMud-era convention this driver does not support: a bare IDENT = EXPR; outside a function is parsed as a type-less attempt to *redeclare* the global, producing error: Type mismatch ( unknown vs mapping ) when initializing moons and leaving the whole file uncompiled. Since adm/simul_efun/total_light.lpc's total_light() -- called from every single look/room-entry via describe_current_room() -> light_blind() -> light_blind_remote() -- calls ASTRONOMY_D->query_eclipse() unconditionally and uncaught, every outdoor look in the entire game silently printed nothing past the point of failure (no room description, no exits, no items) from the very first fresh boot. Reproduced live: a fresh character's very first look at the Offestry town square (an outdoor room) printed only a blank -> prompt.

Fixed by merging the bare declaration and its later top-level assignment into a single proper declaration-with-initializer inside create() (this file already had one), and adding the two missing declarations:

nosave mapping moons;
nosave mapping moonstate, moonphase, moonorbit, moonvisibility, moonillumination;
nosave string* moonphases;
int in_eclipse;

void create()
{
    seteuid(getuid());
    moons = (["sera" : "...", "tyrannos" : "..."]);
    moonphase = ([...]);
    moonvisibility = ([...]);
    moonillumination = (["sera":1, "tyrannos":0]);
    moonphases = ({ "new", ... });
}

Fixing this then exposed a second, previously-masked compile error in the same file (int query_moon_illumination(moon){ -- a parameter with no declared type, which this driver rejects outright), fixed to string moon.

Three more independent instances of the exact same shape, found the same session (see new AGENTS.md §7.123 for the general pattern and detection heuristic): cmds/mortal/_rumors.lpc (arealist/areas -- broke the rumors command referenced by the game's own doc/help/user/newbie text, "<rumors> will tell you where people are currently congregating"), and two mage/magus spells, cmds/spells/e/_elemental_aura.lpc and _elemental_body_i.lpc (shortmap/colormap/elementmap). All fixed the same way (merge declaration+initializer into one statement). A targeted grep (^[a-zA-Z_][a-zA-Z0-9_]* = (\(\[|\(\{|\(:) at column 0) found 18 candidate files corpus-wide; 14 were false positives (unindented code inside a function, or inside a large /* ... */ block of example output/pseudocode) -- each hand-verified against its actual brace context before deciding. A full corpus-wide sweep for this exact shape was NOT attempted given the archive's 43,147-file size and this project's standing memory-safety caution around full lpcc_check.sh batch runs on this lib (see "Known gaps" below); the 4 confirmed instances were all found via this one targeted grep pass plus whatever the live playthrough itself happened to touch.

Verified live, before/after: before the fix, look in the Offestry town square printed nothing past the prompt; after, it renders the full room description, exits, and NPC list correctly. rumors now lists nearby areas by level range instead of failing outright.

Bug 2: daemon/help.lpc called an undefined topic_dir() (a typo for its own topics_dir()) -- broke help completely, for every player, from the very first boot

daemon/help.lpc's query_topics() has one case per help category, all calling the same local helper topics_dir(DIR) -- except case "*bloodlines", which calls topic_dir(DIR_BLOODLINES_HELP+"/") (missing the s), an undefined function. This is a hard compile error on this driver (error: Undefined function topic_dir), so daemon/help.lpc itself never compiled, and cmds/mortal/_help.lpc (the help command) failed every single invocation with *No program in object '/daemon/help'! -- reproduced live: bare help, help newbie, help spar, help combat all printed nothing at all, game-wide, for every player, from the very first boot. Fixed the typo (topic_dir -> topics_dir). While in the same function, also fixed an adjacent if(sizeof(statdata) && statdata[0] = myhold[i]) (a stray = where == was clearly intended, in the *spells/*feats category's file-validity filter -- an always-true condition that silently made the filter a no-op, listing every directory entry rather than only the stat-verified ones).

Verified live: help now renders the full category/topic menu; help *bloodlines lists its 12 sub-topics; help feats renders its full help text.

Bug 3: daemon/feat_d.lpc's directory scan used the exact §7.118 filename-slice bug already documented for this lib's own daemon/command.lpc -- silently disabled the ENTIRE feat/combat-technique system for every class in the game

The single most severe finding this session (see the extended AGENTS.md §7.118 writeup for the full mechanism). build_feat_list() scans every category folder under the feats directory and decides whether each filename ends in .lpc with strsrch(files[y], ".lpc") != (strlen(files[y]) - 2) -- correct only for a 2-character .c extension, and since this port's mechanical rename lengthened every extension to .lpc (4 characters), this check is now false for every single file, so continue (skip) fires on literally every feat file in the archive. __ALL_FEATS was permanently empty, is_feat() returned false for every feat name in the game, and add_feat() silently no-op'd on every feats add/feats <type> <feat> call -- while the confirmation handler (confirm_add_type() in cmds/mortal/_feats.lpc) prints "Congratulations, you have successfully added..." UNCONDITIONALLY, never checking whether the daemon call actually succeeded, so the player sees a convincing fake-success message every time. Downstream, std/living.lpc's central command dispatcher gates every /cmds/feats/* verb behind FEATS_D->usable_feat(), so every feat-granting command (including the game's own tutorial- recommended sweepingblow) failed with the driver's generic "Why?" default-fail message, indistinguishable from a nonexistent command. This hits EVERY new character immediately: the automatic class-granted feats every fighter/etc. receives at creation (light/medium/heavy armor proficiency, shield/simple/martial weapon proficiency) were also silently invisible to feats known/has_feat()/usable_feat(), even though the "Adding fighter class levelling feat: ..." messages print correctly during character creation.

Root-caused live via temporary debug_message() instrumentation in add_feat() (note: this driver's debug_message() writes to the driver's own stdout/stderr, NOT debug.log -- a real trap during tracing) after ruling out has_feat(), __FEAT_DATA storage-key mapping, and a first (wrong) theory that query_player_feats() was missing a "martial" bucket read (it wasn't -- "martial"-type feats are intentionally aliased to the pre-existing "bonus" bucket via get_feats()/set_feats()'s own case "martial": ... query_bonus_feats() mapping, already correctly read back; a would-be fix was drafted then reverted once the real root cause -- is_feat() returning false because build_feat_list() never populated __ALL_FEATS at all -- was found).

Fixed identically to §7.118's established extension-agnostic pattern:

if (strlen(files[y]) < 4 || files[y][strlen(files[y]) - 4..] != ".lpc") {
    continue;
}

Fixing the scan then made build_feat_list() actually attempt to compile all ~130 feat files under cmds/feats/ for the first time ever -- surfacing a second wave of ~25 files with their own independent, ordinary compile bugs that had simply never been reached before. All fixed, same session (grouped by shape, not per-file since most repeat):

Verified live end-to-end on a fresh throwaway character (Feattest, uncontaminated by Testwind's pre-fix state): feats known immediately after creation correctly lists all six automatic class proficiencies; feats martial sweepingblow (yes to confirm) reports success AND the sweepingblow command itself now actually runs (reaches its own two-handed-weapon gameplay check, instead of the driver's generic "Why?"); feats known afterward correctly shows Sweepingblow under TwoHandedWeapons. Testwind's own two attempts to add sweepingblow (one before the fix, one shortly after, before this was traced) both consumed the character's one-time "2 free martial bonus feats" allowance via the bonus_feats_gained counter (which increments unconditionally in add_my_feat(), independent of whether the feat actually persisted) without ever storing the feat -- Testwind is left in this state intentionally, as evidence of the pre-fix bug's exact failure mode (a permanently fake-successful, resource-consuming, no-op grant); a live wizard could restore the two consumed slots via ob->set_bonus_feats_gained(0) if this character is ever used for further testing, but that's a data-repair decision left to a human, not made here.

Bug 4: d/laerad/mon/anti.lpc (a level-22 mid-game boss NPC) failed to load at all, cascading from a stray (string) cast and several void-declared functions with stray return 1;/return N;, transitively taking down two dependent item files with it

Found while spot-checking a daemon/quests_mid background quest-daemon reference in debug.log (*No program in object '/d/laerad/mon/anti'!) during otherwise-unrelated testing -- init()'s aggro check cast query_level()'s int return to (string) before comparing it against int literals ((string)TP->query_level() > 18), a hard type-mismatch error on this driver; heart_beat() and bolt() were both declared void with several return 1; sites. Compiling this one NPC file transitively attempted to load everything its own create() clones (new("/d/laerad/obj/lstealer"), etc.), surfacing two more independent bugs in the same load chain: d/laerad/obj/lstealer.lpc's void save_me(string file){return 1;} (a deliberate no-op override to prevent this unique weapon from auto-loading on restore, per its own header comment -- but declared void while the base std/Object.lpc's real save_me() returns int and IS consumed by callers elsewhere, e.g. std/bag_logic.lpc's j = inv[x]->save_me(fname); -- fixed by widening to int save_me(...), preserving the intended no-op behavior exactly). Fixed the cast (removed the stray (string)), the stray returns (changed to bare return; in the two void functions), and the lstealer.lpc return type. This NPC and its unique weapon are now reachable/loadable, though not personally fought live this session (a level-22 boss well above a level-1 newbie test character's reach) -- fix verified via a clean lpcc --batch single-file compile only, not a live encounter.

Bug 5: daemon/virtual_room_d.lpc's player-room save/registration function was declared void but its ONE caller (cmds/mortal/_save.lpc) captures its return value -- broke the save command's virtual/player-built-room persistence path

register_virtual_room() computes and returns a save_name string, but was declared varargs void register_virtual_room(...) -- the identical bug already present (and left commented-out, itself with the same void+return save_name; mismatch) in an EARLIER, abandoned version of the same function directly above it in the file, suggesting this was never actually fixed across a "recode" the file's own comment describes ("attempting to recode to account for moving/linked virtual rooms - Saide, April 2017"). cmds/mortal/_save.lpc's only call site, file = "/daemon/virtual_room_d.lpc"->register_virtual_room(...), needs that return value. Fixed by widening the declared return type to string (matching the function's own return save_name;) and changing its one early bare return; (for an invalid-object guard) to return 0; to satisfy the new non-void signature.

Bug 6: the general store's stock list was silently empty ("I have nothing on hand.") because two unrelated shared item classes it stocks failed to compile -- masked behind a generic, plausible-sounding shop message

Reached via the newbie tutorial's own recommended first stop ("s;w from offestry center to get to the general store"). list at the Offestry general store (Odds and Ends) printed only "I have nothing on hand." -- a message that reads as ordinary content (an understocked shop), not an error, and would have been easy to accept as intentional. debug.log told a different story: d/common/obj/misc/book.lpc and d/common/obj/ misc/canvas.lpc (two shared "writable item" classes this shop stocks, alongside sacks/lanterns/tools/rope) both failed to compile, so the shop's stock-loading silently skipped every reference to either.

While tracing this, also found and fixed the SAME already-known, already-partially-fixed potion bug class from this lib's own original §8 "Smaller one-off compile bugs" list (void new_do_effect(...) with stray return 1; -- previously fixed only in d/common/obj/potion/advanced_heal.lpc, apparently never swept across its siblings): healing.lpc, newbie_healing.lpc, epic_heal.lpc, full_heal.lpc, legendary_heal.lpc, extra_heal.lpc (6 more files, identical shape, all fixed the same way). These weren't stock items in this particular shop, but were caught by a quick sibling-file grep (awk '/^void new_do_effect/,/^}/' file | grep 'return 1;') run immediately after finding the first instance, per this project's standing "a bug found once has usually been copy-pasted" heuristic.

Verified live: the Offestry general store's list now shows all 16 real stock items (belt pouches, lanterns, rope, tools, the canvas, the book, etc.) instead of "I have nothing on hand." A further, unrelated *No program in object '/d/common/obj/sheath/sheath_inherit'! surfaced in debug.log from a DIFFERENT background subsystem (a weapon-sheath randomizer) while re-testing the shop -- confirmed not to block the shop list itself, and left unfixed as a deliberate stopping point: this 43,147-file live archive has effectively unbounded further transitively-reachable compile bugs in its shared item library once lpcc actually starts reaching previously-dead code paths (the same mechanism as Bug 3's "second wave"), and chasing every one of them is out of scope for a single §10.7 pass — flagged here for whoever next works this lib, not chased further.

What was tested and confirmed working (Testwind's playthrough)

Known gaps -- honestly unverified, not invented or guessed at

WASM verification (wasm_status audit)

First WASM pass for this lib (wasm_status had been left "" since the original port -- see AGENTS.md §1.4's triage playbook). Ran scripts/wasm_client.js against ~/src/fluffos/build-wasm/src with a full fresh registration (name/password/ANSI/screen-reader/email) through the entire 15-stage ROLL_CHAIN creation wizard -- select fighter, select male, select dwarf chosen explicitly to keep the path deterministic (dwarf isn't one of the two is_statmod_race() races [human, half-elf], so the stat_mod stage auto-skips reliably; fighter avoids caster-class complications), select random for the remaining option-list stages, and recommended + done for the stats stage specifically (it's the one ROLL_CHAIN stage that rejects the generic select/select random verb outright -- select_stats() always returns 0 and just reprints the stat-allocation help; confirmed this live on a first attempt that sent select random into it and watched the wizard stall repeating the same screen) -- then finalize, tutorial skip, and look/score/who/say hello world/quit.

Result: clean end-to-end with zero mudlib-side changes needed. The full chargen produced a real character (fighter/male/shield dwarf, svelte body, silver eyes, gray hair, chaotic neutral, The Faceless One, thug fighter-style), landed correctly in the tutorial room, skip reached the rules room, and score/who/say/quit all rendered correct output with no debug.log-equivalent errors, no No program in object, and no driver-level crash anywhere in the transcript.

The only compile-time error seen during boot was the pre-existing, already-documented adm/daemon/ipc.lpc (an admin chat-relay daemon using raw socket_create()/socket_bind()/socket_listen(), which don't exist in the sockets-less WASM build) -- confirmed via a full-corpus grep that this file is referenced from nowhere else in the archive (not preloaded, no IPC_D-style macro, no caller), so its compile failure is inert noise, not a WASM blocker; left unguarded rather than adding a speculative #ifdef __PACKAGE_SOCKETS__ to a file nothing calls. The only other socket-using file in the whole archive, cmds/creator/_netstat.lpc, is a creator-only diagnostic command never exercised by ordinary registration/play and wasn't touched either. No uptime() startup-grace gates, no pcre/regexp() calls in simul_efun/master, and no IP-parsing bans in logind's equivalent (adm/obj/login.lpc's BANISH_D/MULTI_D chain: both start with empty allow/deny lists on a fresh archive and have no IP-format-sensitive logic that could reject a loopback WASM connection) -- none of the usual §1.3 bug classes applied here. std/user.lpc's setup() already hardcodes ip = "127.0.0.1"; for every logged-in player regardless of driver mode, which incidentally makes this lib immune to any future query_ip_number() regression on this specific field (unrelated to the still-live query_ip_number() efun call itself, which was never an issue here since nothing in the login/registration path parses it).

Set wasm_status: "playable" in meta.json. Also took this pass as the opportunity (per the site's ongoing description-quality sweep) to rewrite meta.json's english_description and README.md's intro section, which had drifted into porting-bug narrative instead of game content -- replaced with real specifics pulled from std/races/ (43 race files, several is_restricted()-gated), std/class/ (base vs. is_prestige_class() roster), d/shadowgate/dieties.h's ten-deity NEWPANTHEON, and the d/ zone tree (Tabor/Muuldaan, the city of Shadow and its coliseum, Underdark, a Ravenloft-flavored domain, the Atoyatl/Tecqumin jungle region, Dagger). Also stripped a stray 40232 markdown-bold artifact from the README's "Local run" section (the site's parse_readme() doesn't strip markdown, so it would have rendered as literal asterisks on the site card).

深度功能测试(§10.7,2026-10-04)— negative range ends

scripts/lpc_fix_negative_ranges.py: 1 line(s) in 1 file(s) count a range from the end with < (x[a..-1] -> x[a..<1], x[-2..] -> x[<2..]; KB 06 §7.209). In this driver a negative constant end gave "" / ({ }) and a negative start the whole value, so each of these returned the wrong slice; the compile warning A negative constant as the second element of arr[x..y] is gone from the files that compile. Range lvalues (s[0..-1] = text, the prepend idiom) are left alone. A paired compile of the changed files at HEAD and in the working tree showed no new error.

深度功能测试(§10.7,2026-10-04)— <../x.h> includes resolve

3 files in old areas (d/dragon/old/sewer/fall1.lpc, d/dagger/derro/old/mon/elemental.lpc, d/islands/common/eldebaro/eldebaro/ob/runestone.lpc) include a header named relative to themselves in angle brackets; the driver refuses .. in a <...> name. adm/obj/master.lpc now answers include_file() with "./" + path (KB 04 §6.1) and runestone.lpc loads; the other two include correctly now and fail for their own reasons (old code). The lib's warnings pass has not been done (unused locals in the master and std/room/terrain.lpc).

深度功能测试(§10.7,2026-10-05)— 主控 author_file()/domain_file()

驱动的 mudlib_stats 包在主控载入时各调用一次 author_file(<主控文件>) 和 domain_file("/"),要求返回字符串。本库这两个 apply 只为 domains / realms 目录下的文件返回名字,其余返回 0,所以每次启动都打印 author_file() in the master file does not work, using root_uid as fallback 和 domain_file() in the master file does not work, using bb_ui as fallback 两行,网页版每位访客的终端里都看得到。adm/obj/master.lpc 改成与 fluffos 组织维护的 Dead Souls 上游(deadsouls_fluffos)相同的写法:其余文件的域返回 "BACKBONE",作者返回 "NONAME"。域等于 backbone 域时驱动让对象沿用创建者的域,与返回 0 时相同;作者统计(author_stats())多出一行 NONAME,汇总 realms 目录以外的对象(KB 06 §7.222)。用 lpcc config.fluffos /nonexistent.lpc 前后对比,主控载入段只少了这两行。

2026-10-06 — protected private

adm/daemon/services.lpc 的 build_mudlist() 声明为 protected private(早期把 static 换成 protected 时留下的两个访问修饰),FluffOS 报 Multiple access modifiers;改为 private。该文件仍因缺少 NETWORK_D(本驱动没有网络包下的互联服务)无法编译。

Deep test (§10.7, 2026-10-07) — messages cut by a stray ;

std/class/shadowdancer.lpc prerequisite text: a stray ; ended the string early, so "10 Ranks spent in Athletics Skill" was never shown. Joined.

Changed files loaded at HEAD and in the tree in fresh processes: no regression.

深度功能测试(§10.7,2026-10-08)— .lpc 后缀的定宽切片

.c 改名 .lpc 时只改了字符串没改切片宽度:f[<2..] == ".lpc" 拿两个字符比四个字符,永远不成立(!= 永远成立),按后缀筛文件、去后缀的代码因此从不运行或截成 x.l(KB 06 §7.238)。scripts/lpc_lpc_suffix_slice.py --apply 改了 1 处比较、0 处去后缀(如 cmds/system/_update.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。

Deep test (§10.7, 2026-10-10) -- compile warnings, chunked rescan

Re-run with the chunked scanner (LPCW_CHUNK=600): the earlier pass predates it and its coverage could not be confirmed. scripts/lpc_warnings.py sunshadow --fix plus scripts/lpc_fix_no_effect.py --apply changed 3273 .lpc and 3 .h files (mostly unused locals). Loaded at HEAD and in the tree in fresh processes: 1781 -> 1782 PASS of 3265 changed files; the one "regression" is the d/deku/keep/rooms/jail1 / dungeon4 pair, which swap PASS/FAIL between batch runs (each loads alone). The 76 includers of the changed headers: 59 -> 59. Audit flags in files that do not compile at HEAD were read: all are calls (east_limit(path), uptime()) or names kept in the rebuilt declaration. Boot warnings 50 -> 0. By hand: adm/include/conf.h and config.h both define MUDLIB, MUDLIB_VERSION, MUD_IS_LOCKED, GMT_OFFSET, MAX_LOG_SIZE and MORTAL_POSITIONS with different values, and files include them in both orders (Macro redefined); each define is now preceded by #undef, so the header included last still wins exactly as before. std/daemon.lpc clean_up() is now int (returns 0, which is what the void meant to the driver) because 7 daemons override it as int and return 1 to stay loaded; the 19 daemons that overrode it as void are now int too, with return 0; (same value). Login on a fresh driver: same as HEAD.

深度功能测试(§10.7,2026-10-10)— 站点树缺 access.db;帝国卫兵的装备

深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查

把 shadowgate 提交 6ae0f9a959f 的 72 处 Expression has no side effects 修正移植过来:25 个与 shadowgate 代码逐词相同的文件用 scripts/lpc_twin_port.py adopt 原样套用,其余 33 个文件按 sunshadow 自己的文本手工移植或修正(共改动 58 个 .lpc)。lpc_twin_port.py audit shadowgate sunshadow --pre 6ae0f9a959f~1 复查后,只剩与本次无关的旧差异(sunshadow 的 _heal 已用 rnd 计算伤害;quests_high/mid 少一个 test_quests() 原型)。

HEAD 与工作树新进程分别载入改动的 58 个对象:regress 0。未通过的 25 个(_freeze、两份 river、rooms/spell、cauldron、豺狼人、rumours_d、search_d、unique_d 等)在 HEAD 同样无法载入,是本库原有的其它编译错误。bheur 在第一次批量载入里失败一次:其 create() 有一半机会 new() 出 /d/deku/weapons/winters_chill.lpc,后者 void 函数 return charges; 编译失败(shadowgate 同样),单独重载 3 次均通过,属随机伪影。改后这些文件的“无副作用”警告为 0。58 个对象载入无回退。

深度功能测试(§10.7,2026-10-12):编译警告收尾

能编译的文件中的警告从 27853 条(4003 个文件)降到约 100 条(1 个文件,d/magic/obj/rebirther.lpc,见下)。

本库与 shadowgate 是同一代码,先用 scripts/lpc_twin_port.py adopt shadowgate sunshadow --pre a9c8e7aeead 照搬 258 个代码相同的文件,余下 185 个有差异的文件中 134 个直接套用 shadowgate 的补丁,其余按同样的根源手改(详见 libs/shadowgate/NOTES.md 同日一节):adm/include/priest.h 不再 inherit SPELL;body.lpc 的 ac/size、room.lpc 的 flag、townsman/caravan 的 path/speed 改名;巨魔七种的属性改在 create() 里赋值(此前每只巨魔都没有类型和描述);tsh 的命令历史不再被 pushd 栈顶掉;road10 去掉会把房间整个清掉的 DAEMON;法术叶子不再重复声明 spell.lpc 的 caster/place 等,自身的时长 duration 改名 spell_time(本库另有 17 个);字符串函数指针的消费者(search/items/read、Object::set())认得真正的函数指针;重复继承与两个完整类的拼合;d_robe 改为 PARMOUR、ring 自带 artifact 的成员等。

本库自己的部分:

遗留:d/magic/obj/rebirther.lpc 同 shadowgate(从 user.lpc 抄来的死亡代码作用在它自己身上,去掉 user.h 就无定义,未动)。

验证:改动的 719 个文件加包含改动头文件的文件,共 3250 个对象在 HEAD 与工作树的新进程中分别载入:回退 5 个,逐个核对:newtharis/rooms/north5 因 questor 编译错误,已修;deku/hhouse/rooms/ffr7、sfr3/5/6 随机生成的 good_host/monster_feat_d 本身就编译失败(文件未改动),单独重载 sfr3/5/6 通过、ffr7 视随机结果而定。其余 3245 个对象载入无回退。lpc_audit_removed_locals.py 列出的 85 条中 83 条是巨魔叶子改成赋值的声明,另两条核对无误。

Restored base-class macros (2026-10-18, KB 04 6.13)

inherit IDENT; named a macro that no header of this lib defines (syntax error, unexpected L_IDENTIFIER, expecting L_STRING or '('), so the object never loaded; the base file is in the lib and other libs map the macro to it. Added to the global header adm/include/std.h: ALLEY -> /d/tharis/alley (23 file(s)) (scripts/lpc_restore_macros.py). Loaded one by one with lpcc afterwards: 23 of 23 pass.

Exits to rooms that do not exist, repaired from the room on the other side (2026-10-18, KB 06 7.257)

A room exit named a file the lib does not have, so the player was stuck (a typo, a renamed room, a numbered room the archive lacks). The room at the other end exists and its opposite exit points back, so that is where the exit was meant to go: /d/attaya/crafters/desert/des201 southeast: /d/attaya/crafters/desert/des222.lpc -> /d/attaya/crafters/desert/des223; /d/attaya/crafters/desert/des268 northeast: /d/attaya/crafters/desert/des291 -> /d/attaya/crafters/desert/des246; /d/khalian/stresa/cicatrice/room115 northeast: /d/khalian/stresa/cicatrice/room/108 -> /d/khalian/stresa/cicatrice/room116; /d/khalian/stresa/cicatrice/room134 west: /d/khalian/stresa/cicatraice/room133 -> /d/khalian/stresa/cicatrice/room133; /d/khalian/stresa/cicatrice/room85 south: /d/khalian/stresa/cicatrirce/room100 -> /d/khalian/stresa/cicatrice/room100; /d/khalian/stresa/cicatrice/room92 west: /d/khalian/stresa/cicatraice/room91 -> /d/khalian/stresa/cicatrice/room91; /d/magic/temples/cevahir/archery south: /d/magic/temples/cevhair/cevahir_training -> /d/magic/temples/cevahir/cevahir_training; /d/tharis/Tharis/market5 south: /d/tharis/palace/mountain/m32 -> /d/tharis/Tharis/market8. Every repaired exit now resolves to a file of the lib (scripts/lpc_fix_dead_exits.py; libs whose virtual rooms load anyway are left alone).

Macros the global header lost (2026-10-19, KB 04 6.16)

/adm/include/std.h did not define MOON_DESC; the objects that use them (1 on the list) stopped at Undefined variable/Undefined function, and since the compiler stops at the first undefined name the errors and warnings behind it stayed hidden. The sibling libs of the same lineage carry these lines in their headers: the copy of the sibling with the most files in common was added after the last #define of /adm/include/std.h; every path in a macro body exists in this lib. the listed objects have other errors as well and still do not load. (scripts/lpc_restore_defines.py)

Headers next to the including file, quoted (2026-10-19, KB 04 6.17)

23 files wrote #include <area.h> for a header that sits in their own directory (area_stuff.h, boathouse.h, strongholds.h ...). The old driver searched the includer's directory for an angle-bracket name; this one searches only the include path, so each stopped at Cannot #include .... Rewritten to #include "area.h" (resolved against the includer everywhere: preload, lpcc, run-time compiles). (scripts/lpc_fix_local_include.py)

Calls to an inherited function no parent has (2026-10-19, KB 06 7.259)

/d/antioch/valley/val.lpc, /d/dagger/aketon/rooms/tree1/1one5.lpc (2 objects) call ::create();/::init(); where the inherited bases have no such function. The old driver took such a call for a no-op returning 0; this one stops with Unable to find the inherited function 'init'.. These objects had only this error (it was uncovered once the others cleared); each call is deleted (return ::fn(...) becomes return 0;). (scripts/lpc_fix_dangling_super.py)

Object paths with two leading slashes (2026-10-19, KB 06 §7.276)

5 file(s), 5 site(s) wrote an object path as //… (like carry_object("//clone/cloth/hic_cloth")): /d/azha/town/market2.lpc, /d/azha/town/market3.lpc, /d/azha/town/market4.lpc, /d/azha/town/market5.lpc, /d/azha/town/market6.lpc. The driver these games ran on did not mind (the path was normalised first); this one hands the string to the master's valid_read() as written, securityd looks up //clone, the NPC's euid has no entry and the answer is Read access denied.: the NPC does not load. One slash now.

The rebirther object: a second copy of the player class and a player it never touched (2026-10-19, KB 06 7.278)

Standard repair round (2026-10-19, scripts/toolkit_fix.sh)

Rescanned with the current driver; mechanical fixes: 53 files with values returned from void functions. Every changed object was loaded at HEAD and in the tree: no regression. 1 further float sites need a hand edit (listed by lpc_fix_float_hooks.py).

Type slack of the old driver: core files and mechanical layers (2026-10-19, KB 04 6.19)

After the standard round 2,984 files still failed; the ones other files inherit were ranked by dependents and fixed by hand, the rest went through scripts/lpc_round_files.py (compile only the failing objects, run the fixers, repeat). 230 files changed, 146 objects that never loaded now load (loaded at HEAD and in the tree: no regression), the real warnings of the compiling files are gone.

Old-driver type slack: missing arguments, untyped parameters, array initializers, class variables (2026-10-19, KB 04 6.19, 6.20)

MudOS filled the missing arguments of a call with 0, took an untyped parameter as mixed, never compared a declared type with its initializer and let a variable be named class; this driver refuses all of it at compile time and the whole object fails. 22 file(s) fixed: /cmds/skills/bak/_boost.lpc, /cmds/skills/bak/_cure.lpc, /cmds/skills/bak/_detect.lpc, /cmds/skills/bak/_feed.lpc, /cmds/skills/bak/_heal.lpc, /cmds/skills/bak/_lore.lpc, /cmds/skills/bak/_mend.lpc, /cmds/skills/bak/_offer.lpc ... (22 in all) (the repair rounds of scripts/lpc_round_files.py: zeros written out at the call, mixed for untyped parameters, string X = ({...}) given its star, void functions returning values, and class variables renamed pick_class). Loaded at HEAD and in the tree, per file: 15 of them failed before and load now, none regressed.

One stray backslash after an include (2026-10-19, KB 04 6.1)

d/antioch/valley/rooms/v6.lpc began #include <std.h>\: the backslash joined the next line to the include directive, so #include "../valley.h" was swallowed and inherit VALLEY; was an undefined macro (the same file in shadowgate has it too; its siblings v1..v5 have none). The backslash is removed: the room fails at HEAD and loads in the tree, no warning, no regression (its sibling v5 loads in both).

An unused local in a file that loads since the type-slack rounds (2026-10-19, KB 04 6.10)

d/underdark/mines/spell/emgem.lpc: object obj; in die() was never used, deleted (the final rescan found it; no other real warning is left in the compiling files except the two whole-class diamonds below the cap, d/laerad/dojo/obj/shield.lpc and d/mine2/rooms/mine22.lpc: two full classes inherited on purpose, see the shadowgate NOTES).

x == "a" || "b":后面的字符串常量永远为真(2026-10-19,KB 06 §7.281)

if (arg == "yuyou" || "yu you") 里的 "yu you" 是非空字符串,永远为真,整个条件对任何 arg 都成立;作者要的是每个字符串都和变量比较。具体是:whisper 的语言参数永远被改成 wizish。在每个字符串前补上变量和运算符(scripts/lpc_fix_literal_or.py;forrest2.lpc 是 dir == "north" && "south",手工改成 (dir == "north" || dir == "south") && ...):/cmds/mortal/_whisper.lpc(1 处)、/d/dagger/phederia/rooms/v1.lpc(2 处)、/d/dagger/phederia/rooms/v15.lpc(2 处)、/d/darkwood/yntala/mon/centaur.lpc(5 处)、/d/darkwood/yntala/mon/centaurcl.lpc(4 处)、/d/darkwood/yntala/mon/centaurg.lpc(3 处)、/d/deku/hhouse/study.lpc(1 处)、/d/islands/giant/hill/rooms/r13.lpc(1 处)、/d/shadow/city/room/moffice.lpc(1 处)、/d/shadow/room/githyanki/27,17.desert.lpc(1 处)、/d/shadow/room/githyanki/rooms/0_0.lpc(1 处)、/d/shadow/virtual/desert/27,17.desert.lpc(1 处)。

x != "a" || x != "b" and x == "a" && x == "b": comparison chains of one value that can never differ (2026-10-20, KB 06 7.282)

if (arg != "wan" || arg != "pill") return notify_fail(...) is true whatever arg is, dir == "south" && dir == "north" is never true: the guard under the test never opens, or never closes. The author meant "neither of these" (&&) or "either of these" (||). != chains joined by || became && (only where the statement under the test is a return, a refusal or a guard clause), == chains joined by && became || (scripts/lpc_fix_cmp_chain.py). Changed: /d/antioch/ruins/rooms/graveyard.lpc (2), /d/islands/dreams/rooms/fire.lpc (1). Left as written (the body under the test does something else, == and || may have been meant): /cmds/mortal/_abandon.lpc (1), /d/darkwood/forest/village/priv1.lpc (1), /d/darkwood/forest/village/priv2.lpc (1), /d/darkwood/forest/village/priv3.lpc (1), /d/darkwood/forest/village/priv4.lpc (1), /d/darkwood/forest/village/priv5.lpc (1), /d/darkwood/forest/village/priv6.lpc (1), /d/darkwood/forest/village/priv7.lpc (1), /d/darkwood/forest/village/room1.lpc (1), /d/darkwood/forest/village/room2.lpc (1), /d/darkwood/forest/village/room3.lpc (1), /d/darkwood/forest/village/room4.lpc (1), /d/darkwood/forest/village/room5.lpc (1), /d/darkwood/forest/village/room6.lpc (1), /d/darkwood/forest/village/room7.lpc (1), /d/darkwood/forest/village/well1.lpc (1), /d/darkwood/forest/village/well2.lpc (1), /std/weapon.lpc (1).

Type slack, second layer, and (string) casts of numbers (2026-10-20, KB 04 6.21, 6.22)

scripts/lpc_round_files.py sunshadow --failing ... with the three new fixers: lpc_fix_bad_assign.py (a variable that was handed a number, a string or an object it was not declared for is declared mixed), lpc_fix_void_return.py widened to every declared type (a function whose return contradicts its declared type becomes mixed; the string *query_guild() { return "Iron Fist"; } of the 45 Fist troopers is string query_guild() by hand), and lpc_fix_cast_cmp.py ((string)TP->query_level() > 12 is (int), string light_level that holds test_dark() is mixed). 314 files changed; HEAD loads 2 of them, the tree loads 203 (201 new), no regression, and no real warning in the objects that load. The 45 troopers (d/guilds/fist/oldfist/troopers/*.lpc; shadowgate has the same ones) compile now, but each create() calls ->set_property() on new("/realms/bane/closed/bracers5") or /realms/tristan/healing, wizard-directory items the archive does not have, so none of them loads (KB 06 7.269).

By hand, in the objects that load since: cmds/spells/f/_fell_flight.lpc redeclared heritage, which the SPELL base declares (the spell now sets the base's, so the base's help text shows the pact); d/magic/store_comp.lpc and d/magic/obj/store_comp.lpc declared int set_name over the base's void; d/islands/elf/mon/guard.lpc had ?: with an int and a string branch; d/atoyatl/bridge_monitor_d.lpc bp = to_int(bp + 0.5) (the conversion the driver does anyway, said out loud); d/laerad/obj/ragnarok.lpc holder holds the owner's name (string holder); d/tharis/obj/trunk.lpc base_name(inven[i] == "...") is base_name(inven[i]) == "..." (the misplaced parenthesis handed the result of a comparison to base_name(), so the check for the tree thing never worked; shadowgate has the same line).

Left: of the 2,829 objects that failed, 2,628 still do not compile (a dry round over the whole list): the 1,149 placeholder files named x, the missing ./../../newbie.h (249), ./../inherits/area_stuff.h (98), undead.h (69), castle.h (58), eldebaro_in.h (46), drownew.h (33), areadefs.h (25), the 119 files that hold an FTP 421 Timeout line, and the 36 priest spells under cmds/priest/godspells and old/weapons that call the old set_spell_level(3) with an int where the base takes a mapping (keys(levels) has nothing to work on): archive loss and dead code, not slack.

member_array('/', str) became strsrch(str, "/"): the character-literal member_array is a byte search (2026-10-20, KB 06 7.283)

'/' in member_array('/', str) is an int, so the driver searches the UTF-8 bytes and answers a byte offset (a third argument is a byte offset too). FluffOS 923cdbc0 warns once per call site (Warning: member_array(int, string) searches BYTES) when the string holds non-ASCII text before the match, and str[0..idx-1] counts characters, so a non-ASCII string was cut in the wrong place. Now strsrch(str, "/") (-1 when absent, a character index; scripts/lpc_fix_member_array_char.py, the JSON encoders and a few other sites by hand). Changed: /adm/simul_efun/json.lpc (11).

if (x = CONST): a comparison written as an assignment (2026-10-20, KB 06 7.290)

if (arg = "button" || arg = "pill"), if (!arg || arg = "") return 0;, else if (check = 0 && tempB != ""): = was typed for ==, so the condition is the constant: the branch is always taken for a non-zero number or any string (even "") and never for 0, and the variable is overwritten; the driver does not warn. Every such = became == (scripts/lpc_fix_cond_assign.py, 31 terms). Changed: /cmds/bard/_lore.lpc (1), /cmds/spells/a/_animate_object.lpc (1), /d/atoyatl/tecqumin/city/rooms/balcony.lpc (1), /d/atoyatl/tecqumin/rooms/spell.lpc (1), /d/common/obj/clothing/magerobe.lpc (2), /d/dagger/exchange/horse/storage/horse_master.lpc (1), /d/dagger/islands/wailing_isle/mon/mutt.lpc (1), /d/darkwood/watermargin/pator/H2Osword1.lpc (3), /d/darkwood/yntala/rooms/spassage2.lpc (1), /d/deku/fortress/throne.lpc (1), /d/deku/misc/pet.lpc (3), /d/islands/dreams/rooms/good2.lpc (1) and 11 more (23 files).

A login password over 255 bytes: crypt() raises (2026-10-20, KB 06 7.294)

The driver's crypt() raises (Error in crypt(), check your salt) for a key over 255 bytes, where the old driver hashed any length. A password of 256 bytes or more typed at the login prompt threw an error in the password check and left the connection at a bare > prompt, so the login never completed (the old-password test of password the same); found by the sweep shape of 300 x. /adm/obj/simul_efun.lpc now defines a simulated efun crypt(key, salt) of the same name: only an over-long key is cut (to 255 characters, then character by character until sizeof(to_buffer(key)) <= 255: the limit is in bytes, a CJK character takes three) before it is handed to efun::crypt(); every ordinary password hashes as before and the saved hashes are untouched (scripts/lpc_add_sefun.py crypt).

darkrobe keeps a name in an object variable (2026-10-20, KB 06 7.305)

d/attaya/senund/obj/darkrobe.lpc declares object OWNER; but stores the player's name (a string) in it and compares against it: error: != always true because of incompatible types ( string vs object ), the robe (and the NPC that carries it) never loaded. The declaration is string OWNER; now.

玩家命令 /cmds/mortal 与 refs_d:旧驱动的类型松弛、前向调用、保留字(2026-10-20, KB 04 6.21, 6.19, 6.14, 6.24)

load 检查在 cmds/、adm/、daemon/、std/ 下找到 252 个失败对象,其中 136 个是真对象;这是第一优先组(玩家每天打的命令)。HEAD 上 24 个文件全部加载失败,现在 23 个加载,没有 warning;cmds/mortal/make/m_monster.lpc 因存档损坏仍失败(见末节)。MudOS 对声明类型、前向调用和 (object) 强转都不检查,这个驱动全部拒绝,整个命令对所有访客都不存在。

验证:lpc_listcheck.sh 对这组文件 HEAD 0 个加载,树 23 个加载(只有 m_monster 仍失败),lpc_load_diff.py regress 0。scripts/play_boot.sh 起的真驱动里(play-sunshadow-xq,机器人 xqaab)实际打了:actions、blindfold(对自己、不带目标)、buff(无参、list、diff、add magic missile)、gag、heal(无参、对自己)、hunt、study(无参、study sword)、forage(无参、for herbs、for food)、bury(corpse)、eavesdrop、spells mage、spells cleric、cast(无参、cast fighter x、cast mage ...)、craft(crafted、items weaponsmith)、follower(无参、list)、skin、passwd(再输一个错的旧密码,答 Permission denied.)、gossip:每个命令都有自己的回答,驱动控制台和 log/debug.log 都没有新错误(下面两条除外)。spells mage 是新启动后第一次用 magic_d,它要加载880 个法术文件建索引,调试驱动里 32 秒才答,之后几秒;索引存进 /daemon/save/magic_d.o,以后的启动不再重建(lpcc 里同样的首次加载 28 秒)。skin <活物>(例如 skin xqaab)会走到 do_live(),这个函数只有一行原型 int do_live(object corpse);,shadowgate 也一样,哪里都没有定义,log/debug.log 是 Undefined function called: do_live;没有这个函数的原文,没有改。

法术文件 cmds/spells:类型松弛(2026-10-20, KB 04 6.21)

8 个文件,HEAD 全部失败,现在全部加载,没有 warning:a/_aura_of_healing(全局 flag; 没有类型,flag = 1 是 Bad assignment ( unknown vs int ),改成 int flag;);b/_blasphemy、h/_holy_orb、h/_holy_word、u/_unholy_orb(object limb 装 return_target_limb() 的字符串,damage_targ(object, string, int, string) 拒绝,改成 string limb);d/_dictum、w/_word_of_chaos(string align 装 query_true_align() 的整数,再和 1 / 2 / 3、7 / 8 / 9 比较,永远假,改成 int align);d/_dissolving_touch(基类的 set_immunities(string* arr) 要数组,其余法术都写 ({ "acid" }),这一个传了字符串,改成 ({ "acid" }))。没有改的:_word_of_chaos 的 preSpell() 里 align == 7 || align == 8 || align == 8,第三项看起来是 9(十行之后的过滤器用的是 7、8、9),是一个不影响加载的手误,留给人判断。

守护进程 /daemon、/adm/daemon:类型松弛、缺少的函数、第一次启动没有存档(2026-10-20, KB 04 6.21, 6.19, 7.259, 7.298)

HEAD 失败、现在加载(没有 warning)的 15 个:magic_d、bboard_d、party_d、screen_d、crayon_d、destinations_d、item_level_d、letter、localpost、map_maker_d、news_d、rumours_d、save_area_lists_d、templefunds_d、treesave_d;另有 monster_d(编译一直通过,启动预加载时报错,见下)。

验证:这 16 个对象 HEAD 失败(monster_d 是 HEAD 通过、预加载报错),树加载,regress 0;重新启动 play-sunshadow-xq,log/debug.log 里没有 monster_d 那一行。

其余命令与管理对象(2026-10-20, KB 04 6.21, 6.14, 6.20, 7.287)

验证:这些文件 HEAD 失败,树加载,没有 warning,regress 0。refs_d 修好之后,九个 old/ 目录里继承 REFS_D 的对象也能加载了(cmds/adm/old/_cref、cmds/creator/old/_unmove、_unref、cmds/system/old/_dref、_eref、_move、_ref、_refs、_tref),按规矩没有动它们;其中 _unmove、_unref、_move 各有一个没用的局部变量的 warning。

最后一次全量 lpc_listcheck.sh(cmds、adm、daemon、std 下的全部 2934 个对象):HEAD 2682 个加载 / 252 个失败,树 2759 / 175,lpc_load_diff.py regress 0,新加载 77 个(todo 里的 68 个,加上上面九个 old/ 对象)。

留下的对象(2026-10-20)

第一次 todo 的 136 个对象里还有 68 个没有加载,靠改类型修不了,或者本来就没有问题:

真驱动里碰到、但编译正常因此没有动的问题(2026-10-20)

daemon/letter:if 没有花括号(2026-10-21,KB 06 §7.298)

create() 的循环 if(file_size(DIR_LETTERS+"/"+i) == -2) y += ...; 后面还有 if(y > 9999) break; 和 __LettersDir += get_dir(DIR_LETTERS+"/"+i+"/");,缩进像在 if 里面,实际总会执行。/secure/save/letters/N/ 目录不存在时 get_dir() 答 0,+= 0 抛 Bad argument 2 to (void)+=。sunshadow 的检查树带这些目录所以没有出现,shadowgate 的新树没有,邮件守护进程加载失败。补上花括号,目录都在时行为不变。

map(不带参数)无限自我强制,skin <活物> 调用没有函数体的 do_live()(2026-10-21,KB 06 §7.333)

cmds/mortal/_map.lpc:if (!stringp(str)) { TP->force_me("map"); return 1; }:不带参数的 map 强迫玩家再敲一次不带参数的 map,一直到 Too deep recursion,玩家收到 "contact a dev"(实测)。下面 switch 的 default: 分支对不认识的参数调用 help(),所以不带参数也走 help()(<map view> 等用法说明)。cmds/mortal/_skin.lpc:skin <活物> 走 return do_live(corpse);,do_live() 只有一条原型,归档里没有函数体,调用会出错;活物和其他剥不了皮的东西一样回答 You can't skin that.,原型删掉。sunshadow 和 shadowgate 同一处,实测:map 打出用法,map list 正常,控制台没有错误。

Word of Chaos:混乱阵营的判断重复了 8(2026-10-21)

cmds/spells/w/_word_of_chaos.lpc 的 preSpell() 写 if(align == 7 || align == 8 || align == 8) 来拒绝混乱阵营的目标("Word of Chaos only affects non-chaotic targets!"):阵营按 3×3 编号(1-3 守序、4-6 中立、7-9 混乱),混乱是 7、8、9,同系的 _dictum(守序之言)写的是 1 || 2 || 3。最后一个 8 是 9 的笔误,混乱邪恶(9)的目标没有被拒绝。改成 align == 9。shadowgate 的这个法术是另一份实现,没有这一段。

战斗探针:五个怪物、Stormbringer 和卓尔面具编译不了,Freloc 死在巫师目录的匕首上(2026-10-21,KB 04 §6.22,KB 06 §7.305 / §7.269)

战斗探针登录时的控制台报 d/dagger/marsh/tower/mon/grimmy.lpc:311: != always true because of incompatible types ( string vs object ):if((string)TO->query_current_attacker() != targ)。本库的基类把 query_current_attacker() 声明为 object,所以这个 (string) 转换在五个怪物里是编译错误(d/common/assassins/dryzil.lpc、freloc.lpc,d/dagger/drow/mon/shadow.lpc,d/dagger/marsh/tower/mon/grimmy.lpc,d/darkwood/mon/malgoroth.lpc),作者要的是 (object)。d/dagger/marsh/tower/obj/storm.lpc 把 object owned; 用来存 TPQN(一个名字):改 string owned;。卓尔 shadow 和 Grimmy 带的 d/dagger/drow/obj/mask.lpc 的 scry_func() 里有 this_player(realName(target) != 0),调用了文件里没有定义的函数(Undefined function realName):删掉多出来的这一半条件,前面的 (string)this_player()->realName(target) != "" 才是条件。freloc 在 create() 里两次克隆 /realms/grazzt/assassins/dagger.lpc,这个巫师目录档案里没有,new() 答 0,0->move() 让 NPC 加载就中止:克隆和两条 wield 放在 file_size(...) > 0 后面。这 7 个对象 HEAD 加载 0 个,工作树 7 个。改动:上面列出的文件。

study room <东西>:obj 没有赋值(2026-10-21,KB 06 §7.346)

cmds/mortal/_study.lpc 在检查完房间里确有这件东西之后读 obj->query_obvious_short(),obj 从没赋过值,所以 study room <东西> 一律抛 Bad argument 1 to call_other()。shadowgate 的原版只打印 what;这里把重写过的两段消息换回原版的两句(tell_object(TP, "You carefully begin studying the " + what + " here.") 和对房间的那句)。

兽人小队、树屋平台和扑克荷官:三个拼错的局部变量(KB 06 §7.348,KB 06 §7.350,2026-10-21)

orog.lpc 的 swing_in() 问的是没赋值的 critter(循环变量是 critters[i]);t_house_base.lpc 的 fly_platform() 问 rooms->query_spiked(),对象是 room;扑克荷官 deal() 对离开的玩家播报 absentee->QCN,应是 player(同一个文件里 sizeof(hand !=2) 还让它根本编译不了,见下)。改动:/d/atoyatl/tecqumin/mons/orog.lpc、/d/atoyatl/tecqumin/treehouse/rooms/t_house_base.lpc、/d/shadow/mon/dealer.lpc。

魔怪 fiend.lpc:a != b != c 的比较链编译不了(KB 06 §7.282,2026-10-21)

心跳里的 if (base_name(att[i]) != base_name(TO) != base_name("klauth")) 先比出一个 int 再和字符串比,类型检查报 != always true because of incompatible types ( int vs string ),这个怪物整个加载不了;改成两个比较用 && 连接。改动:/d/islands/common/mon/fiend.lpc。

sizeof(x == n):括号里放了比较(KB 06 §7.350,2026-10-21)

sizeof(tmp != 2)、sizeof(casualties ==1)、sizeof(file_list == 0) 先算比较再取 0 / 1 的 sizeof(本驱动里 sizeof(1) 是 0),守卫或提示的分支永远不执行;扑克荷官的 hand 是 string *,类型检查直接报错,荷官根本加载不了。改成 sizeof(x) == n。改动:/d/atoyatl/tecqumin/tabaxi/obj/catapult.lpc、/d/atoyatl/vis_climb_d.lpc、/d/shadow/mon/dealer.lpc、/d/token_hunt/token_vendor.lpc。

账号登录和 IPC 监视:两个拼错的调用目标(KB 06 §7.349,2026-10-21)

pacct_login.lpc 的 input_to("get__pacct_password", ..)(双下划线)名字对不上 get_pacct_password,输入的密码被吞掉;ipc.lpc 的 call_out("ip_monitor_self", ..) 对应的函数是 ipc_monitor_self。改动:/adm/obj/pacct_login.lpc。

注册到不存在函数的 add_action(2026-10-21,KB 06 §7.349)

add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 82 条注册(54 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/antioch/greaterantioch/mon/zathon.lpc(ask_em)、/d/antioch/wiz_hall/wizard_rm1.lpc(peruse_titles)、/d/atoyatl/tecqumin/base_files/ropebridge_base.lpc(jump_off)、/d/atoyatl/tecqumin/city/rooms/balcony.lpc(address_city)、/d/atoyatl/tecqumin/jungle/rooms/ropebridge_base.lpc(jump_off)、/d/atoyatl/tecqumin/rooms/ropebridge_base.lpc(jump_off)、/d/atoyatl/tecqumin/rooms/t_house_middle.lpc(climb、fly_platform、shoot_rope、throw_rope)、/d/atoyatl/tecqumin/rooms/treetop_base.lpc(swing)、/d/atoyatl/tecqumin/rooms/zigzenith.lpc(release)、/d/atoyatl/tecqumin/tabaxi/rooms/battlement.lpc(kick_em) 等共 54 个文件。最多的函数名:no_kill 16 处、dive_func 5 处、ask_em 3 处、jump_off 3 处、throw_rope 3 处。没有动(读过):/cmds/guild/philosopher/_cmd.lpc 的 relsoul(对象本身编译不过,登记留着,等对象能加载再处理)、/d/antioch/antioch2/d_animal.lpc 的 get_off(对象本身编译不过,登记留着,等对象能加载再处理)、/d/antioch/greaterantioch/obj/healring.lpc 的 use_ring(对象本身编译不过,登记留着,等对象能加载再处理)、/d/atoyatl/tecqumin/obj/jung_maze.lpc 的 place(对象本身编译不过,登记留着,等对象能加载再处理)、/d/atoyatl/tecqumin/obj/tec_maze.lpc 的 clear_maze(对象本身编译不过,登记留着,等对象能加载再处理)、/d/atoyatl/tecqumin/obj/tec_maze.lpc 的 place(对象本身编译不过,登记留着,等对象能加载再处理)、/d/attaya/tunnels/rooms/ugstream.lpc 的 mine_crystals(对象本身编译不过,登记留着,等对象能加载再处理)、/d/magic/new_book.lpc 的 copy(对象本身编译不过,登记留着,等对象能加载再处理)等共 11 处。

基类的 add_action 登记:处理函数只在后代里定义,基类自己被加载时报错(2026-10-21,KB 06 §7.349)

房间、NPC、公告板这类基类,init() 里登记的 add_action("函数", "动词") 的函数只由继承它的后代定义;基类对象自己被直接加载(房间可以走进去、NPC 可以克隆)时,访客输入这个动词,驱动报 *Function for verb 'x' not found.(第一轮扫描把这些当成“有后代定义,是活的”放过了,scripts/lpc_verify_handlers.py 在基类对象上跑 function_exists() 才看出来)。登记前加 if (function_exists("函数", this_object(), 1))(第三个参数 1:默认 function_exists() 把 protected / private 函数当作没有定义),后代照常登记。共 3 处、3 个文件(scripts/lpc_fix_dead_add_action.py --guard):/d/antioch/valley/forest.lpc(observe_em)、/d/shadow/room/pepitom/bar.lpc(read)、/d/shadow/voting.lpc(me_vote)。

注册到不存在函数的 add_action,第二轮:宏路径父类和静态解析不了的对象(2026-10-21,KB 06 §7.349)

第一轮的静态解析读不了经宏路径继承父类的对象(#define ROOM STD_DIR "room/room" 这类定义的正文以另一个宏开头)和它解析不了的继承,这些对象当作 UNSURE 没有扫;第二轮修了解析器,重扫并对这些对象在运行时问 function_exists()。add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 1 条注册(1 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/atoyatl/tecqumin/obj/amulet_of_seeking.lpc(find_all)。最多的函数名:find_all 1 处。

缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

new(路径)->move(...); / carry_object(宏("x"))->wear(); 这类语句,路径指向的文件在这个压缩包里没有:new() 返回 0,->move() 在 0 上报 Bad argument 1 to EFUN call_other()。在 create() 里这个对象永远加载不了,在 reset() 里第一个缺失物品之后的刷新每次 reset 都被跳过。319 个文件里 636 处(new)改成 { object new_ob = new(路径); if (new_ob) new_ob->move(...); }(scripts/lpc_guard_carry_object.py --new:路径按这个文件自己看到的宏解析,宏名冲突、虚拟对象路径一律跳过)。 对这 319 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 139 个,现在 209 个(新增可加载 70 个,回归 0 个)。

/d/magic/scroll.lpc 从来没有编译成功:卷轴和所有刷卷轴的房间 / NPC 都加载不了(2026-10-22)

魔法卷轴 /d/magic/scroll.lpc 是 safe_scroll / newbie_scroll / scribed_scroll / mscroll 的父类,132 个文件(房间、怪物、图书馆)new("/d/magic/scroll") 它或者继承它,它在这个库里编译失败:Undefined function set_clevel(3 处)和 Bad type for argument 1 of set_spell_name ( string vs string * )。原因两个:(1) set_clevel(int l) 是不写返回类型的老式定义,写在用到它的 void init() / varargs void set_av_spell() 后面,这两个函数声明了返回类型,驱动对声明了返回类型的函数里还没见过的函数名报 Undefined function(KB 04 §6.5 前向声明;四个版本对比:只补原型就消掉这 3 处,只给定义补 void 不行);(2) 文件开头的原型清单声明了 int set_spell_name(string str),set_av_spell() 却把一个声明成 string * 的变量传给它(rclass 取自 SCRL_CLASSES[random(...)],是字符串;rspell 是 MAGIC_D->query_random_spell() 的返回)。改法:原型清单补 void set_clevel(int l); void set_spell_level(int l);,两个定义加 void,string * rclass; string * rspell; 改成 string rclass; mixed rspell;。证明(scripts/lpcc_eval.py,HEAD 与工作区各一个新进程):HEAD 上 new("/d/magic/scroll") 报 No program in object;改后连续创建的卷轴都有法术名、法术等级和价格(spell turning 7 级 4900、greater heal 7 级 4900、monster summoning iv 4 级 1600 ……),newbie_scroll、safe_scroll、scribed_scroll、mscroll 和 5 个用到卷轴的房间 / 怪物(/d/deku/sanctuary/tower/library、/d/newbie/rooms/keep/old/floor8、/d/dagger/aketon/rooms/tree3/3two13、/d/islands/argentrock/mon/fey_ri、ambusher)全部能加载。上一节调用链保护里 sunshadow 加载检查的几条随机分支 REGRESS(!random(3) 刷 new("/d/magic/scroll") 的怪物)就是这个文件不能编译造成的,不是回归。

不带前导斜杠的物品路径:缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

carry_object("d/city/obj/jiasha")->wear(); 这类路径不带前导斜杠的写法在这个驱动里按根目录解析(new("d/city/obj/changjian") 能加载 /d/city/obj/changjian),所以和带斜杠的一样:文件不在压缩包里时 new() 返回 0,->wear() 在 0 上报 Bad argument 1 to EFUN call_other(),整个 NPC 加载不了。此前的保护工具把这种写法当作「相对当前目录,没法判断」跳过了。2 个文件里 2 处(d/standard/obj/weapon/orc_slayer、realms/general/weapons/dragonclaw)改成带保护的块(scripts/lpc_guard_carry_object.py)。 对这 2 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 0 个,现在 1 个(新增可加载 1 个,回归 0 个)。

log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)

这个库的代码往 log/ 的 2 个子目录写日志:log/archive(daemon/voting.lpc)、log/magic(cmds/adm/_bestow.lpc)。压缩包、git clone 和站点包里都没有这些目录(本地启动没有写过它们,gen_keep_dirs.py 没记),write_file() 对不存在的目录抛 *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory",调用它的函数在写日志那一行中断,后面的奖励、标志、步骤都没有执行。本库的 log_file() 是 write_file(LOG_DIR + file, text),目录不存在就抛同样的错误(实测)。把 log/archive、log/magic 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。

写死的 "/路径"->函数() 指向库里没有的文件:改成现存的同名文件(2026-10-22,KB 06 §7.359)

6 处语句级的 call_other 字面量路径,库里没有这个文件(区域改了名 / 大小写不对 / 差一个字母 / 文件挪了地方):"/realms/lujke/daemons/destinations_d.lpc"->add_waystations()(/d/atoyatl/tecqumin/obj/far_scanner3.lpc、/d/atoyatl/tecqumin/rooms/destination_room.lpc、/d/atoyatl/tecqumin/rooms/destination_room2.lpc)→ "/daemon/destinations_d.lpc"。call_other() 对不存在的对象抛 couldn't find object,所在函数当场中断。改成库里现存、并且定义了被调用函数的文件(scripts/lpc_fix_call_path.py;巫师目录、备份目录、名字不同的兄弟文件都不算候选)。

深度功能测试(2026-10-27,§6.25 one-arg tell_object 残渣)

与 shadowgate 同形:gossip / rumours / cape / smithy / bloodlust / lair / map / spyglass / snakestomach。

深度功能测试(2026-10-27,§6.25 tell_object 字符串目标)

crushing_hand:tell_object("target", …)→tell_object(target, …)。

深度功能测试(2026-10-27,§6.25 one-arg tell_room)

tell_room("…") 补房间参数(this_object() / ETO / place / environment(this_object()) 等)。