info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
游戏自身配置里写的名字是《小雨西游》,但实际登录横幅打出来的是《小雨西游Ⅱ》(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
内容亮点
- 注册时设有两套独立密码:一组用于找回账号的"管理密码",一组日常 登录密码。
- 驱动刚启动的 30 秒内会拒绝所有连接("驱动程序正在启动过程中,请 稍候再来"),是游戏自带的启动保护设计。
在线试玩
https://mudlibs.fluffos.info/xiaoyuxiyou/
管理员账号 / Admin account
- id:
fluffos - 密码 / password:
Mud@2026(本账号另设有一组独立的"管理/找回密码"Recover@9,登录时用上面的日常密码即可) - 显示名 / display name: 浮浮(Fluffos)
- 权限 / level:
(admin)—— 本 libsecurityd.lpc权限表里的最高一级 (通过/adm/etc/notices巫师名单授予,fluffos (admin))。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此默认密码。
本地运行
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)
- §4 (
load_objectinsidevalid_read/valid_writerecursing to a stack overflow):master.lpc'svalid_writegates viafind_objectonly (if( ob = find_object(SECURITY_D) ) return ...; return 0;) — noload_objectcall in either apply, so the recursion trap this fix guards against cannot occur here. - §7 (missing
get_root_uid/get_bb_uid): both already present and correct inmaster.lpc(return ROOT_UID;/return BACKBONE_UID;). - §14 (2-arg
valid_overridenot handling#included simul_efun fragments correctly):master.lpc'svalid_overridealready usessscanf(file, "/adm/simul_efun/%s", file)to detect the fragment case (the same alternate-but-valid solution AGENTS.md documents forxyxy2, this lib's sibling) — confirmed the one real caller (adm/simul_efun/object.lpc'sefun::destruct()) matches this pattern and is allowed. - §15l (
master.lpc create()destructingSIMUL_EFUN_OB— segfaults this driver):create()here is a singlewrite("master: loaded successfully.\n");, no destruct call at all. - §15n (custom
securityd.lpcblocking the driver's own compile-timeload_object/include— "Read access denied" mid-connection):securityd.lpc'svalid_readhas an allow-by-default-unless-known- data-op shape —if( func != "read_file" && func != "file_size" && func != "stat" && func != "read_bytes" && func != "tail" && func != "ed_start" ) return 1;— which already implicitly allowsload_object/recompile_object/include(none of those func values are in the restricted list), so the ACL never blocks a lazy compile. - §8c (
this_player()silently overriding a privileged caller's identity invalid_read/valid_write):securityd.lpcuses the passed-inuserparameter directly throughout (geteuid(user),get_status(user)), never falls back tothis_player()at all — the footgun this fix addresses doesn't exist in this file's shape. - §8e (
tail()used as if it were a real efun): grepped the whole tree (grep -rn '[^a-zA-Z_]tail(' --include="*.c" .onraw/) — zero hits anywhere, not even in dead/unused admin tooling. - §8f (
TYPE * a, b;comma-declarator array-modifier bug): no barearray name;/array name = expr;declarations found (that specific §15f pattern is from a different, "nitan"-family lineage) and no Dead-Souls-style multi-declarator*scoping issue surfaced in the lpcc sweep (see below). - §15c (bare
.c-suffixed paths inadm/etc/preloadbreaking after the rename):adm/etc/preload's entries are already extension-less (/adm/daemons/securityd, not.../securityd.c) — nothing to fix. - §15r (a
check_config.lpc-shaped driver-version self-checkinherited intomaster/simul_efunerroring on stale#ifdefassumptions): grepped forcheck_config/checkconfiganywhere in the tree — no such file exists in this archive. - §8h (
convertd.lpc's Greek-table stray-backslash typo): this lib'sadm/daemons/convertd.lpchas no Greek-alphabet lookup table at all (a different, simpler charset-conversion daemon shape, allGB2BIG/BIG2GB/input/outputare plain passthroughs) — nothing to fix.
Known, pre-existing, non-blocking issue (documented, not fixed)
data/emoted.o(system-wide emote flavor-text save file) has a genuinely corrupted byte in the ORIGINAL archive — confirmed by runningiconv -f GB18030 -t UTF-8directly against the untouchedraw/xymud/data/emoted.o:iconv: illegal input sequence at position 45464, i.e. this predates any step of this pipeline. This is the exact same file/failure class AGENTS.md §1 already documents for this lineage ("A handful of save-data.ofiles may have genuinely undecodable bytes (seen:emoted.o...)").convert_lib.sh's-cfallback dropped the bad byte(s) and produced a still-malformed mapping literal, so/adm/daemons/emoted'screate()→restore()throws*restore_object(): Illegal mapping format while restoring emote.at every boot. This is caught bymaster.lpc's ownpreload()catch wrapper (err = catch(call_other(file, "??"));), so it is non-fatal to the boot and does not affect any other daemon —emoteditself just ends up with an empty/partial emote-text table (a cosmetic content gap in theemote-command feature, not a login/registration blocker;emotedis not on the registration critical path at all). Left as-is per the same precedent as the pilot lib.- The many
编译时段错误:... warning: Unused local variable 'x'lines visible mid-session in the mudclient.py transcript are harmless compiler warnings, not errors —master.lpc'slog_error()forwards every compile diagnostic (including plain warnings) to the connected player with a "编译时段错误" ("compile-time error") prefix wheneverthis_player(1)is set, which is how this codebase was originally authored (every object's first lazy compile during a live session prints its warnings to whoever's connected). Cosmetically noisy but does not indicate any functional problem; confirmed none of them correspond to an actual compile failure (all say "warning:", none say "error:").
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)
- Run 1 (
qinfeng/秦风, without the final gift-accept step): reached/d/wiz/init's gift-selection screen and a live>game prompt with "你有新的消息没有查看,请用news来查看最新的天界消息。" — confirms the Chinese name, both passwords, email, and gender were all accepted with zero rejection messages. - Run 2 (
qftest/秦风, full flow through9theny): completed registration all the way into the actual starting room:
``
「翠香楼」
这里就是傲来国最有名的饭馆,是早年一大唐来的富商所开,出售的都是长安府
口味的佳肴。传说当时宰相尝后赞不绝口,乃赠翠香二字。在这可看到各处来的游人
,也可打听到天下发生的大事。二楼雅座里正大摆宴席,不知是那家有了喜事。
这里唯一的出口是 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.
- A 3rd run reusing the already-taken id
qinfengcorrectly hit "这个 名字已经被别的玩家使用了..." (expected behavior — save data persists across driver restarts on disk — not a bug), confirming the duplicate-id check also works correctly.
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):
- 25 files (
/quest/tianguan/*— 19 files,/quest/rzr/shushan*— 5 files,/daemon/skill/tianlei-sword— 1 file): all fail with runtime errors (Bad argument 4 to EFUN message()... Got: int(0),F_SKILL: No such skill (hunyuan-zhang)) — the classic §6b false-positive shape: NPC/quest files callingmessage()/skill-lookup against a live player/skill-registry context that doesn't exist whenlpcccompiles them as an isolated top-level object with no real room graph or preload ordering. Confirmed as sweep artifacts, not real bugs: the actual interactive boot+registration test (which exercises real NPCs, rooms, and skills along the way — e.g.翠香客栈's NPC店小二loaded and displayed fine) produced zero errors of this shape indebug.log. - 5 files (
d/obj/amulet/amuletf/amuletfo/amuletm/amuletsheng/amuletstar.lpc): all fail identically —inherit AMULET;whereAMULETis a macro that is never#defined anywhere in the archive (confirmed: noinclude/*.hdefines it, and nostd/obj/amulet.lpc-shaped base class exists anywhere either). This is the AGENTS.md §8g "missing macro, not a broken file" variant (same shape asxyzx3'sWQA_ROOMcase) — 5 nominal failures collapse to ONE real root cause. Per the §13 precedent (don't fabricate missing content), left as a documented content gap rather than inventing a plausibleAMULETbase class from scratch. - **~39 files under
/u/*** (wizard personal sandbox directories,u/snowtu/,u/amy/): duplicate/dev-in-progress copies of real system daemons (u/snowtu/channeld.lpc/channeld2.lpcvs the real, and already-passing,adm/daemons/channeld.lpc), missing intermud/DNS headers (Cannot #include net/dns.h/net/macros.h/net/daemons.h— the same intermud/DNS theme AGENTS.md §15p already flags as out-of-scope network functionality, here as abandoned personal-directory clutter rather than a preload entry), and one file with a literal space in its name (u/amy/xiao amy.lpc) containing what looks like a missing semicolon (pre-existing typo in a personal test file). None of these are reachable from the boot/registration path. - 2 dead admin commands, matching already-cataloged patterns:
cmds/wiz/tail.lpc(§8e,tail()was never a real efun on this driver — exact same file/finding as documented for lib #1) andcmds/adm/dumpsocket.lpc(dump_socket_status— same "never a real efun on this driver" shape astail, just a different missing builtin; confirmed viafind ~/src/fluffos/src -iname '*.spec' | xargs grep— no such efun exists). Both non-fatal, unused wizard-only tooling. - Remaining ~39 files: scattered, isolated pre-existing content bugs/ gaps in individual NPC/book/room files — e.g.
d/npc/xianshi.lpc:78(a dropped+before a trailingtargetvariable in amessage_vision()string-concatenation call, same shape as AGENTS.md §10),d/obj/club/dragon_mark.lpc(another missing macro,CLUB_D, same §8g shape asAMULETabove but only 1 file this time),d/obj/books-nonskill/book-qujing.lpc(missing#include /d/qujing/obstacle.h— a book referencing quest content not present in this archive, §13-shaped gap),d/obj/mieyao.lpc(local undefined- variable typos in one NPC file). None are on the boot/registration critical path; not individually fixed, consistent with this batch job's "breadth over depth" policy.
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)
- LPC formatter applied to all
work/*.lpc(11149 files): 9931 reformatted, 1188 already-clean/unchanged, 30 self-checked errors (skipped, expected on legacy code). - Native re-test against the rebuilt driver (
~/src/fluffos/build-debug/src/driver): booted clean (only cosmetic config-min-value warnings already documented above), full registration flow re-verified end-to-end with a fresh real Chinese name ("秦风十七") — encoding select → if_young gate →new→ English id → Chinese name → admin/super password → login password → email → gender → default-gift entry, reaching the actual game world (翠香楼),look/score/quitall producing correct, correctly-encoded Chinese output;log/debug.logclean of real errors.setsid nohup ... & disown(this pass's launch method, per top-level AGENTS.md) stayed up fine for the whole session this time — did not reproduce the earlier random-death issue noted above. Reformat + new driver build introduced no regressions. - WASM test (
scripts/wasm_client.jsagainstbuild-wasm/src): boots cleanly (same preload sequence, no fatal errors). Could not be driven through registration, but for a lib-specific reason distinct from both the mudlib and the knownquery_ip_number()gap: this lib'slogind.lpc::logon()has its ownuptime() < 30startup-grace gate (destruct(ob)+ "驱动程序正在启动过程中,请稍候再来" if a connection arrives in the first 30 real seconds after boot — see "How to run" above, which already documents waiting 30s+ before connecting natively).scripts/wasm_client.jscallsfluffos_connect()synchronously right afterfluffos_boot()with no pre-connect delay knob, so every WASM attempt hits this gate immediately and the connection is destructed before a single--sendis processed — no amount of--idle/--timeouttuning helps since the object is already gone. This is a harness-timing limitation (the harness has no way to defer its onefluffos_connect()call), not a mudlib bug and not the query_ip_number issue — noted here rather than worked around, since patching either the mudlib's startup gate or the shared test harness is out of scope for this pass.
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):
adm/daemons/band.lpc: addedIS_LOOPBACK_IP()macro (after line 147) and a loopback short-circuitreturn 0;at the top ofis_banned()(~line 154),create_char_banned()(~line 167), andis_strict_banned()(~line 180). Loopback is never treated as banned.adm/daemons/logind.lpc::logon()(~line 107-125): computed anis_loopbackflag fromquery_ip_number(ob); theuptime() < 30startup-grace destruct now only fires for non-loopback, and the per-IP anti-floodlogon_cnt > 15throttle is loopback-exempt. (KEEP note: this removes the "wait 30s after boot" requirement for local/WASM play.)adm/daemons/logind.lpc::encoding()(~line 155-185): wrapped the inline ban + IP-format gate (is_strict_banned, the!ip_name"No IP" destruct, and the non-numeric-char destruct loop) so it is skipped entirely for loopback/malformed IPs.adm/daemons/logind.lpc::get_id()(~line 326): the multi-login-per-IP cap (NowLogin >= 50) is now loopback-exempt.
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):
libs/xiaoyuxiyou/work/data/user/f/fluffos.o(character save)libs/xiaoyuxiyou/work/data/login/f/fluffos.o(login/credential record)
深度功能测试 / 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):
- id
linqian, Chinese name 林前 (男/male) — the main playthrough character. State: reached/d/aolai/cuixiang(翠香楼) normally through the gift wizard, explored the 傲来国 (Aolai) starting zone on foot (北菀街 → 傲云广场 → 东苑街 → 东方武馆 → 武馆教练场 → 武馆大厅 → 储藏室), asked 武馆馆主 东方博玉 (ask dongfang about 学艺) which set theaolai_dongfangtemp flag, then learned扑击格斗之技 (unarmed)to level 1 via the organic teacher-NPC path from his son 武馆教头 东方聪 (learn unarmed from dongfang cong), then won a non-lethalfightagainst a武馆弟子(Wuguan dizi) for 5627 combat_exp/562 potential, quit cleanly, and relogged in after a real multi-minute wall-clock gap with the learned skill still intact (skillscorrectly showed扑击格斗之技 (unarmed) 初学乍练 1/0). Also exercised (but did not complete) the shop path:list/buy jiudai from xiao erat 翠香楼 both function correctly andbuycorrectly rejects with "你的 钱不够" (no crash) for the character's starting 0 gold. - id
wangshu, Chinese name 王舒 (女/female) — created specifically to reproduce and then re-verify the reconnect-mid-wizard bug below, both before and after the fix, across several real disconnect/reconnect cycles (including one prompt reconnect after roughly 9.5 real minutes net-dead — see the "Second bug found and fixed" section below for the full-timeout edge this didn't quite reach, and why). Finished registration, reached 翠香楼, and was quit cleanly at the end of this pass. - id
zhaoyun, Chinese name 赵芸 (女/female) — a throwaway character used for the extended net-dead soak-timing attempt described below (registered, reached 翠香楼, deliberately net-dead disconnected for the full ~615sNET_DEAD_TIMEOUTwindow). Left deliberately un-repaired in/obj/voidas direct evidence of the second bug found this pass (see below) — do not "fix" her location without also re-reading that section. - id
hema, Chinese name 何嫚 (女/female) — a throwaway character whose first registration attempt's connection closed (client-side timeout) before the gift wizard finished, so an unplanned net-dead-mid-wizard reconnect happened instead of reaching 翠香楼 as intended — ended up as an incidental *third* re-confirmation of the §7.21-class fix above (the gift table correctly auto-redisplayed on reconnect) rather than theclean_up()re-verification it was meant for. Finished registration and quit cleanly afterward; not otherwise used. - id
yelan, Chinese name 叶岚 (女/female) — the character that actually re-verified thefeature/clean_up.lpcfix (registered with a longer client timeout this time to guarantee the wizard fully completed before disconnecting — confirmed via a fullscorereaching 翠香楼 before disconnect). Net-dead disconnected alone in that room; see the "Second bug found and fixed" section for the wait duration and outcome. - ids
qinye,qftest,qinfengand a handful of otherq*ids are leftover from earlier registration-flow verification passes (already documented above in this file) and from this pass's own initial English-id-validation trial and error (linfeng/linfeng26were rejected — this driver's English id check is letters-only, no digits, which is stricter than what severalq*-family ids in this file's earlier section imply; worth remembering for future passes on this lib).
Confirmed working end-to-end
- Registration (GBK/BIG5 prompt → id → Chinese name → dual passwords → email → gender → gift-point wizard → world) with a real Chinese name, reaching the actual starting room
/d/aolai/cuixiang(翠香楼). look/score/i/skillsall correct, including after a cleanquit+ real wall-clock gap + relogin (skill data persisted).- Exploration and room navigation (
std/room.lpc-basedexits/objectsall resolve correctly across 8+ rooms visited; no first-visit "Too deep recursion" or eval-cost aborts hit in any of them — §7.17/§7.19/ §7.22/§7.25's shapes did not reproduce here). - The organic NPC-teacher skill-learning path (
东方博玉→ setsaolai_dongfangtemp flag →东方聪'srecognize_apprentice()accepts it →learn unarmed from dongfang congsucceeds, deducts potential, saves). - Non-lethal
fightcombat against a live NPC (武馆弟子), ending in a scripted surrender with a combat_exp/potential reward, no death risk — this lib's own documented safe-sparring convention (help statue/combat), used here in place of chasing down a dedicated training dummy (see "Observations" below). - Shop
list/buycommand plumbing (insufficient-funds path rejects gracefully, no crash). - Net-dead disconnect + prompt reconnect, repeated many times across both test characters in both a normal room (translation: never lost location — see "Confirmed NOT needed" below) and, once fixed, inside the mandatory gift wizard too.
- Clean
quit+ relogin (the full login/restore()path, notreconnect()) with all state intact. debug.logstayed completely free oferror:/Too deep recursion/eval cost/FATALlines through the entire session (many logins, quits, net-dead cycles, a combat, a skill-learn, two shop attempts) — confirmed by grep immediately after everyquit, per §10.7's ownbxsjlesson, not just at the 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 belowThe 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)
- §7.20 (net-dead void-parking without a location-restore path): this lib's
net_dead()/do_net_dead()(obj/user.lpc) never moves the disconnected player at all — noVOID_OB, no temp-room parking; the object just sits in its real room withset_heart_beat(0)until reconnect or theNET_DEAD_TIMEOUT(600s) force-quit. Confirmed live, repeatedly: every prompt reconnect during this pass (both characters, many cycles, one after ~9.5 real minutes net-dead) landed back in the exact same room withlookcorrect, no relocation.reconnect()really is called by the login flow too (grep -rn "->reconnect("findsadm/daemons/logind.lpc's real call site) — not the "nothing ever calls it" flavor of §7.20 either. - §7.19/§7.28 (reentrant or duplicate
enable_commands()/add_actionregistration):feature/command.lpc'senable_player()already guards its ownadd_action("command_hook", "", 1)call behind anosave int enabledflag (if (!enabled) { enabled = 1; enable_commands(); add_action(...); }), so this lib's own architecture is already immune to both the reentrancy and the duplicate-sentence shape — nothing to fix. - §8.3a (
private nomask command_hook):feature/command.lpc:35already readsnomask int command_hook(string arg)with theprivatekeyword commented out one line above it — already fixed by an earlier pass, confirmed by reading the live source. - §7.29 (a restored-by-name-match simul_efun with wrong semantics):
feature/dbase.lpc'squery()/set()/delete()implement their own genuine recursive_query/_set/_deletehelpers for slash-path nested properties — not a passthrough to any real efun by name-match — confirmed this is the same "architecturally correct" dbase this file's earlier section already documented, not the nitan-family bug. - §7.30 (mapping accessor returning raw uninitialized
int 0):feature/skill.lpc'smapping query_skills() { return skills; }is exactly this shape (skillsis genuinely unset, i.e. raw0, for a brand-new character), but every one of the 154 real call sites found viagrep -rn "query_skills()"guards with!skills || !mapp(skills)before using the result — reproduced this live as a brand-new character with no skills learned yet hitting several of these call sites (muren.lpc'saccept_fight,learn.lpc,wg_fighter.lpc) with no crash. The one unguarded call site found (u/snowtu/learn.lpc:93) is an orphaned wizard-personal-sandbox duplicate of the realcmds/std/learn.lpc, not reachable from the live command table (same "u/\* dev-in-progress duplicate" pattern this file's lpcc-sweep section already documented for this lib) — left alone.
Observations (not bugs — documented honestly, not fixed, per §10.7's scope note)
d/aolai/npc/jiaotou.lpc'sdo_answer(): the drill-instructor NPC presents four conversation options (比武/看热闹/捣乱/从军— "spar"/"watch"/"cause trouble"/"join the army") but only actually implements看热闹; every other choice, including the flavorful "join the army" option, falls through to a generic "你胡说什么?别来捣乱!" rejection. This reads like unfinished/stub content (a menu listing options whose questlines were never built) rather than a programming defect — there's no missingreturn, danglingelse, or wrong variable reference, just three of four branches never written. Left untouched.- Shop purchase and sect/menpai joining not completed live — stated explicitly per the task's own allowance for this, not silently presented as tested:
- 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.
d/obj/misc/muren.lpc(a "练功木人" training dummy with a stat-mirroringaccept_fight(), the exact "safe sparring" shape §10.7's checklist describes) is never placed in any room —grep -rl "muren"across the wholework/tree finds only the file itself. Orphaned content, not reachable in play; the organicfight-vs-武馆 弟子combat above was used instead, per this lib's own documented (help statue/combat) safe-sparring convention. Left alone per the established "don't fabricate/wire up missing content" precedent.d/wiz/obj/newbie_gift.lpc(a fully-implemented, multi-stage "新手锦囊" starter pouch —doc/help/newbie/liwuexplicitly tells every new player "进入游戏后,每个人物都会被给予一个「新手锦囊」" — tracks food/water/combat milestones viacheck_gift1/check_gift3call_outchains and hands out combat_exp/potential/gold rewards with in-character hints at each stage) is never instantiated anywhere —grep -rln "newbie_gift"across the whole tree finds only the file itself, sitting in the wizard-workspaced/wiz/obj/directory rather than a live content path. Confirmed live: a brand-new character's inventory is genuinely empty (目前你身上没有任何东西。), contradicting the help text. Reads as unfinished/never-deployed content (the samed/wiz/"sandbox, not live" shape this file's lpcc-sweep section already documents foru/snowtu/) rather than a programming defect — no crash, no wrong efun call, just a whole feature that was written but never wired intoenter_world()/confirm_gift(). Left undeployed; wiring it in would be a content/product decision (when, and under what conditions, should it be granted) that this pass shouldn't make unilaterally.
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 崩溃)
BBS_BOARD、BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 40 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
深度功能测试第三轮 / 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 行,无真实错误)。
本轮修改的文件
config.fluffoswork/adm/simul_efun/file.lpcwork/cmds/arch/update.lpcwork/obj/user.lpc
§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 同源的三处编译警告
include/npc/indian.lpc与masia.lpc共用全局名ma_sname/ma_pname;两个头文件被同一个守护进程(npcd等)一起包含时,后包含的印度名表覆盖马来名表,generate_ma_name()生成的是印度名字。印度表改名in_sname/in_pname(KB 04 §6.10)。
验证:改动的文件和包含 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-10:编译警告第二轮(按驱动报告逐条修):按位 & 改逻辑运算 0 处,返回类型/原型 13 处,NPC+F_UNIQUE 的 init() 0 个,带初始化的未用局部变量 6 个,只在 #if 分支用的声明移进分支 0 个文件;改动 17 个文件,HEAD 与工作树各载入一次无回退(17 objects; HEAD PASS 17, tree PASS 17; regress 0; newly loading 0),这些文件的警告 39 -> 20。
编译警告收尾(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() 报错,两边成败看运气,与改动无关)。
- 芥子袋
d/obj/fabao/jiezi-bag.lpc(存取货币的法宝,F_SAVE存盘)自己声明了mapping Gold,而feature/dbase.lpc也有一个存盘的Gold:存档里写两条Gold,restore()填前一个(基类的)、清空后一个(袋子用的),袋里的钱每次读档都没了(KB 06 §7.233)。删去袋子自己的声明,create()里Gold = ([]);,袋子的代码用唯一那份;旧存档的Gold也正好读进这份。 - 袈裟
d/obj/cloth/jiasha.lpc(CLOTH+F_UNIQUE)只跑 unique 的init(),撕袈裟的tear从没加上;补init() { cloth::init(); unique::init(); }(同 xyj2000)。 d/kaifeng/ground.h的三张表包进#ifndef GROUND_TABLES_INHERITED,层层继承的ground1/2/3.lpc、ground.lpc包含前定义它(同 xyj2000);李靖d/dntg/lijing/lijing.lpc的die/unconcious改void。- 擂台两人
d/kaifeng/npc/ye.lpc、yuwangye.lpc的比赛名title改名game_title,投票箱d/wiz/votebox.lpc的改名vote_title,不再与基类的title同名;突破迷宫五个出口(及backup/里一份)out.lpc的怪物表gg与它继承的room.lpc一字不差,删去重复的那份;d/obj/books-nonskill/book.h的BOOK先#undef;quest/tianguan/npc/other.lpc没用的name删去。
- 2026-10-14:新驱动(FluffOS 970d9f09)报
Float value truncated to int(#1426)。3 个文件 3 处:赋值、初始化、return 改写成to_int(...)(值不变,只是写明截断)。改动的对象新进程各载入一次,无回退(scripts/lpc_fix_float_trunc.py,KB 04 §6.10)。
治疗/疗伤的属性名(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)
下列房间的出口写的文件不存在,而对面恰好只有一个房间的出口指回本房间,所以按对面的房间改正(笔误、大小写、路径少了一级):
/d/luoyang/congming3.lpc的south出口:yunmanlou→yinmanlou
改后房间照常载入(与 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… 行。