Tales of Wulin Heroes: Soul of Yan-Huang (Public Final, 2016-12-08 Build)

✅ 可玩

炎黄武魂public-final-2016-12-08

yhwhpublicfi

🔑 wlqxztest(密码见 README) 更新 1e4e135 2026-09-01 源码 下载 ZIP

▶ 开始游玩 · Play Now

游戏内标题为《武林群侠传》之炎黄武魂,`master.lpc` 文件头注明完整传承脉络:"for ES II mudlib... updated by Doing Lu for hell (2K), modified by Linux@lxtx for yh 2003.3";文件级比对显示本档案与同系列 `yanhuangwuhun`(对方标题干脆加了个"Ⅱ"以示区分)96% 的同名档案逐字节相同、共享同一张 73 区地图,又与另一支同源快照 `yhyxs`(《炎黄英雄史》/皇朝再现)有 87-89% 的重合度,三者其实是同一套 2003 年"yh"分支代码的三次不同品牌打包;三者都继承了 `hell` 的地府轮回投胎仪式(阎罗殿、忘忧池),本档案把创角简化为猛士/智慧/耐力/敏捷/均衡五选一的角色类型菜单,姓名分开输入,管理密码与登陆密码双密码制且没有电子邮箱步骤,地图上还新增了"药王谷"场景,管理员权限按 ID 字符串查表即时生效、无需重新登录。

English

In-game the title reads "Tales of Wulin Heroes: Soul of Yan-Huang" — the master.lpc header traces the full ancestry: ES II → Annihilator → Xiang's XKX → Doing Lu's hell (2K) → Linux@lxtx's 2003 "yh" branch. A file-level comparison found this archive is a near-duplicate of this collection's yanhuangwuhun (96% of common files byte-identical, same 73-zone map) — that sibling even appends a "II" to its own in-game title to tell the two apart. It's also closely related (87-89% overlap) to sibling yhyxs, a third, differently-branded snapshot of the same lxtx-2003 codebase ("Chronicle of Yan-Huang Heroes"). All three inherit hell's underworld reincarnation ritual (Yama's Hall, the Pool of Forgetfulness), though this build simplifies chargen to a direct five-way archetype pick (Warrior/Sage/Endurance/Agility/Balanced) with split surname/given-name entry, dual admin/login passwords, and no email step.

README

内容亮点

本次修复的关键 bug

1. 经典的 §8.1 GBK 字节区间 is_chinese() 问题(奇偶校验 + 176-247/161-254 字节区间判断)——改成逐码点的 0x4e00-0x9fff 判断,且改成检查每一个字符,而不只是第一个。 2. 三处同源的"字节数减半"长度边界 bug,都需要按这台驱动逐码点 字符串索引的方式减半:check_legal_name() 的下限 (strlen<2strlen<1)和上限(maxlenmaxlen/2); get_name() 里姓名合并后的最短长度判断 (strlen(fname)<4<2);以及 named.lpcinvalid_new_name() 滑窗查重逻辑(下限 2→1,切片 name[i..i+3]/name[i..i+5]name[i..i+1]/name[i..i+2], 循环上限 l-4l-2,判断门槛 i+6<=li+3<=l)。修复前,单 字姓氏(比如"张")会被误判"太长",任何两字全名都会被误判"太 短"。 3. master.lpc(注意路径是少见的 adm/single/master.lpc,不是 常见的 adm/obj/master.lpc)的 valid_read()/valid_write() 直接转发给 SECURITY_D,没有标准的 user == this_object() 短路判断——补上后修复了注册流程中 new() 静默失败卡死的问题。 4. versiond.lpcin_server() 计算 get_config(__MUD_PORT__) + VERSION_PORT 在这台 WASM 驱动 下没有得到预期的整数,导致端口变成了字符串拼接结果 "12",每次开机都会在 socket_bind() 上崩溃报错"Bad argument 2"。按 AGENTS.md §7.52(做法和 hell 系同一份 versiond.lpc几 乎一致)掏空了全部 13 个碰 socket 的函式(in_serverconnect_serverclear_syn_info 里的 socket_close 循环、 send_commandsend_client_pending_msgsyn_finish 里的 socket_closein_listen_callbackin_write_callbackin_close_callbackcmd_closesend_pending_msgsend_resultremove_connection 里的 socket_close),周围 不碰 socket 的版本追踪逻辑原样保留。 5. §7.50 is_killing(object)is_killing(string id) 类型不 匹配,在 15 处呼叫点(kungfu/skill/*.lpc 10 处、 clone/user/user.lpcclone/lonely/sheying.lpcd/city/npc/guidao.lpccmds/std/surrender.lpccmds/std/ansuan.lpc)统一改成 ->query("id") 包装。

adm/daemons/ftpd.lpcadm/daemons/network/dns_master.lpc 都 已经在 adm/etc/preload 里被注释掉,而且除了各自 network/ 目录 下的兄弟档案外没有其它呼叫者,属于完全休眠状态,本次没有改动。

后续 §10.7 深度功能测试(2026-08-07)额外发现并修复(详见 NOTES.md):

6. 项目 4 当时只是给 versiond.lpc 一个受害者打了补丁(AGENTS.md §7.52 掏空 socket 呼叫),没有修根——同一份坏掉的 include/runtime_config.h 索引编号还坑了 messaged.lpc(跨服 UDP 聊天精灵,tell/chat 等指令依赖它),且它是每一次新登 录都可能触碰到的懒加载路径,不是可以随手掏空的休眠精灵。这 次把 include/runtime_config.h 整份换成驱动自带的权威版本,逐 一处理了三处仍在用旧符号的呼叫点。 7. adm/simul_efun/message.lpcmessage() 包装函式只声明了 4 个必填参数,但同文件 tell_object()/write() 两处只传 3 个参 数——每次干净重启在预加载阶段就会炸(AGENTS.md §7.88 的第二个 独立实例),且和手足档案 zjdywzb 一样卡在角色创建"选品质"必 经步骤上。已改成 varargs,缺参数补 || ({})。 8. 任务系统共享的 inherit/misc/quest.lpcset_information() 包装函式参数类型(string)比它转发的精灵(mixed)窄,导致 全部 8 种随机任务档案永远编译不出来(AGENTS.md §7.81 的第二个 独立实例,另一次发生在完全不同血统的 nt1 上)。已放宽为 mixed,一次性修好全部 8 个任务档案。

管理员账号 / Admin account

警告:对外公开架设前请务必修改此密码。

注册流程提示(供后续测试参考)

英文 ID → y(确认创建新角色)→ 中文姓氏 → 中文名字 → 管理密码 + 确认 → 登陆密码 + 确认 → 角色类型菜单(1-5,例如 5 均衡型) → 性别(m/f)→ 进入游戏(世外桃源)。score 需要先在游戏内完成 "出生"这一步才有数据,这是游戏设计本身的限制,不是 bug。

本地运行

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

游戏端口:40132

NOTES · 移植与修复记录

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

炎黄武魂public-final-2016-12-08——一个 Doing 血统的构建版本(master.lpc 的档头注明'ES II mudlib... updated by Doing Lu for hell (2K), modified by Linux@lxtx for yh 2003.3'),游戏内标题是《武林群侠传》之炎黄武魂。注册流程用姓+名分开输入(get_surname/get_name),有双重管理员+登录密码,一个 5 选项的角色类型菜单(猛士/智慧/耐力/敏捷/均衡)代替天赋重投,没有电子邮件步骤。WASM 修复了:(1)经典的 §8.1 GBK 字节区间 is_chinese()(奇偶门槛加 176-247/161-254 字节区间检查)重写成对每一个字符(不只是第一个)都做逐码点 0x4e00-0x9fff 循环检查。(2)三处各自独立的、来自同一个 §8.1 血统的字节数翻倍长度界限 bug,全部减半以匹配这个驱动按码点计的字符串索引方式:check_legal_name() 的最小界限(strlen<2 → strlen<1)和它的最大长度界限(maxlen → maxlen/2),get_name() 里姓+名合并后的最小长度(strlen(fname)<4 → <2),以及 named.lpc 的 invalid_new_name() 滑动窗口近似名字去重检查(最小值 2→1,窗口切片 name[i..i+3]/name[i..i+5] → name[i..i+1]/name[i..i+2],循环界限 l-4 → l-2,闸门 i+6<=l → i+3<=l)——不修的话,像'张'这样的单字姓氏会被拒绝为'太长',任何两个字的全名都会被拒绝为'太短'。(3)master.lpc 的 valid_read()/valid_write()(不同寻常地位于 adm/single/master.lpc,不是常见的 adm/obj/master.lpc)在转发给 SECURITY_D 之前缺少标准的 'user == this_object()' 短路判断——两处都已加上,修复了那种静默的 new() 注册卡死失败模式。(4)versiond.lpc 的 in_server() 呼叫 get_config(__MUD_PORT__) + VERSION_PORT,期待一个整数,但 WASM 驱动的 get_config(__MUD_PORT__) 在这里没有解析成期待的类型,产生一个字符串拼接出来的端口号('12'),在每次启动时都触发'Bad argument 2 to socket_bind()'崩溃;按照 AGENTS.md §7.52(和 hell/hell 家族对同一个 versiond.lpc 血统几乎一字不差的先例一致),把全部 13 个碰 socket 的函式(in_server、connect_server、clear_syn_info 里的 socket_close 循环、send_command、send_client_pending_msg、syn_finish 里的 socket_close、in_listen_callback、in_write_callback、in_close_callback、cmd_close、send_pending_msg、send_result、remove_connection 里的 socket_close)都掏空成 no-op/notify_fail,保留周围不碰 socket 的版本追踪逻辑不变。(5)§7.50 类的 is_killing(object) 对 is_killing(string id) 类型不匹配,修复了 15 处呼叫点(kungfu/skill/*.lpc 的 10 处命中、clone/user/user.lpc、clone/lonely/sheying.lpc、d/city/npc/guidao.lpc、cmds/std/surrender.lpc、cmds/std/ansuan.lpc),全部用标准的 ->query("id") 包装函式修复。adm/daemons/ftpd.lpc 和 adm/daemons/network/dns_master.lpc 都处于休眠状态(在 adm/etc/preload 里被注释掉,除了各自 network/ 子目录之外没有其它运行时呼叫者)——保持原样未做改动。管理员账号播种:wlqxztest (admin) 加入 adm/etc/wizlist(wiz_levels 顶层是 (admin);wizlist 查找只按 ID 字符串,所以一个刚注册的角色马上就能显示管理员状态,不需要额外的存档/重新登录步骤)。注册流程多次完整验证过:英文 id→y(确认新角色)→姓→名→管理员密码+确认→登录密码+确认→角色类型菜单(1-5,用的 5)→性别(m/f)→进入世外桃源,全程干净。管理员权限已通过'目前权限:(admin)'确认。score 指令需要先在游戏内完成一个单独的'出生'步骤才能报数据,这是有意为之的游戏设计门槛,不是 bug,保持原样。LPC 格式化工具对全部 10751 个档案运行(写入 10715 个,6 个针对杂乱历史代码的转档之前就存在的错误,30 个未改动)。没有 :: 父类呼叫拆分命中,没有 case 标签带尾随注释的候选,没有 CJK 重新加空格/转义损坏命中。格式化后用同样的完整注册流程重新验证过——干净,管理员权限依然是 (admin)。

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

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

本轮之前 yhwhpublicfi 只做过 WASM 阶段的注册冒烟测试和 §7.86 的编 译级扫描修复,从未做过完整的 §10.7 深挖。这次用原生驱动 (~/src/fluffos/build-debug/src/driver,端口 40132)从 AGENTS.md 已知 bug 类别清单逐项排查,并与同血统手足档案 zjdywzb/zjdy2008wzb (同为 Doing 系 "hell" 血统的独立分支,本 session 早前已深挖)做了 对照检查。全程用 scripts/mudclient.py--idle 0.5-0.6)驱动; scripts/tmux_mud.sh 在一次 born <地名> <名字> 的中文+空格组合输 入上出现了 AGENTS.md §10.2 记录过的本地 telnet 二进制吞字节现象(连 接卡死、后续任何指令包括 look 都没有任何回应),改用 mudclient.py(裸 socket,不经过本地 telnet 进程)后同一条指令立刻 正常执行——按文档指引确认是传输层问题,不是服务器端 bug。

找到并修复的 bug

转档遗漏:三个从未转码的 GBK 遗留文本档案(AGENTS.md §4.1)

用整棵 work/ 树的 Python UTF-8 解码扫描(排除 backup/、字体、 二进制存档等已知非文本内容)找到 3 个仍是原始 GB18030 字节、从未 被转档管线处理过的无后缀纯文本档案,和 §4.1 记录的 yhyxs/ yanhuangwuhun(同为 yh2003 血统的手足档案)一字不差是同一批档 案

kungfu/skill/huashan-quan/MFM1992clone/game/{8,21}_hlp 之外的 clone/game/ 目录里还有几个类似命名的档案——检查过没有任何 .lpc 引用 MFM1992,是未被任何代码路径引用的死档案(file(1) 误判成 "OpenPGP Secret Key",实际是随机二进制游戏数据),未做处理。 adm/etc/font/ 下的位图字库档案(Asc12/Hzk16 等)本来就是二进 制,不在转档范围内。

已确认没有踩中的已知 bug 类别

实机游玩记录

用新注册的测试角色「秦风」(yhwhtest,均衡型,天性阴险奸诈,出 生地"扬州人氏")完整跑了一遍:

1. 完整投胎仪式:英文 id → y → 中文姓"秦"(单字,验证 WASM 阶段修的字节界限 bug 没有回归)→ 中文名"风"(双字)→ 管理密码 +确认 → 登录密码+确认 → 角色类型菜单选"5"(均衡型)→ 性别 m → 世外桃源。对花铁干 out(触发本轮新修的 §7.88 message() 崩溃点)→ 阎罗殿,地藏王塞了本"天书"。washto 20 20 20 20(四 项天赋各 20,合计 80)→ born 扬州人氏 → 进入"客店",score 显示膂力/悟性/根骨/身法各 20、天性"阴险奸诈"、出生地"扬州人 氏",和实际操作路径完全对得上。 2. 移动:客店 → south → 客店茶房(扬州客栈茶园)→ north 回客 店;另一条路线 west → 北大街 → south → 中央广场 → south → 南大 街,沿途房间描述、出口列表、NPC 列表均正常显示,无一处 "No such object"/环境缺失。 3. 留言板post board 打开内建行编辑器,输入标题正文,. 结束,"留言完毕";look board 显示"[ 1] board ... 秦风- yhwhtest",退出重连后未读计数正确显示"1 张留言,1 张未读"—— 确认 §7.86 的跨库修复在这份档案上真实生效,不只是编译期检查。 4. 战斗:南大街对"流氓头"(四位流氓中带头的强化个体,比普通 "流氓"更硬)发起 kill,双方多回合正常拳脚攻防(命中/闪避/擦 伤消息、伤害数字提示),秦风连续中招约 24-40 点伤害后触发游戏 自带的"看来该找机会逃跑了……"自动脱战机制,安全撤到隔壁"赌场" 房间,没有死亡,也没有任何崩溃或异常。之后专门去挑一般"流氓" (数值更弱)重新开战,同样正常攻防几回合后再次自动脱战,全程 debug.log 干净。没有触发死亡/复活循环——这台驱动的自动逃 跑机制在低等级角色明显劣势时会主动撤离,属于正常设计(help newbie 也提到"如果在游戏过程中你不幸身亡,则死后从鬼门关复活 回来,到扬州的武庙,继续游戏",说明死亡复活确有实现,但本轮预 算内没有人为构造出一定会死的场景来强制触发;如实记录为未验证 实况,不是"没有实现")。 5. 退出/重连(存档路径):真实 quit 后立即重连命中"你距上次 退出只有 N 秒钟,请稍候再登录"的退出保留窗口(设计如此,不是 bug);等待超过该窗口后重新登录,你上次光临… 时间戳正确、位 置正确还原到"客店"(上次 quit 前所在房间),留言板未读数正确 保留——存档/还原路径确认正常。 6. 管理员账号:按 AGENTS.md §1.5 标准约定新增播种了 fluffos/ MudLogin2026(这份档案此前 WASM 阶段播种的是非标准 id wlqxztest,密码在当时的会话里已自设、这次无法复用,予以保 留、并列存在),加入 adm/etc/wizlist。实测 update /adm/daemons/eventdupdate /clone/quest/*(见上)、config 指令均成功,确认 (admin) 权限真实生效,不只是登录横幅显示。

一处走查但判定为内容问题、未改动

§7.100 修复(ROOM 基类多余 replace_program(),2026-08-19)

``§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/bgargoyle.lpc, d/death/npc/wgargoyle.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.

第四轮 §10.7 深测:死亡/复活闭环补测(2026-08-20)

本轮专门补测上一轮(2026-08-07)明确标记"未验证实况"的死亡/复活 循环——上次实战里角色在自动脱战机制(约 24-40 点伤害后主动撤离) 介入前始终没有真正死过。这次先读代码理清了机制(不是靠继续苦练 碰运气):feature/damage.lpcunconcious()(qi/jing < 0 时触发, COMBAT_D->player_escape() 郭靖救场,送去"郭府大厅"疗伤,不是真死) 和 die()(eff_qi/eff_jing < 0,或没有 player_escape 接住时才真正 执行)是两个独立分支——player_escape() 只有在"击晕者当前正在和你 交战"这个条件成立时才会拦截,管理员 smash 指令(cmds/arch/ smash.lpc)走的是 receive_damage("qi",1,killer) + ob->die() 直接 调用,killer 不处于与目标的实际战斗状态,player_escape() 直接 判 0 放行,所以 smash <目标> 是一条繁殖代价最低、完全合法(arch 权限指令)的真死路径,不需要去找"打不过就跑"这个自动脱战机制的 反例。

发现并修复的真 bug(不在死亡路径本身,而是新角色 born 流程里, 测试过程中意外撞见)adm/daemons/updated.lpcborn_player() 第 312 行 get_dir("/kungfu/special/")(不带通配符)会把 kungfu/special/ 目录下所有条目都列进来,包括 5 个从未清理掉的历史 .c.bak 残留档(guibian.c.bak/guimai.c.bak/qinzong.c.bak/ shenyan.c.bak/tiandao.c.bak——这 5 个的 .lpc 现役版本本来就在 第 321 行的"转世特技排除名单"里,说明这些残留档正是该被排除的那批)。 第 318 行 sscanf(files[i], "%s.lpc", files[i]) 对不以 .lpc 结尾的 档名(比如 guibian.c.bak)匹配失败,files[i] 保持原样不变,导致 排除名单的字符串比较("guibian" vs "guibian.c.bak")对不上,这 5 个残留档全部原样留在候选池里参与 random() 抽取特殊技能。一旦被 抽中,SPECIAL_D(special)->name()#define SPECIAL_D(x) ("/kungfu/special/" + x))呼叫 /kungfu/special/guibian.c.bak 这个 不存在的物件,直接在 born 指令执行时炸出 *call_other() couldn't find object '/kungfu/special/guibian.c.bak'.—— 实测第一次新建角色 born 就撞上了(约 5/28 的概率)。修复参考同一 份档案里其它地方(storyd.lpc/eventd.lpc/quest/explore.lpc)已经 在用的写法,把 get_dir("/kungfu/special/") 改成 get_dir("/kungfu/special/*.lpc"),让通配符本身过滤掉非 .lpc 档 案,不再依赖脆弱的 sscanf 后缀剥离。修复前后各完整跑了一次注册到 born 的全流程验证:修复前必现上述崩溃;修复后新角色 deathtest 干净通过 borndebug.log 零错误。

死亡/复活全流程实测(deathtest 新角色,均衡秉性由武林类型菜单 随机分到"心狠手辣"):管理员 fluffos 账号对已完成投胎的 deathtest 使用 smash deathtest(跨房间也能命中,smash.lpc 支持 远程雷劈),角色立即"死了!",ghost=1move(DEATH_ROOM) 送入 "鬼门关"(/d/death/gate)。房间内 npc/bai.lpc(白无常)的 init() 在检测到 previous_object()->is_ghost() && !wizardp(...) 后自动排队 death_stage() 五段对话(每 5 秒一句),全程无需玩家主动输入。最后 一句"罢了罢了,你走吧"之后自动 reincarnate()ghost=0eff_qi/eff_jing 满血)、清空随身物品(DROP_CMD->do_drop() 逐件 丢弃,实测复活后 i 显示"身上没有任何东西")、move(REVIVE_ROOM)—— REVIVE_ROOM 宏定义为 /d/city/wumiao("武庙"),和 help newbie 文档写的"死后从鬼门关复活回来,到扬州的武庙"完全对得上。复活后 score 正确显示"你到目前为止总共到黑白无常那里串门一次"(死亡计数 从 0 变 1)、"你最后一次是被雷劈死了"(死因文案正确),角色可以正常 接受后续指令(look/i 均正常响应)。全程(含此前 fluffos 管理员 账号自测的一次死亡+recover+goto手动归位)debug.log 零错误记录。

(旁注:管理员账号 wizardp(previous_object()) 为真时 bai.lpc/ hei.lpcinit() 会直接 return,不会自动播放死亡对话——这是 有意为之的设计(巫师不走凡人复活流程),不是 bug;用管理员账号 fluffos 自测死亡路径时借 recover+goto 指令手动归位,不代表 死亡对话本身对巫师账号是坏的。)

环境问题,非代码 bug,已在本地建目录但未提交smash 触发的 die() 调用链(combatd.lpckiller_reward() 第 1534 行)里有 一处 log_file("nosave/killrecord", ...),本仓库这份档案的 work/log/nosave/ 目录此前从未被创建过(log/ 整体在 .gitignore 里,不受版本控制,驱动启动时也不会自动建这个子目录——手足档案 zjdywzb/xkyxciii 等的本地 work/log/nosave/ 目录已经在,佐证这 纯粹是运行环境搭建缺口,不是代码问题)。第一次触发时 log_file() 因目录不存在直接报错,把整个 die() 调用链从 killer_reward() 那 一行截断,角色被判定"死了"但从未真正进入 ghost/移动到死亡房间/清 除随身物品——是一个值得记录的连带观察:die()log_file() 失败 没有任何防护,一旦该次写入失败(不管什么原因),死亡流程会卡在 "喊了台词但状态没变"的半失败态,但这是运行环境问题触发的,不是这 份代码本身的缺陷,mkdir -p work/log/nosave 后问题消失,未做代码 改动。

标准检查清单快速核对结果(均确认干净,不重新推导)

§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).