LPmud 2.4.5

✅ 可玩

lpmud245

更新 85174a6 2026-10-22 源码 下载 ZIP

▶ 开始游玩 · Play Now

A pristine, pre-LDMud LPMud 2.4.5 snapshot -- the mudlib distributed with Lars Pensjo's own original LPMud driver line, from right before the LDMud/MudOS fork. A real, playable small world (286 files, hundreds of rooms) rather than an infrastructure sketch: new characters land in the village church (which hides a call-button-summoned elevator behind its west wall) and can explore a green, market road, bank, shops, and an adventurers' guild that runs a real fetch quest -- 'Retrieve the Orc slayer from the evil orc shaman, and give it to Leo' -- pointing players at an orc valley with its own orc-treasure room. A twelve-room forest and a 29-tunnel mine flank the village; a giant's lair, path, and confrontation room house a cloned, aggressive level-15 giant NPC; and the village shore road leads out to an entirely separate 'south' region -- its own self-contained ~49-room forest, 18-room island archipelago, and 30-room shoreline.

README

A pristine, pre-LDMud LPMud 2.4.5 snapshot -- the mudlib distributed with Lars Pensjö's own original LPMud driver line, from the era right before the LDMud/MudOS fork. Genuinely FluffOS/MudOS-compatible: a full driver_hook/H_[A-Z_]+/set_driver_hook grep across the whole archive found zero hits (unlike, say, the Docker Hub ldmuddev/lp245 image, which is an LDMud-ported build of the same nominal version). obj/master.lpc's own header comment dates it precisely: "This is the LPmud master object, used from version 3.0... designed to be used with mudlib 2.4.6 or earlier."

This is a real, playable small world -- 286 files, hundreds of real rooms (a village with a church, green, market road, forest, mine, sewers, a maze, a "south" continent, wizard-only areas), NPCs (orcs, wolves, a dragon, guards), a level/experience system, and the classic LPMud spell/combat mechanics -- not a thin infrastructure sketch like the already-onboarded basis.

Provenance

Recovered directly from the still-live ftp.lysator.liu.se LPMud archive mirror (https://ftp.lysator.liu.se/pub/lpmud/mudlibs/other/2.4.5.tar.gz), same as basis -- no Wayback Machine recovery was actually needed, the file is still served as-is.

Highlights (see NOTES.md for full detail)

Registration flow

Type your name -> (new character) password -> confirm password -> email address (or none) -> gender (m/f/o) -> straight into room/church, the starting room.

Local play

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

Port 40262.

NOTES · 移植与修复记录

LPmud 2.4.5 -- porting notes

Source: other/2.4.5.tar.gz, recovered directly from the still-live ftp.lysator.liu.se LPMud archive mirror (https://ftp.lysator.liu.se/pub/lpmud/mudlibs/other/2.4.5.tar.gz). Slug lpmud245, number 960, port 40262. 582 raw files (286 .c sources), mudlib root at the archive's own top level. No driver_hook/ set_driver_hook/H_[A-Z_]+ usage anywhere (confirmed by grep) -- genuinely a pre-LDMud, MudOS-line-compatible snapshot, unlike the LDMud-ported Docker Hub ldmuddev/lp245 image of nominally the same version. obj/master.lpc's own header comment: "This is the LPmud master object, used from version 3.0... designed to be used with mudlib 2.4.6 or earlier."

1. Extraction and conversion

Pure ASCII/English archive, 0 lossy conversions. 286 files renamed .c->.lpc, 5 literal .c" references fixed, 8 files static->nosave.

2. Proactive on-sight checklist findings

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

4. Compile-sweep triage: known-acceptable remaining failures (3 of 286)

5. Live playthrough (native driver)

Full flow verified in one continuous session, port 40262: fresh registration (name / password twice / email none / gender) reaching room/church -> look (correct room description, all objects mentioned) -> south (moves to room/vill_green, correct room description including "There are three obvious exits: north, west and east" -- the exact code path the room/room.lpc recursion bug above broke) -> look again (correct) -> score (correct XP/gold/HP/ spell-points/age display) -> quit (clean save + disconnect). Tested with both m/f gender selections. Re-login (existing-password path) verified separately: password accepted, correct room reached, movement and score both still correct. debug.log fully clean (zero error lines) across every test session after all fixes above.

6. WASM

Boots and plays identically under the WASM build (~/src/fluffos/build-wasm/src, via scripts/wasm_client.js): full registration -> look -> quit, correct output throughout. wasm_status: playable.

7. New AGENTS.md catalog entries added this session

(Alongside the basis-onboarding entries for the closure/function- pointer gap, new/class reserved words, and the extract/ log_file/privp/cat missing-efun list.)

1. file_name() always returns a leading / on this driver -- any archive comparing file_name(ob) == "bare/path" (no leading slash) against a literal path constant will silently ALWAYS be false. Watch for this specifically in "am I my own shared singleton instance" self-identity checks, where a false negative causes infinite self-delegation (a "Too deep recursion" crash) -- as opposed to the more common case of just silently failing to match, which tends to surface as a missing-feature symptom instead of a crash. 2. A non-wizard player's visible error output can be a complete red herring when mudlib error handler is on (the default): this driver replaces the real error text with the configured default error message for any non-wizard command_giver, while still logging the FULL real error (including a complete stack trace) to debug.log every time, unconditionally. A crash whose player-facing symptom looks like ordinary (if terse) game content -- not an obvious error message -- should still prompt a direct debug.log read before concluding "no bug here." This generalizes AGENTS.md §10.3's existing point about logon() swallowing errors to a much broader, config-driven case that can apply to literally any command. 3. command(str, ob) (run a command as a specific, non-current object) is not supported on this driver at all -- command(str) always runs against current_object, never command_giver/ this_player(). Needs the same per-target do_command()-wrapper treatment as move_object()'s 2-arg dialect gap (§7.158). 4. transfer(item, dest) -- a second classic MudOS efun for the same "move an arbitrary object" concept move_object() also covers on old drivers; not real on this driver either. Fix identically: A->move_object(B) via the same per-object shim. 5. add_action("fun"); add_verb("cmd"); -- a two-statement legacy idiom for what modern add_action(fun, cmd) (2-arg, both required on this driver) does in one call. Mechanically mergeable via a regex over adjacent statement pairs. 6. creator_file() is required by this driver's PACKAGE_UIDS build for literally every object load, not just at boot like get_root_uid()/get_bb_uid() (§7.2's existing entry) -- creator_file() deserves its own line in that catalog entry since its absence blocks ALL subsequent loading, not just initial boot.

8. §10.7 deep functional test (2026-08-31)

Full continuous-session playthrough on the native driver (port 40262): fresh registration -> walked the ENTIRE village crossroads area (church -> vill_green -> vill_track -> vill_road1 -> vill_road2 -> shop -> adv_guild -> yard) rather than just the two rooms §5's original pass covered, tested the shop's list/buy/value commands, the guild's list/cost/advance commands, a real multi-round combat against the yard's Beggar NPC (hit/miss/damage messages both directions, correct HP tracking), quit mid-combat, and a fresh reconnect (password accepted, HP correctly persisted at the damaged value, room reset to the starting room/church matching this lib's existing documented save-data scope). Also ran a 200s WASM scripts/wasm_boot_watch.sh lpmud245 200 long-sit.

Severe bug found and fixed: ~80% of the whole game world (70 of 88

rooms) started dark and/or completely exit-less on every fresh boot, for up to 30 minutes, because the create() { reset(0); } fix already applied by hand to a handful of custom rooms (§3's room/church.lpc writeup) was never extended to the two shared macro headers (room/room.h's ONE_EXIT/TWO_EXIT/THREE_EXIT/FOUR_EXIT and room/std.h's identically-named but independent macro set) that generate the vast majority of this archive's actual room files

Found by simply continuing the walk past where §5's original pass stopped: south from the church into room/vill_green worked exactly as documented, but the very next room east (room/vill_track, one hop from spawn) printed "A dark room.", look said "It is too dark.", and every direction command including the one that had just been used to enter the room returned "What?" -- a genuine dead-end softlock for any new player who wandered one room past the tested path, not a cosmetic darkness issue.

Root cause: this whole archive uses the classic reset(arg){ if (!arg) {...} } idiom as its room constructor (exits, light, and descriptions are all populated inside reset(), only on the very first, arg==0 call) -- but this driver's call_create() always marks a freshly-loaded object as "already reset" and defers ITS real first reset() call for up to "time to reset" seconds (room/ church.lpc's own extensive comment, §3, already documents this exactly). church.lpc (and a handful of other hand-written rooms: bankroom, elevator, wiz_hall) got an explicit create() { reset(0); } added individually to force synchronous initialization -- but this archive's OTHER 88 room files are almost all generated by one of two near-identical macro-header systems (room/room.h, inherited via #include "room.h" + inherit "room/room", used by 68 files; room/std.h, a fully standalone macro set with no inheritance, used by 13 files, 4 of which -- including church.lpc itself -- override the macro entirely with hand-written code and already had the fix). Neither macro header itself was ever given the create() fix, so every room instantiated purely by invoking ONE_EXIT(...)/ TWO_EXIT(...)/etc. (which is the OVERWHELMING MAJORITY of the explorable map: every forest*/plane*/eastroad*/giant_* room, plus the shop, the bank, and the adventurers guild) never ran its reset() until the driver's own lazy scheduler eventually got to it.

The two macro systems differ in how bad the resulting symptom is:

Fix: added create() { reset(0); } directly inside each of the 8 macro definitions (4 in room/room.h, 4 in room/std.h) rather than hand-editing 81 individual room files -- confirmed no macro-generated file already defines its own create() (would be a duplicate-function compile error) and no already-fixed hand-written file (church, bankroom, elevator, wiz_hall) invokes either macro. Verified live, post-fix: room/vill_track is lit with both exits working on the very first visit after a fresh boot; room/shop and room/adv_guild are fully described (list/buy/cost/advance all producing correct output) on first entry too. New AGENTS.md catalog entry, §7.185 (this is a coverage gap in an ALREADY-catalogued fix pattern -- §3's own writeup already explains the underlying mechanism correctly -- rather than a new root cause, but the specific "a shared macro header used by the vast majority of content files was missed by a fix applied file-by-file to only the hand-written exceptions" shape is worth its own entry so a future sweep checks macro headers first).

Confirmed NOT a bug -- one remaining gap, low priority, left alone

obj/quicktyper.lpc (a heartbeat-driven wizard debug/auto-typer utility, not player-facing content) is the ONLY file under obj// players/ still missing its own create() fix (every other file in those trees already has one, confirming this archive's original onboarding pass thoroughly covered obj/ by hand -- the miss was specifically the two room macro headers, not a general gap). Its reset() only sets an owner name and allocates a history array for its own debug/logging use, both of which the driver's own eventual lazy reset (or the object's own later use) still populate correctly before they matter; no observed player-facing symptom. Not fixed, noted here for completeness.

Regression check

Full re-walk of the fixed rooms plus every other already-verified command (score, inventory, combat, quit, reconnect) in one final continuous session: zero errors in the driver's own captured stdout (this native build's debug.log is dead for the process's whole life per AGENTS.md §10.9, so stdout is the only reliable error channel).

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

scripts/lpc_warnings.py lpmud245 found 113 diagnostics. Mechanical: 74 static functions that came through as nosave are protected (all add_action/input_to/heart_beat handlers, which the driver calls regardless of visibility; set_short/set_long stay public because other objects call them), 2 more lost the redundant nosave, 23 unused locals deleted. By hand: obj/monster.lpc and obj/monster.talk.lpc declare their own spell_dam next to obj/living.lpc's nosave one; the monster's is set by set_spell_dam() and read by its own heart_beat, while living.lpc's attack() adds and zeroes the other, so the two have always been separate variables here (merging them, as the 1990 driver did, would let the first melee round zero a monster's spell damage); the monster's is now monster_spell_dam. obj/player.lpc redeclared is_npc and hunting_time, which living.h's inherit "obj/living" already provides (its local copy of move_player() now uses the inherited ones; nothing else read either). obj/quicktyper.lpc's object obj; is only used inside an #if 0 block, so the declaration moved into that block. Left as they are: players/lars/test.lpc (a wizard's scratch file with constant divisions by zero) and room/def_castle.lpc (the castle template create_wizard() fills in). Live on a pristine tree: registration, a mortal's first commands, then the same character hand-promoted to level 21 (ls/cd/cat/goto/clone/load/echo/people and the line editor) with no insufficient permission or runtime error on the console.

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

驱动的 mudlib_stats 包在主控载入时各调用一次 author_file(<主控文件>) 和 domain_file("/")。本库主控没有这两个 apply,所以每次启动都打印 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 两行,网页版每位访客的终端里都看得到。obj/master.lpc 补上了这两个 apply:作者返回 get_root_uid(),域返回 get_bb_uid(),正是驱动缺少它们时所用的值。域名等于 backbone 域时,驱动让对象沿用创建者的域,与没有这个 apply 时相同(KB 06 §7.222)。用 lpcc config.fluffos /nonexistent.lpc 前后对比,主控载入段只少了这两行。 \n

深度功能测试(§10.7,2026-10-20)— 巫师指令用到的老 efun(KB 06 §7.291)

用 level 21 的巫师账号把 obj/player.lpc 里的每条指令各按十种参数跑了一遍(scripts/playsweep.py,KB 08 §10.16),控制台上出现的全是同一类:这些指令和 obj/living.lpc、room/shop.lpc 调用了 1990 年驱动内置、FluffOS 没有的函数,运行时报 Undefined function called: ...。全部补在 obj/simul_efun.lpc:

另:room/south/sforst19.lpc 的 east() 写成 this_palyer(),已改 this_player()(那个出口一走就报错)。

再用 level 21 的巫师账号在改后的树上把全部指令各按十种参数重跑(scripts/playsweep.py),控制台上只剩两类,都是巫师指令对文件不存在没有设防:clone <不存在的文件>(clone_object() 在本驱动里返回 0,随后 ob->move_object() 报 Bad argument 1 to call_other)加了 if (!ob) 就写 Failed to clone it.;home(没有 players/<名字>/workroom.lpc 的巫师,move_object 报 could not find destination,巫师什么也看不到)先查文件,没有就写 You have no workroom.。

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

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

注册到不存在函数的 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 1 条注册(1 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/room/wiz_hall.lpc(south)。最多的函数名:south 1 处。