Xiaoyu's Journey to the West

✅ 可玩

小雨西游

xiaoyuxiyou

🔑 fluffos / Mud@2026 更新 66a3bd8 2026-10-28 源码 下载 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.zip → xyxy2) — 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.fluffos:maximum evaluation cost 从 400000(已知 风险区间)提升到 5000000。 2. cmds/arch/update.lpc(AGENTS.md §7.106):缺少 environment(me) && 前置防护,补上(cmds/wiz/update.lpc 已是正 确写法)。 3. adm/simul_efun/file.lpc:log_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.

深度功能测试(§10.7,2026-10-05)— tail() simul_efun

scripts/lpc_add_tail.py --apply: cmds/wiz/tail.lpc calls tail(file), an efun of the MudOS this archive ran on that FluffOS does not have (Undefined function tail: the command did not compile). adm/simul_efun/file.lpc now defines void tail(string file): the last 10 lines of the file, written to the player. Load check (HEAD against the working tree): 1 of the 1 tail command file(s) failed to load at HEAD, all load now, and the simul_efun file loads. KB 04 §6.2.

深度功能测试(§10.7,2026-10-05)— 房间列出的物件缺档时房间照常加载

房间基类 std/room.lpc:make_inventory() 用 catch() 包住 new(file),做不出来就返回 0;reset() 的 case 1: 拿到 0 就跳过这一项(if (!objectp(ob[list[i]])) break;)。档案里没有的 NPC 或物品(或 create() 出错的物件)原先会让列出它的房间整个加载失败(Bad argument 1 to environment(),或对 0 的 call_other()),连带这个房间后面的区域;现在房间照常加载,只是少了那个物件,下次 reset() 再试(KB 05 §7.25;scripts/lpc_resilient_room.py、scripts/lpc_room_reset_guard.py)。 验证:探针(scripts/lpc_room_reset_probe.py:房间基类列出一个不存在的物件,再调用 reset())HEAD 单件 崩溃、多件 崩溃,工作树 单件 OK、多件 OK。

深度功能测试(§10.7,2026-10-06)— 与 zhonghua2 同源的三处编译警告

验证:改动的文件和包含 npc/indian 的守护进程在 HEAD 与工作树分别加载(scripts/lpc_listcheck.sh,新进程),无回退。

深度功能测试(§10.7,2026-10-07)— 编译警告整理

scripts/lpc_warnings.py xiaoyuxiyou --fix 加 scripts/lpc_fix_no_effect.py --apply(d/dntg/doushuai/bgl1.lpc 的 for (n == 0; ...) 改成 =,daemon/skill/xuanyuan-archery.lpc 删去什么也不做的 (: throw_weapon :);),共改动 805 个 .lpc 和若干 .h。HEAD 与工作树分别加载(新进程):789 -> 789 通过,无回退;改过的头文件的 1504 个包含者 1498 -> 1498 通过。

启动时警告 68 -> 0;新进程登录(编码选择、进入游戏菜单、英文名提示)正常。

深度功能测试(§10.7,2026-10-08)— .lpc 后缀的定宽切片

.c 改名 .lpc 时只改了字符串没改切片宽度:f[<2..] == ".lpc" 拿两个字符比四个字符,永远不成立(!= 永远成立),按后缀筛文件、去后缀的代码因此从不运行或截成 x.l(KB 06 §7.238)。scripts/lpc_lpc_suffix_slice.py --apply 改了 1 处比较、1 处去后缀(如 cmds/adm/mem2.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。

编译警告收尾(2026-10-13)

scripts/toolkit_fix.sh 一轮去掉 3 处多余的 F_CLEAN_UP;它还把 d/obj/books-nonskill/ 四本书的 inherit ITEM; 当成多余删了,可这几本书包含的 book.h 把 BOOK 改指向本目录的 book(不带 ITEM),删后编译失败——载入检查当场发现,已恢复,工具也改了(KB 04 §6.10 (8))。搬来 xyj2000(969f36bdd8c)逐 token 相同的文件(feature/alias.lpc 的 last_time 改名 last_cmd_time,三花计数员 sanhua-counter.lpc 的同名警告随之消失);其余手工。能编译的文件警告清零;改动的 24 个对象在 HEAD 与工作树各载入一次:HEAD PASS 23,tree PASS 24,regress 0(多出的那个是 quest/tianguan/npc/other.lpc,它随机挑武功,挑到本库没有的就在 set_skill() 报错,两边成败看运气,与改动无关)。

治疗/疗伤的属性名(2026-10-15,KB 06 §7.241)

receive_heal()/receive_curing() 也只接受本库的种类。本库人物只有 kee/sen(连 gin 也没有):药物的 receive_heal("gin", 60)、NPC 的 receive_curing("gin", ...) 和来自侠客行体系的 qi/jing 调用都报 伤害种类错误。1 个文件按 gin→sen、qi→kee、jing→sen、neili→force 改名(scripts/lpc_port_es_props.py --any-file)。

转码吃掉的引号(2026-10-15,KB 03)

原档在右引号前有一个孤立的 GBK 前导字节,转 UTF-8 时连引号一起丢了(如 return name + HIM "发作身亡了;),字符串一直延续到后面,文件编译失败:/d/obj/books-nonskill/hmeng014。补回引号(scripts/lpc_fix_eaten_quote.py)。

空手使用兵器绝招(2026-10-15,KB 06 §7.246)

perform 指令允许直接点名招式(如 perform stick.ban),这些绝招读了 me->query_temp("weapon") 却从不检查,空手时 weapon->name() 报错(有的已先扣了内力)。在赋值后补 if (!objectp(weapon)) return notify_fail("你必须手持兵器才能使用这一招。\n");(scripts/lpc_fix_perform_weapon.py):yueya-chan/feicha。

技能名拼错(2026-10-15,KB 06 §7.249)

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):molianspells→molian-spells(molian-spells)。

用兵器的绝招空手施展时报错(内联写法)(2026-10-15,KB 06 §7.246)

绝招不存兵器变量,直接写 (me->query_temp("weapon"))->query("name")(或在拒绝判断里写 (weapon = me->query_temp("weapon"))->query("skill_type")),之前又没检查兵器:空手 perform 时报 Bad argument 1 to EFUN call_other(),内力等往往已经扣掉。在第一次用到它的那条语句前补上“你必须手持兵器才能使用这一招”的拒绝(scripts/lpc_fix_perform_weapon.py):liangyi-sword/yin。

蓬莱三星的 ref() 是保留字(2026-10-18,KB 04 §6.14)

福星、禄星、寿星(d/penglai/npc/)里有一个函数叫 int ref (),call_out("ref", 20) 去调它;ref 在 FluffOS 里是关键字(引用),unexpected L_REF,三个 NPC 都载入不了。函数和 call_out 都改叫 reff(本系里 dtxyzjb、zitengzhan 早就这么改,call_out 却没跟着改):d/penglai/npc/luxing.lpc、d/penglai/npc/shouxing.lpc。改后载入:2/2 通过。

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 载入不了,刷出它的房间也少一个人。本库有 29 个对象设了本库没有文件的武功(6 个名字,最多的是 yingzhaogong、sevensteps、taoism)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:29 个里 29 个通过。(scripts/lpc_guard_set_skill.py)

武功名拼错的 NPC(2026-10-19,KB 06 §7.258)

set_skill("kunlun-shnfa", …)、map_skill("dodge", "shaolin-shnfa") 这样的调用把武功名拼错了:本库没有这个武功文件,有的是拼对的那个。以前这种 NPC 在 create() 里报 No such skill 载入不了;武功缺失时跳过之后它们能载入,可是少了那门武功(set_skill 被跳过,map_skill 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 52 处、29 个文件:yingzhaogong→yingzhao-gong(32 处)、hellfire_whip→hellfire-whip(9 处)、lahua-shou→lanhua-shou(4 处)、dagonforce→dragonforce(4 处)、dragon-step→dragonstep(2 处)、yange_blade→yange-blade(1 处)。(scripts/lpc_fix_skill_names.py)

出口指向不存在的房间(2026-10-19,KB 06 §7.257)

下列房间的出口写的文件不存在,而对面恰好只有一个房间的出口指回本房间,所以按对面的房间改正(笔误、大小写、路径少了一级):

改后房间照常载入(与 HEAD 逐个对比,没有退步),出口现在指向存在的房间。

enable_player() 重入保护(2026-10-19,KB 05 §7.19)

feature/command.lpc 的 enable_player() 没有重入保护:门派掌门大弟子 zhangmen.lpc 的 init() 以 me->setup(); 收尾,setup() 调 enable_player(),enable_commands() 又重跑 init(),第一个进房间的活物就触发 Too deep recursion。按 §7.19 加了标志变量(scripts/lpc_fix_enable_player_guard.py)。改后同样的测试(载入掌门房间,再克隆一个活物走进去)不再出错,没有退步。

RANK_D 对 0 的称呼(2026-10-19,KB 06 §7.273)

adm/daemons/rankd.lpc 的 query_respect()、query_rude()、query_self()、query_self_rude() 里 6 个函数开头补上 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 文件。

被注释吞掉的 inherit / include(2026-10-19,KB 06 §7.274)

/cmds/wiz/gift.lpc:#include <ansi.h>。这些指令接在上一行 // 注释的末尾(档案里丢了换行),成了注释的一部分,驱动看不到:缺 inherit 的技能报 Undefined function offensive_target,缺 #include <weapon.h> 的兵器报语法错误。scripts/lpc_fix_swallowed_directive.py 把换行放回去。载入检查 1 个文件,0 个原来编译不过、现在载入,没有退步。

x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)

if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。改动:/d/liandan/baihuagu.h(1 处)。

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 处)。

call_out_info() 的每一项只有 3 个元素(2026-10-20,KB 06 §7.287)

驱动返回的是 ({ 物件, 函数, 延迟 }),不带 call_out 的参数,旧代码读第 4 项 info[i][3](callouts 指令、calloutd、cod 工具)一读就报 Array index out of bounds,指令从第一行起什么也打印不出来。改为 (sizeof(info[i]) > 3 ? info[i][3] : 0)(scripts/lpc_fix_callout_info.py)。改动:/cmds/wiz/callouts.lpc(1 处)。

if (x = 常量):条件里写成赋值的比较(2026-10-20,KB 06 §7.290)

if (arg = "button" || arg = "按钮")、if (!arg || arg = "") return 0;、else if (check = 0 && tempB != ""):把 == 写成了 =,条件的值就是那个常量,分支对所有非零数和任何字符串(连 "" 都算)永远成立、对 0 永远不成立,变量还被覆盖;驱动对此没有任何警告。每一处 = 改成 ==(scripts/lpc_fix_cond_assign.py,共 2 处)。改动:/daemon/condition/xiangliu_hurt.lpc(2 处)。

callouts:sizeof(info[i]) == 4 把每一行都挡掉了(2026-10-20,KB 06 §7.287)

驱动的 call_out_info() 每项只有 3 个元素,输出行前的守卫 if (sizeof(info[i]) == 4) 永远不成立,指令只打印表头。改为 >= 3。改动:/cmds/wiz/callouts.lpc(1 处)。

socket / dumpsocket 指令:dump_socket_status() 驱动里没有(2026-10-20,KB 06 §7.291)

这些巫师指令 write(dump_socket_status());,MudOS 的 efun 在 FluffOS 里不存在,指令编译不过。在模拟 efun 文件里补了 dump_socket_status():用 socket_status() 排出同样的表(没有 sockets 包的构建,比如 WASM,只有表头;scripts/lpc_add_sefun.py dump_socket_status)。

登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)

驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/adm/obj/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。

登录计数文件 UserNumber 缺失(2026-10-20,KB 06 §7.298)

logind.lpc 的 do_counter() 读 /adm/daemons/UserNumber(访客计数,运行中的 mud 才会写);.gitignore 把它排除在仓库之外,所以新检出的树里没有,read_file() 回 0,sscanf(0, ...) 报错。这份副本登录时不调用它,但同样会在调用时出错。加了 if (stringp(file)) 的守卫(与 dtxywzxzb、rzrmud、xiyouji2006、zitengzhan 早先的做法相同)。

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)。改动:/cmds/adm/cfg.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) ||)。改动:/std/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 个存档共 8 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/c/data/npc/╤▌┴╖╒╜.o(8 处)(共 1 个存档)。

巫师命令不带参数时对 0 调用 efun(2026-10-21,KB 06 §7.336)

巫师(管理员)把命令敲成不带参数时,驱动把 0 传给字符串参数;下面的命令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...,这条指令从来没有用过。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py 在第一处使用参数的语句前加守卫:文件自带 int help(object me) 的,守卫是 if (!arg) return help(me);(help() 定义在 main() 之后的 1 个文件补了原型 int help(object me);,否则编译报 Undefined function help);没有 help() 的是 return notify_fail("…\n");。改动(3 个文件,括号里是接住 0 的 efun):/cmds/adm/baiban.lpc(sscanf)、/cmds/adm/dumpprog.lpc(new)、/cmds/imm/setskill2.lpc(sscanf)。加载检查 HEAD 与工作树对照,没有退步。

update 不带参数:valid_read(0) 先于 cwf 缺省(2026-10-21,KB 06 §7.342)

管理员扫描里 update 不带参数报 Bad argument 1 to sscanf() Expected: string Got: 0.(/adm/daemons/securityd.lpc 的 valid_read(),第一句 sscanf(file, "/c/%*s")):main(me, file) 先 SECURITY_D->valid_read(file, me, "read"),之后才 if (!file) file = me->query("cwf")。把“缺省取 cwf、仍为空就拒绝”这两句移到权限检查前面(是移动,不是复制)。改动:/cmds/arch/update.lpc、/cmds/wiz/update.lpc。

拼错的 call_out() / input_to() 目标(KB 06 §7.349,2026-10-21)

计时器或输入提示给驱动的函数名和文件里定义的差一个字母;驱动对不存在的函数静默不理,所以对应的计时从来没有触发过。调用和配套的 remove_call_out() 改成已定义的拼法:find_seal:disapprear → disappear(法术产生的封印十分钟后从不消失)。改动:/d/jjf/obj/find_seal.lpc。

注册到不存在函数的 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 15 条注册(9 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/baihuagu/huahai1.lpc(do_niang)、/d/changan/pinqiting.lpc(do_pass、do_resign)、/d/city/npc/duguoyin.lpc(do_vendor_list)、/d/city/npc/vikee.lpc(do_list)、/d/eastway/npc/petboss.lpc(do_cost、do_list、do_upgrade)、/d/qujing/village/npc/oldwoman.lpc(do_yes)、/d/qujing/wudidong/cave2.lpc(do_push)、/pet/pet.lpc(do_gongji)、/pet/petboss.lpc(do_cost、do_list、do_upgrade)。最多的函数名:do_list 3 处、do_cost 2 处、do_upgrade 2 处、do_push 2 处、do_niang 1 处。

调用了、但这个库自己的守护进程里没有的函数(2026-10-22,KB 06 §7.354)

MACRO->函数() 调用的函数在这个库的守护进程里不存在:call_other() 对不存在的函数不报错、只返回 0,所以这些功能(时间显示、昼夜事件、名字检查、价格文字……)一直没起作用。同一血统另一个库的同名守护进程里有这些函数,原文照抄到本库守护进程末尾(scripts/lpc_port_function.py;调用处传的参数个数、函数体用到的函数 / 全局变量 / 宏 / 字符串键都在本库里有,已核对;守护进程在 HEAD 和工作区都能加载,每个函数用固定参数各调用一次:HEAD 返回 0,现在返回正常的值):/adm/daemons/natured.lpc ← aoxiangtianji:outdoor_room_event()(1 处调用);/adm/daemons/chinesed.lpc ← aoxiangtianji:chinese_period()(1 处调用);/adm/daemons/moneyd.lpc ← aoxiangtianji:price_str()(1 处调用)。

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 9 处(2026-10-22,KB 06 §7.360)

原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:NPC 的称呼和提示里取到两个字而不是一个字。

本库 9 处(9 个文件):query("name")[..] 9 处。例:->name(1)[0..1] → ->name(1)[0..0](/quest/tianguan/npc/baihua2.lpc:245)。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) < 2) || (strlen(name) > 12)) {(/d/city/npc/zhangsunwuji.lpc:167)。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)。本库 汉字循环 / is_chinese 区间 17 个文件;茶馆猜诗 mixup 1 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

中文名 / 验证码仍按 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。

星君/罗汉模板缺 ID 宏(2026-10-27,KB 06 §7.348 Also)

d/dntg/sky/stars/star.lpc 与 d/nanhai/luohan/npc/luohan.lpc 用 title[ID] / first_name[ID],真正带号的副本是 #define ID N 再 #include 本文件;单独编译模板时 Undefined variable 'ID'。加 #ifndef ID / #define ID 0 / #endif,带号 include 仍用各自的 N。证明:HEAD 模板加载失败;改后模板与 stars/5、luohan5 均加载成功。

悬空 ::init / unique::init(2026-10-27,KB 06 §7.259 Also)

kugu-1/2/3、kunxian-suo 写 ::init();,tianyi 写 unique::init();,而 F_UNIQUE / ITEM / feature/unique 没有 init;本驱动编译报 Unable to find the inherited function 'init'(tianyi 带 in file 'unique'),物件无法加载。老驱动把空父调用当返回 0。lpc_fix_dangling_super.py 删掉这些语句;cloth::init() 保留。证明:HEAD 加载失败;改后加载成功。

dami 误调 init_unarmed(2026-10-27,KB 04 §6.25 Also)

d/obj/food/dami.lpc 继承 ITEM+F_FOOD 却调用 init_unarmed(5)(并 #include <weapon.h>),未继承空手兵器基类,驱动报 Undefined function init_unarmed。手足 hymud 已注释该行。删掉该调用与无用的 weapon 头。证明:HEAD 加载失败;改后加载成功。

深度功能测试(2026-10-27,§6.25 tell_object 参数顺序)

tell_object("…", me/who) 参数颠倒(消息当对象);改为 tell_object(me/who, "…")(guest.lpc)。证明:残渣 0;抽样 guest/rukou/forest_c 加载。

深度功能测试(2026-10-27,§6.25 tell_object 缺 me(自动放弃/愿否))

一参 tell_object("…") 在已有 me 时改为 tell_object(me, "…")(city.lpc)。证明:抽样 city / xiaolongnv 加载。

深度功能测试(2026-10-27,§6.25 tell_object 路径当房间)

tell_object("/…", …) → tell_room(load_object("/…"), …)(去掉 .lpc)。

深度功能测试(2026-10-27,§6.25 one-arg message_vision)

message_vision("…") 补第二参(me / TP / this_player())。

深度功能测试(2026-10-27,§6.10 未知 pragma save_binary/optimize)

删除本库中 FluffOS 不认的 #pragma save_binary / #pragma optimize… 行。