QuestMud

✅ 可玩

questmud

更新 ab394ed 2026-10-28 源码 下载 ZIP 上游 vlehtola/questmud

▶ 开始游玩 · Play Now

QuestMud (source: <https://github.com/vlehtola/questmud>) is a real, English-language LPMud restored from a 2012 backup of a game whose "golden age" ran 1996-2000, per its own upstream README: up to 35 concurrent players and over a thousand individual registered accounts at its peak. It's a full game mudlib set in "The Isles of Deleria" -- 20 playable races (from Human and Dwarf through Golem, Lich, Demon, and Spirit rebirth races), 9 base guilds (Fighters, Mages, Clerics, Necromancers, Psionicists, Warlocks, Martial Artists, Abjurers, plus a handful of multiguild combinations like Paladin and Battlemage), a stronghold/ housing system, a mail system, and years of individual wizards' own personal build areas.

README

The port: LDMud, not MudOS

Every other classic-LPMud lib in this collection so far has been MudOS-lineage, close enough to this project's FluffOS target that the compatibility gaps were mostly small dialect differences. QuestMud is different: its original driver was LDMud (bin/ldmud 3000 per the archive's own StartMud script), a genuinely different LPC dialect -- closures (#'name, lambda/unbound_lambda/bind_lambda), a set_driver_hook()-based master-object architecture instead of FluffOS's hardcoded apply conventions, wide/multi-value mappings ((["key": v0; v1; v2]) with map[key, N] column indexing, no FluffOS equivalent at all), and several LDMud-only efuns (cat(), tail(), strstr(), to_string(), object_name(), unshadow()) that simply don't exist on this driver. Porting it was a substantially different job from every other lib here -- see NOTES.md for the full technical writeup.

Severe bugs found

Several genuinely severe, totally-blocking bugs were found and fixed, each of which alone would have made this lib permanently unbootable:

Registration flow

c (create) -> character name (2-10 letters, no digits) -> password (4+ characters) -> confirm password -> race selection room (select <race>, then continue) -> optional special traits (list, select <trait>, continue) -> guild selection (select <guild>) -> the real game world. g logs in as a guest; w lists who's online; q quits from the login menu.

Status

Boots clean under the native build-debug driver with a "lazy resets : 1" config override (see NOTES.md \S1 for why). Full registration verified live and end-to-end with a raw socket client: a brand-new character all the way through race selection, trait selection, and guild selection into the real game world, followed by working look (real room description, "No obvious exits.") and score (a complete, correctly-formatted character sheet -- level, race, guild, stats, HP/SP/EP, bank balance, alignment, age) and a clean quit that saves the character file.

A large compile-sweep tail remains: of the archive's 12,565 .lpc files, 5,829 compile clean and 6,736 do not. The overwhelming majority (6,021 of 6,736, 89%) are inside individual wizards' own personal sandbox directories under wizards/ -- each wizard kept full private copies of core files like player.lpc/living.lpc to experiment on, never loaded by the live game (the canonical obj/player.lpc/ obj/living.lpc etc. are the only copies actually referenced). Of the 715 failures outside wizards/, a large fraction are doc/examples/* tutorial/template content and a handful are the stronghold/ housing subsystem -- none block registration or the verified play session above. See NOTES.md \S3 for the fix patterns that would close most of this tail, if picked up as future work.

WASM status: playable. Shared WASM driver. Full new-character registration (c / wasmqm / password / select human / continue / continue / select fighter) into the Fighter guild of Duranghom (five exits, guildmaster visible), plus look / score ("level 1 Human", "Primary guild: Fighters") / quit ("Saving Wasmqm."), verified with scripts/wasm_client.js. The compile-sweep tail (LDMud leftover syntax in wizard/cmd files) still prints at boot and on quit's drop-all path; none of it blocked the login or play session. Play: https://mudlibs.fluffos.info/questmud/

Local run

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

Game port: 40247.

NOTES · 移植与修复记录

QuestMud -- porting notes

Source: git clone https://github.com/vlehtola/questmud (cloned 2026-08-28). Slug questmud, number 945, port 40247. Mudlib root is the clone's lib/ subdirectory (bin/, ftpd/, share/ alongside it are the archive's own bundled driver/tools, ignored -- this project uses its own FluffOS driver). Confirmed via lib/secure/master.c + lib/secure/ simul_efun.c before cloning; what that early check didn't catch is that this is an LDMud mudlib, not MudOS-lineage like every other classic LPMud in this collection so far -- a materially different porting job. MIT licensed. 22,198 files in the raw clone, 12,565 .c files under lib/.

1. Conversion

scripts/convert_lib.sh on raw/lib -> work: source is English-language and already ~99% ASCII/UTF-8 (735 lossy-conversion files out of ~21,000, mostly Finnish wizard-name comments with accented characters -- the original wizard team, per names throughout the archive like Ahma, Piikki, Nalle, Rag, Siki, was Finnish), 12,565 files renamed .c->.lpc, 11,041 literal .c" references fixed, 6 local angle-bracket includes converted to quotes, 231 files static->nosave.

2. The LDMud-vs-MudOS/FluffOS architecture gap

This is the first LDMud archive onboarded into this collection. LDMud and MudOS/FluffOS share LPC syntax at a surface level but diverge sharply underneath. Closure usage (#'name, lambda/unbound_lambda/ bind_lambda) turned out to be narrow in *scope* (36 files total, mostly secure/master.c and a handful of scattered wizard sandbox files) but every one of the driver-architecture-level gaps below was broad in *impact*, since they sit in secure/master.lpc/secure/simul_efun.lpc or otherwise get exercised by every single connection.

2.1 secure/master.lpc: set_driver_hook() doesn't exist

The entire original create()/inaugurate_master() chain existed to install set_driver_hook()-based behavior for object creation, uid-loading, and command dispatch -- none of which exists on FluffOS. Replaced with a no-op stub, since FluffOS already hardcodes the equivalent behavior directly:

2.2 master::valid_read() never had a case for "load_object"/"include" -- the single highest-impact fix in this whole port

FluffOS's vm/internal/simulate.cc (load_object()/clone_object()) and compiler/internal/lexer_utils.cc (#include resolution) both route through master::valid_read(path, eff_user, call_fun, caller) with call_fun values "load_object"/"recompile_object"/"include" that this LDMud-derived master's valid_read() switch had never needed a case for (LDMud doesn't route object compilation through valid_read() at all). Without a matching case, the switch falls through to its own default (deny), so every single object load and every single #include in the entire mudlib was silently denied from the very first boot -- every preload line failed with a bare "Read access denied", and every file's very first #include <ansi.h>/"log.h" failed with "Cannot #include ...", cascading into hundreds of secondary "Undefined variable"/syntax errors from the missing macros with no indication the real problem was access control, not the files themselves. Fixed by adding both cases to the existing permissive read policy (this lib's valid_read() already allows read_file/read_bytes/file_size to everyone):

case "load_object":
case "recompile_object":
case "include":
case "restore_object": return 1;

2.3 master::creator_file()'s "no special uid" case returned a bare int -- broke loading nearly every non-wizard file

FluffOS's uid system (packages/uids/uids.cc's set_uid()) hard-requires creator_file() to always return a real uid string; any other return type -- including the bare 1/0 LDMud used as its "no special creator, ordinary system object" sentinel -- fatally destructs the object being loaded with "Illegal object to load: return value of master::creator_file() was not a string", *even though the object had compiled cleanly moments earlier*. This hit essentially every non-wizards/-owned file the instant it got past its own compile errors (confirmed live on obj/timer, obj/wizlist, room/house_shop_d, daemons/mastery_d, and would have hit every other ordinary content file in the mudlib). Fixed by returning get_bb_uid() (the existing "Backbone" sentinel) everywhere the original returned a bare int or fell off the end of the function with no return at all.

2.4 move_object() is 1-arg on this driver -- a corpus-wide dialect fix that only partially works

FluffOS's move_object() efun takes one argument and moves this_object() only (void move_object(object | string)); the archive's LDMud/older-MudOS convention is 2-arg (move_object(item, dest), moving an arbitrary object). This project's established mechanical fix for this dialect gap is move_object(A, B) -> A->move_object(B), applied corpus-wide here too (9,873 sites/182 files via a paren-aware script, not a flat regex, so nested-call arguments were handled correctly).

This fix is only reliable when A is already this_object() at the call site. call_other() never falls back to an efun of the same name when the target object has no matching LPC function of its own -- it just returns 0 silently, no error, no exception. Confirmed live: a brand-new character's own myself->move_object("/obj/race_selection") (inside obj/player.lpc's own registration flow, where myself == this_object()) *appeared* to work -- no exception, normal control flow continued, the write()s after it printed fine -- but environment(myself) was still 0 immediately afterward. A bare move_object(dest) call from the same code worked correctly. This was diagnosed by literally inlining a debug_message() with a catch() around the call and comparing find_object()/environment() before and after.

Fixed for the by-far-most-common case: obj/living.lpc (inherited by every player and monster via living.h) now defines a real move_object(dest) method:

mixed move_object(mixed dest) {
    return efun::move_object(dest);
}

This makes X->move_object(dest) a real, correctly-resolving function call for any living (player, monster, NPC) target -- covering the overwhelming majority of ->move_object( call sites in this specific mudlib (players/monsters moving themselves or being moved by daemons). efun::move_object (not a bare recursive call) is required specifically *because* the new function shares the efun's own name; using efun:: here needed its own separate fix, see \S2.5.

Not fixed: any ->move_object( call site whose *target* is not a living (money, weapons, corpses, and other clonable items that don't inherit obj/living.lpc) still silently no-ops. This is a real, confirmed-live, corpus-wide gap that a full future pass should audit -- grep the whole corpus for ->move_object( and, for each target class that isn't already living-derived, either add the same real move_object(dest) wrapper to that class's own base file, or rewrite the call site to run from inside the target's own execution context. Left as documented, unfixed scope for this session given the size of the corpus (12,565 files) and that the verified registration/play path above doesn't depend on any of the remaining cases.

2.5 efun::name(...) outside secure/simul_efun.lpc requires master::valid_override()

The compiler's efun:: override syntax (compiler/internal/ grammar_rules_exprs.cc's rule_efun_override()) gates every use through master::valid_override(calling_file, identifier, main_file); with no valid_override() defined at all, this silently *denies* every use once master_ob is set (a NULL master apply return -> master_approved() returns 0), which happens the moment secure/master.lpc itself finishes loading. secure/simul_efun.lpc's own many efun:: calls (efun:: map_delete, efun::file_name, efun::command, etc., used throughout this file's compat shims) never hit this because simul_efun.lpc loads and compiles *before* master_ob is set (the "no master yet" bootstrap allow case in master_approved()) -- but any efun:: call anywhere else in the mudlib, compiled lazily later during actual play, hit "Invalid simulated efunction override" and failed to compile. First surfaced by obj/living.lpc's new move_object() wrapper (\S2.4) needing efun::move_object. Fixed with a permissive valid_override():

int valid_override(string calling_file, string identifier, string caller) {
  return 1;
}

2.6 Wide/multi-value mappings -- no FluffOS equivalent at all

LDMud mappings can hold multiple values per key ((["key": v0; v1; v2]), accessed via map[key, N] column indexing). FluffOS mappings are always single-value. Every instance found this session was converted to a single array value instead ((["key": ({v0, v1, v2})]), read back as map[key][N]):

daemons/leader_d.lpc's leaders mapping turned out to be a false positive for this pattern: it's genuinely single-valued (leaders[race] = name), but was *read* with leaders[race, 0] LDMud-style column-0 indexing anyway (valid LDMud syntax for a width-1 mapping, equivalent to plain leaders[race]) -- fixed by dropping the , 0 rather than wrapping the write side in an array. Worth checking for this exact false-positive shape (write side is single-value, read side still uses [key, 0]) before assuming every map[key, N] site needs the full array-value conversion.

2.7 FluffOS's reset() is scheduled/lazy, not synchronous-on-load

LDMud calls reset() synchronously right after create() for every new object (this mudlib's original master.c explicitly documented this: "1. reset() will be called first... 4. The game will enter multiuser mode"). FluffOS's reset() only fires on the normal reset-timer cycle ("time to reset", default here 3600s) unless "lazy resets" is enabled in config.fluffos, in which case it fires on the object's *first* apply_low()/move_object() touch instead -- but even with lazy resets : 1 set, several daemons still had their reset()- populated state read before reset() had run, crashing with *Value being indexed is zero on a raw-LPC-default (int 0) mapping. Confirmed live, repeatedly, as the actual blocker for the very first character's own registration flow (each fix below was found by tracing one specific crash during a real registration playthrough, not by static analysis):

This class is confirmed real and repeatable but almost certainly not exhaustively found -- every instance above was discovered by literally playing through registration and reacting to each new crash, not by a systematic corpus grep. A future pass should grep for every mapping/ array global that a file's own reset() populates and cross-check whether anything in that same file (or callable from elsewhere) reads it before reset() could plausibly have run.

2.8 Missing LDMud-only efuns, added as secure/simul_efun.lpc shims

2.9 creator()/domain(): the *other* self-destructing-registration bug

Once \S2.3's creator_file() fix was in place, obj/player.lpc's own reset() had a second, independent bug that also self-destructed every newly-registered character: if (creator(this_object())) { ... destruct(this_object()); } is a "reject this if it looks like a wizard's own ad-hoc clone of player.lpc, not a normal system-created character" guard, and the mudlib's own creator() simul_efun was a bare getuid() passthrough. Under real euid tracking every object always has *some* uid (never LDMud's original int-0/1 "no special uid" sentinel, per \S2.3), so creator() became truthy for literally every object, including perfectly ordinary clone_object("obj/player") calls from master::connect(). Fixed by filtering out the backbone/system uid specifically:

string creator(object ob) {
    string uid = (string)getuid(ob);
    return (uid && uid != (string)MASTER->get_bb_uid()) ? uid : 0;
}

3. Ordinary compile-time bugs (same class as every other lib in this

collection, just discovered via an LDMud lens)

Once the architectural gaps above were fixed, the remaining errors were the same familiar patterns already cataloged in this project's top-level AGENTS.md, just numerous because this is a 12,565-file archive:

4. Compile-sweep results

scripts/lpcc_check.sh: 12,565 files, 5,829 pass, 6,736 fail. Of the failures, 6,021 (89%) are inside individual wizards' own personal wizards/<name>/ sandboxes -- each wizard kept a full private copy of core files like player.lpc/living.lpc/guildrank_d.lpc to experiment on, confirmed via grep to never be referenced from the live game (only the canonical obj/player.lpc, obj/living.lpc, etc. are actually loaded) -- the same "wizard-sandbox clutter" shape this project's AGENTS.md already documents for several other libs, just at a larger absolute scale given this archive's size. Of the remaining 715 failures outside wizards/, the largest identifiable groups are doc/examples/* (114 files, tutorial/template content, several genuinely incomplete even in the original archive per their own comments) and the stronghold/ player-housing subsystem (5 files). None of the 6,736 failures block the registration/play path verified live this session (see README.md). The fix patterns in \S2/\S3 above would likely close a large fraction of this tail if picked up as a future sweep -- the declared-scalar-but-actually-array pattern and the forward-reference script in particular are corpus-wide, mechanical, and already proven safe on the files fixed this session.

5. Known limitations / deliberately left for future work

§10.7 deep functional test (2026-08-31, round two)

Full continuous playthrough against ~/src/fluffos/build-debug/src/ driver config.fluffos (the shared driver, no dedicated worktree needed), several fresh throwaway registrations, following AGENTS.md's own note that \S2.7's lazy-reset class was "confirmed real but not exhaustively audited." It wasn't -- this pass found the single most severe, most far-reaching bug in this whole port, well beyond what \S2/\S7.158 already catalogued. Full technical writeup: new AGENTS.md \S7.192. Summary here.

SEVERE finding: room/room.lpc and obj/monster.lpc were both still missing the \S2.7 lazy-reset create() bridge -- this made essentially the ENTIRE explorable game world unreachable

Registering a fresh character (Bragoth, human Fighter) through the full chargen flow and joining the Fighter guild landed in a room with no description at all and "No obvious exits." -- a hard, silent, total dead end immediately after character creation, for every single new player, every time. Root-caused via vm/internal/base/object.cc's call_create(): it arms next_reset to an hour in the future BEFORE create() even runs, so try_reset()'s lazy-resets check is already false the instant any object finishes loading -- meaning reset() (which is what actually populates short_desc/long_desc/exits via add_exit(), per room/room.lpc's own header comment) never fires until a full hour after that specific room's first load, REGARDLESS of lazy resets : 1. room/room.lpc (inherited by 5,368+ files corpus-wide) and obj/monster.lpc (the base class for every NPC/ monster clone in the game) both had no create() of their own. Fixed both with the same pattern AGENTS.md \S7.177 already established for "universal base class, subclasses each override reset()" (a bare reset(0) can silently no-op on this driver -- route through call_other() instead):

void create() {
    call_other(this_object(), "reset", 0);
}

The reset() gap cascaded into a whole chain of previously-unreachable, independent compile/type bugs -- fixed one link at a time via live reproduction, not static analysis

Once reset() actually started running for the first time ever on this driver, every bug hiding inside it (permanently dead code before the fix) became a real, live, uncaught crash that aborts the calling room's reset() PARTWAY THROUGH -- silently skipping whatever comes textually after the crash point, including the room's own add_exit()/description setup that always follows its initial "populate my furniture/NPCs" clone_object() calls. Traced and fixed via repeated live reproduction (join guild -> crash -> read trace -> fix -> reboot -> repeat), not a static sweep:

Verified live, end to end, after all of the above

Full continuous session, fresh registration each time a fix needed a reboot: name -> password -> race selection (Available races: now lists all 22 real races, not empty) -> special traits -> guild selection (select fighter) -> the Fighter guild room now shows its real description AND all 5 exits -> east onto Gold street (a real citizen and city guard NPC both visible and correctly described, no crash) -> south into Central Square (the game's own central hub -- also showing its real description, all 4 exits, and a patrolling guard NPC) -> north back. quit produced a clean disconnect with log/debug.log never created (no uncaught errors) both before and after every fix in this chain. A ~220s idle long-sit boot watch (real driver stdout capture, not debug.log alone) produced zero new error lines beyond the already-catalogued, unrelated 66-line compile-sweep tail from \S4/\S5.

What was NOT reached, flagged honestly rather than silently skipped

Files modified this pass

room/room.lpc, obj/monster.lpc, obj/living.lpc, obj/monster_data.lpc, obj/base_object.lpc, wizards/siki/base_drink.lpc, cmds/std/_environment.lpc, secure/master.lpc, plus 267 files (guild masters, city NPCs, and wizard-realm monsters/NPCs archive-wide) for the chat_str array-type sweep -- see AGENTS.md §7.192 for the full technical writeup.

WASM measurement (2026-09-03)

meta.json was already playable from the 2026-08-31 deploy-unblock; the README still said "not attempted." Cold-boot under the shared ~/src/fluffos/build-wasm succeeded with no new mudlib-side compile fix. work/ is ~378MB and copies into MEMFS without OOM. Verified with scripts/wasm_client.js (--timeout 300 --idle 0.4): c → wasmqm → password → select human → continue → continue → select fighter. Landed in the Fighter guild of Duranghom with the real room description, five exits, and Anrax the guildmaster visible. look reprinted that room; score showed "Wasmqm. You are a level 1 Human" / "Primary guild: Fighters" / HP 86(141); quit printed "Saving Wasmqm." The already-catalogued \S4 compile-sweep tail (LDMud leftover #' closures, No program in object on /guilds/obj/skillfun, /cmds/std/_drop, etc.) still prints at preload and on quit's drop-all path; none of it blocked the login or play session. Shop/combat/death were not exercised this pass (same gaps as the native §10.7 writeup). The MEMFS copy does not write player saves back to the host, so wasmqm left no work-tree debris.

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

scripts/lpc_fix_negative_ranges.py: 4 line(s) in 4 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-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 两行,网页版每位访客的终端里都看得到。secure/master.lpc 补上了这两个 apply:作者返回 get_root_uid(),域返回 get_bb_uid(),正是驱动缺少它们时所用的值。域名等于 backbone 域时,驱动让对象沿用创建者的域,与没有这个 apply 时相同(KB 06 §7.222)。本库保留上游代码风格(几乎所有文件都未经 §9 格式化),所以只插入代码,不格式化。用 lpcc config.fluffos /nonexistent.lpc 前后对比,主控载入段只少了这两行。

深度功能测试(§10.7,2026-10-05)— 启动预载全部能编译;武器、护甲、频道、角色创建、旧存档

启动时 55 条编译错误 → 0。整树扫描编译/载入失败的文件 5643 → 约 2850(剩下的多在巫师个人目录)。

1. 启动预载(obj/init_file)里的核心对象大多编译不过。 - obj/weapon、obj/armour:用了保留字 class 作变量名,调用在定义之前的函数(LDMud 不需要原型),armour 的 value/weight 声明成字符串却存整数。591 件武器、784 件护甲继承它们,全部无法载入。 - daemons/channel_d(聊天频道)、room/virtual_map_d、daemons/news_d、daemons/area_entry_d:用 LDMud 多列映射(([k: a; b; c])、m[k, N]),改成数组值(m[k][N]),与当年移植 ability_train、邮件、封禁名单的做法一致;virtual_map_d 的第二列(最后使用时间)拆成平行映射 rooms_time。 - daemons/lotto 的 m - ([ key ]) 改 map_delete;std/user_meter 用 new 作变量名;obj/wizlist、daemons/explist、daemons/questlist、room/house_shop_d、guilds/obj/skillfun 的声明类型与实际存放的值不符(LDMud 不检查),按实际改正。 2. LDMud 在对象载入时调用 reset(0),本驱动只调用 create()(KB 06 §7.228)。 房间、怪物的基类早有 create() { call_other(this_object(), "reset", 0); },但武器、护甲、玩家对象和 113 个不继承任何东西的核心对象没有:新克隆的武器 short() 出错(start() 从未运行),频道守护没有频道,新角色跳过性别提问、也不会加入 inform/mud/newbie 频道。config.fluffos 的 lazy resets 注释说会补上,实际不会(驱动只在 next_reset 到期后才调用)。按 §7.177 的写法补上这些 create()。 3. 整个库被当成 GB18030 转码,而原文是 Latin-1(芬兰语,KB 03)。 一对高字节变成一个汉字,落单的高字节连同后面的 ASCII 字节(空格、句点、右引号)一起被吞掉:ensimmäinen paikka on tyhjä 变成 ensimm鋓nen paikka on tyhj,玩家存档的别名 "ä" 吞掉右引号后整个存档无法读回。scripts/latin1_misdecode_repair.py 按 raw/ 原文逐行修复 814 个文件、约 5600 行(只改除了被吞字节外与原文一致的行,之后改动过的行不动)。 4. 旧存档读不回。 玩家存档的 ability_train、notes(LDMud 宽度为 0 的映射)、邮件、封禁名单、道路、新闻、留言板、错误报告、投票都是 LDMud 映射写法。scripts/ldmud_wide_save_convert.py 改成数组值 / key:1 / ([]);逐值调用 restore_variable() 检查,253,085 个值全部可读(原有 2 处损坏按 raw/ 手工修复)。笔记本命令 _note 同时改掉 note += ([ arg ]) 这种写法。 5. 全库机械修复:调用在定义之前的函数补 varargs 原型(scripts/lpc_forward_undefined.py);声明类型与用法不符的变量改 mixed(scripts/lpc_loosen_types.py,运行时行为不变);room/room.lpc 的 string items; 改 string *items;(2400 个房间因此编译失败);参数不足的调用给被调用的那个定义加 varargs;#'func 闭包改 (: func :)。 6. 小问题:channel_d 的 last 只有一条消息时显示 0 条;area_entry_d 道路键有空号时取到 0;_ignore 的列表 [0] 存等级,声明成字符串数组。 7. 验证(只含被跟踪文件的树):启动 0 错误;新建角色(问性别 → 选人类 → 战士公会)、newbie hello all 显示 Qatestw [newbie]: hello all、channels 列出 11 个频道并显示已加入的三个、走动、ignore、quit(Nothing dropped.、[inform]: Qatestw left the game.),驱动无运行时错误。低于 2 级且不满一小时的新角色不存档,是原设计。 8. 仍未处理:留言板(mudboard/wizboard/warlockboard/vboard/bull_board)、bug_d、据点 stronghold/daemons/sh_main_d 的多列映射代码(存档已转换),world/misc/trace 的 transfer(),以及巫师目录里约 800 个有编译错误的文件。

深度功能测试(§10.7,2026-10-06)— 冒险者大厅、留言板、存档里的换行和芬兰文

1. 非生物对象的 X->move_object(dest) 全部无效。 移植时把 LDMud 的 move_object(A, B) 改成 A->move_object(B),而本驱动的 move_object() 只移动 this_object(),call_other() 也不会回落到同名外部函数。当年只给 obj/living.lpc 补了同名函数,所以留言板、奖牌、怪物身上的物品等一切非生物对象都“移动成功”却留在原地(世界目录里有 452 处这种调用)。现在把这个函数放进全局 include /include/empty.h,每个对象都有,obj/living.lpc 里的那份删掉。 2. 冒险者大厅(城里的休息回复点)进不去。 投票板 daemons/vboard 用 LDMud 多列映射编译失败,大厅 reset() 克隆它时出错,west 从旅店进大厅毫无反应。投票板和三块留言板(mudboard、wizboard、warlockboard)改成数组值(m[k][N]);m -= ([ k ]) 改 map_delete;new 是保留字,留言板的 new 改名 new_thread;wizboard 缺 headers() 原型。LDMud 读不存在的键返回 0,数组写法读已删除的主题会出错,所以列表和回复扫描先判断 m_map[n]/r_map[i] 是否存在。连带能载入的还有战士巫师公会 guilds/warlock/warlock、obj/warlock、room/job_room。 3. daemons/bug_d(玩家 bug 命令)同样改成数组值,#'> 排序改 sort_array(x, 1);obj/bull_board 的 add_action("new"); add_verb("note"); 合成一句,new 改名 new_note。 4. 表情列表是倒序的,第三人称代词固定为第一个使用者的。 早先把 sort_array(x, #'>) 改成了 -1(降序),而 LDMud 在 a > b 时交换,是升序,现改 1。daemons/emote_data 在载入时读 this_player()->query_possessive() 拼进 shakes his head 之类的文字:没有玩家时载入出错,有玩家时所有人都用第一个人的性别。改成占位符 $POSS$,由 emote_d 的 third_person() 在每次表情时替换。 5. log_file() 用 LDMud 的 get_dir(f, 2)(旧版表示取文件大小)判断日志大小,本驱动返回文件名,字符串与整数比较报错,新角色注册时写 NEWPLAYER 日志就出错。改用 file_size()。巫师命令 ls/cls 的 get_dir(path, 0x21/2/4) 改 get_dir(path, -1),并补 dir_filter2 原型(两个命令以前都编译不过)。 6. 存档里的芬兰文仍是乱码。 10-05 的逐行修复跳过了已转换多列映射的存档行(和其他被改过的行)。按 raw/ 原文重建:精确模拟当年的错误解码(GB18030 双字节;无效的一对连同后一个字节一起丢掉),工作树的行与模拟结果完全相同才换成正确的 Latin-1 解码,再做同样的映射转换;被吞掉换行而合并的行按原文分组处理。147 个文件、443 行(玩家存档、信件、留言板、日志)。新增的非 ASCII 字符几乎全是 ä/ö/å。剩 AREA_OHJEET 和 log/SHOUT 两个文本文件有后来改动过的行,没动。 7. LDMud 存档里的换行读回来是字母 n。 LDMud 把字符串中的换行存成 \n 两个字符,本驱动存成一个 CR 字节,读档时把 \X 还原成 X。留言板内容显示为 nn Sehän on…!!!n。新脚本 scripts/ldmud_save_newlines.py 把引号内的 \n 改成 CR、\t 改成制表符:1162 个存档、41,888 处(KB 03)。\r(击杀统计里怪物名末尾带的回车)无法表示,保留。 8. 验证(临时副本,新进程):新角色从战士公会走到大厅,看到留言板、金色奖牌、投票板;board list 跳过已删除的 3 号主题;board read 2/4 显示正确的芬兰文和换行;投票板 list/info Mudcon 读出旧票数;look at plaque 显示排行;shake/snap 正常,emotes 按字母升序;bug here … 写入数组格式的存档。非巫师目录 1683 个文件编译:失败数 297 → 284,无新失败。 9. 据点(stronghold)的代码全部编译不过。 sh_main_d(每个据点的数据守护)和 sh_base_room(据点房间基类)的多列映射改成数组值(room_data、hired_guards、door_data、chest_data、guard_data、雇佣守卫列表 mapp[k][N]);add_door 的原型少了第三个参数 flag;daemons/copier 用保留字 in 作参数名,改 src;base/sh_guard 的 LDMud 单参数 remove_action(verb) 改 remove_action("block_move", verb)。stronghold/ 下 9 个文件现在全部编译通过。读这些映射的地方都遍历 keys(),不会读到不存在的键。据点存档在运行时生成,库里还没有,所以只做了编译检查。 10. obj/monster 和 obj/living 各声明了一个 spell_dam(重复声明警告;两边都没有读它,怪物法术按咒语命令施放),删掉 monster 的那个。 11. 巫师工具 world/misc/trace、trace2 编译通过:LDMud 的 transfer(ob, dest)(成功返回 0)改成 catch(ob->move_object(dest));保留字 in 作函数名,改 trace_in(它注册的动作是 "In",大小写不同,原本就没有绑定);函数上的 nosave 改 protected;man 的 string manuals 改 string *manuals。 12. 未做:本库的 2700 多条编译警告。发帖需要 10 级、投票需要 40 级,这两条写入路径只做了编译检查。

深度功能测试(§10.7,2026-10-06)— 编译警告整理

scripts/lpc_warnings.py questmud --fix(无用局部变量、转义、nosave 函数等机械类):1242 个文件改动。改动过的 1240 个 .lpc 在 HEAD 与工作树分别加载(新进程):925 -> 925 通过,无回退;scripts/lpc_audit_removed_locals.py HEAD:3504 个被删声明,0 条需要看。启动时的警告 71 -> 0。手工修的三处:

验证:新进程启动,以访客(g)登录,look、score、quit(存档)正常。剩下 2867 条诊断几乎都是本来就不能编译的文件里的错误(LDMud 专用语法与缺失的头文件),留待逐个阅读。

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

Messages cut by a stray ;: joined in wizards/terror/goblin/roomit/g1.lpc and wizards/daoloth/mansion/path2.lpc (the orphan adds a sentence); deleted in wizards/torspo/areat/ogre/room4.lpc, terror/goblin/roomit/g5.lpc, rimber/cave/room4.lpc (a fragment or copy of the line above). daemons/ability_d.lpc is an LDMud ([ k: v1; v2 ]) table, not a cut message.

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

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

逐条复查了能编译的文件里 156 条 Expression has no side effects / Value of conditional expression is unused 警告,156 条全部修掉。

LDMud 宽映射读法漏改(116 条,最主要的一类)。 移植时把宽映射改成了“值是数组”的 m[k][N],但不少读点还是 LDMud 写法 m[k, N]。在 FluffOS 里这是逗号表达式,等于 m[N],静悄悄读错了键(通常读出 0)。按写入方的实际布局改正:

其他形状(40 条):

复查中顺带发现的两处编译失败(警告扫描看不到):

验证:改动 64 个对象,HEAD 与工作树各在新进程里载入一次:64 objects; HEAD PASS 50, tree PASS 55; regress 0; newly loading 5(_tell、_peer 及三份 peer 副本)。仍载入失败的 9 个都是 HEAD 就失败的旧副本/草稿(_look2、公会备份、bunny_d、kaappi/fix/tell、nalle/ng、rag/guild、yodingui),以及 g8.lpc(它克隆的 ghunter.lpc 运行时出错,与本次无关)。临时探针对象对比 HEAD 与工作树:get_starter_guild("battlemage") 0 -> "mage",give_color(tell) 0 -> 红色转义码,bak.emote_d->emote("ack") 输出 “You 0.” -> “You ack.”。proge/bazaar.lpc 还剩一条 Redeclaration of global variable 'items',未处理。

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

能编译的文件里的编译警告 167 -> 0(scripts/toolkit_fix.sh 去掉 5 个多余的 inherit(wizards/manta/eq/damulet2.lpc 两次 inherit "obj/armour" 一项就占 54 条)、4 个未用局部变量,其余逐条手改,共约 90 个文件;有编译错误的文件不在范围内)。

真实的编程错误:

行为不变的整理: 叶子变量与基类同名而含义不同时改叶子一侧的名字(walker03 的 dead→mom_dead——基类的 dead 表示“已死、不能被攻击”;beldor 三个怪物的 string *short_desc/long_desc→short_descs/long_descs;mummy 的 limbs、lemming 的 name(颜色)、hissi 电梯的 direction、thief/tutor 的 object money、wizards/siki/bow.lpc 自己的 short_desc/long_desc/weight);没用到的 direction(三个 ferry、potup)、bow 的 slot/wielded_by、bazaar 的全局 items 删掉。wizards/daoloth/mansion 等 13 个用 EXTRA_RESET 调 extra_reset() 的房间加原型(调用在宏生成的 reset() 里,早于定义)。necro 的 scythes.lpc/shields.lpc 注释结尾 ~/ 改 */(只吞了注释)。

验证:改动的文件和继承 shop_d/book_d/echo/bow 的对象共 90 个,HEAD 与工作树各在新进程载入(90 objects; HEAD PASS 72, tree PASS 73; regress 0; newly loading 1),72 个对象载入无回退;这些对象的警告 159 -> 0;全库重扫后无编译错误的文件里 0 条警告(10475 个文件能编译)。

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

1 file(s), 2 site(s) wrote an object path as //… (like carry_object("//clone/cloth/hic_cloth")): /wizards/yodin/areas/weird_land/vl1.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.

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

Rescanned with the current driver; mechanical fixes: 1 functions made varargs (callers pass fewer arguments). Every changed object was loaded at HEAD and in the tree: no regression.

Punctuation the conversion lost, put back (2026-10-19, KB 03 4.6)

2 file(s) lost a semicolon, comma, quote or the closing } at the end of the file and did not load. The file stopped one or two } short (its last byte or two went missing); appended: /wizards/torspo/stuff/flagon.lpc, /wizards/walla/testi/huone.lpc. Loaded at HEAD and in the tree: all 2 were failing before, none regressed (scripts/lpc_restore_from_twin.py, lpc_fix_eaten_quote.py --comma, lpc_fix_eof_braces.py).

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. 39 file(s) fixed: /cmds/std/_cast.lpc, /guilds/abjurer/prot_resolve.lpc, /guilds/obj/bak.skillfun.lpc, /guilds/obj/bak2.skillfun.lpc, /guilds/obj/preMoonstar.skillfun.lpc, /guilds/obj/skillfunbeforeMtweaking.lpc, /guilds/obj/spell.lpc, /wizards/astarel/castle/castle_code_with_doors.lpc ... (39 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: 11 of them failed before and load now, none regressed.

string variables and arrays that hold numbers (2026-10-20, KB 04 6.22)

daemons/auctioneer.lpc (and the wizards/siki/ copy): num, price and money (stored by sscanf("%d")) and the arrays last_bid, auction_time, how_long (auction_time[i] - time()) are declared mixed: the auction daemon compiles and loads (the siki copy then stops on save_object() permission, a wizard directory). room/map_daemon.lpc, map_daemon2002.lpc and the wizard copies: light_level = this_player()->test_dark() is a number, declared mixed; the daemon still stops at check_rooms(), which only the virtual_map.lpc files of the wizard areas define (it is their base) (scripts/lpc_fix_cast_cmp.py and by hand).

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. /secure/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).

who did not compile: invis_color() is called before its definition (2026-10-20, KB 04 6.5)

cmds/std/_who.lpc:249:99: error: Undefined function invis_color: the command calls invis_color() (the yellow (I2) of an invisible wizard) 49 lines above its definition, which LDMud allowed; this driver wants a declaration first, so the who command never loaded (the login survey printed it as a play-time console error). The file already had prototypes for bold_color and give_bcolor with the comment Called before their definitions (LDMud allowed that); invis_color was the one it missed (scripts/lpc_forward_undefined.py questmud --compile /cmds/std/_who.lpc --apply). HEAD 0 PASS, tree 1 PASS.

get、eq、ntell、pwho 编译不过:局部变量类型(2026-10-20,KB 04 §6.21)

cmds/std/_get.lpc:object item_o 装 EQ_FILTER 返回的对象数组,再交给 get_all(object *, ...);string *livings 里放的是 all_inventory() 的对象。_eq.lpc:string slots 其实是数组(valid_slot(string slots, ...) 的参数也是)。_ntell.lpc:两处 string ignore_list 装的是 query_ignored() 的名字数组。_pwho.lpc:object *members 先接 query_members() 的映射、再接 keys() 的结果,改 mixed。各按真实内容改类型。加载检查:四个都是 HEAD FAIL → tree PASS。没有动的:_calc(浮点和字符串混着传给 potenssi())、_look2(get_env_color 哪里都没有)、_sshout(缺花括号、变量没声明、tail(),整个文件是残片)、daemons/warlock_d.lpc(is_demon、invite 从没声明)。

copy_file(string in, string out):参数名 in 是保留字(2026-10-20,KB 04 §6.26)

巫师的 cp、rn、mv 系列文件(cmds/wiz/_cp.lpc、_rn.lpc,castle/kk.lpc、castle/sh/daemons/copier.lpc,wizards/*/ 下的几份)把 string in 当参数名:syntax error, unexpected L_IN。scripts/lpc_fix_in_ident.py 改名为 in_var。加载检查 11/11,HEAD FAIL → tree PASS。

add_action() registrations of functions nobody defines (2026-10-21, KB 06 §7.349)

An add_action("fn", "verb") whose function is defined neither by the object, nor by its headers, nor by its parents, nor by any object derived from it (scripts/lpc_scan_callout_targets.py, descendants checked transitively, then function_exists() in a running driver, scripts/lpc_verify_handlers.py). The driver walks the actor's sentences from the newest one: when that sentence's function does not exist it raises *Function for verb 'drop' not found. and tries no other handler of the verb (an older dead sentence is passed over silently). Removed 2 registrations in 2 files (scripts/lpc_fix_dead_add_action.py), the verbs are left to their other handlers: /castle/obj/shopkeeper.lpc (countries), /obj/player.lpc (in_room). Most frequent: countries x1, in_room x1. Left alone (read): /castle/obj/masterkey.lpc owner (the object does not compile; the registration waits until it loads), /guilds/spell_obj/auctioneer.lpc bid (the object does not compile; the registration waits until it loads), /guilds/spell_obj/auctioneer.lpc claim (the object does not compile; the registration waits until it loads), /guilds/spell_obj/auctioneer.lpc collect (the object does not compile; the registration waits until it loads), /world/voting/voting_d.lpc list (the object does not compile; the registration waits until it loads).

new(MISSING)->move() / carry_object(MACRO(..))->wear() chains guarded (2026-10-22, KB 06 §7.73 addendum)

A statement new(PATH)->move(...); / carry_object(ARMOR_D("x"))->wear(); whose path names no file of this archive: new() answers 0 and ->move() on it raises Bad argument 1 to EFUN call_other(). In create() the object never loads, in reset() every spawn below the first missing one is skipped on every reset. 4 statement(s) in 4 file(s) (clone_object) are now { object new_ob = new(PATH); if (new_ob) new_ob->move(...); } (scripts/lpc_guard_carry_object.py --new: the path is resolved with the macros the file itself sees, an ambiguous macro or a virtual-object path is left). Load check of the 4 changed files, HEAD vs tree: 3 load at HEAD, 3 load now (0 newly loading, 0 regress).

Object paths without a leading slash: guarded chains for a missing item (2026-10-22, KB 06 §7.73 addendum)

A path written without the leading slash (carry_object("d/city/obj/jiasha")->wear();) is root-relative in this driver (new("d/city/obj/changjian") loads /d/city/obj/changjian), so it behaves like the absolute spelling: when the file is not in the archive new() answers 0 and ->wear() on it raises Bad argument 1 to EFUN call_other(), and the whole object does not load. The earlier guard pass skipped these paths as "relative to the cwd at run time: cannot tell". 1 statement(s) in 1 file(s) (world/mage/mag/ld_preventer) are now guarded blocks (scripts/lpc_guard_carry_object.py). Load check of the 1 changed files, HEAD vs tree: 1 load at HEAD, 1 load now (0 newly loading, 0 regress).

glued inherit entrance + ball(2026-10-27,KB 04 §6.25 Also)

wizards/walla/area/extension/entrance 的 inherit "room/room"、wizards/yodin/ball 的 inherit "obj/armour" /*…*/ 均缺分号。entrance 的房间描述字符串原先是无副作用表达式,改为 long_desc =。证明:HEAD 失败;改后两件均加载。

深度功能测试(2026-10-27,§6.25 question_d 截断)

daemons/question_d 的 ask_questions 在 if(question / tell_object(ob, question 处截断。按 input_data 的 mapping 用法补完(m_sizeof + random 出题)。证明:HEAD 失败;改后加载。

深度功能测试(2026-10-28,sort_array 方向闭包)

剩余 LDMud sort_array 的 #'> / #'< 改为 1 / -1(KB 06 7.234)。