Ultimate Hell: Legend of Xuanyuan

✅ 可玩

终极地狱-轩辕传说

revive

🔑 fluffos / Mud@2026 更新 fdf581a 2026-09-12 源码 下载 ZIP 上游 MudRen/revive

▶ 开始游玩 · Play Now

游戏内标题为 "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

内容亮点

本次修复的关键 bug

详见 NOTES.md

管理员账号 / Admin account

管理员名单存储在纯文本文件 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)

``lpc if (strsrch(message, "Warning") == -1 && strsrch(message, "warning") == -1) ``

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

```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.

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

Confirmed content gaps (NOT fixed, per project policy)

Boot / registration verification

Not run this session

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):

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:

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)

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:

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.jsonwasm_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.lpcheart_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