Xiaoyu's Journey to the West

✅ 可玩

小雨西游

xiaoyuxiyou

🔑 fluffos / Mud@2026 更新 7d234c5 2026-09-01 源码 下载 ZIP

▶ 开始游玩 · Play Now

游戏自身配置里写的名字是《小雨西游》,但实际登录横幅打出来的是《小雨西游Ⅱ》(v3.0,2013 年建站),两者是同一套引擎在不同年份、不同站点的快照,内容基本一致;它与本项目另一个库 xyxy2 同属一个引擎家族,两者的 chinese.c 文件完全一致;游戏以《西游记》为背景,故事发生在傲来国、翠香楼一带,人物设定、任务、地名都紧扣西游神话世界观,整体偏向传统取经/降妖除魔的养成流玩法,系统里还带有帮派(clan)、天界消息、留言板等比较完整的社区功能。

English

A Journey to the West-themed wuxia/xianxia MUD set around the Aolai Kingdom and Green Fragrance Tower, with characters, quests, and place names closely tied to the classic novel's mythology, emphasizing traditional scripture-seeking and demon-slaying character growth. It shares an identical chinese.c file with this project's xyxy2 lib, pointing to a common engine lineage, and also features a fairly complete clan system, celestial-realm announcements, and message boards.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/xiaoyuxiyou/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40046。驱动刚启动的 30 秒内会拒绝所有连接,请稍候再连。

NOTES · 移植与修复记录

小雨西游.zip → xiaoyuxiyou

Archive: archives/小雨西游.zip (34.7MB). Port: 40046. Status: done (boots clean, full registration flow verified end-to-end including a real Chinese name reaching the actual game world).

What this is

Self-identified in config.xyj as 小雨西游 ("Little Rain Journey to the West", name : 小雨西游), but the live banner text hardcoded in adm/daemons/logind.lpc (and the connection greeting "Welcome to XYCQ!") says 小雨西游Ⅱ ("Little Rain Journey to the West II"), v3.0, "站点创建时间:2013年12月30日" (site created 2013-12-30). This is the same "小雨西游Ⅱ" engine/lineage as archive #3 (20150716未知lib.zipxyxy2) — confirmed via md5sum: adm/simul_efun/chinese.lpc is byte-identical between the two archives. master.lpc/logind.lpc/securityd.lpc differ (different site snapshot/ build), but the shared chinese.c and matching 30-second startup grace period (adm/daemons/logind.lpc's logon(): if(uptime()<30) {...}) confirm common ancestry. Treating this archive's own config.xyj name (小雨西游, no Ⅱ) as authoritative for the slug/port table per the task's "pick a real lib name" instruction, and noting the internal Ⅱ-branding discrepancy here rather than renaming.

Mudlib root in archive: xymud/ (single top-level folder, config at xymud/config.xyj). adm/obj/{master,simul_efun} layout, adm/daemons/* for system daemons, feature/dbase.lpc provides real per-object set/query/delete/add (architecturally correct pattern — not the nitan-family bare-simul_efun-dbase bug, confirmed by reading adm/simul_efun/wizard.lpc, which has no generic set/query/delete at all).

Size: 31735 raw files, 11150 .lpc files after conversion (a mid-sized lib, well below AGENTS.md §6b's mega-lib OOM-risk threshold — the full lpcc_check.sh sweep was run without incident).

IMPORTANT environment note for future agents

/tmp is shared across concurrent agent sessions on this host. Early in this pass, a scratch file this agent wrote to /tmp/logind_utf8.c (a generic, guessable filename) got silently overwritten mid-investigation by what must have been a different concurrent agent's own scratch write to the same literal path — several sed -n reads against that file later in the same investigation showed content (a "夕阳再现"/"江湖风云" banner, BAN_D->is_welcome/vaild_allow_address calls, a different get_name/ get_resp flow) that turned out to belong to a different lib entirely, not xiaoyuxiyou. This was caught by re-deriving the same file two more times (a fresh iconv re-run against the untouched raw/ source, and a direct Read of the post-convert_lib.sh work/ copy) and finding both agreed with each other but disagreed with the contaminated /tmp file on everything except the tail (check_legal_id/check_legal_name, which happened to be identical enough between the two libs' shared-lineage code that the contamination wasn't obvious from that section alone). Lesson: use unique scratch filenames (mktemp, or embed $$/a random suffix) for any multi-step shell-based file investigation in this environment, or prefer direct Read-tool access over intermediate /tmp files entirely. The analysis and fixes below are all re-verified against the genuine, uncontaminated work/ files.

Fixes applied

1. AGENTS.md §15h (the near-universal GBK-byte-range bug), standard shape, two sites: - adm/simul_efun/chinese.lpc's is_chinese(): GBK lead-byte range check (str[0] > 160 && str[0] < 255, strlen(str)>=2) → CJK Unicode codepoint range check (str[0] >= 0x4e00 && str[0] <= 0x9fff), length bound >=2 (bytes) → >=1 (character). - adm/daemons/logind.lpc's check_legal_name(): byte-count bound strlen(name) < 4 || > 8 (message already said "2-4个中文字符") → character-count bound < 2 || > 4; dropped the i%2==0 && gate on the per-character is_chinese(name[i..<0]) sliding-window check (was landing on alternating GBK lead-byte offsets, now checks every character position since UTF-8 indices are already 1 char each). - Verified end-to-end: a real Chinese name (2-char 秦风, then 3-char 秦风二/秦风三 in follow-up tests) was accepted and the flow proceeded through password/email/gender all the way into the game world (see Registration flow test below) — not just "reached a prompt". 2. Proactively added get_include_path() to master.lpc (AGENTS.md §8d/§15o shape) as insurance. Confirmed the underlying need is real: the raw tree has many per-file "flavor" headers included via #include <foo.h> from a file in the SAME directory (e.g. d/dntg/laojun/maze.h, d/liandan/baihuagu.h) — convert_lib.sh's automatic local-angle-bracket-to-quote rewrite (1303 conversions this run) already handles the bulk of these unconditionally (including the preload-time-only timing gotcha get_include_path() alone wouldn't cover), so this addition is defense-in-depth rather than the primary fix; booted clean without hitting a Cannot #include error either way. 3. §15p (DNS/intermud preload exclusion): checked, nothing to exclude. adm/etc/preload has no dns_master/intermud/network daemon entry at all in this archive — confirmed by reading the file directly (26 active entries: securityd, band, backupd, virtuald, logind, cmd_d, chinesed, convertd, emoted, aliasd, fingerd, channeld, monitord, natured, weapond, rankd, combatd, miscd, spelld, obstacled, choosed, memoryd, titled, questd, removed — plus 2 already commented out: feizeid, msgd). Booted in well under 30s wall-clock, consistent with there being no blocking network daemon.

Confirmed NOT needed (verified by reading source, not by hitting a crash)

Known, pre-existing, non-blocking issue (documented, not fixed)

Registration flow (verified against the actual logind.lpc, not

inferred from prompt text)

Flow, in order (logon()encoding()if_young()get_id() → [get_new_id() if id=="new"] → confirm_id() (auto, no separate y/n) → get_name()get_super_password()confirm_super_password()new_password()confirm_password()get_email()get_gender()confirm_gift() (auto) → enter_world()):

1. logon(): 30-second startup grace period (if(uptime()<30) reject with "驱动程序正在启动过程中,请稍候再来。") — same as sibling lib xyxy2. Then a GB/BIG5 encoding-selection prompt. 2. encoding(): needs an answer starting with g/G or b/B — sent gb. 3. if_young(): "请您做出选择:① 进入游戏(Enter) ② 立即退出(Exit)" — any answer not starting with 2 proceeds (sent 1). 4. get_id(): English id prompt, "新玩家请键入 new 注册". Sent literal new (not any unused id — matches the established pattern for this whole family of libs). 5. get_new_id(): asks for a new English id (3-8 lowercase letters, not already taken, checked against NAME_D->valid_id()'s small banned- substring list fuck/shit/mabi/cao/snowtu). Sent a fresh unused id each test run. Auto-calls confirm_id("Yes", ob) on success — no separate y/n confirmation prompt for the id, unlike some sibling libs. 6. confirm_id(): prints instructions (取一个符合〖西游记〗中国神话世界 的中文名字), asks "您的中文名字:". 7. get_name(): the §15h-fixed step. Runs input through CONVERT_D->input() first (confirmed a pure passthrough, return str;, no mangling), then check_legal_name(). Sent 秦风 (2 chars) in the primary test, 秦风二/秦风三 (3 chars) in follow-ups — all accepted on the first try with the fix applied (before the fix, strlen()>=4 would reject even a maximal 4-character UTF-8 name, since character-count 4 used to require byte-count 8). 8. get_super_password()/confirm_super_password(): an admin/recovery password, separate from the regular login password — must be >6 chars, contain both upper- and lower-case letters, and not be all-letters (needs at least one digit/punctuation too). Sent Passw0rd twice. 9. new_password()/confirm_password(): regular login password, >=5 chars, must differ from the super password. Sent abcde123 twice. 10. get_email(): loose format check (needs . and @, sscanf("%s@%s") splits into two non-empty parts). Sent [email protected]. 11. get_gender(): m/f. Sent m. 12. confirm_gift() (auto-called, sets fixed starting stats, no_gift=1) → enter_world(): moves the new character to /d/wiz/init, a talent/gift-reroll room (0-3 to reroll one stat, 9 to accept). Sent 9 then y to accept and enter the actual world.

Transcript outcome (three separate full runs against one continuously-running driver, ids qinfeng/qftest/etc. to avoid the "id already taken" retry noise from re-using the same id across runs)

`` 「翠香楼」 这里就是傲来国最有名的饭馆,是早年一大唐来的富商所开,出售的都是长安府 口味的佳肴。传说当时宰相尝后赞不绝口,乃赠翠香二字。在这可看到各处来的游人 ,也可打听到天下发生的大事。二楼雅座里正大摆宴席,不知是那家有了喜事。 这里唯一的出口是 west。 翠香客栈留言板(Board) [ 4 张留言,4 张未读 本板板主:空缺] 店小二(Xiao er) ㊣ > ` Then look re-displayed the room correctly, and quit` produced a clean farewell ("一阵时空穿梭,神秘的西游世界在你视线里渐渐模糊....你 依依不舍的离开了!") and disconnected gracefully — no crash, no stray error.

Conclusion: registration is fully functional, verified past the point of just reaching a prompt — a real Chinese name is accepted, and the character is played all the way into a real, populated starting room.

lpcc sweep

scripts/lpcc_check.sh libs/xiaoyuxiyou/config.fluffos libs/xiaoyuxiyou/work run against all 11150 .lpc files. Host memory stayed healthy throughout (started ~19.6GB available, stayed above 14GB available at every check, well clear of the AGENTS.md §6b danger zone; this lib's file count is far below the "mega-lib" tens-of-thousands threshold).

New finding: /d/obj/quest/shuijingqiu.lpc hangs the lpcc --batch compiler indefinitely (confirmed: killed after 3.5 CPU-minutes pinned at 100%, zero output growth, RSS flat — not a memory leak, a genuine compile-time infinite loop, most likely in the lexer's error-recovery path given the file's content, see below). This is a pre-existing content corruption in the original archive, not introduced by this pipeline — confirmed by running iconv -f GB18030 -t UTF-8 directly against the untouched raw/xymud/d/obj/quest/shuijingqiu.c: it decodes to the exact same truncated/garbled tail (arg厀)鲦?o~m#烎 followed by raw non-UTF8 bytes where the rest of the function body should be) — the file is simply truncated/corrupted in the archive itself, not a conversion artifact. Nothing else in the tree references shuijingqiu at all (grep -rl shuijingqiu came up empty besides the file itself), so per AGENTS.md §12's precedent (orphaned non-compilable content nothing ever loads), renamed it to shuijingqiu.lpc.corrupted-orig so it can never be mistaken for live code or hang a future sweep again. This is why the final sweep total is 11149, not 11150. First sweep attempt (before this was found) was silently truncated by the hang — killing it early cut the run off after only 3919 of 11149 files with a misleadingly-good 33-failure count; always sanity-check that a sweep's total= matches the expected file count before trusting a pass-rate number.

Result: total=11149, pass=11039, fail=110 → 99.0% pass rate.

Triaged the 110 failures by category rather than fixing each individually (per AGENTS.md §6b):

How to run

cd libs/xiaoyuxiyou
~/src/fluffos/build-debug/src/driver config.fluffos
# wait >30s after boot before connecting (startup grace period, see above)
python3 ../../scripts/mudclient.py 127.0.0.1 40046 --timeout 30 --idle 1.5 \
  --send "gb" --send "1" --send "new" --send "<unused-english-id>" \
  --send "<chinese-name>" --send "<super-password>" --send "<super-password>" \
  --send "<password>" --send "<password>" --send "<id>@test.com" --send "m" \
  --send "9" --send "y" --send "look" --send "quit"

Minor cosmetic boot warnings (harmless, not fixed)

Boot log shows maximum local variables: invalid new value, resetting to default. and living hash table size: invalid new value, resetting to default. — the original config.xyj's values (60 and 200, carried into config.fluffos) are below this driver's hardcoded minimums for those two flags (INT_FLAGS table in src/base/internal/rc.cc: both flags have minValue == defaultValue, i.e. 64 and 256 respectively — any lower value is rejected outright). Purely cosmetic: the driver silently substitutes its own default and boots/runs identically either way.

Environment gotcha: background driver processes started with plain

&/disown/setsid died unexpectedly after ~1-2 minutes

Twice during this pass, a driver process started via nohup ... & disown (once) and setsid nohup ... & disown (once) from a normal (non-run_in_background) Bash tool call died silently sometime after the call returned (no crash trace in debug.log/stdout, no OOM signature in dmesg/free -h) — once mid-idle, once mid-interactive-test. Switching to the Bash tool's own run_in_background: true parameter (the tool-native way to keep a long-running process alive across calls) fixed this — that driver instance stayed up through the entire remaining test session with zero issues. Recommendation for future agents: always launch the boot-test driver via run_in_background: true, not manual shell backgrounding tricks, in this harness.

Re-verification pass: driver rebuild + formatter + WASM (2026-07-23)

WASM-enablement pass (2026-07, loopback/uptime/throttle + admin seed)

Standard WASM-first pass per AGENTS.md §1.3(b)/(e) and §1.5. Gates patched (loopback = 127.0.0.1, a 127. prefix, or an empty/non-string/malformed IP — the last covering older WASM query_ip_number() garbage):

Admin seed: registered fluffos / display 浮浮 through the real flow (super/recovery pw Recover@9, login pw Mud@2026), then added fluffos (admin) to /adm/etc/notices (this is the WIZLIST file, #define WIZLIST "/adm/etc/notices" in include/login.h, read by securityd.lpc::create()). Verified after reboot: login as fluffos then update /adm/daemons/logind → "重新编译 ...成功"; room display shows the wizard-only object path. No new errors in log/debug.log.

Save files for the orchestrator to add (neither is gitignored — both data/user/ and data/login/ are already tracked, so a normal add picks them up):

深度功能测试 / Deep functional test (2026-07-24, round two)

First real hands-on *playthrough* pass on this lib (all prior passes verified registration + look/score/quit + admin login, or watched boot output — see the WASM-enablement pass above). Read doc/help/newbie/newbie in full first — it named the starting inn (翠香楼), the hp/score/i commands, learn <skill> from <teacher>, help menpai/help apprentice for sect info, and help hints/wimpy for combat safety. doc/help/statue/combat separately documents this lib's own safe-sparring convention: fight (non-lethal, ends at unconsciousness/surrender/fleeing the room, no lasting grudge) versus kill (fights to the death). Native driver (~/src/fluffos/build-debug/src/driver config.fluffos), one continuous session per test character via scripts/mudclient.py, per AGENTS.md §10.7. Found and fixed two bugs: one an already-cataloged class (AGENTS.md §7.21) with a notable additional twist not previously recorded for that entry, the other a genuinely new class surfaced only by the extended net-dead soak wait §10.7/§10.8 explicitly encourage attempting when time allows (see below for both).

Test characters (state left behind as playthrough evidence, saves under work/data/user/ and work/data/login/, all with login password abcde123 and admin/recovery password Passw0rd unless noted):

Confirmed working end-to-end

Bug found and fixed: reconnecting mid the gift-allocation wizard strands the player — AGENTS.md §7.21's class, with an additional independent twist

Files:line: obj/user.lpc's reconnect() (~line 219, primary fix); d/wiz/init.lpc's do_block() (~line 365) and a new do_start() wrapper (~line 391, secondary/defense-in-depth fix).

This lib has the exact same structural shape AGENTS.md §7.21 already catalogs (found on rzrmud): every brand-new character is moved into a mandatory /d/wiz/init "limbo" room for gift-point allocation, driven entirely by input_to() prompts, with the room's init() (fired once, on the character's original move() in) registering a catch-all add_action("do_block", "", 1) that blocks every verb except look/help/story/say/restart/quit. input_to() registrations do not survive a net-dead/reconnect cycle on this driver, and reconnect() is a much simpler path than a fresh login — it never calls enter_world()'s no_gift routing again, so nothing re-triggers the lost prompt. Reproduced live: registered wangshu, deliberately let the connection drop mid gift-selection (before answering 9/y), then reconnected — every command produced zero output except look (which just re-showed the bare room short-description) and quit.

> «(pre-fix) reconnect mid /d/wiz/init»
您使用了登陆密码成功登陆!
重新连线完毕。
> 「」
    这里就是傲来国最有名的饭馆,...      ← "look" allowed through by do_block
> «score»                                ← ZERO output
> «start»                                ← ZERO output — see the twist below

The twist, not previously recorded on §7.21's entry: on rzrmud, the room's own intended manual escape hatch (typing start to resume the wizard) already worked correctly once discovered, and became part of how that bug was diagnosed. On this lib, that same manual escape hatch is independently broken by two separate bugs, so even a player who somehow already knew to type start would get nothing: 1. do_block()'s own verb allow-list did not include "start" — the very command init() wires up via add_action("do_start", "start") (previously add_action("get_start1", "start"), see below) was itself caught and blocked by the catch-all sentence registered a line earlier in the same function. 2. Even if (1) were fixed, the original binding add_action("get_start1", "start") was wired directly to int get_start1(object me) — a function whose real, intentional calling convention is "pass the player object" (used correctly from init()'s own direct call get_start1(me)). add_action-bound handlers are always invoked as fn(string arg) by the driver (confirmed against ~/src/fluffos/src/packages/core/core.spec), so typing bare start called get_start1("") — an empty string landed in the object me parameter, and if (!me) return 1; is true for an empty string in LPC, so the handler silently no-opped every time, with no compile-time warning (LPC's calling convention for add_action handlers isn't type-checked against the bound function's real signature).

Net effect pre-fix: a player who net-deads mid this wizard and reconnects is not *permanently* stranded (an admin could always force a fresh login, and quit still works so a full logout+relogin — which correctly re-triggers enter_world()'s no_gift → fresh move() → a real init() firing — recovers cleanly) but is left in a session that looks completely dead for every ordinary command, with zero in-session recovery available even to a player who guesses the documented-sounding start command.

Fix (primary, matches §7.21's established pattern — detect the stuck state in reconnect() and resume directly, no player action required):

// obj/user.lpc, BEFORE:
void reconnect() {
  set_heart_beat(1);
  set_temp("netdead", 0);
  remove_netdead_enemy();
  remove_call_out("user_dump");
  remove_call_out("do_net_dead");
  tell_object(this_object(), "重新连线完毕。\n");
}

// AFTER:
void reconnect() {
  object env;
  set_heart_beat(1);
  set_temp("netdead", 0);
  remove_netdead_enemy();
  remove_call_out("user_dump");
  remove_call_out("do_net_dead");
  tell_object(this_object(), "重新连线完毕。\n");

  env = environment(this_object());
  if (env && query("no_gift") && base_name(env) == "/d/wiz/init")
    env->get_start1(this_object());
}

Plus the independent secondary fix (worth keeping even with the reconnect() fix above, since it also fixes the manual command for any future code path that reaches this room without going through reconnect(), and is a genuine bug in its own right regardless of the wizard-reconnect scenario):

// d/wiz/init.lpc, do_block(): added "start" to the allow-list
  if (verb == "look" || verb == "help" || verb == "story"
    || verb == "say" || verb == "restart" || verb == "start")
    return 0;

// d/wiz/init.lpc, init(): rebound "start" to a new wrapper instead of
// get_start1 directly
  add_action("do_start", "start");   // was: add_action("get_start1", "start")

// d/wiz/init.lpc, new function:
int do_start(string arg) {
  object me = this_player();
  if (!userp(me) || wizardp(me)) return 0;
  if (!me->query("no_gift")) return 0;
  get_start1(me);
  return 1;
}

Verified live, end-to-end, twice: 1. Reproduced the exact broken-reconnect symptom pre-fix with wangshu (registered through the gift-point display, disconnected without answering, reconnected — score/start both produced zero output, only look/quit worked). 2. Restarted the driver with the fix applied, registered a fresh character (also wangshu — same id, since the first attempt never completed registration and had no saved body), disconnected again at the identical point, reconnected — this time the full gift-point table was automatically redisplayed immediately after "重新连线 完毕。", exactly as if init() had just fired; completed the wizard (9, y) and reached 翠香楼 normally, with look/score both correct. No debug.log errors from either run.

AGENTS.md is not edited by this pass (draft-only per the task's own instructions), but the finding is exactly an instance of the existing §7.21 class — worth folding the "the room's own manual resume command can be independently broken by an add_action signature mismatch, not just absent" observation into that entry's text for future passes on other libs in this family (xyxy2 shares this same 小雨西游Ⅱ engine lineage per this file's own lineage note above and is worth a targeted grep for the identical add_action("get_start1", "start")-shaped pattern, though not checked live in this pass — out of scope).

Confirmed NOT needed (checked by reading source, not just by not hitting a crash)

Observations (not bugs — documented honestly, not fixed, per §10.7's scope note)

- A completed purchase requires gold, and a brand-new character starts at literally 0 (feature/dbase.lpc's init_money()). The lib's own documented newbie-funding NPC (小雨/vikee.lpc, "yao gold") lives in /d/city/zhunbei.lpc ("新手准备室"), reachable only from the 长安 (Chang'an) map, which is a different zone from the starting 傲来国 (Aolai) zone — and d/aolai/northgate.lpc's own valid_leave() explicitly gates the only exit toward the wider world/mainland behind query_level() >= 5 ("武士将手中长剑一横, 喝道:看你瘦骨伶仃的样子,出城也是送死!"). A brand-new level-1 character genuinely cannot reach the shop-purchase content within the Aolai starting zone — confirmed this structurally by reading d/aolai/aolai.lpc's exits (west/d/changan/aolaiws) sitting behind that same gate, not merely assumed. list/buy's own code path was still exercised and behaves correctly (see above). - The formal 门派 (sect) system (help menpai's thirteen listed sects: 百花谷/蜀山派/大雪山/东海龙宫/南海普陀山/无底洞/月宫/火云洞/ 方寸山三星洞/阎罗地府/将军府/盘丝洞/五庄观, plus 将军府) all live in zones outside Aolai per the same map/level-gate reasoning above. feature/apprentice.lpc's recruit_apprentice() is the real underlying primitive any such sect-entrance NPC would call to formally set a family mapping (family name/master/generation) — read and confirmed sound by inspection, but no live sect-entrance NPC was reached to exercise it end-to-end.

Second bug found and fixed (NEW class): a room's idle clean_up() only checks interactive(), so it can destruct a room out from under a net-dead player, corrupting their environment() and silently skipping the auto-force-quit's own save

Found via exactly the §10.8-style extended net-dead soak wait the task asks to attempt "if time budget allows" — this is the payoff for doing it. Not the driver-fatal crash class §10.8 catalogs (the process stayed alive throughout, RSS flat ~45→71MB across the whole ~35-minute session from ordinary lazy compilation, nothing runaway) — a distinct, mudlib-level, fully reproducible bug.

Files:line: feature/clean_up.lpc's clean_up() (~line 17-19, primary fix); obj/user.lpc's user_dump() (~line 154, defense-in-depth fix).

Reproduced live: registered a throwaway character (zhaoyun/赵芸), completed the gift wizard, reached 翠香楼 (alone — no other player was in that room at the time), then disconnected without quit. Waited ~616 real seconds (NET_DEAD_TIMEOUT=600s from include/user.h, plus the driver's own 15s net_dead()do_net_dead() stagger — the exact deadline the automatic force-quit is supposed to fire at), monitoring the driver's own stdout throughout (PID confirmed via readlink -f /proc/1857465/cwd, never killed/restarted mid-wait). debug.log then showed a caught runtime error that occurred right at that boundary:

[执行时段错误]: *Bad argument 4 to EFUN message()
Expected: object, array,  Got: int(0).
[程式] /adm/simul_efun/message.lpc(/adm/obj/simul_efun.lpc):178
[物件]: /adm/obj/simul_efun
[回溯]:
user_dump()               /obj/user.lpc  160 行,物件: /obj/user#14 ("赵芸")
tell_room()               /adm/simul_efun/message.lpc(/adm/obj/simul_efun.lpc)  178 行

Reconnecting as zhaoyun immediately afterward confirmed the full extent of the damage: still a reconnect ("重新连线完毕", not a fresh login — meaning the force-quit's QUIT_CMD->main() call never actually ran), and look showed 「十八层地狱」 ("The Eighteen Levels of Hell" — this is obj/void.lpc's short, i.e. VOID_OB) instead of 翠香楼, with only an up exit leading to /d/city/center — a *different, higher-level-gated zone* than her real starting zone (Aolai), reachable with zero level check via this path. Score/skills were otherwise intact — only the location was corrupted, silently, with nothing ever shown to the player explaining what happened.

Root cause, two-part: 1. feature/clean_up.lpc's clean_up() — inherited by every room in the lib, driven by the driver's own idle-object sweep (time to clean up : 300 in config.fluffos) — decides whether a room is safe to unload by checking whether any of its all_inventory() contents are interactive(): ``lpc inv = all_inventory(); for (i = sizeof(inv) - 1; i >= 0; i--) if (interactive(inv[i])) return 1; destruct(this_object()); ` interactive() is specifically false for a net-dead player — the defining characteristic of net-dead is that the connection (and thus interactivity) is gone, while the player's body object is still alive and logically "in" that room, reconnectable. A room containing *only* a net-dead player (no other currently-connected player, no living NPC) is therefore misjudged as empty and genuinely destruct()ed by the driver's own idle sweep after time to clean up (300s) of no other player touching it — a thoroughly ordinary, realistic timing window for 翠香楼 specifically during low-traffic hours, and for almost any less-trafficked room at any time. (Exactly how the driver's C++-level object destruction then relocated the still-referencing net-dead player into /obj/void rather than leaving a dangling reference wasn't traced to a specific line — plausibly a driver-level container- destruct safety net — but the mudlib-level defect, and the fix, don't depend on pinning that exact mechanism down.) 2. obj/user.lpc's user_dump() (the function the net-dead NET_DEAD_TIMEOUT call_out actually invokes) does an unguarded tell_room(environment(), ...) as the FIRST statement of the DUMP_NET_DEAD case, with no check that environment() is a real object. Once (1) has corrupted the player's environment, this throws — and since nothing in user_dump() wraps it in a catch(), the error aborts the rest of the SAME function, meaning the line right after it — QUIT_CMD->main(this_object(), "", 1), the actual force-quit/save that is the entire point of the net-dead timeout safety net — never runs. DUMP_IDLE (the analogous idle-timeout case a few lines below) has the identical unguarded-environment shape, one statement earlier and even more fragile (environment(this_object()) ->query("short") — a method call directly on a value that could be 0`, which throws immediately rather than merely passing a bad argument).

Net effect: a net-dead player whose room happens to get idle-cleaned-up before they reconnect is silently teleported into the void with zero explanation, AND the very safety net that's supposed to force-quit (and, critically, save) truly-abandoned net-dead sessions silently fails to run for them — they remain unsaved and permanently reconnectable in debug.log-invisible limbo (found alongside, not caused by, §7.20's different mechanism — this lib's net_dead() never deliberately void- parks anyone, unlike §7.20's affected libs; here the void-parking is an unintended side effect of an unrelated idle-cleanup timing bug).

Fix:

// feature/clean_up.lpc, BEFORE:
  inv = all_inventory();
  for (i = sizeof(inv) - 1; i >= 0; i--)
    if (interactive(inv[i])) return 1;

// AFTER: userp() reflects the driver's O_ONCE_INTERACTIVE flag, which
// (confirmed against ~/src/fluffos/src/packages/core/efuns_main.cc's
// f_userp()) stays true for a player body across a net-dead disconnect,
// unlike interactive() -- correctly keeps the room loaded either way.
  inv = all_inventory();
  for (i = sizeof(inv) - 1; i >= 0; i--)
    if (interactive(inv[i]) || userp(inv[i])) return 1;
// obj/user.lpc, user_dump(): guard both cases so a null/dangling
// environment can never skip the actual QUIT_CMD->main() force-quit
// (full diff in obj/user.lpc; DUMP_NET_DEAD shape shown):
  env = environment(this_object());
  if (objectp(env))
    tell_room(env, query("name") + "断线超过..." + "分钟,自动退出这个世界。\n");
  QUIT_CMD->main(this_object(), "", 1);

Verified live, end-to-end: driver restarted with both fixes, booted clean, no new compile warnings/errors on either file (grep-confirmed against debug.log); a normal login/look/quit cycle (linqian) still works identically post-fix. Re-ran the reproduction with a fresh throwaway character (yelan/叶岚) — registered with extra client-side timeout headroom to guarantee the gift wizard fully completed (confirmed via a full score reaching 翠香楼 before disconnecting), then net-dead alone in 翠香楼 for ~330 real seconds, past the 300s time to clean up threshold that triggered the original corruption (short of the full 615s NET_DEAD_TIMEOUT, since the point of this specific re-run was isolating the clean_up() fix). Reconnecting afterward showed look correctly still displaying 翠香楼 and score fully correct — the room was no longer destructed out from under a lone net-dead player. (An earlier attempt at this same re-verification, character hema/何嫚, accidentally tested the §7.21-class wizard fix a third time instead, due to her original registration's connection timing out mid-wizard rather than reaching 翠香楼 as intended — corrected by registering yelan with more headroom and confirming her location with score before disconnecting.) Not independently re-verified for the full 615s user_dump()/QUIT_CMD timing a second time (would need another ~10-minute wait beyond what this pass's time budget could repeat a third time) — the user_dump() guard fix is a direct, narrow objectp() check whose correctness is clear by inspection, so this is a reasonable place to stop, but flagged honestly rather than claimed as re-observed live.

zhaoyun (id zhaoyun, Chinese name 赵芸) is deliberately left in /obj/void as direct evidence of the pre-fix bug — her save file (work/data/user/z/zhaoyun.o) still reflects the corrupted location. Not moved/repaired, per this project's "leave representative state as evidence" convention.

Lineages likely affected: feature/clean_up.lpc is credited // by Annihilator@ES2 in its own header comment — worth checking any other ES2-lineage-descended lib (AGENTS.md §11's ES II / 东方故事 family) for the identical interactive()-only presence check in its own clean_up()/equivalent, though not checked live on any sibling in this pass (out of scope). More generally: any lib whose room/container clean_up() uses interactive() as its sole "is anyone here" test rather than userp() (or equivalent) is a candidate for this exact shape, independent of lineage.

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

同一套小雨西游代码库,2013 年站点快照,和 xyxy2/xyxyutf8 同一引擎家族(共享 chinese.c)。状态已从过时的 limited 修正——这份档案自己的 NOTES.md 记录了一次更早一轮会话里已经完成的彻底深度功能测试(§10.7):对本地回环放行了 uptime()<30 开机闸门,另外发现并修复了两个真实 bug(feature/clean_up.lpc 的房间闲置清理检查只用了 interactive() 而不是 interactive()||userp(),会把网络已断但还没重连的玩家静默清空出一个已经被清理掉的房间;obj/user.lpc 的 user_dump() 强制退出/存档安全网对一个未加保护的 ->query('short') 呼叫在环境为空时崩溃,导致被遗弃的断网会话从来没能被强制存档)。这两处修复都已按 NOTES.md 的记录端到端实测过,包括一次约 330 秒的真实断网复现,确认房间不再会在唯一一个断线玩家还没走的时候就被销毁。管理员账号播种:fluffos (admin) 通过 /adm/etc/notices(WIZLIST)播种,密码 Mud@2026。这份档案的 meta.json/README 只是一直没有跟着更新反映这项已经完成的工作——本轮不需要新的测试,只是重新验证了一遍开机+注册能干净到达 id 提示,确认没有出现回归。

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

深度功能测试第三轮 / Deep functional test round three (2026-08-15, post driver-upgrade re-test)

驱动于 2026-08-12 升级后的重测。标准检查清单发现并修复四处问题:

1. config.fluffosmaximum evaluation cost400000(已知 风险区间)提升到 5000000。 2. cmds/arch/update.lpc(AGENTS.md §7.106):缺少 environment(me) && 前置防护,补上(cmds/wiz/update.lpc 已是正 确写法)。 3. adm/simul_efun/file.lpclog_file() 没有 assure_file() 目录预建保护,补上调用及前向声明;cat() 补上 read_file() || "" 空值防护。 4. obj/user.lpc::reconnect()(AGENTS.md §7.108,第十一条独立确 认的血统):本档案的 reconnect() 已经带有 §7.21 类礼物精灵恢 复的既有修复(详见文件内注释),但仍然完全没有 enable_commands()——两个类别互不重叠,礼物精灵修复解决的是 input_to() 状态丢失,不解决指令派发本身。adm/daemons/ logind.lpc 有同款 exec(old_link, user); 踢掉重复登录写法。按 §7.108 记录的写法预防性修复,现场用两个真实连线复现"保持第一个 连线不断开→第二个连线登录→答 y 踢掉旧连线"验证:score 修复后 立即正常显示完整角色档案。

master.lpc::log_error() 已经是正确的 "arning:" 大小写无关写法, 均无需改动;本档案无 adm/daemons/closed.lpc,不受 §7.107 影响。

现场验证摘要

驱动干净启动,管理员 fluffos/Mud@2026 登录(GB/BIG5 选择→ "① 进入游戏(Enter)"菜单需送出字元 1,纯空白 Enter 不推进→id+密 码)确认 目前权限:(admin)update /adm/daemons/logind 成功验证 真实写入权限。踢掉重复登录重连路径现场验证通过(见上)。 debug.log 全程干净(669 行,无真实错误)。

本轮修改的文件

§7.100 sweep (2026-08-19)

Fixed the corpus-wide inherit ROOM; ... replace_program(ROOM); redundant-replace bug (AGENTS.md §7.100). 205 live occurrences deleted: 204 via scripted sweep (fix_710_room.py), plus 1 hand-fixed roommaker-tool template (obj/roommaker.lpc, simple string-builder variant, same lineage as sibling xyxyutf8). 4 already-commented-out instances left untouched. No real .lpc source found under work/data/. Verified via build-debug driver boot: clean compile, zero new "cannot replace"/"cannot bind" debug.log lines; confirmed serving via raw-socket connect on port 40046.

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

深度功能测试第四轮 / Deep functional test round four (2026-08-23) — death/respawn and §7.112 reconnect-race live verification, clean pass

Prior rounds (see round-two/round-three above) already fully verified registration, exploration, non-lethal fight combat, organic NPC-teacher skill learning, shop list/buy plumbing, and multiple netdead/reconnect cycles including a full ~616s soak. The two gaps explicitly left untested by those passes were real death/respawn and AGENTS.md §7.112's death-room reconnect-reentrancy scenario (this lib's d/death/npc/pang.lpc is one of the two named triggers for §7.112's "wave 2" corpus sweep, so it's worth confirming its fix holds up live on THIS lib specifically, not just by static grep). Native driver (~/src/fluffos/build-debug/src/driver config.fluffos), two raw-Python-socket test scripts (not tmux_mud.sh, to sidestep the known Chinese-name tmux-transmission false-alarm risk) driving two concurrent connections (a fresh player + the admin seed account fluffos/Mud@2026) each. Confirmed the driver/mudlib send and expect UTF-8, not GBK, over the wire regardless of the gb/big5 encoding-selection prompt (scripts/mudclient.py's own default encoding) — an early test attempt using gb18030 produced readable- looking-but-actually-mojibake output and a spurious "请您用『中文』 取名字" rejection; re-run with UTF-8 send/recv fixed it immediately, matching this file's own established CONVERT_D->input()-is-a-pure- passthrough finding above.

Death/respawn, forced via the established call <id>->die() admin pattern (character sidiez/死测一, no gold/no way to reach a lethal NPC fight from the Aolai starting zone at level 1 per this file's own documented shop/sect zone-gate finding, so an admin-forced die() is the standard substitute this whole session's methodology already uses): feature/damage.lpc's die() ran cleanly — inventory-drop skipped (no inventory), combat_exp/daoxing penalty math skipped (no killer), life/life_time decremented (寿元 80→79), moved to DEATH_ROOM (/d/death/gate, "阴阳界"), DEATH_ROOM->start_death() no-op'd silently as already documented elsewhere in this project (undefined function on this lib, benign shared-lineage artifact — confirmed harmless again here), score correctly showed 【鬼魂】(ghost) rank and reduced stat bars. No debug.log errors.

§7.112 live reconnect-race repro: registered a second character (sidiey/死测二), force-killed her the same way, then closed the raw socket immediately (simulating a netdead drop) ~1s after death, before pang.lpc's init()-scheduled call_out("death_stage", 5, ...) had fired even once, and reconnected ~2s later — the exact enable_commands()-re-broadcasts-init() scenario §7.112 depends on. Outcome: the guard held. Exactly ONE death_stage sequence played out (all 5 stages' dialogue lines from 崔判官, no duplicates, no garbled interleaving), ending in a single correct move to REVIVE_ROOM ("荒郊小店") with sane post-reincarnation stats (kee/sen at 1/4 max as coded, 寿元 79/80 correctly preserved from the death above — not double-decremented). pang.lpc's existing query_temp("death_stage_active") guard (present before this pass — confirmed via a source read that it already matches the corpus-sweep fix shape, not something this pass had to add) is doing its job correctly under a real reconnect, not just by inspection. No debug.log errors from this run either.

Mail system: obj/mailbox.lpc is a real, fully-implemented mail/forward/read/readmail/dismail object — but per grep -rl mailbox d/, the only placement in the whole tree is as a purchasable item (大眼/bigeye NPC's shop mapping in d/ourhome/npc/bigeye.lpc, "mailbox": (: receive_mail :)), gated behind the same d/ourhome/-zone-plus-gold combination this file's round-two section already documented as unreachable for a fresh, broke, level-1 Aolai character (no exit from Aolai below level 5, no starting gold, the "新手准备室" funding NPC is itself past that same gate). Consistent with the earlier documented shop/sect findings — not re-litigated as a new bug, just confirmed the same structural gate also covers mail.

Result: clean pass, no new bugs found. pang.lpc's §7.112 guard (inherited from the corpus-wide sweep, not touched this pass) held up under a genuine live reconnect race. No fixes applied this round; no commit needed. Two throwaway test-character saves (sidiez, sidiey) were deleted after the test (per this pass's own cleanup instruction, diverging from round-two's practice of leaving test characters as evidence — round-two's finds are already fully documented in prose above, so the saves themselves aren't load-bearing).

AGENTS.md §7.156 regression fix (2026-08-27, sibling-lineage sweep)

Found live on sibling lib xyxyutf8's round-two deep test, then confirmed by a corpus-wide grep sweep for the pattern across every lib that received the original §7.30 accessor fix: cmds/std/learn.lpc's if (!skills || !mapp(skills)) me->set_skill(skill, 1); else skills[skill] = my_skill; re-derives "never initialized" from query_skills()'s return shape, but feature/skill.lpc's own §7.30 fix (return mapp(skills) ? skills : ([]);) means that check is now always false for a brand-new character -- the else branch runs and mutates a fresh, disconnected empty mapping instead of the character's real internal state. The success message and potential-point deduction still fire, but the skill itself is silently discarded: every player's first-ever learned skill was affected. set_skill() in this lib already handles both the never-initialized and already-populated cases correctly (checks !mapp(skills) against the real instance variable), so the fix is to always call it with the final value instead of re-deriving the branch caller-side. Verified via lpcc --batch (single file compile, PASS) -- not re-tested with a full live playthrough this pass, since the fix is a mechanical port of the exact fix already verified live on xyxyutf8. See AGENTS.md §7.156.