Chronicle of Yan-Huang Heroes (a.k.a. "Dynasty Reborn")

✅ 可玩

炎黄英雄史(游戏内也称"皇朝再现")

yhyxs

🔑 fluffos / Mud@2026 更新 66a3bd8 2026-10-28 源码 下载 ZIP

▶ 开始游玩 · Play Now

炎黄英雄史(游戏内也称"皇朝再现")与本站另外两份同源档案 `yhwhpublicfi`(《炎黄武魂》)、`yanhuangwuhun`(《炎黄武魂Ⅱ》)同属 ES II 引擎家族 2003 年"炎黄"分支,文件级比对显示三者 87-89% 的同名档案逐字节相同,是同一套代码的三次不同品牌打包,而非各自独立开发的姊妹作;玩家从"世外桃源"出发,分开输入姓/名两段中文姓名,设管理密码与普通密码两组密码,走到"世外桃源"不同方向的 NPC 面前听劝说词选定猛士/智慧/耐力/敏捷/均衡五种角色定位,再到"阎罗殿"用 `wash` 指令跳入"忘忧池"重掷天赋,最后用 `born <中文地名>` 投胎到二十个候选籍贯/世家之一,死亡后经"鬼门关"由黑白无常接引,固定在扬州武庙复活。

English

Titled "Dynasty Reborn" in-game — like close siblings yhwhpublicfi and yanhuangwuhun ("Tales of Wulin Heroes: Soul of Yan-Huang" / "...II"), this is a separately-branded snapshot of the same 2003 "yh" branch of the Doing/hell engine lineage; a file-level comparison found 87-89% of common files byte-identical across all three, much closer than a simple shared-engine relationship. Character creation is distinctive within the family: split surname/given-name entry, two independent passwords, then a five-way archetype pick (Brute/Wise/Enduring/Agile/Balanced) made by approaching different NPCs at the starting "Land of Peach Blossoms" who each preach their own philosophy, followed by a trip to Yama's Hall to re-roll stats in the Pool of Forgetfulness, and finally `born <name>` into one of twenty birthplace/clan options. Death sends the ghost through the Ghost Gate Pass, escorted by the Black- and White-Guard psychopomps, reviving fixed at the Yangzhou Martial Temple.

README

内容亮点

深度功能测试新发现的 bug(详见 NOTES.md)

发现并修复了一类此前本项目从未记录过的 bug:help/rules(首次登 录/help rules 显示的规则说明)和 clone/game/{8,21}_hlp(拱猪/ 21点两个纸牌小游戏的说明文字)三个无扩展名的纯文本帮助文件,从原 始转码阶段起就一直是未转换的 GB18030 原始字节,一直没被 convert_lib.sh 或历次针对 .lpc/.h 的补漏扫描捕捉到,玩家看到 的是一整屏乱码。已用 iconv 转换为 UTF-8 并现场验证显示正常(详见 AGENTS.md §4.1 新增条目)。另外死亡/复活系统的 d/death/npc/ {hei,bai}.lpc(黑无常/白无常)有 AGENTS.md §7.68 归档的复活软锁死 bug,已修复,并现场完整验证了一次真实死亡→鬼门关→复活的全流程 (角色被欧阳克这个金庸小说反派角色叠加降雪冻伤效果击杀)。

测试过程中还意外撞上了 AGENTS.md §10.8 已归档的驱动级内存腐化崩溃 类的第六个独立实例——driver 自己的周期性 reset 扫描导致段错误, debug.log 毫无痕迹,只有 driver 自身的 stdout 捕捉到了完整堆栈; 这是驱动层问题,不是 mudlib bug,未做任何代码改动。

在线试玩

https://mudlibs.fluffos.info/yhyxs/

管理员账号 / Admin account

警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。

本地运行

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

游戏端口:40061。

NOTES · 移植与修复记录

炎黄英雄史 (Yanhuang Heroic History) — conversion notes

Archive: archives/炎黄英雄史.rar (archive #67). Extracted root: raw/yh2003/mud/ (the archive bundles the mudlib source alongside a prebuilt Windows MudOS driver/binaries — MudOS.exe, MudOSxp.exe, mudos.dll, runmud/runqym launch scripts, an addr_server helper — all ignored, only the LPC mudlib under mud/ was converted).

Identity / naming

Lineage: confirmed shared codebase with yanhuangwuhun (archive #66)

Per the task's explicit instruction to check this, diffed this lib's core files directly against libs/yanhuangwuhun/work/ (both are "yh"/炎黄- branded forks of the same ES II-family codebase, per the identical "Linux@lxtx for yh 2003.3" master.lpc credit line — confirmed NOT a coincidence of similar titles, but genuinely close-cousin lineage):

| file | diff size vs yanhuangwuhun | verdict | |---|---|---| | adm/single/master.lpc | 44 lines | near byte-identical; all diffs are wuhun's own already-applied fixes (§14 3-arg valid_override, .c→.lpc refs) or its bespoke STATIC_VAR_TAG/STATIC_FUNC_TAG macro naming choice (this lib used the standard nosave sed instead, see below) | | adm/daemons/securityd.lpc | 44 lines | same story — content identical, diffs are wuhun's §15n fix (already ported here) + macro-naming choice | | adm/daemons/chinesed.lpc | 33 lines | same engine, different site credit ("modified by Xiang@XKX" vs wuhun's "modified by [email protected]"); one extra chinese_date() arg difference (unrelated, cosmetic) | | adm/daemons/named.lpc | 69 lines | same PATH()/sliding-window shape, diffs are wuhun's already-applied §15h fix | | adm/simul_efun/chinese.lpc | 17 lines | identical is_chinese() GBK byte-range bug, byte-for-byte, diffs are wuhun's already-applied §15h fix | | adm/simul_efun/message.lpc | large | identical §15aa forward-reference bug (bare message(...) calls before the file's own override), diffs are wuhun's already-applied fix | | adm/daemons/logind.lpc | 192 lines | same overall flow (id → confirm → surname → given-name → admin-password → password → type → gender → enter_world); diffs are wuhun's fixes plus genuine site-specific content differences (banner name, item drop tables, banned_id list wuhun added, balance numbers) — NOT a byte-identical file, a real (if close) sibling, not a re-host | | adm/single/simul_efun.c/.lpc (composition list), wizard.lpc, object.lpc, gender.lpc, override.lpc | 0-13 lines | effectively identical; confirms no simul_efun-based set/query/dbase architecture bug (§15) in this lineage — same as every other ES II-family lib processed so far (es1_win/esI/xkx2001/rzrmud/xo/yueyingqiyuan/wuhanzhan/haiyang2/yanhuangwuhun) |

Verdict: genuinely the same underlying "yh 2003" ES II derivative as yanhuangwuhun, most likely two different sites' snapshots/branches of the same base rather than one being a copy of the other (both have real, independent gameplay-content differences, not just branding). Every proven fix from yanhuangwuhun's pass was ported directly here after confirming each one's exact shape still matches (rather than re-discovered from scratch), and this DID save real time — but two NEW bugs were found here that yanhuangwuhun's NOTES.md never mentions (see below), so this was not a rubber-stamp port; each fix was verified against this lib's own source before applying.

Fixes applied (with why)

1. §15h — is_chinese() GBK byte-range check (adm/simul_efun/ chinese.lpc): byte-for-byte identical bug to every other lib in this project — str[i] < 161 || str[i] == 255 / !(i%2) && (str[i] < 176 || str[i] >= 248) are GBK lead/trail-byte range checks, meaningless against this driver's UTF-8 codepoints. Rewritten as a CJK Unified Ideographs codepoint range check (str[i] >= 0x4e00 && str[i] <= 0x9fff), strlen(str) < 2 relaxed to < 1 (was "at least one full GBK char" = 2 bytes). 2. §15h — named.lpc's PATH() macro + sliding-window + length guards: PATH(name) used name[0..1] ("first GBK char", 2 bytes) → name[0..0] (first character). Every strlen(name) < 2/< 4 guard halved to < 1/< 2. The similar-name sliding-window loop (name[i..i+3] 2-char window / name[i..i+5] 3-char window, i <= l - 4) converted to character-width (name[i..i+1] / name[i..i+2], i <= l - 2). 3. §15h — logind.lpc's check_legal_name(): byte-length bound maxlen (called as check_legal_name(arg, 4) for both surname and given name) halved via maxlen / 2 inside the function; minimum bound strlen(name) < 2 → < 1. Separately, the combined surname+given- name length guard (if (strlen(fname) < 4) after concatenating both parts, message says "至少要有两个汉字" — "at least 2 Chinese characters") halved to < 2, matching the message text exactly (this is the same "second site, found only by testing all the way through" gotcha documented in AGENTS.md §15h — a text search for check_legal_name alone would have missed this one). 4. §14 — valid_override() 2-arg → 3-arg (adm/single/master.lpc): added the main_file parameter and the main_file == SIMUL_EFUN_OB || main_file == MASTER_OB check, so an efun:: override written in an #included simul_efun fragment (e.g. message.lpc, #included into simul_efun.lpc) is correctly recognized as legitimate. 5. §15n — securityd.lpc's valid_read ACL allowlist: added the switch(func) { case "load_object": case "recompile_object": case "include": return 1; } early-allow (same insertion point as every other lib with a custom securityd — right after the existing func == "file_size" check), so the driver's own compile-time lazy load/recompile/#include never gets attributed to a fresh not-yet-authenticated connection's (player) status and denied. 6. §15aa/§15s — message.lpc's forward-reference + int-0-exclude bug (adm/simul_efun/message.lpc): tell_object()/tell_room()/ shout()/write()/say() all call the bare message(...) name BEFORE the file's own message() override is textually defined further down — silently binding straight to the real efun (no "undefined function" error, since message IS a real efun name) and bypassing the override's exclude-argument fix entirely for every one of those call sites. Fixed with a varargs void message(...) forward declaration at the top of the file, and the override itself made varargs with an explicit if (!exclude) efun::message(arg,message,target); else efun::message(arg,message,target,exclude); guard (a bare int 0 passed as the 4th/"exclude" argument does not satisfy this driver's void | object | object * type for that parameter and throws "Bad argument 4 to EFUN message()" at runtime otherwise). 7. §15p — dns_master excluded from adm/etc/preload: was present (/adm/daemons/network/dns_master), removed proactively before the first boot attempt. Grepped the whole lib for inline DNS_MASTER/ Mud_name() calls from login/display code per §15ab's follow-up lesson — none found outside adm/daemons/network/ itself and header macro definitions, so the preload exclusion alone is sufficient here (unlike haiyang2, which needed a second inline-call fix too). 8. NEW — clone/user/user.lpc's is_killing(ob) type mismatch (found via interactive testing, not the lpcc sweep — this file is only ever reached live via new(USER_OB) mid-connection, never preloaded): accept_kill(object ob) called is_killing(ob) directly (a bare, non--> call) where is_killing(string id) (defined in feature/attack.lpc) declares a string parameter — every one of the ~50 other call sites in the lib correctly calls is_killing(ob->query("id")). Because it's a direct function call (not ->), this driver's static type checker enforces the declared parameter type strictly and refused to compile the whole /clone/user/user.lpc file — silently blocking make_body() right after gender selection, with zero error visible to the connecting player (the connection just stalled with no room/prompt after "您要 扮演男性(m)的角色或女性(f)的角色?"). This is the exact same failure shape documented in AGENTS.md §15b's "General lesson" (found originally on nitan_ceshi/archive #60, unrelated lineage — same bug pattern recurring independently, not shared code). Root cause confirmed by reading debug.log's compile-error section (clone/user/user.lpc:568:26: error: Bad type for argument 1 of is_killing ( string vs object )) rather than guessing. Fixed by changing the one call site to is_killing(ob->query("id")). This bug is NOT present in yanhuangwuhun's NOTES.md — a genuine new finding on this sibling lib, not something that could have been ported. 9. NEW — master.lpc's error_handler() given a permanent log-write insurance (§15w's second finding): the existing error_handler() only calls tell_object() when there's an interactive this_player() present; its own comment ("whatever we return goes to the debug.log") is stale — this driver's error_handler apply is declared void, so the return value is discarded, not logged. Added an unconditional efun::write_file(LOG_DIR + "RUNTIME_ERRORS", standard_trace(error, caught)); at the top of the function so runtime errors are never silently lost when there's no player to show them to. (This lib's log_error() — the OTHER apply, for compile *warnings* — already had its player-facing broadcast lines commented out by the original author, so §15w's main "warning-spam" bug does NOT apply here; see "confirmed not needed" below.) 10. §3 — static → nosave blanket sed (via convert_lib.sh, 147 files touched) — standard mechanical fix for the "static on a function is a hard parse error" issue. 11. §3 counterexample — string-literal collision revert (moniHuafu- style, found independently here, same magnitude as yanhuangwuhun's own 105-occurrence finding): the blanket sed also rewrote 104 occurrences across 64 files of "static/..."-prefixed property keys and log-file paths (e.g. log_file("static/CRASHES", ...), me->query("static/marry"), me->add_temp("static/drop_npc", 1)) to "nosave/...", silently changing on-disk log paths and in-memory property key names away from what the rest of the (unconverted, or correctly-untouched) code and existing save data still expect. Reverted all 104 with sed -i 's/"nosave/"static/g' scoped to the 64 affected files. Confirmed correct (not a guess) by checking that log/static/ is a real, pre-existing, populated directory in this lib's work/ tree (log/static/bug, log/static/call_player, etc.) — the sed would have orphaned all of that.

6 more genuine fixes were found via the lpcc sweep (not needed for the registration path itself, but real compile bugs nonetheless) — see the "lpcc sweep" section near the end of this file for the full writeup: inherit/condition/damo.lpc (missing-quote typo, §10), inherit/misc/ quest.lpc (set_information() signature), adm/daemons/questd11.lpc (9 split-identifier/string corruptions, a new pattern), adm/daemons/ eventd.lpc + adm/daemons/storyd.lpc (§2 rename-width bug, [0..<3] → [0..<5], also applied proactively to adm/npc/luban.lpc), and d/city/npc/guidao.lpc (a second, independent instance of the is_killing() type-mismatch bug).

Confirmed NOT needed (and how confirmed)

New finding NOT yet in AGENTS.md's catalog: get_config()/

runtime_config.h numeric-ID mismatch (flagged for the main session to consider cataloguing)

adm/daemons/versiond.lpc:245 does port = get_config(__MUD_PORT__) + VERSION_PORT; then socket_bind(vfd, port). __MUD_PORT__ is defined in this lib's own include/runtime_config.h as CFG_INT(0) under an old-MudOS-era numbering scheme (BASE_CONFIG_INT = 14, so __MUD_PORT__ = numeric ID 14) — but this modern FluffOS driver's actual get_config() implementation uses its OWN internal numeric-ID table (unrelated to this old header's numbering), so get_config(14) silently queries the WRONG config slot on this driver (empirically returned an empty string, not the real port int). "" + VERSION_PORT (a string + an int) then string- concatenates instead of adding, producing the literal string "12", which fails socket_bind()'s type check (Bad argument 2 to socket_bind() Expected: int Got: "12"). This is a non-fatal runtime error (caught by the driver at the point of the bad efun call, doesn't crash the whole process or block boot/login) that fires exactly once per boot (versiond calls this from a single call_out("in_server", 2) 2 seconds after create(), not on a repeating heartbeat).

Confirmed harmless to the registration/login flow, not just assumed: read versiond.lpc's setup() — version_ok is set to 1 synchronously inside create() (before the 2-second delayed in_server() call ever fires) whenever RELEASE_SERVER() == "local", which this lib's adm/etc/config does declare (release server : local). logind.lpc's login-gate check (! VERSION_D->is_version_ok() && ! VERSION_D->query_temp("operator")) is therefore already satisfied by the time any player could possibly connect, completely independent of whether the later broken socket_bind() call succeeds — unlike zhonghua2's §15m finding, this is NOT a case of a crashed create() silently blocking a readiness flag. Left unfixed (out of scope: versiond's peer-sync listening socket is release-server-to-release-server functionality, irrelevant to a single standalone instance in this project), but flagged here in case another lib in this batch hits the same get_config() numeric-ID mismatch in a context that DOES matter (e.g. if some other lib's registration-critical code path also calls get_config() with one of these old-style numeric macros) — this project's fix catalog (AGENTS.md) doesn't have a section for this class of bug yet.

Other observations (not bugs)

Registration flow — exact transcript outcome

Read logind.lpc's full input_to callback chain first (logon → get_id → confirm_id → get_surname → get_name → [invalid_new_name check] → new_ad_password → confirm_ad_password → new_password → confirm_password → get_type → get_gender → make_body/enter_world) before scripting the test — no hidden pre-id gate (no BIG5/student-age/client- version prompt); the very first prompt genuinely is the English id.

Gotcha hit while testing (worth recording as it re-confirms an existing AGENTS.md lesson): a first attempt used qinfeng2 as the English id — REJECTED (check_legal_id requires pure English letters, 3-10 chars), which loops back to the SAME "您的英文名字" prompt. Every subsequent --send in that run (y, 秦, 岭, ...) was silently consumed as a FAILED retry of the id prompt (each one also failing the letters-only check) until the literal string "look" (from later in the send list) accidentally satisfied the id format and got treated as a real id attempt, followed by "quit" being consumed as its y/n confirmation answer — producing a confusing, cascading-looking transcript that had nothing to do with the Chinese-name logic at all. Re-verified with a corrected id (qinfeng, no digit) and the flow proceeded correctly (same "always read the actual callback chain" + "re-verify one prompt at a time" lessons AGENTS.md already documents for other libs, re-confirmed here for a completely mundane reason — a bad test id, not a mudlib bug).

Full successful run (mudclient.py 127.0.0.1 40061, one continuous connection, after the is_killing() fix):

--send "qinfeng"     -> accepted as new id
--send "y"           -> confirmed new-character creation
--send "秦"          -> accepted as surname (real Chinese character)
--send "岭"          -> accepted as given name (real Chinese character,
                        combined "秦岭" passes is_chinese/length/
                        invalid_new_name checks)
--send "adminpass1"  -> admin password set
--send "adminpass1"  -> admin password confirmed
--send "userpass1"   -> normal password set
--send "userpass1"   -> normal password confirmed
--send "5"           -> character type "均衡型" (balanced) selected
--send "m"           -> gender "男性" (male) selected

Result: character successfully created and entered the actual game world, landing in the starting room 「世外桃源」 (same start-room name as yanhuangwuhun, another lineage-consistency data point), full room description with exits (west/east/north/south) and two NPCs (水笙/狄云) rendered correctly in UTF-8, followed by the live-ticking time-based command prompt. Save files confirmed written to disk (data/user/q/ qinfeng.o, data/login/q/qinfeng.o).

Second, separate connection (existing-user login + command handling, --idle 0.3 to beat the once-per-second ticking prompt that otherwise prevents the idle-gated client from ever sending queued input):

--send "qinfeng"     -> recognized as existing id, save file found
--send "userpass1"   -> password accepted, "重新连线完毕。" (reconnected
                        successfully)
--send "look"        -> full 「世外桃源」 room description printed again,
                        correctly rendered
--send "quit"        -> "欢迎下次再来!" (graceful disconnect message)

Confirms save/restore, password auth, and ordinary command dispatch (look/quit) all work correctly end-to-end, not just the one-shot creation path.

Before the is_killing() fix (2 separate attempts, both with a valid id): registration proceeded correctly through the Chinese name, both passwords, character type, and gender prompt — then silently stalled with no further output at all (no room, no error visible to the client). debug.log showed the real cause: /clone/user/user.lpc:568:26: error: Bad type for argument 1 of is_killing ( string vs object ) followed by *No program in object '/clone/user/user'! — the player body class failed to compile, so make_body() returned 0 and the connection just hung. This is exactly the failure mode AGENTS.md §15b warns about: never consider a registration flow verified just because it reaches password/type/gender prompts — it must actually complete into the game world.

lpcc sweep

scripts/lpcc_check.sh libs/yhyxs/config.fluffos libs/yhyxs/work — file count 11,001 (well under the mega-lib bracket), run in full, three times (once to find the initial failure set, twice more after each round of fixes to confirm progress). Host memory monitored throughout every run (stayed under ~2.2GB RSS for this lib's own lpcc --batch process; other agents' concurrent sweeps on other libs pushed system-wide available memory down to ~1.3GB at one point, but this lib's own process was never the concern and finished without incident).

Final result: 10,980 / 11,001 pass (99.81%), up from 10,965/11,001 (99.67%) on the very first sweep — the gap was closed by 6 genuine fixes found via the sweep (in addition to the is_killing() bug already found via interactive testing, listed above):

1. inherit/condition/damo.lpc — a genuine pre-existing typo (§10-style missing closing quote: return name + HIM "发作身亡了;, missing the " before the semicolon) desynced the parser for the rest of the file, producing a cascade of nonsensical "Illegal character 0xe9/0x95/0xbf" errors on a much later, perfectly-valid line of UTF-8 Chinese text (confirmed the file's UTF-8 is completely well-formed throughout via iconv -f UTF-8 -t UTF-8 round-trip — the "illegal character" reports were 100% a downstream parser-desync artifact of the one real typo, not a second bug). One inherited file, fixed once — resolved this file plus all 4 files that inherit it (kungfu/condition/damo_luanqi, damo_shangshen, damo_zheyuan, kungfu/class/misc/laonu's sibling kungfu/class/sky/sky5 was unrelated) in one shot (§8g pattern). 2. inherit/misc/quest.lpc's set_information() signature — declared string info but virtually every quest file's create() calls it with a closure (set_information(NPC1_NAME, (: ask_npc1 :))); QUEST_D's own real implementation already declares this parameter mixed. Fixed the wrapper to match, resolving all 7 affected quest template files (capture, deliver, explore, judge, search, shen, supply) in one shot — the exact same shared-root shape documented for nitan_san (a different, unrelated lineage) in AGENTS.md, recurring independently here. 3. adm/daemons/questd11.lpc — a distinct, NEW corruption pattern not yet in AGENTS.md's catalog: several places in this ONE file have a literal newline inserted in the middle of an identifier or string literal (e.g. me, w on one line, ho); starting the next — splitting the identifier who in two; ob->na / me() splitting name(); db / ase[...] splitting dbase; bas / e_name splitting base_name; query_enti / re_dbase() splitting query_entire_dbase; $(kno / wer)) splitting a closure macro parameter name knower), plus several purely-cosmetic string-literal splits ("experien / ce", q["i / d"], "reply_ / by", "family/fami / ly_name", "potential / ") that don't break compilation (LPC allows an embedded literal newline inside a string) but silently corrupt the stored value (e.g. a property key becomes "experien\nce" instead of "experience"). Confirmed identical corruption already present in the RAW pre-conversion archive (diffed against raw/.../questd11.c) — not something our pipeline introduced. Looks like the original file was edited at some point with a tool that hard-wrapped long lines at a fixed column width, occasionally landing mid-token. Found all 9 instances with a small Python scan (line N ends with an identifier-continuing character, line N+1 starts with one) and fixed each by hand (rejoining the split token/string) rather than a blind regex, per AGENTS.md's standing caution about bulk fixes on this class of corruption. 4. adm/daemons/eventd.lpc / adm/daemons/storyd.lpc's [0..<3] → [0..<5] rename-width bug (§2's documented pattern, recurring independently — same shape as yanhuangwuhun's own finding, ported directly once recognized): both build an event/story name list via get_dir(DIR + "*.lpc") then strip the extension with a hardcoded map_array(names, (: $1[0..<3] :)), correct for the original 2-char .c extension but leaving a trailing .l after the .c→.lpc rename (e.g. emei.lpc → emei.l instead of emei). This broke EVERY event/story lookup with call_other() couldn't find object '.../emei.l' — surfaced in the sweep as 8 unrelated-looking failures (u/rcwiz/mailbox/mailbox, u/rcwiz/xmas/man, d/city/zuixianlou2, d/city/npc/guidao, adm/daemons/network/services/ping_q, adm/daemons/network/messaged, adm/daemons/story/pangtong, adm/daemons/story/mengzi) all sharing the identical error string, resolved in one shot by fixing the 2 daemon files (§8g pattern again). Proactively applied the identical fix to adm/npc/luban.lpc's two own [0..<3] occurrences (a room-building NPC feature, confirmed via reading that both operate on .lpc-suffixed filenames) even though neither was caught by the sweep or reachable in our registration test — this NPC's "build a room" feature is never exercised by either, so this fix is unverified by any live test, flagged here honestly rather than claimed as confirmed. 5. d/city/npc/guidao.lpc's is_killing(who) type mismatch — the exact same bug shape as clone/user/user.lpc above (direct call passing an object where is_killing(string id) expects a string), independently present in a second file. Fixed identically (is_killing(who->query("id"))); this also resolved the cascading d/city/zuixianlou2 failure (the room that creates this NPC).

Remaining 21 failures, all confirmed non-critical / expected noise (none appear anywhere in log/debug.log across 3 separate boot+play sessions):

2026-07-23: driver rebuild retest + LPC formatter + WASM check

2026-07-23 (integrity review): the previous pass's combined.lpc hand-fix was incomplete — stray brace fixed

The previous pass's hand-fix of the formatter's ::move line-wrap corruption in inherit/item/combined.lpc restored the if (::move(dest, silent)) call but left the corruption's stray extra } behind (line 64), so the file did NOT compile (syntax error, unexpected '}') — despite that pass's NOTES claiming it was lpcc-verified. Every money/stackable item inheriting /inherit/item/combined would have failed to load at runtime. Removed the stray brace; structure now matches the pre-format git blob exactly (verified against commit 3501d9782f) and the sibling yanhuangwuhun's correct copy; lpcc compile now passes. Full registration + look/score/quit retested clean afterwards.

WASM-enablement pass (loopback-allow / gate bypass / admin seed)

Standard WASM-first pass per AGENTS.md §1.3b/c/e and §1.5. Ported the sibling yanhuangwuhun's fix shapes (near-identical band.lpc/ logind.lpc, confirmed by diff before patching). Gates patched:

- logon()'s BAN_D->is_banned() gate (~line 68) — loopback exempt. - the WASM registration blocker: both VERSION_D->is_version_ok() call sites (banner ~line 84, get_id() hard gate ~line 160) — guarded with find_object(VERSION_D); daemon absent (as under WASM, where versiond's socket_bind()/socket_create() usage keeps it from compiling) ⇒ notice/gate skipped. This was the documented "no id prompt under WASM" hard blocker described above — same root cause as yanhuangwuhun's. - No uptime() startup-grace gate exists here. The iplimit > 3 same-IP multi-login cap (~line 106) is already dead code (wrapped in #if 0 in the original archive) — nothing to bypass; noted only, no change made.

Correction applied mid-pass (fail-closed retrofit, 2026-07-24): the loopback helpers above were initially written matching the project-wide convention at the time (AGENTS.md §1.3b), which also treated an empty/non-string/malformed-IP query_ip_number() result as loopback, defensively, because the WASM driver used to return garbage there. That underlying driver bug is now fixed (fluffos commits e33bb5da "fix: query_ip_number() returned uninitialized garbage under WASM" and 007bb863 "feat: synthetic resolve() on WASM instead of raising an LPC error", both 2026-07-23; confirmed the locally-built build-debug/build-wasm binaries already postdate both commits), so treating unparseable IPs as trusted is no longer justified and is a fail-open gap. Retrofitted both helpers to the fail-closed form: loopback is now strictly ip == "127.0.0.1" || ip == "::1" || ip[0..3] == "127." (with a stringp() guard before the slice) — a malformed/empty IP now falls through to the NORMAL gate instead of being treated as local. Retested after tightening: fresh driver boot clean, fluffos login from 127.0.0.1 still shows "由127.0.0.1连线进入" (loopback path still taken) and update /adm/daemons/band still succeeds — the tightening did not regress local/native play.

Admin account seeded: id fluffos, pw Mud@2026, name 浮浮 (surname prompt skipped with bare ENTER — this lineage's surname+given-name split flow rejects 姓=名, same as yanhuangwuhun; "浮"+"浮" does not work, so skip the surname and enter the two-character given name "浮浮" directly). Granted (admin) via fluffos (admin) appended to adm/etc/wizlist. Verified: real registration flow (fluffos/y/[enter]/浮浮/admin-pw×2/ pw×2/5/m → entered 世外桃源, save written to data/user/f/fluffos.o + data/login/f/fluffos.o); restarted driver to pick up the new wizlist entry; relogin as fluffos → banner shows "目前权限:(admin)", update /adm/daemons/band → "重新编译 /adm/daemons/band.lpc:成功!", quit → clean farewell. One pre-existing non-fatal runtime error noted during the first get_passwd()/check_ok() of each boot (lazy-loading adm/daemons/network/messaged.lpc's startup_udp()): *Bad argument 2 to socket_bind() — same class as the already-documented versiond socket_bind() config-mismatch noise, harmless, does not block login; not patched (pre-existing, out of scope for this pass).

Retest: fresh normal registration (id qintest, surname skipped, given name 秦风, type 猛士型, gender m) end-to-end — look/quit correct, landed in 世外桃源; test character saves removed after (data/user/q/qintest.o, data/login/q/qintest.o). Zero new debug.log errors beyond the one pre-existing messaged.lpc warning above. Note: this lib's login prompt has a per-second live clock once in-world (AGENTS.md §8.3 item 1) — mudclient.py --idle 0.4 was occasionally too tight and dropped queued sends into "什么?" (unknown command); --idle 0.6 was reliable. Not a mudlib bug.

Save files for the orchestrator to add (none gitignored, normal add):

WASM 修复摘要(迁移自 meta.json 的 group_note)

关系很近的手足档案,内容独立(炎黄英雄史)。状态已从过时的 limited 修正——和手足档案 yanhuangwuhun(045)共享完全相同的真实 bug,两份都已修复:adm/daemons/logind.lpc 的 check_ok() 在每一次成功登录时都未加保护地呼叫 MESSAGE_D->find_chatter(),而 MESSAGE_D 在 WASM 下编译不过(原始 socket 呼叫)——未捕获的'No program in object'错误会让 check_ok() 执行到一半就中止,之后每一条指令(包括 quit)都回显'什么?',从未真正进入任何房间。已用 find_object(MESSAGE_D) 保护这个呼叫来修复。修复后重新验证干净:管理员登录(fluffos/Mud@2026)正常进入游戏世界,'目前权限:(admin)',quit 正常('欢迎下次再来!')。这份档案的提示符会显示实时时钟——测试时用的是 --idle 0.3。

深度功能测试(§10.7,2026-08-04)

原生 driver(端口 40061)跑了一遍超出注册流程的完整游玩——注册流 程本身和这批档案里常见的模式差异很大:分开输入姓/名两段中文姓 名、两组密码(管理密码+普通密码)、走到不同 NPC 面前听劝说词来选 "性格类型"(本轮走到"花铁干"处选了"天性阴险奸诈",只是玩家口味 选择,不是 bug)、在阎罗殿用 wash 指令跳入"忘忧池"重掷天赋、最后 用 born <中文地名> 投胎(选了"扬州人氏")。这份档案的提示符会持续 显示实时时钟(每秒刷新),用标准的 --idle 节奏发送指令会因为"从未 真正安静"而卡住送不出后续指令——继承自本档案自己 NOTES.md 里已经 记录过的经验,用 --idle 0.3-0.5 才能正常send through。

主动检查:adm/daemons/logind.lpc 没有 printf("%O\n", ob) 调试残留(§7.34 未命中),没有 §8.9 食物/饮水按错误对象 age 初始化 的问题(age 检查本来就读 user->query("age"),写法正确)。 MESSAGE_D->find_chatter() 的调用已经用 if (find_object(MESSAGE_D)) 包住——这是这份档案自己 WASM 修复阶段已经修好的 bug,本轮只是确认 仍然正确,不是新发现。command_hook 有三份拷贝(feature/ command.lpc 是 F_COMMAND 宏实际指向的活文件,cmds/chat/ command.lpc 和 feature/feature/command.lpc——注意这个诡异的嵌套 路径——都是未被引用的死拷贝),三份都是干净的 nomask,不是 bug。

注册与地图探索:注册测试角色(秦岩 / id qinyan),投胎到"扬 州人氏",落在扬州客店。逛了北大街、醉仙楼、钱庄方向,地图连通性 和房间描述全部正常。

商店测试:客店的店小二(d/city/npc/xiaoer.lpc)其实不是 vendor(没有 F_VENDOR,没有 vendor_goods),纯粹是场景装饰 NPC—— 按新手指南提示改到"醉仙楼"(d/city/zuixianlou.lpc,用的是 npc/xiaoer2.lpc),list 正确显示了六种食物/水袋商品和价格; buy 因为身上没钱被正确拒绝("店小二冷笑道:穷光蛋,一边呆着去!") ——这是新角色没有初始资金的正常内容状态,不是程序 bug。

战斗+死亡+复活测试(真实死亡,undisturbed 完整现场验证):北 大街站着"欧阳克"(金庸小说反派角色,白驼山少庄主——和 jyqxc 里独立遇到的是同一个角色设定,看来是这整个大家族里反复出现的固定 布景角色)。第一次 kill ouyang ke 几乎没受伤(战力差距这次没有 jyqxc 里那么悬殊),第二次交手加上当时的降雪/冰冻环境效果叠加, 真的把测试角色打死了("你的眼前一黑...你扑在地上挣扎了几下...死 了!",谣言频道播报"秦岩被活活冻死了"——环境效果和战斗伤害叠加导 致死亡,是有趣的系统联动,不是 bug)。死亡后被送到"鬼门关", "白无常"(刚修复的 bai.lpc)在场。没有主动打断复活序列,等 待完整走完五阶段对话后重连确认:角色已经复活、站在"武庙"(和新 手指南承诺的"扬州的武庙"完全一致),hp 显示精气/气血都已满血 (100/100),潜能从 99 减半到 50(符合死亡惩罚),食物/饮水仍有 余量。这是本轮系列测试里第二次(第一次是 jyqxc)现场完整验证了 §7.68 修复后 undisturbed 路径的正确性。

发现并修复 §7.68 的又一实例:d/death/npc/{hei,bai}.lpc(黑 无常/白无常,属于 yh2003 血统自己的死亡系统,和之前几个不同家族 的"gargoyle"系列文件不是同一套代码,但 bug 形状完全一致)都有 if (!ob || !present(ob)) return;,同样是五阶段 death_stage() 循环。已按已验证的修法拆分修复。

发现并修复一类全新的、本项目此前从未记录过的 bug:extensionless GBK 文本残留,穿过了原始转码流程和后续的 WASM 修复流程(新增 AGENTS.md §4.1 归档条目):注册后进入客店,客店留言板显示"131 张 留言未读",随即自动弹出一段很长的分页文字("== 未完继续 56% =="), 渲染出来是一整屏 U+FFFD 替换字符构成的乱码。追查后发现真正的源头 是 help/rules(游戏规则说明,首次登录或 help rules 时显示)—— 这个文件从未被转成 UTF-8,driver 原样把 GB18030 字节吐给客户端。 对整个 work/ 树做了一次完整的 Python UTF-8 解码扫描(不只是 .lpc/.h),又额外发现两个同类问题:clone/game/8_hlp、 clone/game/21_hlp(拱猪/21点两个纸牌小游戏的说明文字)。三个文 件都是无扩展名的纯文本帮助文件——正是 AGENTS.md §4.1 早就警告过的 "extensionless banners/help text 也是 GBK"这一类,但从未被 convert_lib.sh(只认识 .c .lpc .h .txt .log .cfg .conf .map 等已知扩展名)或历次针对 .lpc/.h 的补漏扫描捕捉到,一直藏到这 次 §10.7 深度测试才现场触发。用 iconv -f GB18030 -t UTF-8 转换 三个文件,人工核对转换结果是通顺的中文(规则说明提到"东方故事Ⅱ", 证实了这条血统的引擎归属;两个小游戏说明署名"猫部猫(Catyboy)"), 现场用 help rules 重新验证显示正常。同一次全树扫描还发现两个非 文本的历史遗留文件(kungfu/skill/huashan-quan/MFM1992——纯随机 二进制数据,没有任何 .lpc 文件引用它;adm/tmp/swapfile.*——driver 运行时状态文件,也不是源码)——两者都是很久以前的转换/挂起会话遗 留物,未做任何处理,确认不在任何代码路径上,不影响游戏。

意外撞上 AGENTS.md §10.8 已归档的驱动级崩溃类,第六个独立实例: 死亡/复活测试完成、hp 显示正常之后,下一次尝试重新连线收到 CONNECT_FAILED: Connection refused——driver 进程已经在两次连线 之间的空档期真的段错误崩溃了(Segmentation fault),debug.log 里没有任何痕迹(最后几行还是普通的编译警告),boot.log(driver 自己的 stdout)捕捉到了完整的 C++ 崩溃堆栈:崩溃发生在 driver 自 己的周期性 reset 扫描内部(backend_run_one_gametick → look_for_objects_to_swap → reset_object → object_visible, 写 ob->next_reset 时崩溃),不是任何玩家指令或可捕获的 LPC 错 误,紧邻崩溃前是一长串测试角色从未去过的地区(青城/洛阳/泉州/开 封等)第一次被懒编译的记录——和 AGENTS.md 已经记录的前五个同类崩 溃案例(xjcq2000/shiji/shenzhou/xlqy_new2007/ nitan170911)机制高度吻合:"环境世界模拟迟早触发大范围懒编译, 最终腐化某个对象/字符串的内部状态,下一次无关的驱动内部操作触发 时整个进程崩溃"。没有尝试修复(这是驱动层内存腐化问题,不是 mudlib 可修的 bug),已按已有惯例记入 AGENTS.md §10.8 作为第六个 独立实例,未重启 driver 继续这份档案后续的测试。

git status --short libs/yhyxs/ 复查干净:只有五处源码修改(三处 GBK→UTF-8 转码 + 两处 §7.68 修复)是跟踪变更,测试角色的新存档 (data/{user,login}/q/qinyan.o)保持未跟踪、未提交。

更正(2026-08-05):§7.68 复活软锁"修复"已撤销

上面提到的"鬼魂离开/不在场时被永久放弃复活流程"曾被当作 AGENTS.md §7.68 记录的一类 bug 修复(把单次判定改成每 5 秒重试)。经用户指出并 重新审视:这更可能是有意的游戏设计,不是 bug——大多数这类档案里 鬼魂根本无法自行移动,所以"不在场"要么从未真正发生,要么是"离开去 在阴间游荡,想回来时再走回这个房间、流程会通过 init() 重新从头开始" 这种有意为之的宽松机制,而不是需要强制追上玩家的错误。强行重试还可能 引入新问题:如果鬼魂之后又走回这个房间,旧的重试和 init() 重新触发的 新一轮流程可能同时运行,导致对话重叠错乱。已把这处改动撤销,恢复成 原始的 if (!ob || !present(ob)) return; 单次判定写法(bmxkx2001 除外——那份档案里这确实是一个真实存在、经过实际复现验证的 bug:鬼魂 本身完全无法移动,是另一个不相关的 NPC 强行把鬼魂拖走导致的)。详见 AGENTS.md §7.68 顶部的撤销说明。

§7.86 跨库扫描修复(留言板 post 崩溃)

AGENTS.md §7.100 修复(2026-08-19,批次三)

ROOM 基类冗余 replace_program(ROOM); 自崩溃地雷(详见 AGENTS.md §7.100):3431 处命中。自带建房工具 clone/misc/roommaker.lpc 的 字符串拼接模板同样修复。

git diff --stat:3430 files changed, 1 insertion(+), 3431 deletions(-), 与预期精确吻合。

验证:build-debug 驱动真实冷启动,端口 40061 正常监听。既有管理 员账号 fluffos/Mud@2026 登录,目前权限:(admin),世外桃源→ 桃源茅屋→世外桃源多房间走访无误(因本 lib 每秒刷新的活动时钟提示 符会打断 mudclient.py 的"静默等待"轮询,改用固定间隔的裸 socket 脚本测试),quit 干净退出,全程无新增 "cannot replace"/"cannot bind" 日志行。

``§7.112`` residual-gap closure (2026-08-20)

Corpus re-scan (grep -rl 'call_out("death_stage"' ... | filter for missing guard) found unguarded init()-scheduled death_stage() call_out chain(s) in d/death/npc/bai.lpc, d/death/npc/hei.lpc that the original two-wave sweep (see AGENTS.md §7.112) missed -- same reconnect-triggered duplicate-chain bug, different filename/lineage. Added the standard query_temp("death_stage_active")/set_temp/delete_temp re-entry guard, adapted per file's own exit points. Compile-verified via lpcc --batch.

§7.30 uninitialized-mapping accessor sweep (2026-08-20)

Corpus-wide mechanical sweep of the feature/skill.lpc shared-lineage bug (confirmed independently on xiakexing2017/jqxz2015/haiyang2 via round-four testing): 5 accessor(s) in this file returned a raw never-initialized mapping instance variable (defaults to int 0, not ([]), until first assigned), crashing any unguarded keys()/sizeof()/indexing caller for a fresh/untrained character. Fixed at the accessor level (mapp(x) ? x : ([])) per the documented remedy. Verified via lpcc --batch static compile check only (not a live boot) as part of a large mechanical sweep; not individually functionally re-tested live on this lib.

§7.125 sibling-sweep fix: premature set("registered", 1) defeats the registration gate (2026-08-28)

Flagged in AGENTS.md §7.125 as a sibling worth checking (originally found on zhyx, same yh2003/ES2 lineage). Confirmed byte-identical: adm/daemons/logind.lpc's enter_world() unconditionally ran user->set("registered", 1); //user->set("born",1); right after handing out starting clothing, on every login — permanently short-circuiting the register-room's exit gate and channeld's registration check from a character's first-ever login onward, since d/register/npc/shuisheng.lpc's do_decide() (the correct, sole place this flag should be set, confirmed present and correct in this lib too) could never matter — the flag was already permanently 1 before the player ever got a chance to register. Fixed by deleting the premature line in enter_world(). Verified via a clean native driver boot only (not an independent live walk-out-of-the-room reproduction on this specific lib — the bug shape and remedy are a verbatim port of an already live-verified fix from the shared-lineage sibling zhyx).

§10.7 深度功能测试(回合三 · 批次三,2026-09-01):五个全新角度,全部干净

上一轮(2026-08-04)已经深度测试过注册流程、地图探索、商店、真实 死亡+undisturbed 复活、GBK 乱码新 bug 类等。本轮受命从五个明确未 覆盖的角度重新测试,刻意寻找编程 bug(而非内容/平衡问题):

① 拜师入门机制(区别于死亡系统的"白无常"):cmds/skill/bai.lpc 是真正的拜师指令,最终会对目标 NPC 调用 ob->attempt_apprentice(me)。 本档案里能真正收徒的门派入口分散在各 kungfu/class/<门派>/*.lpc 里,且各家老大门槛都很高(如丐帮帮主洪七公要求 combat_exp >= 400000),新手要到达要花很长时间;但丐帮在中央广场树洞 (enter dong → /d/gaibang/inhole)里的黎生(kungfu/class/ gaibang/li.lpc)没有战力门槛(仅要求 permit_recruit() 通过 + shen >= 0),是新手唯一能立即验证的真实入口。用测试角色"王道" (wangdao)现场 bai li,全程走完 attempt_apprentice() → command("recruit ...") → cmds/skill/recruit.lpc 的完整链路, 真实状态变化确认:称号从"普通百姓"变成"丐帮第二十代传人"(显示 "小叫花"),score 显示"师父是黎生",family/family_name = "丐帮"。干净,没有发现 bug。(顺带发现 d/gaibang/npc/{1..7}dai. lpc 这些普通丐帮弟子场景 NPC 虽然通过 create_family() 设了 family,却没有 inherit F_MASTER 也没有定义 attempt_apprentice()——理论上对它们 bai 会触发一次"函数不存在" 的 call_other,但这更像是"这些低阶弟子本来就不该被拜师"的场景设 计缺失防御性检查,而不是会崩溃或产生错误数据的编程 bug;受时间 限制未继续深挖,留作观察,未作修改。)

② 留言板 post/read 真实往返:客店留言板(clone/board/ kedian_b.lpc,inherit BULLETIN_BOARD)此前已被 §7.86 跨库扫描 处理过(删除多余 replace_program()),但那次只做了编译检查,未 做过真实的 post 命令回归验证。本轮现场 post test-post-yhyxs + 编辑器正文 + . 结束,留言完毕,留言数从 131 增至 132,read new 成功读出第一条留言(无崩溃、无"cannot bind an lfun fp"错 误)。§7.86 修复在这份档案上首次得到真实的 live 回归验证,干 净。

③ 货币跨 quit/reconnect 持久化:先确认 inherit/item/money.lpc 的 query_autoload()(AGENTS.md §7.199 类 bug 的关键函数)本身 就是启用状态(未被注释掉,和风云系血统的 §7.199 bug 不同), 这份档案不属于风云系血统。现场验证:管理员 clone /clone/money/ gold 50 + give gold to wangdao 给测试角色五十两黄金,i 确 认在身上;quit("欢迎下次再来!",没有"你丢下一些黄金"的丢弃 提示);等待退出锁定窗口过后重新连线,i/score 确认五十两黄 金完整保留。干净,没有 §7.199 那类 bug。

④ 死亡对话中途net-dead硬断线(对抗性测试):区别于上一轮"不 打断、等待完整走完"的验证方式,本轮刻意在死亡序列进行中制造真实 的原始 socket 断线(不发 quit,直接关闭连接),测试 d/death/ npc/bai.lpc(含 §7.112 补丁的 death_stage_active 重入守卫) 在这种最坏情况下是否会产生重复对话/状态损坏/卡死。用管理员 smash wangdao 触发真实死亡,立即连线捕获到死亡对话第 1、2 阶 段("白无常用奇异的眼光盯著你" / "从袖中掏出一本像帐册的东西翻 看著")后,硬断开 socket(脚本进程直接关闭连接,不发送任何命 令)。等待足够时间让剩余阶段在服务端后台跑完后重新连线,确认 score 显示"你到目前为止总共到黑白无常那里串门二次"(正确的次 数,没有因为断线重连触发第二条 death_stage() 调用链而重复计 数)、死亡原因正确记录一次("你最后一次是被浮浮用雷劈死了")、 落脚点正确(复活至"武庙"),全程 debug.log 保持空白无报错。 §7.112 的重入守卫在真实的 net-dead 中途硬断线场景下依然完全 正确,干净。

⑤ §7.19 类 enable_commands() 重入检查:feature/command.lpc 是全档案唯一的 enable_player()/enable_commands() 调用出口 (nomask void enable_player()),本身已经有正确的重入守卫:

nosave int enabled = 0;

nomask void enable_player() {
  ...
  if (!enabled) {
    enable_commands();
    enabled = 1;
    add_action("command_hook", "", 1);
  }
  ...
}

配对的 disable_player() 也会把 enabled 重置回 0 再调用 disable_commands(),是完整对称的实现——比 §7.19 记录的 living(this_object()) 猜测式守卫更精确(不依赖"当前是否 living"这个间接信号,而是直接跟踪"enable_commands() 是否已经 调用过"),对 mhxy 那种合法重入(sleep/wake 类流程)也不会误 伤。全档案 grep 未发现任何绕过这个统一出口、直接调用 enable_ commands() efun 的 NPC/房间代码。结构性免疫于 §7.19 这一类 bug,干净。

总结:五个角度全部现场验证,没有发现新的编程 bug,也没 有需要移植给 yanhuangwuhun/yhwhpublicfi 的修复。测试全程原 生 driver(端口 40061)跑了约 24 分钟,本轮没有复现上一轮遇到的 §10.8 驱动级懒编译崩溃(同一份档案上一次是该崩溃类的第六个独立 实例)。测试角色"王道"(wangdao)的存档(data/{user,login}/w/ wangdao.o)按惯例保持未跟踪、未提交;测试过程中意外修改的三份 已跟踪共享状态文件(data/board/kedian_b.o 的测试留言、data/ dbased.o 的 ID 计数器、管理员 fluffos 账号的登录位置/时间戳) 已用 git checkout 还原,本次没有产生任何需要提交的源码变更。

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

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

深度功能测试(§10.7,2026-10-05)— 房间列出的物件缺档时房间照常加载

房间基类 inherit/room/room.lpc:make_inventory() 用 catch() 包住 new(file),做不出来就返回 0;reset() 的 case 1: 拿到 0 就跳过这一项(if (!objectp(ob[list[i]])) break;)。档案里没有的 NPC 或物品(或 create() 出错的物件)原先会让列出它的房间整个加载失败(Bad argument 1 to environment(),或对 0 的 call_other()),连带这个房间后面的区域;现在房间照常加载,只是少了那个物件,下次 reset() 再试(KB 05 §7.25;scripts/lpc_resilient_room.py、scripts/lpc_room_reset_guard.py)。 验证:探针(scripts/lpc_room_reset_probe.py:房间基类列出一个不存在的物件,再调用 reset())HEAD 单件 崩溃、多件 崩溃,工作树 单件 OK、多件 OK。

深度功能测试(§10.7,2026-10-06)— 与 zhonghua2 同源的三处编译警告

验证:改动的文件和包含 npc/indian 的守护进程在 HEAD 与工作树分别加载(scripts/lpc_listcheck.sh,新进程),无回退。

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

先用 scripts/lpc_twin_port.py adopt nitan_ceshi yhyxs 把 nitan_ceshi 警告整理提交(ae7fbb619a5)里的改动搬到 279 个修改前与它一致的文件(逐文件核对非 ASCII 文字:除 nitan_ceshi 自己删掉的以外没有丢失或改动),再用 scripts/lpc_nitan_family_fixes.py yhyxs(adm/daemons/combatd.lpc 的空语句 your_temp["guarding"];、d/jingzhou/luan2.lpc 的死字符串)。剩下的再跑 scripts/lpc_warnings.py yhyxs --fix 加 scripts/lpc_fix_no_effect.py --apply(d/tiezhang、d/wudu 几个房间删去 return 后面的 "\n";),共改动 824 个 .lpc 和若干 .h。以上都不改变行为。 HEAD 与工作树分别加载(新进程):822 -> 822 通过,无回退;改过的头文件的 152 个包含者 151 -> 151 通过。

启动时警告 84 -> 0;新进程登录正常(这个库回应较慢,输入名字后十秒左右才回)。

编译警告收尾(2026-10-13)

搬来同族已整理的改法:yanhuangwuhun(89552da089f)的 feature/food.lpc、feature/liquid.lpc do_effect() 改 varargs int(与重载它、被 eat/drink 用返回值的蜂蜜、猪肉等一致),zhonghua2 的 clone/megazine/obj/duka_3b.lpc 删去不用的那份赔率声明,yhwhpublicfi 的镖师 d/beijing/npc/qianzhenglun.lpc count 改名 my_count。能编译的文件警告清零;改动的 6 个对象在 HEAD 与工作树各载入一次,regress 0。

负数伤害当作消耗(2026-10-15,KB 06 §7.241)

receive_damage("neili", -52)、receive_damage("qi", -12) 之类把 add() 的负数写进了 receive_damage():本库 feature/damage.lpc 对负数伤害报错(或归零),练功/运功时报 F_DAMAGE: 伤害值为负值 或消耗失效。已改为正数伤害,内力/精力类改为 add(type, -N)(scripts/lpc_fix_damage_calls.py):/kungfu/skill/duanyun-fu、/kungfu/skill/feifeng-whip、/kungfu/skill/pangen-cuojiefu。

find_player() 只认在世界里的身体(2026-10-15,KB 07 §8.26)

simul_efun 的 filter_player() 加上 environment(ob):注册中途断线留下的、不在任何房间的身体不再被当成玩家,两个以上时 find_player() 不会再报 The player(<id>) has existed in the world.(wllz3 实测过的问题)。本库若注册流程不会留下带玩家 uid 的身体,这一改动不改变行为。

技能名拼错(2026-10-15,KB 06 §7.249)

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):choushui-zhang→chousui-zhang(chousui-zhang);cuhixiao-jifa→chuixiao-jifa(xiaoao-jianghu);hengshan-jianfa→hengshan-jian(/d/henshan/tianquan-hole);lenyue-shengong→lengyue-shengong(lengyue-shengong);matrial-cognize→martial-cognize(qiankun-danuoyi/nuoyi);sun_finger→sun-finger(sun-finger/heal);taiji-shengon→taiji-shengong(taiji-shengong);xuxiang-zhi→wuxiang-zhi(wuxiang-zhi/wuse);yaowang-mishu→yaowang-miaoshu(yaowang-miaoshu)。

技能名拼错:技能文件里拼错自己的名字(2026-10-15,KB 06 §7.249)

技能自己的文件(或它的绝招目录)里,query_skill() 读的是差一个字母的自己的名字,永远得 0:“基本功夫要高过本技能”之类的限制从不生效,按等级取招、计算伤害的那一项一直是 0。改为本技能的名字(scripts/lpc_fix_skill_typo.py 的 self 判据):jiujin-shengong→jiuyin-shengong(jiuyin-shengong/perform/zhang);xuanyuan-arror→xuanyuan-arrow(xuanyuan-arrow)。

存档读不出来:转码损坏(2026-10-15,KB 03 §4.4)

/data/board/gumu_b.o 在转码时坏了:原档里孤立的 GBK 前导字节被 iconv -c 连同后面的引号一起丢掉,restore_object() 报 Illegal mapping format,读档的对象(玩家、留言板、信件、表情)从空白开始。按 raw/ 原档重新解码(孤立字节丢掉、引号保留),驱动核对全部能读回(scripts/lpc_save_encoding.py --from-raw、scripts/lpc_save_restore_check.py)。

family 映射内联取下标(2026-10-15,KB 05 §7.30)

ob->query("family")["master_name"]、me->query("family")["family_name"](泥潭式为 query("family", ob)[...])直接对门派映射取下标:没有门派的人(新玩家、叛师者、大多数 NPC)在 NPC 的打招呼和问话、绝招的门派判断、房间的 valid_leave 里报 Value being indexed is zero,而不是得到拒绝。1 处(1 个文件)改为 dbase 路径查询 query("family/master_name"):有门派时值相同,没有时得 0;原有的判断照常生效(scripts/lpc_fix_family_index.py)。给映射赋值的写法未动。

NPC 带着本库没有的物品,载入失败(2026-10-15,KB 06 §7.73)

9 个文件、9 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/d/tulong/yitian/npc/obj/gong、/d/tulong/yitian/npc/obj/tongbo、/d/tulong/yitian/npc/obj/tiezhang)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 9 个(9 个原来载入不了),没有退步。

set_skill() 遇到本库没有文件的武功不再抛错(2026-10-18,KB 06 §7.256)

feature/skill.lpc 里的 set_skill()、map_skill()、prepare_skill() 碰到没有武功文件的名字就 error("F_SKILL: No such skill (…)"),调用它们的 NPC create() 整个中断:NPC 载入不了,刷出它的房间也少一个人。本库有 1 个对象设了本库没有文件的武功(1 个名字,最多的是 six-finger)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:1 个里 1 个通过。(scripts/lpc_guard_set_skill.py)

武功名拼错的 NPC(2026-10-19,KB 06 §7.258)

set_skill("kunlun-shnfa", …)、map_skill("dodge", "shaolin-shnfa") 这样的调用把武功名拼错了:本库没有这个武功文件,有的是拼对的那个。以前这种 NPC 在 create() 里报 No such skill 载入不了;武功缺失时跳过之后它们能载入,可是少了那门武功(set_skill 被跳过,map_skill 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 16 处、10 个文件:jinyin-baiguzhao→jiuyin-baiguzhao(4 处)、changquana→changquan(4 处)、tiaji-jian→taiji-jian(2 处)、shenhuo-shengong→shenghuo-shengong(2 处)、taiji-shenfong→taiji-shengong(1 处)、yan-fly→yanfly(1 处)、zhoangnan-zhi→zhongnan-zhi(1 处)、jinwu-goufan→jinwu-goufa(1 处)。(scripts/lpc_fix_skill_names.py)

RANK_D 对 0 的称呼(2026-10-19,KB 06 §7.273)

adm/daemons/rankd.lpc 的 query_respect()、query_rude()、query_self()、query_self_rude() 里 4 个函数开头补上 if (!objectp(ob)) ob = this_object();。NPC 在 create() 里写 "…" + RANK_D->query_respect(this_player()) + "…"(inquiry 对话、chat_msg),驱动自己跑 reset() 时没有人在场,this_player() 是 0。旧驱动里 0->query("age") 返回 0,称呼走默认分支;新驱动直接报 Bad argument 1 to EFUN call_other(),房间的 make_inventory() 吞掉错误,被打死的 NPC 再也刷新不回来。用 scripts/lpc_reach_census.py --walk 找到(xkx100 的武当弟子、一灯、汴梁书生…);修在守护程序里,不动 1,800 个 NPC 文件。

x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)

if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。具体是:cross pubu(穿瀑布)永远回答“你要往哪儿穿?”,瀑布后面的密室进不去。改动:/d/mingjiao/shanmen.lpc(1 处)、/d/tiezhang/hhyuan-3.lpc(1 处)。

member_array('/', str) 改为 strsrch(str, "/"):字符字面量的 member_array 按字节搜索(2026-10-20,KB 06 §7.283)

member_array('/', str) 里的 '/' 是整数,驱动按 UTF-8 字节搜索,返回字节偏移(带第三个参数时起点也是字节);最新的 FluffOS(923cdbc0)在字符串里有非 ASCII 文字时,对每个调用点警告一次(Warning: member_array(int, string) searches BYTES)。而 str[0..idx-1] 按字符切,中文字符串会在错误的位置截断。改为 strsrch(str, "/")(找不到同样返回 -1,按字符计数;scripts/lpc_fix_member_array_char.py,JSON 编码器等少数几处手改)。改动:/cmds/arch/sameip.lpc(1 处)、/cmds/archBAK/sameip.lpc(1 处)。

if (x = 常量):条件里写成赋值的比较(2026-10-20,KB 06 §7.290)

if (arg = "button" || arg = "按钮")、if (!arg || arg = "") return 0;、else if (check = 0 && tempB != ""):把 == 写成了 =,条件的值就是那个常量,分支对所有非零数和任何字符串(连 "" 都算)永远成立、对 0 永远不成立,变量还被覆盖;驱动对此没有任何警告。每一处 = 改成 ==(scripts/lpc_fix_cond_assign.py,共 2 处)。改动:/cmds/adm/watch.lpc(1 处)、/cmds/admBAK/watch.lpc(1 处)。

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

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

get_config(__MUD_PORT__) 经旧版 runtime_config.h 得到字符串(2026-10-20,KB 06 §7.302)

本库的 include/runtime_config.h 是老 MudOS 的编号(整数设置从 15 起,本驱动从 256 起),get_config(__MUD_PORT__) 读到的是另一项设置(字符串),(int) 又只是编译期标注,于是 socket_bind(fd, LOCAL_PORT()) 报 Bad argument 2 to socket_bind(),版本守护进程 port = get_config(__MUD_PORT__) + VERSION_PORT 是字符串相加。头文件自己的注释写明 get_config(__MUD_PORT__) == __PORT__,所以代码里的这个调用(连同外面的 (int) / to_int())改成了驱动的预定义 __PORT__(scripts/lpc_fix_mud_port.py)。改动:/include/getconfig.h(1 处)。

NPC 的 chat() 在 chat_msg 为空数组时下标越界(2026-10-21,KB 06 §7.334)

NPC 基类的 chat() 是 if (arrayp(msg = query(...))) { if (random(100) < chance) { rnd = random(sizeof(msg)); if (stringp(msg[rnd])) ...,不检查列表是否为空。本库现有的 NPC 文件里没有字面写成空 chat_msg 的(扫描只认 set("chat_msg", ({ })));守卫挡的是运行时才为空的列表和以后新增的 NPC。在 random(100) < chance 前面加 sizeof(msg) &&(scripts/lpc_fix_chat_empty.py;if (random(100) > chance) return 0; 的写法是 !sizeof(msg) ||)。改动:/inherit/char/npc.lpc(共 1 个文件)。

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

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

blockade / recover:data/keep 不存在,解封之后世界仍然锁着(2026-10-21,KB 06 §7.340)

管理员扫描里 blockade 报 Error copying file from /data/user/f/fluffos.o to /data/keep/fluffos, Error: No such file or directory,随后的 recover 在第一个 cp() 上死于 */data/keep/fluffos: lstat failed,来不及执行最后的 LOGIN_D->set_madlock(0):封锁解不开。data/keep/ 是运行时目录,git 克隆里没有空目录。blockade 在第一次写之前 if (file_size("/data/keep") != -2) mkdir("/data/keep");(mkdir() 失败答 0,不抛错),recover 对没有保存档的玩家 if (file_size(src) < 0) continue;,其余照常恢复、照常解锁(scripts/lpc_fix_keep_dir.py,hy5 上实测:没有保存档时 recover 答“时空解封完毕”,blockade 建出 data/keep 并保存,recover 取回并删除)。改动(4 个文件):/cmds/arch/blockade.lpc、/cmds/arch/recovermud.lpc、/cmds/archBAK/blockade.lpc、/cmds/archBAK/recovermud.lpc。

dual 不带参数:evaluate_shell() 对 0 取下标(2026-10-21,KB 06 §7.336)

dual(cmds/arch/dual.lpc)把参数原样交给 me->evaluate_shell(arg, flag),那里第一件事是 arg[<1] == ';',参数为 0 时报 Value being indexed is zero.(feature/shell.lpc);静态扫描只认直接传给 efun 的参数,看不见这种经过 call_other 的。命令开头加 if (!arg) return notify_fail("指令格式 : dual [detail]\n");(同系 16 个 dual 里另外 7 个本来就有这个检查)。

注册到不存在函数的 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 6 条注册(5 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/adm/npc/referee.lpc(do_halt)、/clone/megazine/obj/maikaren.lpc(do_drop)、/d/tianlongsi/munitang.lpc(do_exit)、/d/wudu/huxue2.lpc(do_train)、/inherit/misc/fboard.lpc(do_list)。最多的函数名:do_halt 2 处、do_drop 1 处、do_exit 1 处、do_train 1 处、do_list 1 处。

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

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

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 18 处(2026-10-22,KB 06 §7.360)

原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:拜入少林 / 武当 / 峨嵋等派的玩家被改成错误的法名(二字名 张三 变成一个字的 澄,更长的名字取到第三个字);各派掌门的辈分判断(name[0..1] == "澄")永远不成立;NPC 的称呼和提示里取到两个字而不是一个字。

本库 18 处(13 个文件):拼名字的赋值 10 处、与汉字常量比较 6 处、to_chinese()[..] 2 处。例:name[2..3] → name[1..1](/kungfu/class/emei/fengling.lpc:128);name[0..1] → name[0..0](/kungfu/class/emei/tobebonze.h:40);chk[0..1] == "你" → chk[0..0] == "你"(/adm/npc/ganjiang.lpc:542)。scripts/lpc_fix_gbk_slice.py 把下标按字符重算(社区的 UTF-8 版 xkx100utf8 里就是同样的写法,作对照);没有改的:截断(name = name[0..7],现在最多 8 个字,只是放宽)、ASCII 的区间(fname[0..3] == "room"、ctime(time())[4..19])和上下文看不出是汉字的区间。证明(scripts/lpcc_eval.py,bixiecanyang;其余各库是同一份文本):让澄字辈掌门 attempt_apprentice() 一个名叫「张三」的访客,HEAD 的法名是 慧,改后是 慧三;抽样 28 个库的 HEAD / 工作树加载对比 0 退步。工具不幂等([0..3] 折半得 [0..1],又是对齐区间),改过的文件记在 scripts/gbk_slice_converted.txt,不要重跑。

长度检查按字节写,旁边的提示按汉字写:2 处(2026-10-22,KB 06 §7.362)

老档的 strlen() 数字节(一个汉字 2 字节),转成 UTF-8 后它数字符:提示写「必须是 2 到 6 个中文字」的检查还是 strlen(name) < 4 || strlen(name) > 12,于是两三个字的房名、帮会名、群名、聊天室名、称号被提示自己说允许的那句话拒绝,上限却是提示的两倍;留言板的标题检查 i < 4 || i > 24 || i % 2 还会拒绝所有奇数个字的标题(post 你好吗)。本库 2 处(1 个文件):if ((strlen(name) < 4) || (strlen(name) > 12)) {(/adm/npc/luban.lpc:1769)。scripts/lpc_fix_strlen_gate.py 以提示为准:条件里每个 strlen 门槛都恰是提示所说数字的两倍才折半,存长度的变量(i = strlen(arg);)的门槛和 || i % 2 奇偶项、其后循环的 i % 2 == 0 && 一并处理;提示里有英文 / 字母 / 字节的不动。证明(scripts/lpcc_eval.py,bxsj 的留言板,求值对象继承 NPC 并重写 edit() 记录调用,command("post 标题")):HEAD 拒绝「你好」和「你好吗」、放行 23 个字的标题;改后接受前两个、拒绝第三个;shenmo 的 named.lpc invalid_new_name("张三"):HEAD 回 名字只能是2-4个中文字。,改后回 0。抽样 24 + 15 个库的 HEAD / 工作树加载对比 0 退步。

按字节写的汉字循环、茶馆猜诗和同盟频道标题(2026-10-23,KB 06 §7.363)

老档按字节做「一个汉字」:while (i--) if (i % 2 == 0 && !is_chinese(name[i..<0])) 只检查偶数下标,奇数位上的拉丁字母(张a)会过;茶馆猜诗 mixup() 用 strlen(str)/2 再 j=j+j、str[k..k+1] 交换两字节;泥潭系 channeld 的 switch (strlen(fname)) case 4/6/8/10 和 strlen(family)==4/6 按字节长度给同盟 / 门派名加空格。本驱动的 strlen() 和区间都按字符算。scripts/lpc_fix_byte_char.py:去掉奇偶项、把区间改成 name[i..i],按字符重写 mixup() 并把 strlen(first)>=14 折成 7,频道标题的 case / 门派长度改成字符数(对照 aoxiangtianji)。本库 汉字循环 / is_chinese 区间 1 个文件;channeld 同盟 / 门派标题 1 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

中文名 / 验证码仍按 GBK 取「两字节一字」(2026-10-23,KB 06 §7.373)

pname = pname[(random(sizeof(pname)) & 0xFFFE)..<1]; pname = pname[0..1]; 在按字符计下标时会取出两个汉字。scripts/lpc_fix_pname_slice.py 去掉掩码并把收尾改成 [0..0](pname/pname2/sec_name/str/str1)。本库 4 处。证明:nt1 生成名长度 2–4;HEAD 常见 3–5。

yuanwai 孤儿 call_out announce_success(2026-10-27,KB 06 §7.349 Also)

d/changan/npc/yuanwai.lpc(及 aoxiangtianji 的 d/city 副本)在 check_daughter 里 call_out("announce_success", …),但本文件从未定义该函数;同系也无实现。到点会 *Undefined function*。cry_daughter 已完成认亲与赠书,删除这对 remove_call_out/call_out。证明:扫描 HIT call_out→announce_success 且无定义;改后残渣 0。

深度功能测试(2026-10-27,§6.25 zhongling tell_object 引号错位)

d/wanjiegu/npc/zhongling(及同路径副本)的 tell_object("who, 钟灵脱下一双绣花鞋。\n") 把对象参数写进了字符串;改为 tell_object(who, "…")。证明:原文错位;改后加载,语料残渣 0。

深度功能测试(2026-10-27,§6.10 未知 pragma save_binary/optimize)

删除本库中 FluffOS 不认的 #pragma save_binary / #pragma optimize… 行。