TMI-2 (The Mud Institute)

✅ 可玩

TMI-2

tmi2

更新 f1b9b2e 2026-10-22 源码 下载 ZIP

▶ 开始游玩 · Play Now

A historically significant, widely-copied English-language LPMud mudlib from 1992-1996 whose std/ base classes, command-soul dispatch, and /adm/-rooted security-daemon layout were inherited by a large family of later mudlibs (this project's own catalog refers to a 'TMI-2/Falcon family' of descendants). This port is the 'famous TMI-2 quad' release itself: a landing zone with a general-purpose bulletin board, an Intermud-3 network-discussion room, a MudOS-driver-discussion room, a bug-report room, and a link down into /d/Fooland, plus the full TMI-2 wizard toolset (file/object inspection commands, the channel system, a student/teacher tutorial subsystem, FTP and HTTP daemons, and a resolv_str/resolv_ref scripting mini-language for wizard reference variables) and character creation across 13 playable races (dwarf, elf, gnome, human, orc, troll, halfling, sprite, shadow, ent, siren, skeleton, zombie). This collection's other TMI-2 1.4alpha-descended archive, mortremains, shares this lib's own credit line ("Truilkan@TMI"). Booting this lib also opens a real outbound connection to the public Intermud-3 router network, matching genuine upstream behavior rather than a bug.

README

TMI-2 ("The Mud Institute", version 2) is one of the historically significant, widely-copied English-language LPMud mudlibs of the 1992-1996 era. Its std/ base classes, command-soul dispatch, and /adm/-rooted security-daemon layout (master, access, groups) were inherited and re-derived by a large family of later mudlibs -- this project's own catalog already refers to a "TMI-2/Falcon family" of descendants because of how far this lineage's conventions spread through the wider LPMud ecosystem.

This port is the "famous TMI-2 quad" release itself: a landing zone with a general-purpose bulletin board, a network-discussion room (Intermud-3), an MudOS-driver-discussion room, a bug-report room, and a link down into /d/Fooland, plus the full TMI-2 wizard toolset (file/object inspection commands, the channel system, the student/teacher tutorial subsystem, FTP and HTTP daemons, a resolv_str/resolv_ref scripting mini-language for wizard reference variables) and character creation covering 13 playable races (dwarf, elf, gnome, human, orc, troll, halfling, sprite, shadow, ent, siren, skeleton, zombie).

Provenance

The original lpmuds.net distribution site is now defunct. This archive (tmi2_fluffos_v3.zip, a maintained, already-FluffOS-adapted release) was recovered from the Wayback Machine snapshot <https://web.archive.org/web/20160306040625if_/http://lpmuds.net/files/tmi2_fluffos_v3.zip> and content-verified (real 1996-dated LPC source) before porting.

Highlights

Registration flow

name -> confirm (y/n) -> password -> confirm password -> gender (male/female/neuter/hermaphrodite) -> race (dwarf/elf/ gnome/human/orc/troll/halfling/sprite/shadow/ent/ siren/skeleton/zombie) -> email address (user@host form required) -> real name -> the game world.

Admin account

Registered through the normal registration flow (as the very first character on this install), then given root file-group membership. Verified live: update /cmds/wiz/_goto and update /adm/daemons/quota both succeed ("Updated and loaded.").

Live network connection at boot

Like imud in this collection, booting this lib makes a real outbound connection to the public Intermud-3 router network (a hardcoded router IP in include/net/i3.h) and exchanges a real mudlist/registration with it -- confirmed by seeing dozens of real other muds (with real IPs) come back in the saved I3 state after a boot. This is correct, intended upstream behavior, but means this lib should not be swept into high-frequency automated re-boot testing loops the way fully-sandboxed libs in this collection can be. See NOTES.md \S5.

Status

Boots clean: zero compile errors and zero uncaught runtime errors in log/runtime across repeated fresh-driver boots. Full registration (a brand-new character all the way through race/gender/email selection into the actual game world), look/score/inventory/quit, re-login as an existing character, and wizard-level update all verified end-to-end with a real driver session and a raw socket client.

A compile-sweep tail of 28 files (of 715) remains beyond the fixes above -- confirmed to be either unreferenced/dead legacy content (personal wizard scratch files, dated backup copies of live files, documentation stubs, a subsystem explicitly disabled in the archive's own shipped preload file) or one likely lpcc-harness-only artifact (a create() guard that self-destructs outside its expected caller, the same known class of false-FAIL this project has documented before). See NOTES.md \S4 for the full breakdown. None of it blocks a real boot or blocks play.

WASM status: playable. Login as fluffos into the TMI-2 quad is verified under the shared WASM driver (NOTES.md WASM status update). Play: https://mudlibs.fluffos.info/tmi2/

Local run

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

Game port: 40220.

NOTES · 移植与修复记录

TMI-2 -- porting notes

Source: a maintained, already-FluffOS-adapted ZIP (tmi2_fluffos_v3.zip) originally distributed from lpmuds.net (now defunct). The live site is down; this exact archive was recovered from the Wayback Machine snapshot https://web.archive.org/web/20160306040625if_/http://lpmuds.net/files/tmi2_fluffos_v3.zip and content-verified before use (real, dated 1996-era LPC source, e.g. lib/cmds/file/_tsh.c). Slug tmi2, number 173, port 40220.

TMI-2 (The Mud Institute) is one of the historically significant, widely descended-from LPMud codebases of the early-to-mid 1990s -- this project's own AGENTS.md already refers to a "TMI-2/Falcon family" of later mudlibs because so much of the LPMud ecosystem traces its std/, command-soul, and security-daemon conventions back to it. The archive itself bundles a full FluffOS 2.16-ds05 driver source tree (fluffos-2.16-ds05/) and prebuilt Windows binaries (win32/, bin/) alongside the actual mudlib (lib/) -- both ignored per this project's standard convention of using its own driver rather than whatever a bundled archive shipped.

1. Extraction and conversion

Mudlib root: raw/tmi2_fluffos_v3/lib/. Master file /adm/obj/master, simul_efun /adm/obj/simul_efun, global include <mudlib.h> (looked up via /include) -- a CD-lib-style layout (/adm/obj/, /adm/etc/, /adm/daemons/) distinct from the /secure/-rooted layout genesis uses, but recognizably the direct ancestor of it.

scripts/convert_lib.sh on raw/tmi2_fluffos_v3/lib -> work: this is a genuinely English-language, already-ASCII archive (1677 of 1712 files already valid UTF-8, 0 real lossy conversions -- the 2 "LOSSY" hits were a stray invalid byte each in two doc/ prose files, not source), so the encoding step was a no-op as expected. 715 files renamed .c->.lpc, 217 literal .c" references fixed, 14 local angle-bracket includes converted to quotes, 115 files static->nosave.

2. Compile-time / boot-time driver-compat fixes

- adm/daemons/channels.lpc: create_channel()'s object ref parameter and register_channel()'s object ref; local, plus every use of the identifier in between. This file IS in the live preload list (/adm/daemons/channels) and backs the real in-game channel system (gossip, etc.) -- a boot-blocking failure for a live feature, not dead code. Renamed the identifier to refob throughout (7 sites). - adm/daemons/ref_d.lpc's resolv_ref(): local string ref;. Renamed to refstr (3 sites). This daemon backs the wizard ref/ refs/unref reference-variable commands (cmds/object/_ref.lpc etc., which use "ref"/"refs" only as plain strings/mapping keys -- those were never affected). - cmds/file/_changelog.lpc's pop_coder(int ref). Renamed the parameter to refidx (3 sites). Swept the whole tree for other bare \bref\b identifier uses afterward (grep -rn '\bref\b') -- the remaining hits are all either ref/refs used purely as string literals/mapping keys (fine) or refs/ref_help as variable names (fine, not the reserved word itself), so no further sites needed changing.

3. Admin account

4. Compile-sweep tail (28 of 715 files)

scripts/lpcc_check.sh after the fixes above: 687 pass, 28 fail. Checked every failure for live references (grep -rn '/path/to/file\b' across the whole tree) before writing it off:

5. Live outbound network connection at boot (Intermud-3)

Like imud (§2.3 in AGENTS.md), this lib has a genuine, intentional live-network side effect at boot, unlike almost every other lib in this corpus (which are all sandboxed with no outbound connections). adm/ daemons/network/I3.lpc is preloaded at boot (via the live channels preload chain / first check_router() call) and connects out to a real, hardcoded public Intermud-3 router (include/net/i3.h: I3_ROUTER "97.107.133.86 8787", ROUTER_NAME "*dalet") using the real socket_create()/socket_connect() efuns in adm/daemons/network/ socket.lpc. Confirmed live: booting this lib updates adm/etc/I3.o's saved mudlist_id/mudlist fields with a real, current snapshot of the live public I3 mudlist (dozens of real other muds -- CoffeeMud, VargonMUD, CircleMUD-family servers, etc. -- with real IPs/ports), i.e. a real outbound connection and a real registration/mudlist exchange with a public third-party service, not a local-only simulation.

This is correct, intended upstream behavior (not a bug introduced by this port), but it means this lib should NOT be swept into high-frequency automated re-boot loops (round-two/round-three-style long-sit scans, periodic re-verification passes) the way every other sandboxed lib in this corpus safely can be -- each boot is a real network event against a real third party's infrastructure. Treat this the same way imud is already flagged.

6. Status

Boots clean: zero compile errors, zero uncaught runtime errors in log/runtime across repeated fresh-driver boots. Full registration (a brand-new character all the way through gender/race/email/real-name into the actual game world -- the "famous TMI-2 quad"), look/score/ inventory/quit, re-login as an existing character, and wizard-level update (after the groups fix in \S3) all verified live with a raw socket client.

Registration flow: name -> confirm (y/n) -> password -> confirm password -> gender (male/female/neuter/hermaphrodite) -> race (dwarf/elf/gnome/human/orc/troll/halfling/sprite/ shadow/ent/siren/skeleton/zombie) -> email address (validated, user@host form required) -> real name -> drops into the game world.

WASM status update (2026-08-26, another session)

Promoted wasm_status from "" to playable. Same root-cause class as ds386/discworld/deadsouls_fluffos: adm/simul_efun/ dump_socket_status.lpc (part of the eagerly-loaded simul_efun object) called socket_status() unconditionally, undefined on this driver build (no sockets package). Gutted to a safe stub (diagnostic admin tool only, not on the boot/login path). No other socket-related eager-load failures found. Verified with a scripted WASM session: login as fluffos/FluffOS2026Admin, arrival in the famous TMI-2 quad room with the correct description. look/score/quit weren't recaptured distinctly in this transcript (absorbed by the first-login news pager) but are already verified clean under native testing above and untouched by this fix.

§7.122 sibling check: load_autoload_obj() class-marker duplication (2026-08-27)

Targeted check (not a full §10.7 pass), prompted by AGENTS.md §7.122 finding the exact same compute_autoload_array()/destroy_autoload_obj()/ load_autoload_obj() mechanism duplicating class-marker items in mortremains (another TMI-2 1.4alpha-descended lib -- both this file's own header and mortremains's credit "Truilkan@TMI" verbatim, confirming the shared lineage §7.122 flagged as a plausible sibling).

Found and fixed the same defect, live-confirmed. std/user/autoload.lpc's load_autoload_obj() unconditionally cloned a fresh copy of every auto_load entry with no check for whether a matching item was already present in inventory -- byte-for-byte the same unguarded-clone shape as mortremains. Reproduced live via the admin eval command (/cmds/file/ _eval.lpc) on a real bank card (/std/bank_card, this lib's own query_auto_load()-marked class item, normally granted via open account at the bank): calling this_player()->load_autoload_obj() twice in a row on an already-carrying body cloned two more copies (1 -> 2 -> 3), and the mechanism compounds on every abrupt disconnect too -- net_dead() (this lib's own abrupt-disconnect handler, std/user.lpc:1302) calls save_data() (which calls compute_autoload_array(), re-scanning *current* inventory) but never calls destroy_autoload_obj(), so each net-dead event appends another duplicate-looking entry to the saved auto_load array for every physical copy currently held. Confirmed the live trigger path differs slightly from mortremains (whose bug surfaces across an abrupt-disconnect-then-full-restore cycle): here it's most directly reachable via adm/daemons/logind.lpc's force-takeover path (exec_old_copy(), line ~397-400) re-calling setup() -- and hence load_autoload_obj() -- on an already-live body object when a same-account login arrives from a different source IP while the old session is still interactive; the root-cause function itself is identical either way. Also confirmed (unlike mortremains) that this driver's plain save_object() does not independently serialize inventory sub-objects here -- checked the raw save file (data/std/user/f/fluffos.o) before and after reproducing the bug and only ever saw the auto_load array grow, never a separately-serialized copy of the card's own properties -- so the specific "baked into inventory before destroy runs" half of mortremains's repro doesn't apply verbatim to this port, but the reload-side defect (load_autoload_obj()'s missing idempotency check) is the same bug and fires from a real, independently-confirmed live path regardless.

Fix applied (identical pattern to mortremains's AGENTS.md §7.122 fix): load_autoload_obj() now snapshots all_inventory(this_object()) before the clone loop and skips any auto_load entry whose base_name() already matches an object already present, appending each newly-cloned item to that same snapshot so later entries in the same call also see it. Verified live end to end: pre-fix, two successive eval-triggered load_autoload_obj() calls took the bank card from 1 -> 2 -> 3 copies, and the resulting abrupt-disconnect churn (during testing) left the saved auto_load array at 9 duplicate-looking entries (one appended per net_dead() while 9 physical copies were briefly held). Post-fix, on a genuine fresh driver boot + restore against that same corrupted save file, load_autoload_obj() correctly collapsed back down to exactly 1 physical card (the first auto_load entry clones it, the remaining 8 are recognized as already-present and skipped), and three further eval-triggered re-invocations of load_autoload_obj() on the live body held steady at 1 -- confirming the guard is idempotent under repeated calls, including from a starting state the pre-fix bug had already corrupted (this port's save mechanism made a full self-heal possible here, unlike mortremains's note that its fix "does not retroactively clean up saves already corrupted before it landed" -- a difference in how each port's save file happens to be structured, not in the fix itself).

Driver killed cleanly by exact PID after verification, both before and after the fix.

Deep functional test (round two, AGENTS.md \S10.7)

First full *playthrough* pass on this lib (everything above was either onboarding-tier verification or the earlier narrow \S7.122 sibling check). Read doc/help/start first (a generic "welcome to TMI-2" text with no lineage-specific quest pointers -- this archive is the base engine itself, not a heavily reskinned derivative like its sibling mortremains), then played a full, continuous session as a real English-named character (Bramfield) on the native driver, using a second admin connection (fluffos/FluffOS2026Admin, per \S3 above -- NOT the project-wide Mud@2026 convention, since this account was already seeded with its own documented password before that convention was standardized; confirmed it still logs in correctly) for eval/ update/diagnosis. Given mortremains (the sibling TMI-2 archive already deep-tested this session) had 5 severe bugs -- a filename-slice movement bug, two inherit-ordering typos, a broken safe-sparring dummy, and the autoload duplication bug -- this pass specifically checked for each of those exact shapes here too, not just the standing cross-cutting list.

1. d/Fooland/tower2.lpc's vampire "window" exit pointed at the wrong directory -- a pre-existing 1990s typo, not a port regression

tower2.lpc (the second floor of the wizard's tower, reached from the very first room in the game via look at painting -> eyes() -> /d/Fooland/tower1 -> up) is the vampire NPC's coffin room. Its exits mapping has:

"down" : "/d/Fooland/tower1",
"window" : "/d/grid/9,14.grid.lpc"

Every other grid-room reference anywhere in this codebase (dozens of them, both in hand-authored rooms like orccave4.lpc's correct "/d/grid/rooms/9,14.grid" and in the grid rooms' own auto-generated exits) uses the /d/grid/rooms/X,Y.grid form -- this one line is missing the rooms/ path segment, and, unlike every sibling, still carries a literal .lpc suffix. Confirmed via the raw archive (raw/tmi2_fluffos_v3/lib/d/Fooland/tower2.c) that this exact typo ("/d/grid/9,14.grid.c") predates this project's port -- the .c-> .lpc conversion just faithfully carried the bug forward, same as it did for every correct reference.

Live impact: this driver's virtual-object mechanism (adm/daemons/ virtual_d.lpc -> /std/virtual/compile.lpc's compile_object()) happens to intercept ANY path matching /d/grid/* and dynamically manufacture a NEW grid-room instance at whatever exact path was requested -- so this typo doesn't produce a hard crash or an unreachable exit, but a silently-orphaned DUPLICATE virtual instance of cell (9,14) at /d/grid/9,14.grid, completely disconnected from the canonical, shared /d/grid/rooms/9,14.grid mesh every other exit in the game actually uses. The vampire's own move_player() override (vampire.lpc) unconditionally redirects EVERY departure from the coffin room through this exact exit (regardless of which direction its own move_around() wandering logic randomly picked), so every single time the vampire NPC tries to leave its coffin room at night, it lands in this disconnected pocket instead of the real grid network.

Verified live: before the fix, eval find_object_or_load("/d/grid/ 9,14.grid.lpc") returned OBJ(/d/grid/9,14.grid) (the orphaned instance) while the real network lives at OBJ(/d/grid/rooms/ 9,14.grid). Fix: "/d/grid/9,14.grid.lpc" -> "/d/grid/rooms/9,14.grid" (matching the established bare-extension convention every sibling exit uses). Verified via update /d/Fooland/ tower2 (loaded cleanly) plus a live repro: cloned a vampire, forced it to night state (receive_message with the night-start string), then called move_around() six times in a row via eval -- pre-fix it would have wandered into the orphaned pocket every time it chose "window"; post-fix each departure correctly landed the vampire in the real, shared grid mesh (/d/grid/rooms/9,14.grid <-> 9,13.grid <-> 10,13.grid, wandering normally from there).

(A second, much more minor issue found alongside this one, NOT fixed: tower2.lpc's own out_the_window() checks previous_object()==VAMPIRE where VAMPIRE is a local #define VAMPIRE "/d/Fooland/vampire" -- comparing an object to a string, which can never be true on this driver. In practice this has zero observable effect: the "vampire turns into a bat" announcement this check exists to gate is already unconditionally broadcast via a different path a few lines earlier in vampire.lpc's own move_player() override, which never checks out_the_window()'s return value at all before proceeding with the move. Documented here as an observation per this task's scope boundary, not fixed, since it changes no observable behavior and any "fix" would require guessing the author's original intent for how to identify the caller.)

2. Movement/room-exit sweep -- no other reachable broken exits found

Ran a whole-tree scan extracting every quoted destination in every exits-shaped mapping literal and checking it against the filesystem (accounting for the missing-extension driver convention). Beyond the fix above, the only other "misses" were: (a) every grid-room's own internal cross-references, which are all correctly resolved by the same virtual-object mechanism (false positives, not bugs); (b) /d/TMI/tower1 (in the dead, unreferenced d/Fooland/oldhall.lpc -- confirmed zero references anywhere in the tree, a superseded draft of the live hall.lpc, matching this lib's own already-documented \S4 "unreferenced/dead content" policy) and /d/TMI/adminrm (in d/Conf/room/centre.lpc, part of the whole d/Conf/ conference-room subsystem, which has zero live entry point anywhere in the reachable game -- confirmed via a grep for any exit/command referencing d/Conf/ room/entrance, its own nominal front door). Both left untouched as genuinely dead code, consistent with this lib's own established convention for such files.

Full movement was otherwise clean: walked the whole reachable Fooland town (quad -> hall -> smain -> square -> nmain/ebazz/wbazz -> shop -> bank -> fight_guild -> magic_guild -> tower1/tower2), including the painting/eyes() easter-egg teleport from the very first room, with zero broken exits, zero crashes, and zero log/runtime entries.

3. Safe-sparring / skill acquisition -- this lib's design deliberately has no combat-risk training dummy at all, and its actual mechanism works correctly

Unlike mortremains (which has a dedicated, buggy dummy.lpc), this lib's own std/guild.lpc header explicitly documents a design choice to skip a "kill the dummy" mechanic entirely in favor of a zero-risk gold-for-skill-improvement system at each guild (fighter's, mage's, thief's). Verified live: train Combat spells at the mage's guild (magic_guild.lpc) correctly deducted the exact listed cost (18 gold) and incremented the skill from 0 to 1; list correctly priced every trainable skill; learn read (the free read-magic-scroll spell) also worked. No combat risk involved anywhere in this path -- confirmed clean.

4. Live combat, death-avoidance, and corpse creation

Fought the hall's own "ugly orc" (obj/orc.lpc, 10 hp, sword+shield) unarmed first -- a genuinely dangerous fight for a fresh, skill-0 character (dropped to 5/60 hp), confirming this is real difficulty tuning, not a bug (fled successfully via a normal directional move, "The combat is over." fired correctly, no stuck-in-combat state). Healed via admin eval, armed with the room's own "gleaming two-handed battle axe," and re-fought to a clean kill: weapon-swing/miss/hit messages, shield/weapon-disarm-on-death messages, and corpse creation all worked correctly with zero runtime errors.

5. Shop and bank economy -- fully live-tested, clean

buy shield (10 gold) and sell shield (7g5s, matching this shop's get_ob_value() split) both correctly adjusted wealth/gold and wealth/silver with exact-match arithmetic -- no \S7.121-style float corruption anywhere in the money path (confirmed also via a targeted grep: no file combining float with wealth/gold/price/cost outside inert, disabled usage_d/usage_i CPU-stat daemons). Opened a real bank account (open account, 25 gold fee, correct card message) and deposited coins (deposit 50 gold) -- both exact-match.

6. \S7.122 sibling-fix re-verification: still holds under a fresh reconnect cycle

Re-verified the already-fixed load_autoload_obj() idempotency guard (see the dated 2026-08-27 entry above) using the SAME live trigger path that entry documented -- a same-account force-takeover reconnect (exec_old_copy()'s "Your other copy is still interactive. Do you want to throw it out?" flow) -- rather than re-deriving the fix from scratch. Gave Bramfield a real bank card via the normal open account flow (not a synthetic clone -- std/bank_card.lpc's own move() override rejects a card being handed to anyone but its actual named owner, so a shortcut clone-and-move attempt via eval was correctly rejected, confirming that guard also still works), then opened a SECOND connection as Bramfield twice in a row while the first stayed interactive, answering "y" to throw out the old copy each time. i after each force-takeover consistently showed exactly 1 bank card -- the guard added on 2026-08-27 holds. No re-fix needed.

7. Quit / reconnect persistence

quit while carrying the axe correctly printed "You stop wielding..." / "You drop your whole inventory on the ground" (this lib's own by-design "can't quit-save carried items outside a safe location" mechanic, not a bug) and returned to the login prompt cleanly. log/ runtime had zero entries both immediately after and after a real 12-second wall-clock gap. Reconnecting showed the full news/banner sequence and a genuine "Last logon" timestamp (a real restore_object() cycle, not a live/interactive reattach) with gold (482 -> persisted correctly through training/shop/bank transactions), skills (Combat spells: 1), and hit points all correctly restored.

8. Standing cross-cutting bug-pattern checks -- all clean

Grepped and/or live-verified each of the following; none present:

9. Test cleanup

All throwaway test-character saves (Bramfield, plus two earlier abandoned mid-registration attempts, Wistleford/Hollisgate, left over from iterating on the registration script) were deleted from data/std/user/ and data/std/connection/ before committing, per this pass's instructions -- only the seeded fluffos admin account remains. Driver killed cleanly by exact PID after this pass; zero log/runtime entries accumulated across the whole session.

深度功能测试(§10.7,2026-10-03)— round three, fresh angles

The 2026-08-27 pass covered the combat/shop/bank/reconnect player path. Two facts kept the rest of the lib unexercised: AUTO_WIZHOOD is defined, so every new character is a wizard (the player-only code paths -- suicide, the non-wizard ghost revive -- are never reached by a visitor), and the 08-27 working tree still had the archive's empty tmp/ and open/ directories on disk. This pass registered fresh characters, demoted one with dewiz (admin: path -add /cmds/adm first, the admin path is not in NEW_WIZ_PATH), and drove death, ghost, revive, mail, boards, the editor and the communication commands. Six real bugs, all fixed:

1. tmp/ and open/ missing: mail, board post, every editor-opening command, and eval were dead. mail <user> printed the Subject prompt, then *Wrong permissions for opening file /tmp/<name>-47.m for append. "No such file or directory" (trace in log/runtime) and swallowed the rest of the input; post and eval failed the same way (/open/ eval.fluffos.lpc). Cause: git does not track empty directories; the archive's lib/tmp and lib/open were not carried into work/ (they only existed in the old working tree). Fix: tracked work/tmp/.gitkeep and work/open/.gitkeep (the zip and a fresh clone both create the directories). The other 231 empty directories of the archive were checked and left alone: the data/mail/mesg|net|mbox and data/std/* buckets are created on demand (mkdirs(), assure_user_save_dir(), confirmed live for mbox/q, std/user/q), adm/harass is created by _harass, and no code references the rest. 2. Bare help printed a runtime error and nothing else (every user and ghost): cmds/std/_help.lpc had #define STD_HELP "/help/help_screen" but the file is /doc/help/help_screen (the older help-050596.lpc and ohelp.lpc already use that path), so read_file() returned 0 and message("help", 0, ...) raised Bad argument 1 to receive(). Fixed the path. A scan of every read_file/more/file_size target in cmds/ std/ adm/daemons/ against the filesystem found no other dead reference. (u/l/leto/cmds/_bhelp.lpc still has the stale path; it is a dormant personal copy on nobody's path, left as is.) 3. SEVERE: every death duplicated the player's coins. die() moved the coins into the corpse and set wealth to ([ ]) but never saved; the ghost is turned back into a body by restoring the save file (ghost.lpc revive() -> switch_body() -> restore_body()), so the revived character got back the purse of its last save (login/autosave) while the corpse also held the coins. Controlled test: 500 gold, save, die, pray -> corpse gold coins x500 and the revived player Gold 500 at the same time. Fix: save_data() in die() right after the purse is emptied (any other unsaved progress made since the last save also survives death now). 4. First removal after boot being a death left the dead body lying in the room. die() ends with call_out("remove", 0), where there is no previous_object(); remove() then did seteuid(euid) with an unset euid, so free_locks() (FLOCK_D->...) could not load the lock daemon (Can't load objects when no effective user., log/runtime) and the removal aborted. Live proof on an unmodified tree: the old /std/user#7 stayed in the quad next to the revived body in the cemetery. It only showed on a fresh process because any earlier quit loads FLOCK_D, which is why the 08-27 pass never saw it (and why a one-visitor WASM session is the likely victim). Fix: default euid to the body's own euid, then override from previous_object() as before. 5. suicide: the "busy" flag was never released after an abort. confirm_suicide() printed "Suicide attempt aborted." and set busy = 1 (the reset in pass_check() is never reached), so after one n the command answered "presently busy" for every player until the daemon was reloaded; a player disconnecting at the prompt wedged it the same way. busy now holds the player in the dialog and is cleared on abort; a holder that is no longer interactive does not block others. Latent in this install (wizards cannot suicide) and reachable in mortremains, which has the identical file (fixed there too). 6. A connection dropped at the "Press ENTER to continue" prompt raised an error in net_dead(). The body has no environment yet, so message(..., environment(), ...) got 0 (Bad argument 3 to EFUN message(), log/runtime) and the rest of net_dead() (heart beat off, link removal) was skipped. That prompt follows every login, so closing a browser tab there hits it. Fixed with an if (environment()) guard (also in mortremains, same code).

Verified on a pristine tree (git archive of the tracked files plus the fixes, log/ created as the site loader does) on a fresh driver process: 24 scripted checks pass (eval, bare help, mail compose/send/read, board post, death + pray with no coin duplication and the corpse holding the gold, suicide abort/retry/disconnect, exactly one body after death, one bank card after death + revive, disconnect at the news pager and relogin), log/runtime was never created, log/catch holds only the boot-time weather_d.c preload miss. The same death sequence on an unmodified copy reproduces bugs 3 and 4.

Also exercised and clean: death in combat while the connection is dropped (net-dead body keeps fighting, reconnect re-attaches), wizard and non-wizard ghost (look/say refused/score/who/help, revive vs cemetery pray), quit as a ghost and relogin ("You suddenly realize that you are still a ghost."), corpse retrieval, say/tell/reply/shout/emote with %s %d and a 3000-character line, finger, board read/post, ed/ls/ cat/cp/tail/mkdir/rmdir/rm. Bank and give reject non-positive amounts in code. Not fixed, documented: one look at corpse after a combat death listed two blank 0 entries (not reproduced in four later deaths, no error logged); the boot preload entry /adm/daemons/weather_d.c carries a stale extension (the daemon loads on demand, room weather text works); finger leto says "no such user" (a dormant 1990s wizard save with no connection record). Formatter not run (minimal edits to files that keep their original style). Test characters and mail/board data deleted by exact name; the driver was killed by PID.

Local run

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

Game port: 40220.

深度功能测试(§10.7,2026-10-03)— compile warnings

scripts/lpc_warnings.py tmi2 found 961 diagnostics; 45 are left, all in the disabled or scratch files listed in §4 (usage_d/usage_i, I3-0505, channels0505, missile_spell_j, the wizards' blah/doh/nntp, shsh, www, barney, istack, o_server, func_spec, net/macros.h). Mechanical (--fix): 420 static-turned-nosave functions made protected and 62 dropped the redundant nosave, 323 unused locals, 13 varargs, 2 bare returns, one range end, one escape. The unused locals that the code only uses inside an #ifdef/#if 0 branch (logind.lpc, prune_logdir.lpc, _debugmalloc.lpc, both _diff.lpc, _fref.lpc, std/user.lpc) had their declarations moved into the branch rather than deleted (KB 04 §6.10).

Hand fixes, all verified against what the code was written to do:

Live as the seeded admin on a pristine tree: clean boot (no log/catch), call, clone, stat, a fight with a cloned orc (and death), help, help bug/praise/typo/idea, fref, date, people, users, the admin's cast/spells refusals; nothing on the console.

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

驱动的 mudlib_stats 包在主控载入时调用一次 author_file(<主控文件>),要求返回字符串,否则每次启动都打印 author_file() in the master file does not work, using root_uid as fallback 一行,网页版每位访客的终端里都看得到。本库 adm/obj/master.lpc 的 author_file() 只为 /u/<字母>/<名字>/ 下的文件返回作者,其余返回 0。而且驱动传入的路径是 "/" 加上配置里的 /adm/obj/master,即 //adm/obj/master,sane 模式的 explode() 会保留一个开头的 ""。praise 和 find -author 直接调用 master()->author_file(),把 0 当作“没有作者”,所以没有把 0 改成字符串。现在先去掉空段(explode(...) - ({ "" })),/adm 下的文件返回 ROOT_UID(与同文件 domain_file() 的 adm 分支一致),这样也回答了开机探测;/u 分支加了长度检查(原来 /u/<x> 这样的短路径取 path[2] 会越界出错,而 author_file() 在每个对象载入时都会调用)(KB 06 §7.222)。用 lpcc config.fluffos /nonexistent.lpc 前后对比(主控和 preload 全程),只少了这一行。

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

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

Removed predefine SAVE_EXTENSION (2026-10-18, KB 04 6.11)

Older MudOS predefined SAVE_EXTENSION (".o") in every LPC object; the FluffOS ChangeLog (COMPAT_BUSTER) removed it in favour of __SAVE_EXTENSION__. This lib never #defines it, so each file that names it was Undefined variable 'SAVE_EXTENSION' and the object never loaded. Now __SAVE_EXTENSION__ (scripts/lpc_fix_predefined_macro.py): adm/daemons/network/netmail.lpc, adm/daemons/virtual/o_server.lpc, obj/shells/shsh.lpc. Loaded one by one with lpcc after the change: 2 pass, 1 still stop on another error (adm/daemons/network/netmail: syntax error, unexpected L_BASIC_TYPE, expecting L_ASSIGN or ';' or '(' or ',').

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/Conf/room/centre south: /d/TMI/adminrm -> /d/Conf/room/entrance. 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).

call_out_info() 的每一项只有 3 个元素(2026-10-20,KB 06 §7.287)

驱动返回的是 ({ 物件, 函数, 延迟 }),不带 call_out 的参数,旧代码读第 4 项 info[i][3](callouts 指令、calloutd、cod 工具)一读就报 Array index out of bounds,指令从第一行起什么也打印不出来。改为 (sizeof(info[i]) > 3 ? info[i][3] : 0)(scripts/lpc_fix_callout_info.py)。改动:/cmds/xtra/_callouts.lpc(1 处)、/obj/tools/aclock.lpc(1 处)、/obj/tools/cod.lpc(3 处)。

深度功能测试(§10.7,2026-10-20)— 指令参数扫描

用 scripts/playsweep.py(KB 08 §10.16)把 cmds/std 的玩家指令按十种参数、再按十七种参数各跑一遍(880 + 1,760 行),再以种子管理员 fluffos 把 cmds/{wiz,file,object,xtra,adm,ghost} 的巫师指令跑两遍(1,350 + 2,700 行):控制台上没有一条运行时错误;屏幕上只有三个真问题,都是原版 TMI-2 里有、这份压缩包里缺东西的指令:

其余 needed-escape(bug、describe、converse、mudlist、who、apropos 的分页或编辑器提示,要 q、~q、. 才能关)是正常的。dref me 把管理员自己断开,也是它该做的。

登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)

驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/adm/obj/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。

存档里的 .c 文件名改成 .lpc(2026-10-21,KB 03 §4.2 第 9 项)

存档里写着 "/d/city/npc/wei.c" 这样的文件名;extract 时源文件已经全部改成 .lpc,而驱动对带扩展名的路径只认字面,new() / load_object() / move() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 1 个存档共 1 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/data/adm/daemons/doc_d/Root.o(1 处)(共 1 个存档)。

战斗探针(2026-10-22):obj/tools/barney.lpc 的 inherit 在全局变量之后

scripts/play_combat_probe.py tmi2 --account fluffos:... --clone ... 克隆 /obj/tools/barney 时报 10:9: Illegal to inherit after defining global variables(string *list; int which, lalala; 写在 inherit MONSTER; 前面,老驱动不管)。把 inherit MONSTER ; 挪到全局变量前面,文件现在能加载。googol 一出来就走,bozo 不能被攻击(You are not permitted to attack that.),没有控制台错误。