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

✅ 可玩

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

yhyxs

🔑 fluffos / Mud@2026 更新 981b7f0 2026-09-01 源码 下载 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 — staticnosave 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.lpcemei.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.lpcF_COMMAND 宏实际指向的活文件,cmds/chat/ command.lpcfeature/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_hlpclone/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_gameticklook_for_objects_to_swapreset_objectobject_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.lpcinherit 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.lpcquery_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 还原,本次没有产生任何需要提交的源码变更。