info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
游戏内标题为 "EMPEROR OF ULTRA HELL(BLOOD & MAGIC) FROM 1999.12.12",和本项目已收录的 `hell`(游戏内标题 "REVIVE OF ULTRA HELL...FROM 1999.11.13")同属 Doing Lu 的"地狱"血统家族(master.lpc 的作者传承注释一字不差),但这份 GitHub 上的 `revive` 仓库是一份规模小得多的 "底层代码"发行版——仓库自述即为"mud 底层代码",只随附了投胎/阎罗殿 (`death`)、比武场 (`pk`) 和注册入口 (`register`) 三个地图分区,原版 `hell` 里的近 54 个门派/城市地图(武当、少林、峨嵋、大理、丐帮……)和整个武功技能树都没有包含在这份发行版里。
English
A stripped-down 'base engine' snapshot of the same Doing Lu 'hell' ES II-lineage codebase as this collection's hell/hellxg/zjmudhell, self-titled 终极地狱-轩辕传说 (Ultimate Hell: Legend of Xuanyuan) and opening on the banner 'EMPEROR OF ULTRA HELL (BLOOD & MAGIC) FROM 1999.12.12.' Only three domains ship in this particular release -- the reincarnation/afterlife hall, the arena (pk), and the registration entry -- rather than the roughly 54 city and sect domains of the full hell release, though the shared engine's own registration flow, combat system, and daemons are all intact. New characters go through the lineage's classic reincarnation ritual: an English login id, an optional Chinese surname and given name, dual admin/regular passwords, a 1-5 aptitude choice (猛士/智慧/耐力/敏捷/均衡), and a gender choice, arriving in the 世外桃源 (Peach Blossom Land) entry scene where the NPC 水笙 greets newly arrived souls and nudges them toward the register command to claim an email address for password recovery.
README
内容亮点
- 注册流程和同宗
hell/zhyx完全一致:英文账号名(3-10 个英文 字母)→ 确认建立 → 中文姓氏(可留空)→ 中文名字 → 管理密码 → 确认 → 普通密码 → 确认 → 角色资质(1-5,猛士/智慧/耐力/敏捷/ 均衡)→ 性别(m/f)→ 进入游戏世界,落在"世外桃源"入口场景,水笙 NPC 会主动提醒"你还没有注册,快注册(register)吧"——完整的"投胎" 仪式(register/decide/选品性/wash/born)沿用同一血统的 设计,此前score显示"还没有出生呐"是正常游戏设计,不是 bug。 - 虽然只是"底层代码"版本,核心引擎(姓名系统、频道系统、留言板、 门派 dbase 存档架构、投胎流程)都是完整的,可以正常游玩注册 流程本身;但由于地图内容大幅缺失,投胎后能去的目的地非常有限。
本次修复的关键 bug
master.lpc的log_error()完全没有区分"警告"和"真正的错误", 比同宗zhyx/naruto已知的"大小写判断失误"还要严重——这份档案 里根本没有任何判断逻辑,导致每一次场景/NPC 懒加载编译产生的任何 警告都会原样广播给当时在线的玩家。已加上大小写不敏感的 "warning"子串判断。is_chinese()/check_legal_name()/named.lpc的经典 GBK 字节 长度误判(本驱动按字符而非字节计数),已按本收藏的既定手法修正。- 严重 bug:
calc_sec_id()(角色存档完整性校验)在这份档案里从未 被真正实现,永远回传固定字串"none",导致存档校验永远无法通过, 任何玩家在驱动重新开机之后都无法再次登录自己已经创建的角色 (报错"无法读取你的数据档案,您需要和巫师联系")。已比照同宗hell里这个函数真正实现所使用的crypt()配对写法,让这个尚未 实作的占位函数变成真正的空操作,而不是必然失败的陷阱——不涉及 编造任何游戏内容或校验算法本身。 virtuald.lpc的虚拟房间判断把"文件不存在"误判成"文件存在",导致 任何指向这份精简版缺失内容的引用都会陷入无限递归,触发 "Inherit chain too deep";连带修复了房间物件生成函数缺少保护的 问题——这两个 bug 叠加在一起,此前会让每一个全新玩家的注册流程 在进入游戏世界的最后一步就静默失败。
详见 NOTES.md。
管理员账号 / Admin account
- id:
fluffos - 密码 / password:
Mud@2026 - 管理密码 / admin password:
Mud@2026Adm - 权限 / level:
(admin)
管理员名单存储在纯文本文件 adm/etc/wizlist 里;账号本身通过正常
注册流程创建,"目前权限:(admin)" 已在游戏内跨越一次驱动重启确认
显示正确。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。
本地运行
cd libs/revive
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40236。
注意:ftpd.lpc 等几个网络精灵在启动时会尝试绑定真实的 TCP/UDP端口(详见 NOTES.md),不建议高频率自动重启此档案。NOTES · 移植与修复记录
revive (终极地狱-轩辕传说) -- porting notes
Source: git clone https://github.com/MudRen/revive (commit
240ea5c4c7cd8cf2d854781648a16398e6df3b16, cloned 2026-08-26). The
GitHub repo's own description is "终极地狱-轩辕传说 mud 底层代码"
("Ultimate Hell - Legend of Xuanyuan, MUD base/engine code"). Slug
revive, number 934, port 40236.
Lineage -- same Doing-Lu "hell" family already in this collection
adm/single/master.lpc's header comment is byte-identical to
hell/hellxg/zjmudhell's: "for ES II mudlib / original from Lil /
rewritten by Annihilator (11/07/94) / modified by Xiang for XKX
(12/15/95) / updated by Doing Lu for hell (2K)" (no yh2003-specific
"Modify By Linux@lxtx" line, distinguishing it from the separate
yanhuangwuhun/zhyx sub-family). The raw archive's adm/etc/welcome
banner reads "EMPEROR OF ULTRA HELL(BLOOD & MAGIC) FROM 1999.12.12",
internet mud name : hell.local, and smtp sender : [email protected]
in adm/etc/config -- the same author/codebase as hell's own "REVIVE
OF ULTRA HELL...FROM 1999.11.13" banner, just a different dated
snapshot. A whitespace/brace-style-normalized diff of master.c
against hell's already-converted master.lpc produced only cosmetic
differences (indentation, static->nosave, .c->.lpc -- the same
mechanical transforms convert_lib.sh already applies), confirming
this is the same underlying codebase, not merely a similar title (per
AGENTS.md's standing caveat that similar Chinese titles alone are NOT a
lineage signal in either direction -- this one was verified by diff).
Not a duplicate, but a MUCH smaller release than its hell sibling:
1,901 files total (vs. hell's 8,549) and only 3 of the original
domain directories under d/ -- death, pk, register -- vs.
hell's ~54 (baituo, beijing, changan, dali, emei, fuzhou,
gaibang, hangzhou, huashan, kunlun, shaolin, songshan,
suzhou, wudang, xiaoyao, xingxiu, ...). kungfu/skill/ doesn't
exist at all in this archive (only kungfu/special and
kungfu/condition, the skill *framework*, survive). This matches the
repo's own self-description as "底层代码" (base/engine code) -- it
reads as a stripped release of the shared Doing-Lu engine with the
actual built-out game world/content held back, not a conversion
failure or an accidental subset.
Pipeline
1. Cloned directly into libs/revive/raw/revive/ (git-hosted source,
.git stripped before copying in, per this project's convention).
2. scripts/convert_lib.sh libs/revive/raw/revive libs/revive/work
GB18030: already_utf8=422 converted=1461 lossy=11
skipped_binary=7; renamed 1,169 files to .lpc; fixed 252 literal
.c" references + 5 #include <....c> refs; converted 9 local
angle-bracket includes to quotes; static->nosave touched 106
files. No string-literal ("static/...) or
#define nosave/protected static shim collisions found on
inspection (both known §4.3 classes checked and clear).
3. Full-tree straggler sweep (plain Python UTF-8-decode walk, per
AGENTS.md §4.1 beyond what the extension-based scan covers): 6
non-UTF8 files remained after the main pass, all genuine GB18030
text that file(1)'s binary heuristic had misclassified
(skipped_binary=7 counted these plus one genuinely-tiny
no-magic file, adm/etc/banned_sites, correctly left alone):
doc/board/{yb010,yb028}, doc/legend/xkx10 (dev-era player
message-board/story archive text, same extensionless-help-text gap
class already documented for zhyx/yanhuangwuhun, though
different specific files) and clone/game/{8_hlp,helppai,21_hlp}
(card-minigame help text -- again the same class, different
content, as zhyx's 8_hlp/21_hlp). Converted with
iconv -f GB18030 -t UTF-8 in place.
No uppercase-.C stragglers found (unlike zhyx).
4. No hardcoded MUD_PORT/PORTNO constant found in include/*.h
(§5.3 checked, clear).
Fixes applied (driver-incompatibility / robustness bugs)
log_error()had NO warning-vs-error distinction at all (a worse variant of the miscapitalized-check bug documented forzhyx/naruto) --adm/single/master.lpc'slog_error(file, message)unconditionally echoed every compiler diagnostic straight to whichever player/wizard was connected at that moment ("\n编译时段错误:" + message), with no substring check onmessageat all. Since this driver's compiler calls thelog_errormaster-apply for every diagnostic including plainwarning: ...messages (confirmed by reading~/src/fluffos/src/compiler/internal/compiler_utils.cc'ssmart_log()/report_compile_diagnostic()-- both callAPPLY_LOG_ERRORunconditionally, gated only by whether a VM context exists), this meant every lazy-load compile warning site-wide was broadcast as a hard-error notice. Fixed by adding the same case-insensitive filter this lineage's sibling libs use:
``lpc
if (strsrch(message, "Warning") == -1 && strsrch(message, "warning") == -1)
``
is_chinese()GBK byte-range check (adm/simul_efun/chinese.lpc) -- the classic whole-string lead/trail-byte-range check (str[i] < 161,i%2==0lead-byte gate,str[i] < 176 || str[i] >= 248). Rewritten as a CJK Unified Ideographs codepoint range check (str[i] >= 0x4e00 && str[i] <= 0x9fff), matchinghell's own already-fixed copy exactly; floor changed< 2->< 1.check_legal_name()/named.lpc"deep" length-halving family (adm/daemons/logind.lpc,adm/daemons/named.lpc) -- the full shape already documented forzhyx/yanhuangwuhun:
- check_legal_name(name, maxlen): strlen(name) < 2 ||
strlen(name) > maxlen -> < 1 || > maxlen / 2.
- get_name()'s combined surname+given-name floor: strlen(fname) <
4 -> < 2.
- named.lpc's #define PATH(name) (name[0..1] + "/" + name) ->
name[0..0]; every strlen(name) < 2 guard (remove_name,
map_name, who_is, invalid_new_name) -> < 1; the
name[0..1] bucket-key reference in remove_name -> name[0..0];
invalid_new_name()'s sliding-window near-duplicate check: guard
strlen(name) < 4 -> < 2, loop bound i <= l - 4 -> i <= l -
2, 2-char window name[i..i+3] -> name[i..i+1], 3-char window
name[i..i+5] (guarded i+6<=l) -> name[i..i+2] (guarded
i+3<=l).
command.lpc'scommand_hookwrongly declaredprivate(AGENTS.md §8.3a) --feature/command.lpc'sprivate nomask int command_hook(string arg)silently downgrades theadd_action("command_hook", "", 1)catch-all registration for anyORIGIN_EFUNcall (i.e. any NPC using thecommand()efun to script another object's input, not a player's own direct typing). This lineage's water/scenery NPC dialogue (水笙in the register entry room) drives itself this way. Fixed: droppedprivate, keptnomask.message()'s unconditional 4-argefun::message()call (adm/simul_efun/message.lpc) -- same shape already documented forzhyx/yanhuangwuhun:tell_room()'s call site passes a bareint 0for the exclude-list argument on every ordinary call, which failsefun::message()'svoid|object|object*4th-arg type check. Fixed by guarding on truthiness (call the 3-arg efun form whenexcludeis falsy)..c->.lpcrename-width bug ([0..<3]->[0..<5], same shape aszhyx/yanhuangwuhun/nitan_ceshi) --adm/daemons/eventd.lpc,adm/daemons/storyd.lpc, and 2 sites inadm/npc/luban.lpcall strip what used to be the 2-character.cextension from aget_dir()/filename result with[0..<3], now leaving a stray.lon every.lpcfilename. All 4 sites widened to[0..<5].get_config(__MUD_PORT__)under a bare(int)cast (this driver's(int)cast is compile-time-only bookkeeping, not a real runtime conversion -- this project's own standing reference note) -- hit in bothadm/daemons/versiond.lpc'sin_server()(port = get_config(__MUD_PORT__) + VERSION_PORT) and the sharedinclude/getconfig.h'sLOCAL_PORT()macro (used byadm/daemons/network/messaged.lpc'sstartup_udp()). Both switched toto_int(get_config(__MUD_PORT__)).- UDP DATAGRAM socket
read_callbackdeclaredstring, receives aBUFFER-- bothadm/daemons/network/dns_master.lpc(on the real preload list) andadm/daemons/network/messaged.lpc(also preloaded, and itscreate()unconditionally callsstartup_udp()) declareread_callback(int sock, string msg, string addr), but this driver'ssocket_create(DATAGRAM, ...)delivers the packet as abuffer. Widened both tomixed msgand addedif (bufferp(msg)) msg = string_decode(msg, "UTF8");at the top of each callback, matching thezhyxdns_master.lpcfix exactly. accept_kill()passes an object whereis_killing()expects a string id (AGENTS.md §7.50, the same-lineage bug already fixed inhellitself) --clone/user/user.lpc'saccept_kill(object ob)calledis_killing(ob)(an object) againstfeature/attack.lpc'svarargs int is_killing(string id). Fixed:is_killing(ob->query("id")).- A genuine pre-existing syntax typo, not a conversion artifact --
clone/user/user.lpc'scalc_sec_id()stub (see below) was missing a semicolon on itsreturn "none"line, which cascaded into 4 further spurious parse errors through the rest of the file (query_info()'s body). This is the single bug that actually blocked the player-body class (USER_OB) from compiling at all -- found live, mid-registration, the moment a new character reached gender selection (the first pointmake_body()/USER_OBgets compiled). Fixed by adding the missing;. virtuald.lpc's virtual-room file-existence check was truthy for-1(file-not-found), turning any reference to genuinely-missing content into infinite mutual recursion instead of a clean "no such object" -- new bug class, not previously seen in this collection.adm/daemons/virtuald.lpc::compile_object(file)(the handlermaster.lpc::compile_object()calls whenever the driver can't find a file to load) doesif (file_size(name + ".lpc")) { ob = name->query_virtual_room(...); ... }--file_size()returns-1for a missing file, and-1is truthy in LPC, so a target that does NOT exist was treated as if it DID, andname->query_virtual_room()triggered another attempt to load/compilenameitself, which (sincenamestill doesn't exist) recurses intomaster::compile_object()->virtuald::compile_object()again, forever, until "*Inherit chain too deep: > 60 when trying to load '/d/city'." First caught trying to load/d/register/entry(the actual registration entry room -- see below), and reproduced identically for every/d/<missing-city>/...reference this stripped archive's own board/NPC/item content still makes (60+clone/board/*_b.lpccity-board clones, severaladm/npc/*.lpccontent NPCs, etc. -- see "Confirmed content gaps" below). Fixed the actual logic bug:if (file_size(name + ".lpc") > 0). This alone turns every one of those references into a clean, standard*call_other() couldn't find object '/d/<city>/...'runtime error (still a failure, since the content genuinely isn't shipped, but no longer a driver-endangering infinite-recursion crash).inherit/room/room.lpc's room-population helper had no guard aroundnew()/->set()/->move(), the exact shape already documented in AGENTS.md §7.25 -- once thevirtuald.lpcfix above turned the missing-content case into an ordinary runtime error instead of infinite recursion, that error was still UNCAUGHT: the registration entry room's ownd/register/npc/diyun.lpcNPC doescarry_object("/d/city/obj/cloth.lpc")->wear()in itscreate()(a wardrobe item from the missing/d/citydomain), andinherit/room/room.lpc::make_inventory()callednew(file)with nocatch()at all -- so this ONE missing content item's failure propagated all the way up throughdiyun'screate()->make_inventory()->reset()-> the entry room's owncreate(), making/d/register/entry-- the actual starting/registration room every new player is moved into -- fail to load entirely.logind.lpc::enter_world()'s owncatch(load_object(startroom))then (correctly, defensively) fell back toSTART_ROOM = "/d/city/kedian"-- which ALSO doesn't exist in this stripped archive, so the fallback itself then threw a second, uncaught error. Net effect before this fix: every single new-player registration died silently right at the world-entry step, with no visible error to the player and no crash to the driver -- just a broken session. Fixed per the established §7.25 pattern, guarding the population helper without fabricating any missing content:
```lpc object make_inventory(string file) { object ob;
catch(ob = new(file));
if (! objectp(ob))
return 0;
catch(ob->set("startroom", base_name(this_object())));
catch(ob->move(this_object()));
return ob;
}
``
...plus an objectp(ob[list[i]]) guard added to reset()'s case 1
branch before its own subsequent environment(ob[list[i]]) call
(the default branch already guarded correctly via its
! objectp(...) { ...; continue; } shape -- only case 1 needed the
fix). With both changes, /d/register/entry loads cleanly (diyun`
just silently has no clothes, a narrow, cosmetic content gap) and
every new player reaches the actual game world.
- SEVERE:
calc_sec_id()shipped as a never-implemented placeholder, permanently locking every player out of their own saved character after any driver restart.clone/user/user.lpc's own comment reads "自行设计计算完整性数据和的方法" ("design your own way to compute the integrity checksum") -- this codebase's author left an intentional customization hook, but the function that actually shipped in THIS archive is just:
``lpc
string calc_sec_id(int raw) { return "none"; }
`
save() stores this raw, un-hashed placeholder verbatim
(set("sec_id", calc_sec_id())); restore() then checks
crypt(calc_sec_id(1), sec_id) != sec_id -- i.e. re-hashes the SAME
literal "none" using the stored value as the crypt salt, and
compares the result against that same stored value. Since the stored
value is the raw plaintext "none", not a real crypt hash, crypt()
can never reproduce it byte-for-byte, so this check failed
unconditionally, for every account, every time, setting
user_cracked = 1 and making restore() return 0. Traced through
adm/daemons/logind.lpc::check_ok()'s if (user->restore()) {
...enter_world...} else { destruct(user); write("无法读取你的数据
档案,您需要和巫师联系。\n"); ... } -- confirmed live: a freshly
registered, fully-saved character (testqin, real Chinese name,
admin+regular passwords, full stat block on disk) could complete
registration and play normally within the SAME driver session (any
still-net-dead reconnect skips restore() entirely via a different
check_ok() branch), but could never log back in again after any
driver restart -- exactly reproduced, then fixed and reproduced
clean, via two full kill+reboot+reconnect cycles. This is the same
structural idiom already used correctly, with a REAL non-stub
implementation, by sibling lib hell's own calc_sec_id()
(clone/user/user.lpc:932, a genuine checksum over the whole
dbase/skills/inventory, gated by the exact same if (!raw) str =
crypt(str, 0); return str; shape) -- confirming this placeholder was
simply never finished in this specific archive, not a driver
incompatibility invented by conversion. Rather than inventing a real
checksum algorithm (out of scope -- that would be completing missing
game-content logic the original author explicitly punted on), this
was fixed as a narrow, mechanical round-trip correction: restore the
missing crypt()-salt gating so the SAME placeholder value
round-trips consistently (i.e. make the un-implemented check a true
no-op instead of an always-fail trap), reusing the exact gating shape
already present verbatim in hell:
``lpc
string calc_sec_id(int raw)
{
string str;
str = "none";
if (! raw)
str = crypt(str, 0);
return str;
}
``
Verified: a fresh account registered, saved, and quit; driver killed
and restarted; the SAME account reconnected cleanly all the way back
into the game world ("你连线进入revive" / room redisplay / clean
quit`) -- reproduced the failure once pre-fix (exact "无法读取你的
数据档案" message) and the fix twice post-fix (including once through
this lineage's legitimate, unrelated 30-second reconnect-cooldown
gate, which is intentional throttling, not a bug).
- Missing runtime directory, the §7.11 pattern:
/log/nosave/(referenced byclone/user/user.lpc's ownsec_id-mismatch logger,clone/medicine/{wuchang,shengsheng,jiuhua}.lpc,clone/ultra/ xuanhuang.lpc,adm/daemons/eventd.lpc,feature/vi.lpc, and others) never shipped in this archive and was created as part of pre-boot setup, alongside the fulldata/{login,user}/{a-z}/shard tree,data/{npc,item,board,room}/,backup/,version/,dump/,temp/, andbinaries/(all referenced byadm/daemons/versiond.lpc exclude_dir/variousDATA_DIR+subpath writes, none shipped in this stripped archive either). Before this directory existed, the exact samecalc_sec_id()mismatch above additionally crashed with an UNCAUGHT*Wrong permissions for opening file /log/nosave/user for appendon every single login attempt (a second, independent failure mode stacked on top of the always-mismatching check itself).
Confirmed content gaps (NOT fixed, per project policy)
- 51 of the original ~54 domains never shipped in this archive --
/d/city(this archive's ownSTART_ROOMtarget,/d/city/kedian),/d/wudang,/d/shaolin,/d/emei, etc. all simply don't exist. Everyclone/board/*_b.lpccity message-board clone (60+ files), severaladm/npc/{ganjiang,moye,youxun}.lpccontent NPCs,clone/weapon/{jsbaojia,xtbishou}.lpc, and thedata/room/moman/*subtree all reference rooms/items in these missing domains and fail to load with a clean (post-virtuald.lpcfix)*call_other() couldn't find object '/d/...'-- narrow, individually-diagnosed content gaps, not fixed. kungfu/skill/doesn't exist at all -- only the skill *framework* (kungfu/special,kungfu/condition,feature/ skill.lpc) survived this stripped release.adm/npc/luban.lpc(set_skill("literate", ...)),d/death/npc/{bgargoyle, wgargoyle}.lpc(dodgeskill -- these ARE inside the one domain that DOES ship,d/death, but reference a skill this archive doesn't), andclone/npc/killer.lpc(bladeskill) all fail with*F_SKILL: No such skill (...). Not fixed -- inventing an entire skill-content tree is squarely out of scope.adm/daemons/cruised.lpc, entirely dead/unreachable code with pre-existing author bugs -- not on the preload list, not referenced by any macro or call site anywhere else in the archive (grep-confirmed). Fails to compile on its own merits regardless of content gaps: missing#include <net/socket.h>(undefinedSTREAM_BINARY), missinginherit F_DBASE(undefinedset()), and a genuine typo (CAHNNEL_DforCHANNEL_D). Per this project's standing "don't spend time making dead code compile" policy (§2.3), left as-is.adm/daemons/quest/girl.lpc: references undefined variables (rcv_npcs,supply_objs) that are presumably meant to come from a shared base class this stripped archive'sadm/daemons/quest/subtree doesn't fully carry -- a genuine incomplete-content gap in the shipped quest-daemon set, not fixed.adm/daemons/story/{master,pangtong,baguadao}.lpc"fail to load" with NO error text at all -- confirmed NOT a bug. Read each file'screate():story/master.lpcself-destructs unlessuptime() >= 86400(a 24-hour-gated "华山论剑" special event, by design);pangtong.lpc/baguadao.lpcself-destruct whenever their ownselect_character()helper can't find a qualifying live player to feature (also by design -- these are special "current server population" narrative-event objects, not meant to persist in an empty/freshly-booted world).story/{caishen,dabei,beihai,hyshan, ...}.lpc(no such uptime/population gate) load and pass cleanly in the same batch sweep, confirming the difference is each file's own intentional self-destruct condition, not a shared bug.adm/simul_efun/{message,util}.lpc"fail to load" standalone -- confirmed a batch-sweep artifact, not a real bug. Both files are textually#included into/adm/single/simul_efun.lpc(after#include <ansi.h>/<localtime.h>already appear there) and rely entirely on that inclusion context for their own ANSI-color-macro references (HIW,NOR, etc. --message.lpc) and<localtime.h>-derived macros (util.lpc); neither file#includes those headers itself.lpcc_check.sh's batch sweep tries to compile every physical.lpcfile as its OWN standalone object, which correctly fails for these two (their macros are genuinely undefined in that isolated context) -- but the REAL simul_efun object (/adm/single/simul_efun, which DOES textually include both files in the right order) compiles and boots 100% clean, confirmed via a direct single-filelpccrun before ever touching the full batch sweep. Not a bug; not fixed.
Boot / registration verification
~/src/fluffos/build-debug/src/lpcc, single-file, confirmed clean on the master (adm/single/master) and simul_efun (adm/single/simul_efun) chain first, then a scoped--batchrun over all 110feature/*.lpc+inherit/**/*.lpcfiles -- 110/110 passed on the first attempt (per this project's RAM-safety discipline: core base classes verified individually/in a small scoped batch BEFORE ever running the full sweep).- Full
lpcc_check.shbatch sweep (all 1,169 objects): peaked at ~2.6GB RSS (well under the 10GB safety threshold, monitored every ~5s throughout), completed in well under a minute each of the 3 times it was run this session. Final count: 1,081 / 1,169 = 92.5% pass (88 failures, all triaged above into: missing city-domain content, missing skill-tree content, one dead/unreferenced file with pre-existing author bugs, one incomplete quest daemon, 3 self-destruct-by-design story-event objects, and 2 batch-sweep-only standalone-compile artifacts). None of the 88 affect the actual boot/registration/gameplay path exercised below. - Real
build-debugdriver boot on port 40236: clean, zero fataldebug.logerrors after all fixes above (only benign compile warnings, correctly routed to the master's own log file after thelog_error()fix, never shown to a connecting player). - Full registration flow verified via raw Python socket scripts (telnet IAC-stripping, scripted
input_to()sequence with per-prompt waits --mudclient.py's idle-based receive loop proved unreliable against this lineage's live per-second clock prompt, the documented "recv loop breaks on live clock prompt" gotcha, so a purpose-built raw socket script was used instead), 4 independent full runs with real Chinese names (surname/given-name: 秦/风, 秦/云, 秦/测, plus the admin account below), covering all 5 character-type options and both genders across the runs: each reached the actual starting room "世外桃源" ("Peach Blossom Land" -- the same entry room already documented for the wider yh2003/ES2 family inzhyx's notes), with NPC 水笙 present and reactively greeting the new arrival,lookcorrectly re-displaying the room,scorecorrectly returning the graceful "还没有出生呐,察看什么?" (not-yet-born, matching this lineage's own intentional pre-birth response, not an error), andquitcleanly returning "欢迎下次再来 !". One of the 4 runs (the first, before thecalc_sec_id()fix) demonstrated the SEVERE bug directly: a same-session net-dead reconnect worked, but a clean reconnect after a driver restart hit "无法读取你的数据档案" every time -- reproduced twice pre-fix, disproven twice post-fix (including once through the lineage's own legitimate 30-second reconnect-cooldown gate). - Admin account seeded: id
fluffos, 普通密码(login)Mud@2026, 管理密码(recovery)Mud@2026Adm, display name 浮浮 (surname skipped with a bare newline -- a lone浮alone is rejected by the 2-character-minimum combined-name check, the same intentional-content floor already documented forzhyx; given name浮浮instead). Granted(admin)viafluffos (admin)appended toadm/etc/wizlist(same plain-textwiz_name statusformat as the existingivy (admin)entry). Verified across a driver restart:fluffos/Mud@2026login succeeds, the online-count banner correctly counts 1 巫师 (wizard) rather than 1 玩家 (player), andlookshows the wizard-only room-path suffix (世外桃源 - /d/register/entry), confirming(admin)status is live;quitclean. - Save files for the orchestrator to add (not gitignored, normal add):
libs/revive/work/data/user/f/fluffos.o,libs/revive/work/data/login/f/fluffos.o. All other test characters' save data (testqin,qinfeng,qinyun-- 3 throwaway ids across the session, one per major fix-verification pass) was deleted before finishing, per convention. - Outbound network check (per task step 6): a real, active listening side effect confirmed --
adm/daemons/ftpd.lpcis on the preload list and, in this sandboxed test environment, successfully bound and LISTENed on TCP portmud_port + 21(40257, confirmed viass -ltnpshowing a liveLISTENsocket owned by the driver process) the moment the driver booted. Separately,adm/daemons/network/ dns_master.lpcandadm/daemons/network/messaged.lpc(both preloaded) andadm/daemons/versiond.lpc(lazily loaded on the first connection attempt, vialogind.lpc::logon()'sVERSION_D->is_version_ok()call) each unconditionally attempt a realsocket_create()/socket_bind()at boot for UDP portsmud_port+4/mud_port+10and TCP portmud_port+12respectively -- all 3 FAILED in this specific sandboxed environment (log/{dns_master,messaged,version}show repeated "Failed to bind socket ... error = -10", almost certainly a sandbox network-namespace restriction rather than a port conflict), but the code path is identical tozhyx's already-documenteddns_master.lpcfinding and would very likely succeed on an unrestricted host. This lib should therefore NOT be swept into high-frequency automated reboot loops (§10.0-style long-sit scans, round-two/round-three re-test cron) the way most sandboxed libs in this collection safely can be --ftpd's listening socket in particular is a real, unauthenticated network service exposed at every single boot in an environment where the bind actually succeeds.
Not run this session
- LPC formatter (§9): not run against the edits above (matches the precedent set by
zhyx, wherenodewas unavailable in that session's environment too). Every edit in this session was small (a handful of lines each) and manually reviewed against the surrounding code's own indentation/brace style. - Full deep §10.7-style playthrough / WASM pass: out of scope for this onboarding session per the task brief (native boot + registration verification only). The
ftpd/dns_master/messaged/versiondreal-network side effects are an extra reason to be deliberate about how often this lib gets rebooted in any future automated long-sit/WASM-enablement pass.
2026-08-27 深度功能测试 / Deep functional test (round two, §10.7)
Read help/newbie first (registration -> pick personality via out in
one of 4 rooms -> wash for stats -> born <地名> into the world).
Ran a real, continuous session with a fresh Chinese-named test
character (surname/given-name 秦/石, then 秦/言 for a second,
never-born control character used for the restart/reconnect check),
plus the seeded fluffos/Mud@2026 admin for goto-based exploration
of the two non-register domains. Native build-debug driver, port
40236, one lib at a time, killed by exact PID between reboots.
SEVERE, confirmed live: every new player's first move out of the
registration room threw an uncaught driver error (d/register/npc/
diyun.lpc's unguarded carry_object()->wear())
d/register/npc/diyun.lpc::create() (the "狄云" NPC who sees new
players off at the register-room exit) did:
carry_object("/d/city/obj/cloth.lpc")->wear();/d/city is one of the ~51 domains this stripped archive never
shipped (already documented above), so carry_object() (inherit/
char/npc.lpc) correctly returns 0 on the missing file -- but the
call site immediately dot-calls ->wear() on that 0 with no
objectp() guard, which the driver rejects as *Bad argument 1 to
EFUN call_other() Expected: object, string, array, Got: int(0).
Reproduced live: a freshly-registered character's very first east
(or any exit) out of /d/register/entry triggered d/register/
entry.lpc::valid_leave()'s own ad-hoc new(__DIR__"npc/diyun") (not
the room's normal make_inventory()-populated objects list, so the
onboarding session's make_inventory() catch() fix -- which only
covers population via reset() -- never protected this path), which
ran diyun's create() uncaught, dumping a full driver stack trace
straight to the connecting player:
执行时段错误:*Bad argument 1 to EFUN call_other()
程式:/d/register/npc/diyun.lpc 第 16 行This is universal -- every single player who successfully registers
and decides hits it on their first move, though it's non-fatal (the
error is swallowed after being printed and the move itself still
succeeds; diyun just has no visible clothes afterward). Fix
(same shape as the onboarding make_inventory() fix, §7.25 family --
guard the call, don't fabricate the missing content):
object cloth;
...
if (objectp(cloth = carry_object("/d/city/obj/cloth.lpc")))
cloth->wear();Verified live post-fix: east out of /d/register/entry now prints
only "狄云对你一抱拳,道:人生路全靠自己走,朋友走好!" with zero
error trace, across a fresh boot.
Same exact unguarded carry_object(missing)->wear()/->wield() shape
found and fixed at 3 sibling call sites (grepped every carry_object(
...)-> call site in the tree, cross-checked which referenced targets
are actually missing in this archive vs. real, shipped /clone/...
items -- most of the ~30 hits are fine, these 3 aren't):
adm/npc/youxun.lpc(carry_object("d/city/obj/cloth")->wear()) -- not reachable in normal play (its home city is missing) but crashes the instant anything clones/loads it (confirmed via a scopedlpccrun: post-fix it correctly falls through to the already-documented, separate "kungfu/skill/ doesn't exist" content gap instead).clone/npc/meng-zhu.lpc(2 sites:/d/shaolin/obj/changjian+/d/city/obj/cloth, plus a 3rd/4th site in therestore()-branch using a savedweapon/armorpath that could equally be stale) -- same treatment, same post-fix fallback to the skill-content gap.clone/megazine/room/npc/mm.lpc(/d/city/npc/obj/qunzi) -- this one now compiles and loads 100% clean post-fix (no residual content gap behind it).
All 4 fixes verified via a scoped lpcc --batch run before and after
(diyun and mm flipped FAIL->PASS cleanly; youxun/meng-zhu flipped from
the wear()-crash FAIL to the pre-existing, correctly-untouched
*F_SKILL: No such skill FAIL) and a full lpcc_check.sh sweep
(1081/1169 -> 1087/1169, zero new failures anywhere else). Likely to
recur in hell/hellxg/zjmudhell: this is boilerplate NPC-dressing
code copied across many files in this lineage; those siblings ship the
full city content so their own carry_object() targets probably
resolve, but it's worth a quick grep of carry_object(...)-> there too
in case any of *their* target paths are themselves stale/renamed.
SEVERE, confirmed via lpcc: the entire quest subsystem (5 of 6
quest-type files) failed to compile -- a shared wrapper's parameter type didn't match how every real caller used it
inherit/misc/quest.lpc::set_information(string key, string info) --
but clone/quest/{shen,supply,explore,search,judge}.lpc (every quest
type except the already-content-gapped girl.lpc and the plain
avoid.lpc) call it with a function pointer as the second
argument (set_information(NPC1_NAME, (: ask_npc1 :)), etc.) so an
NPC's own name/id can trigger a dynamic answer instead of a fixed
string. The underlying daemon side (adm/daemons/questd.lpc::
set_information(object qob, string key, mixed info)) already declares
info as mixed and handles this fine -- only the thin per-quest-
object wrapper in quest.lpc had the wrong, narrower string
declaration, which this driver enforces strictly at compile time:
error: Bad type for argument 2 of set_information ( string vs function )Since EVERY quest-type file needs this call, all 5 real quest classes
failed to compile entirely, and the quest daemon's own heart_beat()-
driven start_quest() (an ambient, ~15-minute-interval background
process, ran unprompted mid-session) threw a fresh uncaught *No
program in object '/clone/quest/search'! (and identically for the
other 4) every time it tried to spin one up -- caught by the driver's
error handler so it didn't crash the mud, but it silently broke 100%
of this stripped archive's quest content and spammed debug.log
forever. Fix: widen the one declaration to match its own daemon
counterpart and every real caller:
void set_information(string key, mixed info)Verified via lpcc --batch: all 6 quest files (shen, supply,
explore, search, judge, avoid) now PASS; girl.lpc still fails
on its own, separate, already-documented undefined-variable content
gap (untouched, correctly). This is a narrow shared-file type-
declaration bug, not a content decision -- fixing it doesn't invent
any of the missing quest content, it just lets the existing quest
*code* run instead of refusing to compile.
Companion fix, found via the same ambient heart_beat error:
adm/daemons/quest/{supply,explore}.lpc::start_quest() both do
env = get_object(room); ...present(rcv_npcs[room], env)... with no
objectp(env) check -- since rcv_npcs' keys are real, well-formed
room paths but every single one of them lives in a domain this
archive never shipped (/d/city3, /d/dali, /d/changan, /d/city,
/d/beijing, /d/shaolin, /d/suzhou, /d/hangzhou, /d/fuzhou,
/d/quanzhou, /d/lingzhou -- a pure content gap, not a code bug),
get_object() always returns a non-object, and present(x, 0) throws
*Bad argument 2 to present() Expected: object Got: 0, also caught
but also spamming debug.log every ~heart_beat forever. Fixed with a
one-line ! objectp(env) guard added to each, mirroring the
already-fixed quest/girl.lpc sibling (which coincidentally can't
even reach this line, since it fails to compile first on its own
undefined-variable gap). adm/daemons/quest/{judge,search,shen}.lpc
use a different, already-guarded room-selection shape and needed no
change at THIS call site -- but see the next finding, which hit all
three of them at a different call site.
Second content gap uncovered by the same set_information() fix,
in the very next line the fix unblocked: once the quest files could
actually compile, letting the daemon's heart_beat() reach past the
old compile failure, clone/quest/{search,judge}.lpc::init_quest()
(npc1 = new(CLASS_D("generate") + "/questnpc"); ... npc1->set_temp(
...)) and clone/quest/shen.lpc::init_quest() (same shape, single
npc/"/shennpc") immediately hit a FRESH ambient uncaught error,
live, unprompted, within the very next heart_beat cycle after the
reboot:
执行时段错误:*Bad argument 1 to EFUN call_other()
Expected: object, string, array, Got: int(0).
程式:/clone/quest/search.lpc 第 67 行Root cause: CLASS_D("generate") expands to /kungfu/class/generate,
and this WHOLE directory -- not just /kungfu/skill/, a separate,
already-documented gap -- never shipped in this stripped archive
(confirmed: find . -path "*kungfu/class*" returns nothing at all).
new() on the missing .../questnpc/.../shennpc template correctly
returns 0 (per the onboarding session's virtuald.lpc fix), but
none of the three call sites checked before dot-calling ->set_temp()
on the result -- the exact same missing-guard shape as the carry_
object()->wear() family above, just one more content-gap directory
deep. Fix, same established pattern (guard + self-destruct via the
function's own pre-existing "can't proceed" idiom, already used one
branch up in the very same functions for the "item already claimed by
another quest" case):
npc1 = new(CLASS_D("generate") + "/questnpc");
npc2 = new(CLASS_D("generate") + "/questnpc");
if (! objectp(npc1) || ! objectp(npc2))
{
destruct(this_object());
return;
}(single-npc variant for shen.lpc). Verified via lpcc --batch
(all 3 still PASS) and live: rebooted again, let the driver idle
through 3+ heart_beat cycles (supply/explore/search/judge/shen all
fire on their own timers), debug.log grepped clean of any 执行时段
错误/*-prefixed runtime error for the whole idle window post-fix.
/kungfu/class/generate/ is a third, previously-undocumented missing
content subtree (distinct from /kungfu/skill/), also referenced
unguarded by adm/daemons/questd.lpc (receiver.lpc, killed.lpc --
the letter-delivery and bounty-quest NPC generators) and adm/daemons/
npcd.lpc (chinese/japanese/european/indian generic-NPC
templates). Those 6 additional call sites were deliberately left
unfixed: unlike the 3 quest-heart_beat sites above, none of them are
reachable via any live path in this archive -- they only fire from
inside quest-giver dialogue that no NPC in the 3 shipped domains
actually offers, so fixing them would be speculative hardening against
an unreachable trigger rather than a live, in-scope bug, and the
missing-content directory itself is the same well-established
"don't fabricate content" boundary as /kungfu/skill/. Flagged here so
a future session doesn't have to rediscover the same gap from scratch,
and doesn't mistake "reachable via heart_beat" for "reachable at all"
when triaging which of a batch of similar-looking call sites are worth
fixing.
§8.3a variant confirmed: two more private mixin functions
dispatched by name via call_out(), not just add_action()
Grepped every private-declared function in the tree against same-
file call_out("name", ...) / add_action("name", ...) call sites,
then filtered out the (majority) false positives where the private
function lives in a standalone daemon/NPC/command file that's never
inherited into a different top-level body (for those, the driver's
own internal dispatch on this_object() never crosses the
private/DECL_HIDDEN boundary, so there's no bug). Two real, live mixin
instances remained, both already named verbatim in this project's
own §8.3a catalog as confirmed demonangel sibling instances of the
exact same class:
feature/action.lpc::start_call_out()doescall_out("eval_function", delay, fun), buteval_functionwasprivate.feature/action.lpcisF_ACTION, inherited byinherit/char/char.lpc(CHARACTER), inherited byclone/user/ user.lpc-- i.e. the actual player body.start_call_out()is the ONLY delayed-callback primitive used throughout this codebase for anything that must survive the calling object being destructed mid-delay (its own header comment explains why): kungfu temporary- condition effects (kungfu/special/{agile,hatred,power}.lpc'sremove_effect), sleep/meditation wakeup (cmds/std/sleep.lpc,cmds/skill/jingzuo.lpc), room-cart arrival (inherit/room/ trans.lpc), combat's owncontinue_attack(adm/daemons/ combatd.lpc, 6 sites),steal's completion,makelove'sdo_over, the quest daemon's own follow-up callbacks, and several more -- a very wide, central blast radius, though not independently live-reproduced this session (no reachable room in this stripped archive hassleep_roomset, and there's noeval-equivalent wizard command to force a synthetic repro; the fix rests on the identical, already-proven-livedemonangelprecedent plus a cleanlpccrecompile).inherit/item/combined.lpc::set_amount(0)doescall_out( "destruct_me", 0)to self-destruct an emptied stackable item (money down to 0, a depleted material stack, etc.), butdestruct_mewasprivate.COMBINED_ITEMis inherited byinherit/item/ money.lpc,inherit/weapon/throwing.lpc,inherit/medicine/ powder.lpcand severalclone/misc/*.lpcitems -- meaning every coin/silver/gold object, thrown weapon, medicine powder, etc. that ever gets reduced to a zero count would silently never self-destruct and instead linger as an inert zero-quantity husk forever.
Fix (both, matching the established pattern): drop private, keep
the function otherwise unchanged --
// feature/action.lpc
void eval_function(function fun) { evaluate(fun); }
// inherit/item/combined.lpc
void destruct_me() { destruct(this_object()); }Verified via lpcc --batch: no regressions (all previously-passing
files affected by either inherit chain still pass). Likely to recur
in hell/hellxg/zjmudhell: feature/action.lpc and inherit/
item/combined.lpc read as core, shared engine files in this lineage
(not stripped-archive-specific content), so this exact
start_call_out/eval_function and combined_item/destruct_me
shape is a strong candidate to check on those 3 siblings the next time
any of them gets a §10.7 pass -- grep private.*eval_function and
private.*destruct_me directly.
Confirmed clean (all six standing cross-cutting patterns checked)
- §7.121 (float-arithmetic function declared
int, noto_int()): no currency/economy function in this lib does real float math --inherit/item/money.lpc::value(),feature/ banker.lpc::do_convert(), andfeature/dealer.lpcall use plain integer division/multiplication throughout; everybase_valueis a literal integer. Clean. - §8.3a (
privatemixin function dispatched by name): the already-fixedcommand_hookinstance holds (confirmed live --d/register/npc/lu.lpc'scommand("chat ...")on personality selection worked with zero error). Two NEW instances found and fixed this session, see above. A full grep of every otherprivatefunction against same-filecall_out/add_actiontargets found no further live (inherited-mixin) instances -- the remaining ~25 hits are all standalone daemon/NPC/command files, correctly unaffected. - §7.122 (autoload-class marker-item duplication): this codebase has no
compute_autoload_array/destroy_autoload_obj/load_autoload_obj/query_auto_loadmechanism at all (grepped, zero hits) -- not applicable to this lineage. - §7.123 (bare file-scope
IDENT = (...)statement): grepped for a column-zeroIDENT = ([/({pattern across every.lpcfile -- zero hits (every match found was properly indented, i.e. inside a function body). Clean. - §7.124 (0.0-1.0 fraction vs. 0-100 percentage threshold):
wimpy(the auto-flee threshold,inherit/char/char.lpc+cmds/usr/ wimpy.lpc) defaults to plain integer0and is set/compared as a 0-100 percentage throughout, matching its own field type consistently -- no float-literal mismatch anywhere. Grepped the whole tree for= 0\.[0-9]+;assigned to anint-declared field: zero hits. Clean. - §7.126 (stale pre-
.lpcextension in a door/exit save-file macro-placeholder resolved by a customfile_path()-style helper): this codebase has no coordinate-gridAREA/file_path()engine at all -- rooms use plain hard string exits, resolved directly byload_object(), no macro substitution layer. The one place a stale.cextension WAS found in persisted data (data/dbased.o's/d/huashan/ziqitaientry,"test":"/data/room/doing/xiaoyuan.c") is dead, unreachable data --/d/huashanis itself one of the ~51 missing domains in this stripped archive, so this exact save-file entry can never actually be loaded/hit regardless of the stale extension; confirmed viainherit/room/room.lpcnever consuming any dbase-stored"exits"data at all for exit resolution in this archive. Not a live bug, not fixed.
Registration / personality / born / quit-reconnect -- all confirmed
working correctly (post-fixes)
Full continuous session with 秦石 (male, 光明磊落 personality via
the east-room NPC 陆天抒, matching the newbie-help-documented flow
exactly): register -> decide -> out of the east personality room ->
arrived at 阎罗殿 (d/register/yanluodian.lpc) -> wash (rolled
str/int/con/dex 19/22/19/20) -> born 扬州人氏 correctly failed
gracefully ("牛头一呆,搔搔头说:怎么好像有问题...", the
already-documented /d/city content gap, zero crash) -> score/i
both rendered a full, sensible status display (age 14, personality,
starting clothes from account creation) despite the failed born.
quit was clean both times tested (dropped the now-worthless newbie
book, correct farewell message), debug.log grepped after each quit
(clean both times, post-fix). A second, never-washed/never-born
control character (秦言) was registered specifically to verify the
onboarding session's calc_sec_id() restart-lockout fix still holds
after this session's edits: registered -> decide -> quit -> driver
killed by exact PID and rebooted -> reconnected after the restart ->
correctly restored into /d/register/entry with the right "上次光临"
timestamp and pre-birth score message -> quit cleanly again. The
fix holds.
One notable downstream interaction, not a new bug: d/register/
yanluodian.lpc::do_born() sets me->set("startroom", dest) *before*
checking whether the destination object actually loaded, so a failed
born (this archive's universal case, since every born target is a
missing domain) still permanently records a nonexistent startroom.
A player in this state who later quits and reconnects will hit
logind.lpc::enter_world()'s own catch(load_object(startroom))
failing (a SEPARATE, already-existing catch from the onboarding
session, working exactly as designed) and land in the graceful "你无
法进入这个世界" fallback message with no environment, rather than
back in the register room -- reproduced live on 秦石 post-fix-boot.
This is a downstream consequence of the well-documented, deliberately-
untouched missing-city-domains content gap (not a new code bug: the
existing catch already prevents a crash, and there is no live-
reachable born target in this archive that would ever leave a
character in this state on a fully-built release), so left as-is --
flagged here only so a future session doesn't mistake it for a new
regression.
Combat / skill / sect / shop / death-respawn: **not reachable
live in this stripped archive** -- content gap, not a code bug
Confirmed via direct testing that born cannot succeed for ANY of the
18 listed destinations (every single one targets a domain this
archive never shipped: /d/city, /d/guanwai, /d/beijing, /d/
taishan, /d/changan, /d/shaolin, /d/xingxiu, /d/xiangyang,
/d/suzhou, /d/hangzhou, /d/fuzhou, /d/city3, /d/dali, /d/
foshan, /d/baituo, /d/yanziwu, or the intentionally-disabled 0
placeholders for 蒙古/黔中), so a player can never leave the register
domain through the game's own intended path. The only other two
shipped domains (d/death, d/pk) were explored directly via admin
goto instead:
d/pk(entry/ready/turen1-12) is a scheduled PK-tournament arena (乌老大/wu laodasigns players up for an uptime-gated "屠人大赛" event, matching thestory/master.lpc-style 24-hour-gate pattern already documented above) -- no safe-sparring dummy exists here or anywhere else in the shipped content (greppedaccept_fightproject-wide: the only hit outsideinherit/char/{fighter,npc, punisher,challenger}.lpc's shared base logic iscmds/std/ fight.lpc, the "较量" command documented inhelp newbieitself -- but it requires a real opposing character, and the only NPCs in the 3 shipped domains are non-combative dialogue/quest-giver roles).d/death(gate/gateway/road1-3/inn1-2/block) is the ghost-gate/resurrection domain matchinghelp newbie's own description. All rooms load and render cleanly viagoto. Its two guard NPCs (黑无常/bgargoyle,白无常/wgargoyle) hit the already-documented "kungfu/skill/doesn't exist" content gap (*F_SKILL: No such skill (dodge)) inside their owncreate()-- correctly swallowed by the onboarding session'smake_inventory()catch()fix, so the room still loads fine, just without the NPC present. Both gate rooms areno_fightzones (confirmed viakillreturning "这里不准战斗。").
Net: skill/sect acquisition, real combat, shop/economy, and death/
respawn are all unreachable via any live path in this archive --
not a testing gap, a genuine, already-well-documented content gap (no
kungfu/skill/ tree, no sect NPCs, no shops, no city domains at all
survive in this "engine only" release). This was verified directly
this session rather than assumed from the onboarding notes.
wasm_status 审计(2026-09-01):VERSION_D 登入闸门缺 find_object() 守卫,验证为 playable
meta.json 的 wasm_status 此前一直留空(此前只做过原生驱动验证,
NOTES.md 已注明 WASM/§10.7 深测"out of scope for this onboarding
session")。本次批量审计(见 [[project_wasm_status_audit]])补上这
一步。
scripts/wasm_client.js 起跑本身没有触发编译期致命错误(这份
"底层代码"精简发行版没有自己的 simul_efun 级 socket 依赖),但每一
个新玩家连线都会被 adm/daemons/logind.lpc 直接踢掉,提示"现在本站
正在同步版本中,暂时不能登录,请稍候再尝试。"——这正是 AGENTS.md
§1.3(c) 目录里"中华英雄/终极地狱血统家族的 VERSION_D->is_version_ok()
式闸门"那一类(此前在 zhonghua2/zhongjidiyu/zjdyzj/
yanhuangwuhun/yhyxs 都出现过):adm/daemons/versiond.lpc 自己
的 socket_create()/socket_bind()/socket_listen()/socket_close()
调用在 WASM 下编译失败(没有 sockets 包),使得 VERSION_D 这个
对象始终"不存在",VERSION_D->is_version_ok() 每次都以运行期错误
收场、等效于返回 0,而 logind.lpc 把"未确认在线"当成"正在同步
版本"直接拒绝所有非巫师连线。
修复(标准 find_object() 守卫,缺席即放行,与 AGENTS.md 现有目录
写法一致):adm/daemons/logind.lpc 两处新玩家/老玩家登入检查、
adm/daemons/questd.lpc 的任务系统启动检查、adm/daemons/closed.lpc
的 heart_beat()(从开机起每 3 秒执行一次,此前每次都要重新尝试
编译一遍上千行的 versiond.lpc,是明显的额外开销)都加上
find_object(VERSION_D) && 前缀。cmds/arch/version.lpc(巫师手动
version 指令)未改,不在登入路径上,属正常的"外围指令编译失败"。
修复后跑通一次完整会话:英文 ID(3-10 个英文字母)→ 中文姓氏(留空)
→ 中文名字 → 管理密码×2 → 普通密码×2 → 角色资质(3)→ 性别(m) →
落地"世外桃源",水笙/狄云 NPC 均在场,水笙主动提示注册 email →
look 正常显示房间描述 → quit 干净退出("欢迎下次再来!"),全程
无未捕获错误。踩到一个纯测试脚本层面的坑,记录以免下次误判为
bug:这个 lineage 的游戏内提示符自带每秒刷新的实时时钟
(19:43:47> 这种),wasm_client.js 的空闲检测是"距离上一次驱动
输出满 N 秒才发送下一条指令"——只要 --idle 设得比 1 秒长,输出
永远赶在空闲阈值之前刷新一次,后续指令就会一直排队发不出去,直到
外层 --timeout 超时为止(观察到的现象是:注册正常走完、进入
游戏、然后卡住不再有任何指令生效)。换成 --idle 0.3(小于时钟
刷新间隔)后 look/quit 才能正常送达并看到回显。wasm_status
设为 playable。
深度功能测试(§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-06)— 编译警告(机械部分)
scripts/lpc_warnings.py revive --fix:删 62 处 #pragma save_binary,15 个 nosave 函数改 protected,删 435 个未用局部变量,补 9 处 varargs。lpc_audit_removed_locals.py 复核 558 个,提示 1 个(adm/daemons/newsd.lpc 的 int time,后面读的是外部函数 time(),不是这个变量)。改动的 216 个 .lpc 在只含被跟踪文件的树里 HEAD 与新版各编一次:都是 201 个通过,无差异。启动警告 105 → 2,错误 0(剩下 natured.lpc 的 lt[LT_MON]; 空语句,原档案如此)。实测 fluffos 登录、look(世外桃源,水笙、狄云在场)、i、quit 正常。本库未经 §9 格式化。
深度功能测试(§10.7,2026-10-06)— 与 zhonghua2 同源的三处编译警告
include/npc/indian.lpc与masia.lpc共用全局名ma_sname/ma_pname;两个头文件被同一个守护进程(npcd等)一起包含时,后包含的印度名表覆盖马来名表,generate_ma_name()生成的是印度名字。印度表改名in_sname/in_pname(KB 04 §6.10)。cmds/std/team/swear.lpc:结义失败提示被多余的;截成两句,fail只剩"……可惜现在有人不在,$N",后半句"的提议只好作罢。"丢了;接回去。
验证:改动的文件和包含 npc/indian 的守护进程在 HEAD 与工作树分别加载(scripts/lpc_listcheck.sh,新进程),无回退。
深度功能测试(§10.7,2026-10-08)— 编译警告整理
scripts/lpc_warnings.py revive --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 2 个 .lpc(多为删去未用的局部变量)。HEAD 与工作树分别加载(新进程):1 -> 1 通过,无回退。启动时警告 2 -> 0;新进程登录(输入名字)正常。
深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查(批 3)
adm/daemons/combatd.lpc反击:your_temp["guarding"];丢了= 0(ES2 原版是set_temp("guarding", 0)),一次防守姿态可无限次触发反击;补上= 0。- 改动的对象 HEAD 与工作区都能载入,无回退。
- 2026-10-12:
clone/misc/swmuding.lpc(神木王鼎)自己声明了一张mapping insects虫表,与继承的clone/misc/muding.lpc的insects同名;读表的是木鼎的代码,读到的是木鼎自己的表,神木王鼎的虫表从来没用上(Redeclaration of global variable 'insects')。改名sw_insects,create()里insects = sw_insects;交给木鼎(nitan170911、wxddym、zjdy 系同修)。HEAD 与工作树各载入一次,regress 0。
编译警告收尾(2026-10-13)
scripts/toolkit_fix.sh 一轮后只剩一处,手工清零;改动的对象在 HEAD 与工作树各载入一次,regress 0。
clone/megazine/obj/duka_3b.lpc(赌球卡 B)同一个赔率变量声明两次(peis=3与peis=4),代码用的是后一份;删去前一行,赔率不变(4.5)。
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 的身体,这一改动不改变行为。
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 载入不了,刷出它的房间也少一个人。本库目前没有对象设了缺失的武功;三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。(scripts/lpc_guard_set_skill.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 文件。
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 编码器等少数几处手改)。改动:/adm/daemons/vrm_server.lpc(1 处)、/cmds/arch/sameip.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)。改动:/adm/daemons/cruised.lpc(1 处)、/adm/daemons/v2.lpc(1 处)、/adm/daemons/versiond.lpc(1 处)、/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() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 1 个存档共 1 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/data/dbased.o(1 处)(共 1 个存档)。
注册到不存在函数的 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 4 条注册(3 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/adm/npc/referee.lpc(do_halt)、/clone/megazine/obj/maikaren.lpc(do_drop)、/inherit/misc/fboard.lpc(do_list)。最多的函数名:do_halt 2 处、do_drop 1 处、do_list 1 处。
log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)
这个库的代码往 log/ 的 1 个子目录写日志:log/user(adm/daemons/examined.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/user 记进 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] == "澄" 等 4 处(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 的称呼和提示里取到两个字而不是一个字。
本库 4 处(2 个文件):与汉字常量比较 3 处、to_chinese()[..] 1 处。例:chk[0..1] == "你" → chk[0..0] == "你"(/adm/npc/ganjiang.lpc:592);to_chinese(martial)[4..7] → to_chinese(martial)[2..3](/cmds/skill/perform.lpc:131)。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:1927)。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)。本库 channeld 同盟 / 门派标题 1 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。
血条 / 表情标题按字节算显示宽度(2026-10-23,KB 06 §7.365)
老档把汉字当两字节:bar_string[0..(val * N / max) * 2 - 1] 把「格数」换成字节下标,半格血条在本驱动(按字符算区间)上会整条画满;巫师表情 edemote 的 repeat_string("─", 13 - (strlen(emotion) / 2)) 也会少减汉字宽度。scripts/lpc_fix_display_pad.py:去掉 CJK 血条 / 空白条上的 * 2(jings/qis 赋值和 blank_string3[0..(23-a)] 的 23→11 一并改),表情标题去掉 / 2;ASCII 空格的 none_string / empty_string 仍保留 * 2(一格两个空格)。本库 2 处(1 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。
中文名 / 验证码仍按 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。
深度功能测试(2026-10-27,§6.10 未知 pragma save_binary/optimize)
删除本库中 FluffOS 不认的 #pragma save_binary / #pragma optimize… 行。