TMI-2 (The Mud Institute)

✅ 可玩

TMI-2

tmi2

更新 368a3d0 2026-09-04 源码 下载 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.

Local run

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

Game port: 40220.