Ocean II (Star and Moon Legend)

✅ 可玩

海洋II

haiyang2

🔑 fluffos / Mud@2026 更新 66a3bd8 2026-10-28 源码 下载 ZIP

▶ 开始游玩 · Play Now

本游戏自称 海洋II,游戏内还有一个更常用的招牌名字——星月传奇。属于经典的 "ES II" 引擎家族(与本项目中的 es1_win、xkx2001、rzrmud、xo、武汉站、炎黄武魂等同宗),是当年一批中文武侠 MUD 中流传较广的一支。`hymud`(海洋V·星月传奇)是它独立演化的续作,共享的却是本项目 `hy5`(海洋5)那套 191 个地图分区的骨架,而非海洋II自己规模较小的世界。真正与海洋II血缘更近的其实是 `hy5`:两者 master.lpc 逐字节相同,地图文件约九成字节相同——本质上是同一份世界基础,`hy5` 只是在此之上扩建了约五十个新地区(越南、朝鲜、日本、西藏、新疆等)。海洋II 本身近三万个文件构成的世界以门派江湖、拜师学艺、行侠仗义为主线,地图涵盖江南水乡、明教据点、高昌乃至日本分区,并叠加了完整的婚姻系统与帮派玩法。

English

A large, mature ES II-lineage wuxia MUD -- self-titled Ocean II but far better known in-game by its banner name, Star-Moon Legend (星月传奇) -- sharing engine lineage with this collection's es1_win, xkx2001, rzrmud, xo, Wuhan Station and Legend of the Yellow Emperor's Soul, and one of the more widely-circulated Chinese wuxia MUDs of its era (hymud, “Ocean V,” is its independent successor, sharing its own 191-domain map skeleton with hy5 rather than haiyang2's smaller one — see hy5's entry). A closer relative than that lineage list suggests: hy5 (Ocean 5) elsewhere in this collection has a byte-identical master.lpc and roughly 90% byte-identical domain files — effectively the same base world, extended with about 50 more zones (Vietnam, Korea, Japan, Tibet, Xinjiang) rather than an independent creation. Nearly 30,000 files build out a genuinely large world: sect-based jianghu with map regions spanning Jiangnan's water towns, the Mingjiao stronghold, Gaochang, and even a Japan zone, plus a full marriage system and gang/faction play layered on top of the standard skill and quest content.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/haiyang2/

管理员账号 / Admin account

警告:Mud@2026 是本地游玩用的公开默认密码。若要正式对外开放主机,
请先修改此密码(或删除该账号及 securd.lpc 中的授权行)。

本地运行

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

游戏端口:40057。

NOTES · 移植与修复记录

haiyang2 — 海洋II (Ocean II)

Archive: 海洋II 2010 正式无错完整版下载.rar (archive #63; dup: 海洋II 2010 正式无错完整版下载 (1).rar, not processed, per AGENTS.md's duplicate-archives table). Port: 40057. Status: done (boots clean, full registration flow verified end-to-end twice with two different real Chinese names, both reaching the actual game world with zero runtime errors on the second clean run).

What this is

The raw archive extracts to a top-level bin/ (a prebuilt Windows MudOS binary release: MudOS.exe, runmud, runqym, ini files — ignored, we use our own driver) plus the real mudlib root at hy3/. The mudlib's own config file (bin/config.hy, labeled "MudOS 0.9.20") self-identifies via name : HY2; include/mudlib.h's MUDLIB_NAME/INTERMUD_MUD_NAME constants decode (GBK) to "海洋II" ("Ocean II"), matching the archive's own title, and the live in-game banner says "〖海洋II〗" / "星月传奇" ("Star-Moon Legend", an alternate/sub-brand name used throughout the UI text) — so the archive's Chinese title is accurate, no identity mismatch to flag (unlike several previous libs in this batch).

Lineage: adm/single/{master,simul_efun}.c layout, master.c's own header comment says "for ES II mudlib... original from Lil... rewritten by Annihilator... modified by Xiang for XKX" — this is the same "ES II" lineage already seen many times in this project (es1_win, esI, xkx2001, rzrmud, xo/xo_final, bmxkx2001, kxkj, yueyingqiyuan, wuhanzhan, ...). Small simul_efun set (atoi, chinese, file, gender, object, path, wizard, message — 7 files, ~350 lines total) confirms the correctly-architected per-object F_DBASE storage pattern (verified: feature/dbase.lpc has real local set/query/delete, and grep for efun::set(/ efun::query(/efun::delete( across the whole tree comes up empty) — the nitan-family §15 shared-simul_efun-dbase architecture bug does not apply here, confirmed by reading the source rather than assumed.

Size: 29,689 raw files, 26,184 .c files, 26,182 .lpc files after the rename (one directory-named-like-a-file quirk below reduced the count by one net item). This puts it in the "tens of thousands of files" range AGENTS.md §6b flags as a real memory-pressure risk for the full lpcc_check.sh sweep (comparable to nitan6's 23,100 files, which was skipped entirely) — ran it anyway with active free -h/RSS monitoring throughout and it completed without concerning memory pressure; see "lpcc sweep" below for the actual numbers observed.

Fixes applied

1. AGENTS.md §15h (is_chinese) — adm/simul_efun/chinese.lpc's is_chinese() used the GBK lead-byte range check (strlen(str)>=2 && str[0]>160 && str[0]<255), always false against this driver's UTF-8 codepoints. Rewritten to strlen(str)>=1 && str[0]>=0x4e00 && str[0]<=0x9fff (also relaxed the length floor from 2 to 1 — the sliding-window caller in check_legal_name calls this with genuine 1-character suffixes at the last loop position, which the old >=2 guard would always fail). 2. AGENTS.md §15h (check_legal_name) — adm/daemons/logind.lpc's check_legal_name(): halved the byte-calibrated length bound (strlen(name)<4||>10 → <2||>5, matching the message text's own "2 到 5 个中文字" which was already correct) and dropped the i%2==0 && GBK-byte-parity gate from both sliding-window is_chinese() checks (now checks every character position, not every other one). 3. AGENTS.md §15h (chinesed.lpc, dead code) — adm/daemons/chinesed.lpc's check_chinese() had the identical i%2==0 && shape; fixed the same way for consistency even though grep confirms it has zero callers anywhere in the lib (harmless dead code, fixed as cheap insurance/consistency). 4. AGENTS.md §14 (valid_override 3-arg) — adm/single/master.lpc's valid_override(file, name) upgraded to the documented 3-arg (file, name, main_file) signature, checking main_file too so a fragment #included into simul_efun.lpc/master.lpc can legitimately efun:: override. 5. AGENTS.md §8d/§15o (get_include_path) — master.lpc had no get_include_path() apply at all. Added the standard one (prepends the compiling file's own directory + :DEFAULT:). Needed because this lib has genuine per-directory local headers, e.g. d/marry/hunyin.h sitting next to d/marry/chen.c/d/pingan/marry/chen.c, included via #include <hunyin.h> (angle brackets, which only search the configured include path without this apply). convert_lib.sh's own automated pass already converted 13 other local angle-bracket includes to quoted form directly; this apply is additional insurance for any it didn't catch and for mid-connection lazy compiles (§8d's documented preload-time consult gap). 6. AGENTS.md §15n (custom securd.lpc ACL blocking compile-time reads) — adm/daemons/securd.lpc (SECURITY_D) is a genuinely custom ACL daemon (per-directory exclude_read/trusted_read tables, not the simpler find_object-only pattern) with no func-discrimination for compile-time operations at all. Added an early switch(func){case "load_object": case "recompile_object": case "include": return 1;} to valid_read(), before the real ACL logic — confirmed necessary: adm/daemons/logind.lpc's gb_big5() (part of the very first thing every connection runs) unconditionally calls BAN_D->is_banned(...) and UPTIME_CMD->main(), neither of which is ever preloaded, and a fresh unauthenticated connection's default this_player() status is (player) — without this fix the very first connection's lazy compile of band.lpc/uptime.lpc would hit "Read access denied" (this exact failure mode was reproduced and fixed on shujian2008, the precedent for §15n). 7. Preload path typo (both adm/etc/preload, the live path per master.lpc's epilog() reading CONFIG_DIR+"preload" where CONFIG_DIR = "/adm/etc/", AND the stray unused adm/daemons/preload decoy copy — same "dead duplicate preload file" shape documented for wmkj/archive #55): both listed /adm/daemons/securityd, but the real file on disk is adm/daemons/securd.c/.lpc (matches include/globals.h's #define SECURITY_D "/adm/daemons/securd"). Fixed both copies' typo'd path so the security daemon actually preloads (with proper create()/restore_list() bootstrap ordering) instead of silently never loading via this entry (it would still get lazily loaded on first real access either way, per §15n's fix above, but preloading it properly is the correct/intended behavior and avoids any ordering surprises). 8. New §15p manifestation: DNS_MASTER triggered from inline registration-flow code, not preload. adm/etc/preload does NOT list dns_master (nothing to exclude there — checked proactively per standing policy) — but adm/daemons/logind.lpc's gb_big5() (again, the very first thing every connection executes) unconditionally calls DNS_MASTER->query_muds()/DNS_MASTER->query_svc() to render an intermud "connected MUDs" table. DNS_MASTER (/adm/daemons/network/dns_master) does real resolve()/ socket_create()/socket_bind() work in its create() against a hardcoded intermud boot server unreachable in this environment — the same hang risk class documented in AGENTS.md §15p, just reached via a direct call instead of a preload entry. Since the whole block is purely cosmetic (a table of other connected MUDs, never needed for registration), disabled it: mud_list/mud_svc are now set to empty mappings directly instead of ever touching DNS_MASTER. Separately, the Mud_name() macro (include/net/macros.h) — used unconditionally elsewhere too, e.g. adm/daemons/channeld.lpc's chat-verb formatting — also force-loads DNS_MASTER on every call; since DNS_MASTER's own query_mud_name() just returns the constant INTERMUD_MUD_NAME, redefined the macro to return that constant directly (#define Mud_name() INTERMUD_MUD_NAME), byte-for-byte equivalent behavior with zero risk of ever loading dns_master. Verified via grep that no other macro in that header (udp_port(), index_add, index_call, dns_log, dns_warning) is used anywhere outside adm/daemons/network/ (the network daemon family itself, never preloaded/reachable) — only Mud_name()/mud_nname() needed the fix. 9. NEW bug (not yet in AGENTS.md's catalog as of this pass): securd.lpc's get_status() crashes on any object with no euid/uid assigned yet. get_status(mixed ob) falls through both the objectp/stringp branches when ob is an object whose geteuid() AND getuid() are both null (common for a freshly-loaded/cloned object before anything ever calls seteuid() on it — e.g. any quest NPC clone at create() time, and even master_ob itself early in boot, since master.lpc's own create() never calls seteuid()), leaving euid as the plain int 0. The very next line, member_array(euid, wiz_levels), then throws *Bad argument 2 to member_array(): Expected: string or array Got: 0 — reproduced live during boot's own preload (every quest NPC created via questd.lpc's spread_quest() calling set_skill()/map_skill(), which do a file_size() check that flows through valid_read() → get_status()) and would recur for any other not-yet-euid'd object performing a file operation. Fixed by returning "(player)" (the function's own existing final-fallback value, so a null/absent euid gets exactly the same "no elevated privilege" outcome the code already intended for a merely-unrecognized real euid string) whenever euid isn't a real string, before the member_array() call. This single fix also benefits wiz_level()/get_wiz_level() (the adm/simul_efun/wizard.lpc simul_efun, called pervasively including from logind.lpc's own wiz_level(arg) < wiz_lock_level check), which route through the same get_status(). 10. NEW bug: clone/user/login.lpc's receive_message() missing a !stringp(str) guard. Unlike the real player body's copy (feature/message.lpc/feature/message1.lpc, both correctly start with if (!stringp(msg)) return;), the pre-login connection object's receive_message() called receive(str) unconditionally. write(0) is a routine idiom throughout this lineage (cmds/usr/uptime.lpc's write(read_file("/log/nosave/LASTCRASH")), which legitimately returns 0 on a fresh install with no crash history yet) — that flows through message.lpc's write() simul_efun → message("write", 0, this_player()) → login.lpc's receive_message("write", 0) → receive(0), which this driver rejects with *Bad argument 1 to receive(): Expected: string or buffer Got: 0, silently killing every single fresh connection right at the very first banner (before the GB/Big5 prompt even fully renders) — this was the very first crash found, reproduced on the first interactive test attempt. Fixed by adding the same if (!stringp(str)) return; guard the real player body already has. 11. AGENTS.md §15s (message() 4th-arg type rejection) — adm/simul_efun/message.lpc's tell_room(ob, str, exclude) (varargs, exclude defaults to int 0 when called with only 2 args — the overwhelmingly common call shape) passed that literal 0 straight through to the real message() efun's 4th ("exclude") parameter, declared void | object | object * — a literal 0 argument doesn't satisfy that union (only *omitting* the argument, or a real object/array, does), throwing *Bad argument 4 to EFUN message(): Expected: object, array, Got: int(0). Reproduced live: every quit triggers clone/user/user.lpc's user_dump() → tell_room(), which hit this on the very first full successful registration run (the connection/save still completed fine, but the "player entered the room"-style broadcast silently failed and the error was logged). Fixed with exclude || ({}). Also hardened shout(str) the same way (this_player() can legitimately be 0) even though no live call site was observed to actually hit it during testing — same shape, same shared root file, cheap insurance. Confirmed via grep that no other direct 4-arg message() call site anywhere in the ~26,000-file tree passes a bare possibly-falsy variable as the 4th arg (the only other 4-arg calls found are message_vision/message_combatd's ({me, you}), always a real array).

Directory-named-like-a-file quirk (AGENTS.md's tooling edge case)

convert_lib.sh's blind .c→.lpc rename hit a directory literally named u/hxsd/workrom.c/ (containing one file, workroom.c, itself never renamed since the rename only recurses into files) — the classic "orphaned duplicate-content directory" pattern already seen on xjcq2000 (archive #40). Confirmed nothing references workrom/workrom.c anywhere else in the tree (grep came up empty besides the directory's own contents), and a real workroom.lpc file already exists as a sibling — renamed the directory to workrom.orphaned-dir so it's never mistaken for a compilable object.

Encoding

convert_lib.sh's automated pass: 28,007 converted, 1,085 already-UTF8, 117 lossy (invalid bytes dropped via the iconv -c fallback — mostly skill/NPC/room content files: d/gaibang/mishi.c, d/gaochang/obj/{map, wenbook01,fake_map}.c, d/gaochang/npc/obj/map.c, d/yanjing/biwu/mu.c, u/yaodm/skill/{qingyi-jian.c,readme}, u/hxsd/tangmen/skill/ zhuihun-biao.c, u/hxsd/skill/{qingyi-jian.c,readme}, cmds/{imm/hpt.c, usr/hp.c,arch/recover.c,adm/adup.c} — 15 distinct lossy-conversion warnings across the run), 480 skipped as genuinely binary.

One additional GBK straggler found and fixed manually (NOT caught by convert_lib.sh's text-extension allowlist, since .o is deliberately excluded from the forced-text list per AGENTS.md's own documented rationale — genuine binary .o files exist alongside plain-text save-data .o files in this ecosystem): adm/daemons/securd.o (SECURITY_D's own save file) had one un-converted GBK run (\xb0\xb2\xc8\xab\xbe\xab\xc1\xe9, decoding to "安全精灵") embedded in its channel_id field — file classified the whole file as generic "data" so the automated pass skipped it. This was NOT cosmetic: it crashed securd.lpc's own create() → restore() with *restore_object(): Invalid utf8 string while restoring dbase (non-fatal only because master.lpc's preload() wraps the call_other in catch() — but it meant restore_list() never ran, silently leaving every ACL table empty/default instead of restored). Converted the file directly with iconv -f GB18030 -t UTF-8; re-checked all 561 .o files in the tree afterward (byte-by-byte UTF-8 validation) and confirmed this was the only offender. Also re-ran the full straggler check across all 26,407 .lpc/.h files (per AGENTS.md's documented check) — zero hits, confirming no other un-converted GBK text remains.

Config

config.fluffos adapted from bin/config.hy (labeled "MudOS 0.9.20", GBK→UTF-8 converted before any other edit, per AGENTS.md §5). Port changed from the original 8888 to 40057. mudlib directory points at /home/sunyc/src/mudlib/libs/haiyang2/work (absolute path). log directory set to /log (resolves relative to the driver's CWD per §6 — libs/haiyang2/log/, sibling of config.fluffos, driver always launched via cd libs/haiyang2 && setsid nohup .../driver config.fluffos). Tuning knobs (eval cost, array/mapping/string size limits, etc) copied from the established template used for the last many libs in this batch (matches xkx2001's config shape, same ES II lineage). name : HY2 kept as-is (matches the original; no code anywhere depends on the literal string "HY2" except a wizlist.lpc display banner, checked via grep).

What was checked and confirmed NOT needed

Registration flow — verified end-to-end, twice, in two separate clean sessions

Read logind.lpc's actual input_to callback chain before scripting the test (not inferred from prompt text): logon() → gb_big5 (GB/Big5 charset prompt — no hidden BIG5/age-gate/client-version question beyond this one) → get_id (English id, 3-12 lowercase letters, several reserved words banned) → confirm_id (y/n to create a new character; also a 60-second same-source-IP registration throttle, shared static variables across the whole driver process — confirmed by reading the code, not hit blind) → get_name (real Chinese name, check_legal_name) → new_password/confirm_password (6+ chars, needs upper+lower+digit) → select_gift/get_gift (0-4 choice, then a y/n to accept a randomly-rolled attribute spread) → get_email → get_gender (m/f) → init_new_player/enter_world.

Run 1 (before the message()/§15s fix was applied): id qinfeng, real Chinese name 秦风 (accepted immediately, no rejection — confirms the §15h fix works), password Test123/Test123, gift 0→y, email [email protected], gender m — reached the actual starting room (金庸 NPC, exits to 8 zones), look worked, quit saved cleanly ("您的档案已经自动存盘"). debug.log showed exactly one runtime error during this run: the §15s message() 4th-arg crash (see fix #11 above), triggered by quit's own user_dump()/tell_room() call — found here, fixed, and reverified clean below.

Run 2 (after ALL fixes, completely clean): id qinling, real Chinese name 秦岭 (2 characters — also exercises the different length end of the 2-5 character bound), password Test123/Test123, gift 0→y, email [email protected], gender f — reached the actual starting room, "【江湖通告】:热烈欢迎新玩家 秦岭(qinling)来到HY2" broadcast rendered correctly, look worked, quit saved cleanly. debug.log for this entire run: zero matches for bad argument|denied|recursion|segmentation| undefined function|cannot #include|couldn't find object|too deep|abort| fatal (only the expected non-fatal Unknown #pragma/Unused local variable compile warnings, streamed to the connecting client the first time each never-preloaded file is lazily compiled — cosmetic noise, not errors).

Save file confirmed correct: data/login/q/qinling.o contains "name":"秦岭" as clean UTF-8, alongside a properly-hashed password and all the other registration fields.

lpcc sweep

Ran the full sweep (26,182 .lpc files is in the "tens of thousands" range AGENTS.md §6b flags as a real OOM risk — comparable to nitan6's 23,100 files — so it was run with active free -h/RSS monitoring throughout rather than skipped outright). Peak lpcc --batch RSS observed: ~5.8GB, system available memory never dropped below ~14.6GB (out of 23GB total) at any point — well within safe margins, no OOM risk materialized, unlike the nitan170911/xo_final precedents that had to be killed early.

Result: 26,183 objects tested, 25,242 PASS / 941 FAIL = 96.4% pass rate.

Triaged the 941 failures by error-message category (AGENTS.md §6b/§8g method) rather than treating each as an independent bug:

None of the failing files are on the master/simul_efun/preload/ registration/starting-room path exercised by the two successful interactive tests above — consistent with this being real long-tail content-gap/typo noise rather than anything blocking the lib's core functionality.

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

WASM-enablement pass (2026-07: loopback-allow + throttle exemptions + admin seeding)

Standard pass per AGENTS.md §1.3b/e + §1.5. Gates found and patched (loopback = query_ip_number() of 127.0.0.1, any 127.*, an empty string, or a non-string/malformed non-dotted-quad value — the latter two defend against WASM builds where the efun returns garbage):

- :331/:338 — per-IP concurrent-connection caps (iplimit > 50 / NATSERV > 80); - :544 — "同一IP login 1人" one-login-attempt-per-IP-at-a-time gate; - :573 — 60-second relogin-after-quit throttle; - :580 — 10-second wrong-password retry throttle (same-IP branch); - :704 — confirm_id()'s 60-second same-IP new-registration throttle (the one documented in the registration-flow section above).

Admin account: fluffos / Mud@2026 / 浮浮, registered through the real native flow (encoding g → id → y → name → password ×2 → gift 0/y → email → gender m). Granted (admin) via adm/daemons/securd.lpc:34's hardcoded-door mechanism (same idiom as the author's own hxsd entry in restore_list() — survives securd.o resets). Verified after restart: login shows 目前权限:(admin), update /cmds/wiz/update recompiles successfully. Save files (must be committed): work/data/user/f/fluffos.o, work/data/login/f/fluffos.o.

Retest: fresh registration (秦风/ceshizhe) end-to-end + look/score/quit clean; fluffos login + update clean; debug.log free of new errors; test character saves removed; data/topten.o runtime churn reverted.

Dual-mode verification pass (2026-07-24)

深度功能测试 / Deep functional test (2026-07-24, AGENTS.md §10.7)

Full hands-on playthrough per the round-two methodology (this lib had already been boot/registration-verified many times over per the sections above, but never actually *played* through zones/training/reconnect edge cases). One real character, qixiazi / 祁侠子 (3-character real Chinese name, gift 0/random, password Test123), taken through registration → exploration → a safe-training mechanic → sect-recruiter interaction → clean quit → unclean (net-dead) disconnect/reconnect ×2 → clean quit + real wall-clock wait + reconnect. Also cross-checked against the sibling lineage note: hymud's own investigation (documented in the orchestrating session, not in this repo) looked for a command-dispatch bug reachable via command_hook/add_action and did NOT find a reproducible one on that lib. This pass DID find one on haiyang2 — see the bug below. Both share the ES II feature/command.lpc-style dispatcher; worth checking hymud's own enable_player() for the identical shape (see "Lineage" note at the end).

1. Newbie help: read doc/help/newbie in full before touching the client. Confirms: start room is 扬州客店 (or 武庙 after death), the fight/较量 command is the intended safe-PvP-avoiding spar (stops at 50% qi/jing or on halt), and several sects (少林/武当/峨嵋/华山/桃花岛) have their own 木人 (wooden dummy) for safe combat practice — the general 武馆 (dojo, reachable directly from the welcome room's wg exit, no sect commitment required) has the same mechanic via its own 木人桩 (da muren zhuang).

2/3. Registration + continuous session: native driver, port 40057, scripts/mudclient.py, one --send verified at a time through the wizard (encoding gb → English id → y confirm → real Chinese name 祁侠子 → password ×2 → gift 0→accept → email → gender m) — matches the documented flow above exactly, no desync. Landed in /d/welcome/ welcome (the actual START_ROOM); look/score/i all correct immediately after registration and again after re-login. Mid-pass interruption, not a mudlib bug: the driver process died to a bare SIGTERM from the shared test environment ~2 minutes into the first session (FATAL ERROR: SIGTERM: Process terminated in the driver's own stdout, zero mudlib-side cause — consistent with AGENTS.md §10.5's documented "stray SIGTERM" environment hazard, not §10.8's driver-internal-corruption class). Restarted by exact PID/cwd verification and continued on the same saved character without any data loss (confirms the save-on-quit/relogin path is itself sound).

4. Exploring the starting zone: wg (welcome room's direct exit to the general dojo) → 武馆大门 → north (大院) → west (长廊) → west (西练武场). All room descriptions, exits, and NPC listings rendered correctly at each step; read d/wuguan/*.lpc source to confirm the path rather than guessing blind.

5. Safe-sparring mechanism: 西练武场 (d/wuguan/wuchang1.lpc) has a 木人桩 (training dummy stake); da muren zhuang worked correctly — "你站好马步,运气于拳,开始和木人桩对打起来。", consumed a small, bounded amount of jing/qi (visible via before/after score), no risk of death or PK. Used this instead of hunting a "weak enough" live NPC, per the checklist.

6. Skill/sect path: a 星宿弟子 (Xingxiu-sect recruiter) NPC present in the same training room responded correctly to both an informational approach (加入星宿...看 help xingxiu) and a direct bai xingxiu dizi attempt (correctly rejected: "既不属於任何门派,也没有开山立派,不能拜师" — this recruiter is a flavor/pointer NPC, not itself a real sect master, matching the code in cmds/skill/bai.lpc's mapp(ob->query("family")) gate). This is working as designed, not a bug — real sect masters live in each sect's own remote zone, out of scope to reach given the time budget for a single pass; not verified live: actually completing a bai onto a real family-holding master and a full xue skill-learn cycle. Documented here as an explicit gap rather than silently assumed tested.

7. Clean quit + debug.log: quit printed the expected "正在退出游戏 ,档案保存中......"; debug.log grepped immediately after (both the raw tail and the standard bad argument|denied|recursion|segmentation| undefined function|cannot #include|couldn't find object|too deep|abort| fatal pattern) — zero matches beyond the expected first-compile warnings for lazily-loaded files. Repeated after every subsequent quit/reconnect cycle in this pass — always clean.

8. Unclean (net-dead) disconnect, twice, two different code paths:

9. Clean quit + real wall-clock wait + reconnect: quit cleanly with combat_exp at 5000, waited a genuine ~90 real seconds, reconnected. Landed in the welcome room (the correct, saved startroom); score showed 实战经验:5000, confirming persistence across a real gap, not just an in-process resume. (Also incidentally resolved a red herring from earlier in the pass: the very first post-registration re-login showed "你上次光临海洋II是 Wed Dec 31 16:00:00 1969" — epoch 0 — for the last_on/last_from fields; this was an artifact of that first session's driver having died to the stray SIGTERM described above *before* a real quit ever ran, not a mudlib bug — every subsequent clean-quit-then-reconnect in this pass showed the correct real timestamp.)

10. Shop/combat/death: not verified live, stated explicitly per the checklist's own fallback clause. Reaching a real shop purchase or death/respawn would have required either a lengthy walk to a priced shop + earning/being granted spendable coin, or deliberately losing a real fight — both reasonable but out of this pass's time budget given the NEW bug found and root-caused below. Left as an honest gap, not silently presented as tested.

Bug found and fixed: unguarded add_action("command_hook", ...) in enable_player() — matches AGENTS.md §7.28

feature/command.lpc's enable_player() (the shared function that registers the central command dispatcher, called once from inherit/char/char.lpc's create() on every login/registration) had no idempotency guard around its add_action("command_hook", "", 1) call. grep -rn "enable_player()" --include='*.lpc' . (excluding the definition itself) finds 17 other call sites across the tree that legitimately re-invoke it mid-session on the SAME already-living() player object — the two most universally reachable, ordinary-gameplay ones being:

Each such re-invocation calls the bare, non-idempotent add_action() again, stacking one more duplicate wildcard sentence for the identical command_hook dispatcher on the same object — exactly the §7.28 shape ("Redundant enable_player()/enable_commands() calls stack duplicate add_action sentences, silently re-running FAILED commands' side effects"), previously found on dtsl. Confirmed live, not just by code inspection, via the commands() efun (exposed by this lib's own query_commands() wrapper) inspected through the admin call command (cmds/arch/call.lpc):

Fix (the same pattern §7.28 already documents — a mhxy-style living()-gated guard was deliberately NOT used, since this lib's own disable_player() legitimately calls bare enable_commands() again while already living(), which a living() guard would wrongly no-op):

// feature/command.lpc, enable_player():
  delete_temp("disabled");
  enable_commands();
  remove_action("command_hook", "");   // added — guarantees exactly one
  add_action("command_hook", "", 1);   // sentence regardless of call count

Verified live, post-fix, driver restarted from disk: baseline query_commands() → 1 command_hook entry; enable_player() invoked three additional times via admin call → still exactly 1 entry. Ordinary look/score/i dispatch confirmed still correct (single, non-duplicated output) on a fresh login afterward. git diff confirms feature/command.lpc is the only file touched by this pass.

Lineage note: haiyang2's documented sibling hymud (per AGENTS.md's ES II family list) very likely shares this exact feature/command.lpc-shaped enable_player() — the orchestrating session's own targeted hymud investigation (looking for a command-dispatch bug from a different angle, a player-reported "can't do commands after registering") did not find or fix this specific shape. Worth a proactive grep (enable_player() call count + add_action("command_hook" guard) on hymud and other ES II-lineage siblings sharing this dispatcher file, out of scope for this pass.

Files modified: libs/haiyang2/work/feature/command.lpc (one-line fix, see above).

Process hygiene: driver run natively on port 40057 throughout (PID confirmed via readlink -f /proc/<pid>/cwd before every kill, never a broad pkill); killed by exact PID at the end of the pass. Test character qixiazi save files (data/user/q/qixiazi.o, data/login/q/qixiazi.o) removed after testing; data/topten.o and data/user/f/fluffos.o runtime-churn timestamps reverted via git checkout. No instrumentation was left in place — the only diff against HEAD is the one-line remove_action() fix above.

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

此前被错误标记为某个不存在于本项目任何档案的原始压缩包文件名的 duplicate_of;这里的内容其实是完全独立、可游玩的游戏(有自己的 -N 变体编号、自己的端口、自己的 README)——duplicate_of 已清除。

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

深度功能测试(2026-08-13,round two,新驱动重测)

Re-tested against the freshly-rebuilt build-debug/src/driver(post 全库 quest_times/win_times %-operator 修复 + Warning/warning 驱动文本回退)。这份档案是本轮"XKX/hy 家族" log_error() 扫荡里唯一 已经提前修复过的一个——adm/single/master.lpc 的 log_error() 早就有正确的 strsrch(message, "arning:") 判断(注释里引用了更早一 轮的 "AGENTS.md #15w"),管理员账号 fluffos 也早已用真实注册流程 创建并提交(work/data/{login,user}/f/fluffos.o,WASM-enablement 那 次提交)。本轮只发现一处新 bug:

发现并修复的 PROGRAMMING bug

1. log_file()(adm/simul_efun/file.lpc)完全没有 assure_file() 保护(AGENTS.md §7.11-class 的又一确认实例,与 fqyy2/fy2mg/ fys 同一形状):全档案 quest/shenshu/man.lpc 等文件多处 log_file("nosave/ZS", ...) 调用依赖 LOG_DIR 下的 nosave/ 子目录存在。这台开发机本地的 work/log/nosave/ 目录当前确实存 在(一个 EXP 文件,Jul 24 的旧测试痕迹),但 work/log/ 整 条路径被项目级 .gitignore 规则(libs/*/work/**/log)排除在 外,本地存在不代表新检出安全——真正的一次全新 clone 仍会在首次 触发这条路径时未捕获抛出。修复:补上 assure_file(LOG_DIR + file);(含前向声明),使其与目录是否被 提交无关,运行时自愈。

Proactive checks(无需改动)

实测过程

以已提交的管理员账号 fluffos/Mud@2026 登录(连线后有一个 GB/Big5 选码提示,选 g),score/update /adm/simul_efun/file (就是本轮改过的那个文件)确认可正常重新编译,屏幕上零次诊断刷 屏。log/debug.log 时间戳全程未变化,确认无新增未捕获运行期错 误。登录本身产生的存档时间戳类微小 diff(data/user/f/fluffos.o 的 last_on 字段)已用 git checkout 撤销,不提交。驱动最终按精确 PID kill,ps -p 确认已退出。

Round three deep functional test (2026-08-18, AGENTS.md §10.7)

Went deeper than round two's zone-walk/net-dead pass: real board post/read, a real weak-NPC kill (not just training-dummy sparring), shop list/buy, and a targeted live stress-test of the death corridor for the newly catalogued §7.112 pattern. Also checked the two other newly catalogued 2026-08-18 bug shapes (§7.111 standard_trace(), logind.lpc enter_world() missing ->save()) — neither applies here (details below).

Proactive checks — not applicable

Bug found and fixed: §7.112 — unguarded staged init() → call_out()

chains, duplicated by a reconnect's enable_commands() re-broadcast

Same pattern as the freshly catalogued §7.112 class. Grepped the whole tree for call_out inside files defining init(), narrowed with a *_stage naming heuristic, and manually inspected every hit outside d/death/npc/ (the directory the task briefing specifically flagged). Found the exact unguarded shape — init() unconditionally calling call_out("death_stage"/"..._stage", N, previous_object(), ...) with no per-target idempotency flag — in 13 files, all fixed the same way (jym/xuanjianlu wgargoyle.lpc reference shape: a set_temp/ query_temp/delete_temp("death_stage_active") guard around the scheduling call in init(), cleared at every exit point of the callback):

1. d/death/npc/{wgargoyle,bgargoyle,mengpo,panguan,panguan2,pusa}.lpc (6 files) — the actual post-death ghost-realm corridor (d/death/{gate,gateway,naihe2,yanluo,mengpo,dizang}.lpc, walked by every player who dies), each NPC's init() scheduling its own multi-stage death_stage dialogue/reincarnation chain with zero guard. A player who reconnects (or has enable_commands() invoked on them by any other path) while standing in one of these rooms would get a second, fully-independent death_stage chain stacked on top of the first — duplicate dialogue, and for wgargoyle/bgargoyle/pusa, double execution of reincarnate()/inventory-drop/move()/save() side effects. 2. d/shaolin/npc/yu-zu2.lpc — identical shape, a 少林 "jailer" NPC running its own 5-stage death_stage chain (its own reincarnate() call is already commented out from an earlier edit, but the tell_object/move() side effects still duplicate without the guard). 3. d/bwdh/{sjsz,sjsz2,sjsz3}/{east,west}_xiangfang.lpc (6 files) — NOT an NPC; these are the 比武场 (PK-arena) staging rooms for the 门派论剑 (sect tournament) event. Unlike the death-corridor NPCs, this init() has no wizardp() exclusion at all, so literally anyone (player or wizard) entering the room schedules a 90-second death_stage (misleadingly named — it's actually the team-assignment/bodyguard-spawn callback, nothing to do with dying) that assigns a team, teleports the player into the arena, and can spawn an extra weishi (guard) NPC. A reconnect while waiting in this room would duplicate all of that — most consequentially, an extra weishi spawned per stray reconnect, silently inflating one team's guard count in a competitive event.

Live verification (fresh driver rebuild, both scenarios): used the committed admin account (fluffos) plus a fresh test character (qintongshan) in two separate sockets, teleported the test character into /d/death/gate (via summon) and separately into /d/bwdh/sjsz/west_xiangfang, and used the callouts death_stage wizard command (cmds/arch/callouts.lpc) as ground truth for exactly how many death_stage call_outs are live system-wide:

Files modified: the 13 files listed above (one-line init() guard + one-line clear at each death_stage/..._stage exit point, same shape throughout). git diff --stat confirms no other lines changed in any of them — the _xiangfang.lpc files originally have mixed CRLF/LF line endings, so those six were fixed with a binary-mode (rb/wb) Python literal-byte replacement rather than the text-mode Edit tool, after the first Edit-tool attempt was caught silently normalizing the whole file's line endings to LF (reverted via git checkout and redone).

Lineage note: worth a proactive call_out.*_stage + missing-guard grep on other libs sharing this "ES II" / bwdh 门派论剑 content family — out of scope for this pass (single-lib rule), flagging per the task briefing's "recurring pattern, don't fix elsewhere yourself" instruction.

Other round-three coverage (all clean, no new findings)

Process hygiene

Native driver, port 40057, rebuilt/restarted once after the §7.112 fixes to confirm clean compilation and re-verify live. Both sessions' PIDs (58051, then 61890) confirmed via readlink -f /proc/<pid>/cwd matching this lib's work/ before every kill; killed by exact PID each time. Runtime-churn saves from testing (data/board/kedian_b.o, data/npc/menpai.o/menpai1.o — an unrelated periodic sect-of-the-day daemon effect, not caused by this pass — data/topten.o, data/user/f/fluffos.o) reverted via git checkout; test character qintongshan's save files removed (not committed). boot.log scratch file removed. git status --short confirmed clean except the 13 intentional source fixes before committing.

§7.100 跨库扫描修复(ROOM 冗余 replace_program() 关闭包炸弹,2026-08-19)

同一形状覆盖到几乎所有房间基类(机制详见 AGENTS.md §7.100)。本库属 于该扫描已知最大规模的 10 个库之一。二进制模式脚本机械删除了 8340 个文件里 8341 处独立、未注释的 replace_program(ROOM); 整行(其中一 个文件 d/huangshan/banshan.lpc 里同一 create() 有两处独立冗余调用, 分别在 setup() 前后各一处,均属同一 bug 形状,一并删除)。另外手工 清理了造房工具 clone/misc/roommaker.lpc/adm/roommaker.lpc 代码生成 模板里内嵌的同一形状(4 处,含一处 heredoc、三处字符串拼接)。删除 总计 8345 行,与本次扫描 FINDINGS.md 记录的 haiyang2 存活命中数完全 一致,git diff --stat 与脚本自报数字吻合。

验证:干净启动一次真实调试驱动,端口 40057 正常监听, work/log/debug.log 全程无新增内容。用已播种的 fluffos/Mud@2026 管理 员账号连线,在欢迎村新手教学区往返走了十余个房间 (newbie1/newbie2/newbie4),look/who 均正常。未产生需要撤销的存档 改动。驱动按精确 PID kill。

Round four deep functional test (2026-08-20, AGENTS.md §10.7)

Targeted pass closing the three gaps round three's own NOTES explicitly left open (sect apprenticeship + skill learning, shop purchase, combat death/respawn), plus a fast standard-checklist sanity re-confirm. Native driver, port 40057, PID 850332 (confirmed via readlink -f /proc/850332/cwd before every action and before the final kill), driven via two persistent raw-socket Python sessions (a FIFO-fed telnet client written for this pass, /tmp/.../mudsession.py, since mudclient.py's one-shot --send list doesn't support inspecting a response before deciding the next command) — one for the committed admin account (fluffos/Mud@2026), one for a fresh test character (qinwudang/秦武当).

1. Real sect apprenticeship + skill learning — RESOLVED, fully completed live

Traced cmds/skill/bai.lpc's real gate (mapp(ob->query("family"))) back to feature/apprentice.lpc's create_family(), then grepped for generation-1 (create_family(".*", 1, ...)) callers to find genuine grandmaster NPCs rather than guessing. Picked kungfu/class/wudang/ zhang.lpc (张三丰/Zhang Sanfeng, 武当派 generation-1 grandmaster, combat_exp 9,000,000) — placed in /d/wudang/xiaoyuan.lpc ("后山小 院"), reachable in-fiction via /d/xiangyang/caodi6.lpc's south exit (/d/wudang/wdroad5) from the 襄阳 (xy) hub off the welcome room, several rooms deep. Used admin goto/summon to reach it directly (the established precedent from round three's death-corridor test) rather than spending the session's budget on the walk itself, since the goal was to exercise the real bai/xue mechanics at a genuine family-holding master, not re-prove pathfinding.

zhang.lpc's own attempt_apprentice() gates recruitment behind real stat thresholds (guarded>=5, taiji-shengong>=60, taoism>=80, shen>=2000, int>=30 — a deliberately steep, legitimate late-game questline, not a bug) — used admin call qinwudang->set(...)/set_skill(...) to satisfy them directly (same "reach real content without an unreasonable grind" precedent as round three's combat_exp bump). First bai zhang attempt (before the stats were set) correctly failed with the loyalty message and left pending/apprentice set; a bare retry after setting stats was silently absorbed by bai.lpc's own "still pending, ask again later" short-circuit (if (ob == old_app) return notify_fail(...) — it does NOT re-invoke attempt_apprentice() on a repeat call) — this is correct, intentional command semantics (you must bai cancel first to retry), not a bug; re-tested with bai cancel → bai zhang and got a full, real recruitment: "张三丰决定收你为弟子。...恭喜您成为武当派的第 二代弟子。" (real family mapping populated, generation 2, confirmed via score).

Followed with a full xue cycle: xue zhang force 1 was first rejected by inherit/char/master.lpc's prevent_learn() (shen > 0 && me->query("shen") < shen — Zhang's own shen is 200000, a legitimately steep "your moral standing isn't high enough yet" master-teaching gate, not a bug) — bumped shen to 250000 via admin call (same rationale as above) and retried: "你向张三丰请教了一句有关「基本内功」的疑问。你听了张三丰的 指导,似乎有些心得。你的「基本内功」进步了!" — real skill acquisition, confirmed via query_skill("force", 1) going from 0 to 1. Zero debug.log errors throughout either the bai or xue sequence.

Real bug found and fixed during this investigation — 3rd confirmed instance of AGENTS.md §7.30 (mapping-typed accessor returning its raw never-initialized int 0 instead of an empty mapping): feature/ skill.lpc's query_skills()/query_learned()/query_skill_map()/ query_skill_prepare()/query_wprepare() all had the exact bare-return <instance var>; shape previously confirmed crashing on xiakexing2017 and jqxz2015 (same lineage family). Grepped every call site of query_skills() (164 files) across the whole tree for unguarded keys(...)/sizeof(...)/direct-index usage and found real, live-reachable unguarded crashes:

(d/zangbei/npc/xiaodie.lpc, d/zangbei/star_room3.lpc's own keys(...) call sites were already correctly truthiness-guarded — confirmed clean, not fixed.)

Fix: applied the documented §7.30 remedy at the accessor level rather than patching every call site — return mapp(x) ? x : ([]); for all five feature/skill.lpc mapping accessors (skills, learned, skill_map, skill_prepare, wprepare). This closes all found and any not-yet-found call sites at once (confirmed via grep: 164 files call query_skills() alone), including the lyjob.lpc indexing case (an always-real mapping now safely returns 0 for an absent key instead of crashing on the index itself). Verified live: the bai/xue cycle above was run *after* this fix was applied, so the real skill-learning path was exercised post-fix with zero errors. git diff confirms only libs/haiyang2/work/ feature/skill.lpc changed (5 one-line accessor bodies).

2. Shop purchase — RESOLVED, fully completed live

药铺 (d/city/npc/huoji.lpc, room d/city/yaopu.lpc) — same shop round three used for list. Granted spendable coin via the sanctioned admin clone+give pattern for this lib's actual coin objects (/clone/money/gold, set_amount(), give <item> to <target> — note give.lpc's own arg-parsing quirk: give <target> <item> mis-parses as target=<item> item=<target> for the no-"to" form, so give X to Y is the reliable syntax). buy dust (化尸粉, the cheapest listed item at 70 base copper) succeeded: "你从药铺伙计那里买下了一颗化尸粉。你的「讨价 还价」进步了!" — item received (confirmed via i), price correctly deducted with correct change-making across all three coin denominations (50000 base units in, item cost 70, result 4 gold + 99 silver + 30 copper = 49930, exact). Zero debug.log errors.

3. Combat leading to death/respawn — RESOLVED, fully completed live, real organic death

Per this session's cross-lib precedent, checked kill.lpc directly rather than assuming a spar-gate applies — confirmed no such gate exists for real NPC targets. First kill zhang (against the same Zhang Sanfeng, a massively overtuned combat_exp-9,000,000 opponent, guaranteeing a fast loss) hit feature/damage.lpc's "新手不死" (newbies don't die) mechanic: any userp() character with combat_exp < 2,500,000 (and not already flagged a killer/high-PK/zhuanshen) who would die is instead rescued with a "白光" (white light) message, healed, and moved to /d/ pingan/guangchang — a deliberate, clearly-commented, intentional design gate (same category as this lib's already-documented aggressive netdead newbie reaper), not a bug, left alone. Bumped combat_exp to 3,000,000 via admin call (same "clear a design gate to reach the real mechanic" rationale used throughout this pass) and retried: real death this time — "你「啪」地一声倒在地上,嘴角溢出几丝鲜血,痛苦的挣扎了几 下就死了。" — moved to /d/death/gate's "鬼门关" (Ghost Gate) death corridor with 白无常 (d/death/npc/wgargoyle.lpc, one of the 13 files fixed for §7.112 in round three) present.

Let the full death_stage dialogue chain run to natural completion (5 stages, ~5s apart) rather than short-circuiting it — this is the first genuinely organic real-combat death this lib has seen tested (round three's §7.112 verification used admin summon directly into the death corridor, not a real kill-triggered death), giving the strongest possible confirmation of that fix: reincarnation completed correctly, character landed in /d/city/wumiao ("武庙", matching the lib's own documented death-respawn room), and callouts death_stage plus qinwudang->query_temp("death_stage_active") both confirmed 0 afterward — the guard cleared itself with zero stacking, fully organically triggered. score post-respawn showed consistent, correctly-updated state (离奇死亡: 一, 上次死因: 被zhang sanfeng杀死, combat_exp reduced from 3,000,000 to 2,970,075 as an expected death penalty). Zero debug.log errors across the entire combat → death → reincarnation sequence. (The doubled "你有武功为零级了,请重新enable 所有的武功,特别 是force!" notice from feature/skill.lpc's post-death skill-reset handling printed twice, from two separate call sites — cosmetic UI messaging, no error signature, not chased per scope.)

Standard checklist re-confirm (all clean, no new findings)

Process hygiene

Driver PID 850332 (readlink -f /proc/850332/cwd confirmed matching this lib's work/ both before every call/teleport action and immediately before the final kill). Both test sessions quit cleanly (quit from qinwudang, quit from fluffos) before the driver was killed. Runtime churn from testing (data/login/f/fluffos.o, data/user/f/fluffos.o, data/npc/menpai.o/menpai1.o — the same unrelated periodic sect-of- the-day daemon effect round three noted) reverted via git checkout; test character qinwudang's save files (data/login/q/qinwudang.o, data/user/q/qinwudang.o) deleted, not committed. git status --short confirmed clean except the one intentional feature/skill.lpc fix before committing.

Round five deep functional test (2026-08-27, AGENTS.md §10.7, new driver + 13-pattern sweep)

Follow-up pass on the freshly-rebuilt driver, focused on the 13 newly catalogued cross-cutting bug shapes (§7.121/§8.3a/§7.122/§7.123/§7.124/ §7.126/§7.129/§7.130/§7.131/§7.132/§7.133/§7.134/§7.135) plus one more full continuous playthrough (registration → exploration → safe-spar → sect-recruiter gate → real combat → quit/debug.log/reconnect-after-real- gap). Rounds two/three/four (above) already delivered full live coverage of sect apprenticeship+xue, shop purchase, and a complete organic combat-death-reincarnation cycle, so this round did not repeat those in full — it re-confirmed general soundness post-fix and went deep only on the newly catalogued patterns.

Bug found and fixed: 4th confirmed instance of AGENTS.md's uninitialized-mapping-accessor class (this lib's own §7.30 sibling shape, but on F_DBASE itself), matches §7.135's "one accessor missing the sibling guard" fingerprint

feature/dbase.lpc is this lib's core per-object property store (set()/ query()/delete()/add() on a mapping dbase; with no initializer). Every real accessor/mutator consistently guards the uninitialized case — set() does if (!mapp(dbase)) dbase = ([]);, query()/delete() do if (!mapp(dbase)) return 0; — except query_entire_dbase(), which did a bare return dbase; with no guard at all (and the parallel tmp_dbase/query_entire_temp_dbase() pair had the identical gap). query_entire_dbase() is used at 90+ call sites project-wide, including the core combat daemon (adm/daemons/combatd.lpc), score/hp commands, and inherit/char/char.lpc's own heart_beat() — any of which indexing the bare 0 this would return (e.g. my["eff_qi"]) would throw a hard "indexing a value that is not an array/mapping" error.

Practically unreachable in this specific lib, confirmed by reading every real call path: dbase gets lazily created by the very first set() call, and every object that ever reaches query_entire_dbase() (a real player mid-registration, or an NPC inside CHAR_D->setup_char()) has already had at least one set() run on it first (registration writes stats immediately; setup_char() calls the race-specific setup_human()/ setup_beast()/setup_monster(), which itself calls set(), before ever querying query_entire_dbase()). No live crash was reproduced or expected under ordinary play. Fixed anyway, following this session's own established §7.30 remedy and the explicit "worth checking for a bare, uninitialized declaration" guidance under §7.134/§7.135 in AGENTS.md, since it costs nothing and closes the class for any future caller that doesn't share this lib's own "always set() before query_entire_dbase()" invariant:

// feature/dbase.lpc
mapping query_entire_dbase() {
  return mapp(dbase) ? dbase : ([]);
}
mapping query_entire_temp_dbase() {
  return mapp(tmp_dbase) ? tmp_dbase : ([]);
}

Verified: driver rebuilds clean, and the live playthrough below (which exercises score/hp/combat, all real query_entire_dbase() callers) ran with zero debug.log errors after the fix.

Empirical finding: §7.121/§7.124-shaped "declared int, computed via float arithmetic, no to_int()" is NOT a live bug on this codebase+driver combination — verified by direct test, not assumed

This lib's ES-lineage skill/level-curve code has a widespread recurring idiom, level = ceil(pow(exper * 10.0, 0.333333)); into a declared-int local (12+ files: quest/menpaijob/baituo/qiandan.lpc, d/wudang/sanqingdian.lpc, d/feitian/shulin{,1,2,3,4}.lpc, d/shaolin/wuchang.lpc, cmds/usr/hp.lpc/cmds/imm/hpt.lpc, adm/daemons/chard.lpc, etc.) and two skill files (kungfu/skill/fengyun-shou/foguang.lpc, kungfu/skill/taiji-quan/zhenup.lpc) that do int damage; ...; damage *= at; where at is a float ratio — the exact shape flagged by §7.121/ §7.124 on other libs (nightmare4, ninetears, dsIII). Rather than pattern-match and "fix" ~14 files by inspection, this was verified directly against the live driver: a throwaway test file (/tmp_test_random.lpc, compiled via update, exercised via the admin call command, deleted before committing) confirmed that **on this project's current FluffOS build, assigning/returning a float value through a declared-int local, global, function-return, or compound *= target is silently truncated to a real integer at the point of assignment/return** — e.g. int test3() { float x = 100.0/3.0; return x; } returned 33 (not a float, not 33.333333), and int damage=100; damage *= 3.5; produced 300 (i.e. the float operand itself gets truncated toward int before the multiply — 100*(int)3.5), and a random(damage/2) call immediately downstream of that same contaminated damage did not crash. Live hp/score output on a real freshly registered character also showed clean integer values (潜能...上限 [50000], no stray .000000). This is a materially different result from the (int)-cast-is-compile-time-only reference note and from the other libs' confirmed §7.121/§7.124 instances — most likely because those bugs involved a field assigned a float literal directly with no arithmetic (a compile-time-foldable constant) combined with a declared-float setter/getter pair that re-widened the value after the fact, a combination not present anywhere in haiyang2. Conclusion: checked thoroughly, not applicable here — left untouched. Worth re-verifying with the same direct-eval technique (not just code reading) before "fixing" this idiom on any ES-family sibling, since the fix would be a no-op change on a driver where this already doesn't crash.

§7.122 (autoload/class-marker duplication on reconnect) — checked, structurally NOT vulnerable, and confirmed live

feature/autoload.lpc is this lib's own version of the TMI-2-style "reconstruct untracked-but-persistent items on login" mechanism (also used by real money objects — inherit/item/money.lpc defines query_autoload() too, so a duplication bug here would double actual currency, not just class-marker trinkets). Unlike the confirmed-buggy mortremains/tmi2/es1 shape, restore_autoload() here does self-guard: if (!pointerp(autoload)) return; at the top, and clean_up_autoload() (called at the end of every restore_autoload()) sets autoload = 0, so a second call before a fresh save_autoload() repopulates it is a guaranteed no-op. Traced every real call site of setup()/restore_autoload(): clone/user/user.lpc's reconnect() (the net-dead-recovery path) never calls setup() at all — confirmed by reading it directly, matching round two/three's own independent finding that a real reconnect lands the player back in their pre-disconnect room with zero re-initialization. adm/daemons/logind.lpc's own reconnect() likewise only calls user->reconnect(), never user->setup(). The only other setup() call site touching a live player body is cmds/usr/quit.lpc's "return from a possessed body" branch (link_ob->is_character()), an unrelated niche mechanic, not the ordinary reconnect path.

Verified live with the admin account (fluffos): cloned one /clone/family/jade marker item and one /clone/money/gold (both query_autoload()-tracked) into inventory, then (1) a real quit + fresh relogin — inventory showed exactly 1 jade + 1 gold, not 2; (2) an abrupt disconnect (killed the raw socket process, no quit sent) + reconnect — fluffos's own low combat_exp triggered the fast 1-second user_dump(DUMP_NET_DEAD) force-quit-and-save path (same mechanic round two documented), landing on a genuinely fresh restore()+setup() cycle on relogin — inventory again showed exactly 1 jade + 1 gold. No duplication in either case. Not applicable — this lib's self-clearing guard already closes the exact gap es1 (its own direct ancestor!) was found vulnerable to; worth noting for the wider ES-family sweep that this specific fork does NOT need the es1/tmi2/mortremains fix.

Remaining 10 patterns — checked, not applicable (no live/structural instance found)

Live playthrough summary

New test character qintester/秦测风 (3-character Chinese name, gift 0/random, password Test123, matching this lib's documented password policy). One continuous session: registration → look/ score/i (clean) → wg→north→west→west to 西练武场 → da muren zhuang safe-sparring (学习潜力 99→100, confirmed via score before/after) → bai xingxiu dizi sect-recruiter gate (correctly rejected, matching round two) → clean quit (debug.log grepped immediately after: no log/debug.log file exists at all, i.e. zero runtime errors logged this entire session) → real wall-clock gap (~5 minutes) → reconnect: landed in the correct saved startroom (welcome room), score showed 学习潜力=100 persisted correctly → admin summoned into /d/city/nandajie1 and fought the real (not admin-boosted) 小混混/hunhun (combat_exp 200 vs. a brand-new character) — lost the fight, correctly caught by the documented "新手不死" newbie-protection rescue (combat_exp < 2,500,000, white-light rescue to /d/pingan/guangchang, 上次死因 stayed "无" since a rescue isn't a real death) — confirmed via score post-rescue. log/debug.log still did not exist after the combat sequence — zero errors end to end.

Process hygiene

Native driver, port 40057, PID 1708062 (confirmed via readlink -f /proc/1708062/cwd before every teleport/call action and immediately before the final kill). Three raw-socket Python sessions used throughout (admin fluffos/Mud@2026, test character qintester, plus a brief second admin reconnect to exercise the abrupt-disconnect autoload test); all quit cleanly before the driver was killed by exact PID. The throwaway /tmp_test_random.lpc diagnostic file (used only for the §7.121/§7.124 empirical test above) was deleted before committing — git status never showed it as untracked at commit time. Runtime churn from testing (data/login/f/fluffos.o, data/user/f/fluffos.o, data/npc/menpai.o/menpai1.o — the same unrelated periodic sect-of-the-day daemon effect prior rounds noted) reverted via git checkout; test character qintester's save files (data/login/q/qintester.o, data/user/q/qintester.o) deleted, not committed. git status --short confirmed clean except the one intentional feature/dbase.lpc fix before committing.

深度功能测试(§10.7,2026-10-04)— negative range ends

scripts/lpc_fix_negative_ranges.py: 5 line(s) in 2 file(s) count a range from the end with < (x[a..-1] -> x[a..<1], x[-2..] -> x[<2..]; KB 06 §7.209). In this driver a negative constant end gave "" / ({ }) and a negative start the whole value, so each of these returned the wrong slice; the compile warning A negative constant as the second element of arr[x..y] is gone from the files that compile. Range lvalues (s[0..-1] = text, the prepend idiom) are left alone. A paired compile of the changed files at HEAD and in the working tree showed no new error.

深度功能测试(§10.7,2026-10-05)— here-document terminators

scripts/lpc_fix_heredoc_terminator.py --all --apply: 2 here-document block(s) repaired (2 with the terminator glued to the last text line, 0 with it indented), so End of file in text block no longer hides the room or object behind them: d/tiezhang/taijie-2.lpc, d/dali/yuxu/yuxuguanmen.lpc. The terminator must start its line (the lexer's own test, KB 04 §6.10); a glued one lost its newline in the GBK -> UTF-8 conversion together with a half character, which stays out.

深度功能测试(§10.7,2026-10-04)— paths named in another case

The archive comes from a case-insensitive host: files are stored as BAGUA0.lpc, longjing.C, d/SHAOLIN/, the code names them in lower case (__DIR__ "bagua0", "tea/longjing", "/d/shaolin/..."), so the rooms, NPCs and items behind them could not be loaded (a .C file is not an .lpc file; KB 03 §4.5). scripts/lpc_case_paths.py --convert --apply haiyang2: 185 file(s) renamed to lower case (.C -> .lpc, .H -> .h) and 0 path reference(s) in 0 file(s) rewritten to the new spelling; 0 skipped, 0 ambiguous. Load check of the 204 objects that were renamed, edited or name a renamed path (HEAD against the working tree, an object is the same under any spelling of its path): 21 PASS -> 198 PASS, 0 that loaded before fail now, 177 more load.

185 files moved (139 .C, 41 .lpc, 4 .h, 1 .H); mostly under d/sdxl/heizhao (64), d/sdxl/baihuagu (48), d/city/npc (10), u/hxsd/long (2). A rescan with scripts/lpc_warnings.py haiyang2 is still due: the renamed files are compiled for the first time.

深度功能测试(§10.7,2026-10-05)— room base survives a raising NPC

scripts/lpc_resilient_room.py --apply: make_inventory() of inherit/room/room.lpc loads each inventory object as if (catch(ob = new(file)) || !objectp(ob)) return 0;. new() of a file that is not there returns 0 (the room just lacks that NPC), but new() of a file whose create() raises (a skill file for set_skill() this archive never had, a carry_object() item that is not there, a header that never shipped) raised inside the room and the room did not load at all. The wrong-case renames of this lib (the section above) make the NPCs behind them reachable for the first time, so a room that holds one with such a content gap would have gone from "NPC missing" to "room missing"; now it loads without that NPC, as it did before. The room base compiles and loads with the change (HEAD against the working tree). KB 03 §4.5.

深度功能测试(§10.7,2026-10-05)— 任务表里的 .c 路径与任务守护进程的防护

scripts/lpc_dynamic_rows.py:clone/medicine/dynamic_location、quest/dynamic_location 里 6738 行写的是原档案的 .c 路径。转档时文件已改名 .lpc,而显式后缀按原样解析,load_object("/d/x.c")、new("/d/obj/quest/x.c") 返回 0 且不报错(KB 03 §4.2,KB 06 §7.220),所以 adm/daemons/questd.lpc、p/npc/teamjob.lpc、quest/menpai/teamjob.lpc、u/hxsd/questd.lpc、u/hxsd/questnew.lpc 读到的每一行都加载不出来:随机任务和任务物品从未生成,在 create() 里直接对结果调用 tar->set() 的版本还会崩溃,连带加载它的 cron 守护进程和 give 的任务钩子。这些行已改为 .lpc;另有 10 行指向的文件两种后缀都不存在(档案缺内容),保持原样。 scripts/lpc_dynamic_quest_guard.py:3 处 spread_quest() 调用(init_dynamic_quest() 的循环里)包进 catch(),缺失的任务物品或编译不过的房间只跳过那一个任务,守护进程照常加载、其余任务照常生成。 验证:adm/daemons/questd.lpc、p/npc/teamjob.lpc、quest/menpai/teamjob.lpc、u/hxsd/questd.lpc、u/hxsd/questnew.lpc和 cron 守护进程在新进程里加载(HEAD 一次,工作树三次,选房是随机的):HEAD 通过 6 个,工作树三次分别通过 6、6、6 个(共 6 个),无回退。

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

房间基类 inherit/room/room.lpc: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、多件 OK。

深度功能测试(§10.7,2026-10-06)— 明教张无忌编译不过

quest/menpaijob/mingjiao/zhangwuji 第 289 行附近的 command("say " + … + " 我教目的在与反抗元兵,现在正在练兵 "); 少了右引号:原文这里是 A3 22(半个全角字符后面紧跟引号),MudOS 按字节读没问题,转 UTF-8 时这一对被当成无效字符连同引号一起丢掉。整个文件因此编译不过,明教的任务 NPC 张无忌(romnpc2/romnpc3 里也会引用他)从未载入。补回引号。hy5、hymud、haiyang2 三个同源库同样修改。

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

1. scripts/lpc_warnings.py --fix 机械修复 2608 个文件,启动警告 508 → 0。HEAD 与修改后各编译一遍改动的文件:2574 = 2574,无回归;全库扫描里多出的 18 个载入失败不在改动文件之中,有编译错误的 71 个文件两边错误相同(其余是 NPC 随机内容造成的载入失败,HEAD 重跑也会变动)。被删局部变量审计 48 处,所在文件都没有 #if,读到的都在注释里或是同名函数调用。 2. 手工:quest/shenshu/npc/man2 的 order、data 各声明两次(后一个生效;前一份在两个 #include 之前,被包含的文件不用它们),删去前一份;多余的 int ask_job(); 原型与定义 string ask_job() 冲突,删去;man/man2 的 kill_ob 改 void,与 feature/attack 一致(函数不返回值)。与 hy5 同一处代码。 3. 验证(新进程,临时副本):GB → fluffos 登录 → look、score、i、quit 正常,驱动无运行时错误。

深度功能测试(§10.7,2026-10-07)— 空的 if (current_water == 0) {}

3 个房间的 valid_leave() 里有一行什么也不做的 if (current_water == 0) {}(编译警告 Expression has no side effects),删去,行为不变:d/xingxiu/nanjiang2.lpc、d/xingxiu/nanjiang3.lpc、d/xingxiu/shanjiao.lpc。

深度功能测试(§10.7,2026-10-07)— 郭靖任务的名单被覆盖

quest/guojob/guo.lpc 先后 #include "zzzjob.lpc" 和 "teamjob.lpc",两个文件都声明全局 mixed names、内容不同(Redeclaration of global variable 'names'),后包含的 teamjob 名单覆盖了 zzzjob 的,"保护"任务的目标其实从 teamjob 的名单里抽。teamjob 的表改名 team_names(KB 04 §6.10)。guo.lpc 在 HEAD 与工作树分别加载(新进程)通过。

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

scripts/lpc_warnings.py haiyang2 --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 10 个 .lpc(多为删去未用的局部变量)。HEAD 与工作树分别加载(新进程):10 -> 10 通过,无回退。启动时警告 0 -> 0;新进程登录(输入名字)正常。

深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查

- 四份 control.lpc 删去与 int auto_check() 不符的两条 void 原型。 - adm/roommaker.lpc 删去被覆盖的 special_props 表。 - clone/box/gboxa.lpc 去掉注释里嵌套的 /*。 - feature/alias.lpc 的 last_time 改名 last_cmd_time,与神医 NPC 的存档字段分开。 - testman、man 的 kill_ob 改 void。 - 改动的 75 个对象 HEAD 与工作树各载入一次,无回退。 - 剩下的警告都在编译不过的文件里:qiankun-danuoyi.lpc 括号错乱、action.lpc 片段、cheng.lpc、wujincao.lpc。

负数伤害当作消耗(2026-10-15,KB 06 §7.241)

receive_damage("neili", -52)、receive_damage("qi", -12) 之类把 add() 的负数写进了 receive_damage():本库 feature/damage.lpc 对负数伤害报错(或归零),练功/运功时报 F_DAMAGE: 伤害值为负值 或消耗失效。已改为正数伤害,内力/精力类改为 add(type, -N)(scripts/lpc_fix_damage_calls.py):/kungfu/skill/shaolin-tantui。

东方故事/西游记体系的技能文件(2026-10-15,KB 06 §7.241 第三类)

这些 kungfu/skill 文件来自 gin/kee/sen/force 体系,本库人物只有 jing/qi/neili:receive_damage("kee", 30) 报 伤害种类错误,query("force") 永远为 0,技能学不了也练不了。按 kee→qi、gin/sen→jing、force→neili、max_force→max_neili、force_factor→jiali 改了属性名(scripts/lpc_port_es_props.py),共 12 个文件。

其他体系的属性名(2026-10-15,KB 06 §7.241 第三类,--any-file)

另有 9 个文件(绝招、状态、物品、NPC 等,不限于 kungfu/skill)来自 gin/kee/sen/force 体系,同样按 kee→qi、gin/sen→jing、force→neili 改名(scripts/lpc_port_es_props.py --any-file)。

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

receive_heal()/receive_curing() 也只接受本库的种类。1 个文件(多为只调用 receive_heal/receive_curing 的药物、NPC)来自其他体系,按本库的属性名改名(scripts/lpc_port_es_props.py --any-file)。

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

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

family 映射(再补)(2026-10-15,KB 05 §7.30)

mapping fam; fam = (mapping)ob->query("family");(先声明后赋值)和泥潭式的 query("family", me) 同样直接取下标:无门派的新人拜师(attempt_apprentice/recognize_apprentice)、NPC 的 greeting、房间的 valid_leave、ask_me 等报 Value being indexed is zero。17 处补上 if (!mapp(fam)) fam = ([ ]);(刚 recruit_apprentice() 之后的除外)。

空手使用兵器绝招(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):tmjian/jianshen、wuyue-blade/jiao。

把经验、属性、内力当技能读(2026-10-15,KB 06 §7.247)

经验、属性、内力上限不是技能,xkx/ES 的 query_skill() 对它们只返回 0(风云的还会去载入同名技能文件而报错)。改为 query("...")(scripts/lpc_fix_pool_as_skill.py):屠龙刀法 yitian-tulong/tulong(比较经验时目标一侧读成 0)。

妙手回春的银针检查(2026-10-15)

miaoshouhuichun 的 valid_learn() 用了未赋值的 object ob,ob->query_temp("weapon") 每次都报错,谁也学不了;还把兵器对象和字符串 "银针" 比较。照 chidi 的修法改为 ob = me->query_temp("weapon"),再用 id("yin zhen")/id("yinzhen") 判断是否银针。

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

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):buddhsim→buddhism(qianye-shou);douzhuan-xignyi→douzhuan-xingyi(/d/mr/obj/shu1);hujia-dao→hujia-daofa(daojian-guizhen);jinwu-dao→jinwu-daofa(jinwu-daofa);lenyue-shengong→lengyue-shengong(lengyue-shengong);yinyun_ziqi→yinyun-ziqi(yinyun-ziqi/cure);zhengqi-jue→zhengqijue(/d/bwdh/human)。

嵩山剑法「蓄力」没穿布衣时报错(2026-10-15)

songshan-jian/xuli 用 cloth = me->query_temp("armor/cloth") 取衣服,随后直接 cloth->query("material") 判断要不要显示“衣袖鼓起”的描写;身上没有布衣时(裸身或只穿铠甲)这里报错,而此时 apply/speed 已经扣掉、复原的 call_out 还没排上,身法就一直少了一截。两处判断前补 objectp(cloth) &&。

技能名拼错:技能文件里拼错自己的名字(2026-10-15,KB 06 §7.249)

技能自己的文件(或它的绝招目录)里,query_skill() 读的是差一个字母的自己的名字,永远得 0:“基本功夫要高过本技能”之类的限制从不生效,按等级取招、计算伤害的那一项一直是 0。改为本技能的名字(scripts/lpc_fix_skill_typo.py 的 self 判据):chanyun-tui→chuanyun-tui(chuanyun-tui/shuangjue);xiyu-tiezhang→xiyu-tiezheng(xiyu-tiezheng)。

银索金铃「点穴」对空手的对手报错(2026-10-15,KB 06 §7.246)

yinsuo-jinling/dian 失手时三种描写之一是“真气贯注”加对手兵器的名字,对手空手时 ob 为 0,三次失手有一次报错。改为 (objectp(ob) ? ob->query("name") : "全身")(泥潭一系原本就写作“真气贯注全身”)。

family 映射内联取下标(2026-10-15,KB 05 §7.30)

ob->query("family")["master_name"]、me->query("family")["family_name"](泥潭式为 query("family", ob)[...])直接对门派映射取下标:没有门派的人(新玩家、叛师者、大多数 NPC)在 NPC 的打招呼和问话、绝招的门派判断、房间的 valid_leave 里报 Value being indexed is zero,而不是得到拒绝。5 处(4 个文件)改为 dbase 路径查询 query("family/master_name"):有门派时值相同,没有时得 0;原有的判断照常生效(scripts/lpc_fix_family_index.py)。给映射赋值的写法未动。

存档读不出来:字符串里的换行、空行(2026-10-15,KB 03 §4.4)

clone/gift/npc/bingqipu.o:字符串里有原样的换行(save_object() 存成 \r,restore_object() 逐行读,原样的换行让字符串收不了尾),或有空行(Illegal file format),读档的对象(留言板、NPC、玩家)从空白开始。换行改写成 \r、删去空行(scripts/lpc_save_encoding.py --from-raw),驱动核对全部能读回(scripts/lpc_save_restore_check.py)。

移进不存在的巫师房间(2026-10-15,KB 06 §7.251)

用完的物品、要“离开”的 NPC、掷在空中的兵器被移进巫师的工作室(/u/wind/),本库没有这些房间。本库的 move() 用 call_other(dest, "???") 载入目的地,找不到文件就报 call_other() couldn't find object,后面的语句都不执行:被雇来打晕或杀人的李凌(d/jingzhou/npc/liling2)办完事要离开时报错,留在原地。1 个文件改为移进本库的虚空房间 VOID_OB(/clone/misc/void),与原作把它们“停放”在巫师房间的用意相同。

燃木刀法「焚天」的效果一秒就消失(2026-10-15,KB 06 §7.246)

每秒检查的 check_fight() 声明了 object weapon; 却从不赋值,条件里的 !weapon 永远为真,效果在第一次检查时就被撤掉。把这一项改为 !objectp(weapon = me->query_temp("weapon")),先存兵器再判断刀的种类。

NPC 带着本库没有的物品,载入失败(2026-10-15,KB 06 §7.73)

3 个文件、3 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/clone/obj/demon_staff、/d/city/obj/tudao、/d/guanwai/obj/cwbdao)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 2 个(2 个原来载入不了),没有退步;其余 1 个另有原因(多为 set_skill() 了本库没有的武功)。

kungfu/skill/qingyi-jian 第一式的 skill_name 丢了收尾引号(2026-10-18,KB 03 §4.6)

"skill_name" : "大悲式, 的收尾引号没了(转码时跟半个汉字一起丢了),字符串连到下一行,damage_type 成了代码:Illegal character 0xe5,整个武功载入不了。补回 ",(同系的 bixiecanyang、syxjl 等库里有完好的同名文件)。载入:通过。

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 载入不了,刷出它的房间也少一个人。本库有 8 个对象设了本库没有文件的武功(4 个名字,最多的是 shenlong-quanfa、xingxiu-duzhang、meiren-sanshi)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:8 个里 8 个通过。(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 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 80 处、63 个文件:bixie-sword→pixie-sword(32 处)、kunlun-shnfa→kunlun-shenfa(17 处)、shaolin-shnfa→shaolin-shenfa(12 处)、tiaji-jian→taiji-jian(4 处)、lingbo-weibu→lingboweibu(3 处)、yunu-xinfa→yunv-xinfa(3 处)、yan-fly→yanfly(2 处)、meimv-quan→meinv-quan(1 处)等共 14 个名字。(scripts/lpc_fix_skill_names.py)

feature/damage.lpc 丢了 set_ghost()(2026-10-19,KB 06 §7.262)

feature/damage.lpc 里有 int ghost 和 is_ghost(),没有 set_ghost(int i);木人、狼等 NPC 在 create() 里调用它,整个 NPC 报 Undefined function set_ghost 载入不了。从 xkx100 补回这一行函数,受益 NPC 9 个:d/nanshaolin/npc/muren-fighter1、d/nanshaolin/npc/muren-fighter2、d/nanshaolin/npc/muren-fighter3。(scripts/lpc_transplant_function.py)

全局头文件丢了 2 个宏(2026-10-19,KB 04 §6.16)

/include/globals.h 里没有定义 EVENT_DIR、MAILDIR,用到它们的物件(名单里 2 个)编译时报 Undefined variable/Undefined function,整个物件载入不了(编译器在第一个未定义的名字上停下,后面的错误和警告都看不到)。同系的库在各自的头文件里都有这几行:照其中和本库共有文件最多的那个库,原样补到 /include/globals.h 最后一个 #define 之后,宏体里出现的路径在本库里都存在。名单里的物件除了这几个宏之外还有别的错误,暂时仍载入不了。(scripts/lpc_restore_defines.py)

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

adm/daemons/rankd.lpc 的 query_respect()、query_rude()、query_self()、query_self_rude() 里 4 个函数开头补上 if (!objectp(ob)) ob = this_object();。NPC 在 create() 里写 "…" + RANK_D->query_respect(this_player()) + "…"(inquiry 对话、chat_msg),驱动自己跑 reset() 时没有人在场,this_player() 是 0。旧驱动里 0->query("age") 返回 0,称呼走默认分支;新驱动直接报 Bad argument 1 to EFUN call_other(),房间的 make_inventory() 吞掉错误,被打死的 NPC 再也刷新不回来。用 scripts/lpc_reach_census.py --walk 找到(xkx100 的武当弟子、一灯、汴梁书生…);修在守护程序里,不动 1,800 个 NPC 文件。

两个斜杠开头的物件路径(2026-10-19,KB 06 §7.276)

1 个文件、1 处物件路径写成 //…(如 carry_object("//clone/cloth/hic_cloth")):/d/gaoli/npc/xiake.lpc。当年的驱动不介意(路径先被规范化);新驱动把原样的字符串交给 master 的 valid_read(),securityd 按 //clone 查目录,NPC 的 euid 没有权限,报 Read access denied.,NPC 载入不了。改成一个斜杠。

玩家打造兵器模板没有打造者时(2026-10-19,KB 06 §7.275)

/d/npc/m_weapon/weapon/m_blade.lpc、/d/npc/m_weapon/weapon/m_club.lpc、/d/npc/m_weapon/weapon/m_hammer.lpc、/d/npc/m_weapon/weapon/m_staff.lpc、/d/npc/m_weapon/weapon/m_sword.lpc、/d/npc/m_weapon/weapon/m_whip.lpc(共 6 个)的 create() 开头 me = this_player(); 之后无条件读 me->query("weapon/name") 等打造数据。房间刷新时没有人在场(me 为 0),或者 NPC 在别的 NPC 的命令环境里 carry_object() 这个模板(NPC 的 weapon 属性是文件名字符串),旧驱动里读出来是 0 或字符串,switch 照样落到 default;新驱动报 Bad argument 1 to EFUN call_other() / Bad argument 1 to switch,NPC 和它所在的房间载入不了(--walk 普查里一个玩家走进房间就触发)。在 //读入数据 之后加 if (!me || !mapp(me->query("weapon"))) { setup(); return; }:没有打造者就留一个没有名字的空模板,玩家打造的流程不变。

补回丢失的标点(2026-10-19,KB 03 §4.6)

转换时丢掉的分号、逗号、引号或文件末尾的 },共 6 个文件,修前 HEAD 载入失败。别的库里同路径的完好副本只比这里少几个标点,按它补回(括号里是那个库):/d/baituo/npc/obj/bowl.lpc(cctx)、/d/baituo/obj/bowl.lpc(cctx)、/d/city2/npc/zhengmen.lpc(jqxz2008)。一行文字在逗号前丢了结尾的引号(下一行是同一个表的下一项),补上:/kungfu/skill/awenzhang-unarmed.lpc、/kungfu/skill/emperor-unarmed.lpc、/kungfu/skill/kunlun-zhang.lpc。修后与 HEAD 逐个对比载入:这 6 个物件全部载入,没有退步(scripts/lpc_restore_from_twin.py、lpc_fix_eaten_quote.py --comma、lpc_fix_eof_braces.py)。

补回标点后新载入的物件里的未使用局部变量(2026-10-19,KB 04 §6.10)

之前载入失败、补回丢失的标点后才载入的物件里还有未使用的局部变量警告,共 1 个文件:/kungfu/skill/kunlun-zhang.lpc。删掉没有用到的声明;用 scripts/lpc_listcheck.sh 对比 HEAD 和改后,物件仍然载入,没有警告。

== !"字符串" 多出来的 !(2026-10-19,KB 06 §7.280)

!"taoist" 是整数 0,x == !"taoist" 就是“x 等于 0”,作者为其余所有人写的那一支永远走不到。具体是:龙女的 class 判断永远走不到“不是道士”那一支;龙女的 class 判断永远走不到“不是道士”那一支。全部改成 != "字符串"(scripts/lpc_fix_not_literal_cmp.py):/d/gumu/npc/longnv.lpc(3 处)、/quest/menpaijob/gumu/zf/longnv.lpc(3 处)。同样的行在 xkx 一系的 35 个库里都有。

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/changan/ca2.lpc(1 处)、/d/mingjiao/shanmen.lpc(1 处)、/d/sdxl/baihuagu/yangfeng.h(1 处)。另有 /d/city/npc/majiang.lpc(2 处)、/d/jinan/npc/mj.lpc(2 处) 的条件下面做的是别的事(== 和 || 也可能是本意),保持原样。

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/arch/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,共 37 处);其后 x == "a" && x == "b" / x == "a" || "b" 这类被 == 暴露出来的链也随之改成 || 或补全变量(/d/hainan/npc/xiaohe.lpc)。改动:/adm/daemons/combatd.lpc(1 处)、/clone/npc/man.lpc(1 处)、/d/city/npc/matong.lpc(3 处)、/d/city/shufengguan.lpc(1 处)、/d/city4/mj/1mj.lpc(3 处)、/d/city4/mj/mj.lpc(3 处)、/d/city4/npc/majiang.lpc(3 处)、/d/city4/npc/matong.lpc(3 处)、/d/city4/obj/mj.lpc(3 处)、/d/city4/puke-room.lpc(4 处)、/d/hainan/npc/xiaohe.lpc(3 处)、/d/hangzhou/npc/meipo.lpc(1 处) 等 19 个文件。

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

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

mcheck:query_ip_rport() 驱动里没有(2026-10-20,KB 06 §7.291)

指令要显示连线的远端端口,MudOS 时代的 query_ip_rport() 在 FluffOS 里不存在(query_ip_port() 给的是本地端口),指令编译不过。文件里加了一个返回 "?" 的占位函数(dtsl 的同名指令早就这么做)。改动:/cmds/imm/mcheck.lpc。

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

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

目录读出后的后缀截取宽度(2026-10-20,KB 06 §7.296)

.c 改名成 .lpc 之后,get_dir("*.lpc") 读出的文件名还按两个字符截取($1[0..<3]):baguadao.lpc 变成 baguadao.l,用它拼出的路径加载不到对象(故事、战场、事件、帮助、自造物品数据、召唤列表等)。改成 [0..<5](scripts/lpc_fix_getdir_strip.py)。改动:/adm/daemons/eventdbak.lpc(1 处)。

sefun cat():缺文件时 write(0) 报错(2026-10-20,KB 06 §7.297)

void cat(string file) { write(read_file(file)); }:文件不存在(压缩包没带的欢迎/公告文件,或只有运行中的 mud 才会写的日志)时 read_file() 回 0,write(0) 在本驱动里报 Bad argument 1 to receive(),调用者(登录对话、cat 指令)被中断;老驱动什么也不打印。改成 write(read_file(file) || "");(scripts/lpc_fix_cat_unchecked.py)。改动:/adm/simul_efun/file.lpc。

访客计数 howmany_visitor() 读文件不检查(2026-10-20,KB 06 §7.301)

howmany_visitor() 读访客计数文件(VISITORS / COUNTER / BBSVISITOR,只有运行中的 mud 才会写;.gitignore 把它排除在仓库之外),新检出的树里没有,read_file() 回 0,sscanf(0, ...) 报错。这份副本登录时不一定走到它,但调用时同样会出错。在读文件之后加了 if (!stringp(content)) return 0;(xkx100、xiakexing100、zzfy 早先的写法,scripts/lpc_fix_visitor_count.py)。改动:/adm/daemons/logind.lpc。

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])) ...,不检查列表是否为空。本库有 12 个 NPC 文件写成 set("chat_msg", ({ }))(或 chat_msg_combat)并带正的 chat_chance(例如 /d/city/npc/lulixian.lpc、/d/zangbei/npc/archer.lpc),每次心跳有 chat_chance% 的概率 random(0) 为 0、msg[0] 报 Array index out of bounds(驱动捕获,心跳继续,但每次一条运行时错误),这个 NPC 在内存里就一直发生。在 random(100) < chance 前面加 sizeof(msg) &&(scripts/lpc_fix_chat_empty.py;if (random(100) > chance) return 0; 的写法是 !sizeof(msg) ||)。改动:/clone/guard/baoer.lpc、/clone/guard/long2.lpc、/clone/npc/npc.lpc、/clone/npc/npc1.lpc、/d/chengdu/npc/npc1.lpc、/d/chengdu/npc/npc2.lpc(等共 31 个文件)。

存档里的 .c 文件名改成 .lpc(2026-10-21,KB 03 §4.2 第 9 项)

存档里写着 "/d/city/npc/wei.c" 这样的文件名;extract 时源文件已经全部改成 .lpc,而驱动对带扩展名的路径只认字面,new() / load_object() / move() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 1 个存档共 51 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/data/o/orchid.o(51 处)(共 1 个存档)。

lsusers:用档案里没有的 /clone/misc/player(2026-10-21,KB 06 §7.299)

管理员扫描里 lsusers 报 Bad argument 1 to EFUN call_other() Got: int(0)(cmds/adm/lsusers.lpc):指令 new("/clone/misc/player") 一个对象,用它的 new_object(name) 逐个还原玩家存档;本库没有 /clone/misc/player.lpc(同系的 hy3、jhfy、xyzx 等 27 个库带着它,new_object() 只定义在那里),new() 答 0,后面就是对 0 的调用。new() 之后加 if (!ob) return notify_fail(...),指令回一句话而不是系统错误;文件本身补不上,那个旧的玩家对象已经不是本库的用户对象了。

blockade / recover:data/keep 不存在,解封之后世界仍然锁着(2026-10-21,KB 06 §7.340)

管理员扫描里 blockade 报 Error copying file from /data/user/f/fluffos.o to /data/keep/fluffos, Error: No such file or directory,随后的 recover 在第一个 cp() 上死于 */data/keep/fluffos: lstat failed,来不及执行最后的 LOGIN_D->set_madlock(0):封锁解不开。data/keep/ 是运行时目录,git 克隆里没有空目录。blockade 在第一次写之前 if (file_size("/data/keep") != -2) mkdir("/data/keep");(mkdir() 失败答 0,不抛错),recover 对没有保存档的玩家 if (file_size(src) < 0) continue;,其余照常恢复、照常解锁(scripts/lpc_fix_keep_dir.py,hy5 上实测:没有保存档时 recover 答“时空解封完毕”,blockade 建出 data/keep 并保存,recover 取回并删除)。改动(2 个文件):/cmds/arch/blockade.lpc、/cmds/arch/recover.lpc。

selfthinking、qlian:缺乏实战经验的提示读了不存在的老师(2026-10-21,KB 06 §7.348)

自创武功的自习指令在“技能等级³/10 大于实战经验”时打印 printf("也许是缺乏实战经验,你对%s的回答总是无法领会。\n", ob->name())。这一句是从 learn 抄来的,ob 是老师;自习没有老师,object ob; 从没赋值,ob->name() 抛 call_other() ... Got: int(0)。改成 你对「%s」的疑问总是无法领会 加 to_chinese(skillname),去掉没用的 object ob;。改动:/cmds/skill/selfthinking.lpc。

黄蓉 ask_husband:找优胜者的那段被注释掉,user 没赋值(2026-10-21,KB 06 §7.348)

ask rong about 丈夫 对谁都抛 Bad argument 1 to call_other() ... Got: int(0):优胜者(nvxu->query("winner"))不是提问的人时,原来先 find_player(id),找不到就 new(USER_OB, id)->restore() 把离线的人读出来;这段被注释掉,下面的 user->query("death_count") 照用。默认没有优胜者(winner 为 0)。改成只找在线的优胜者(user = stringp(id) ? find_player(id) : 0),没有优胜者或优胜者离线时直接回答“我的丈夫就是”加女婿 NPC 的名字。hy5 上实测:HEAD 抛错,改后 “黄蓉说道:我的丈夫就是精确!”。改动:/kungfu/class/taohua/rong.lpc。

xue:禁学技能的检查读了还没有赋值的 ob(2026-10-21,KB 06 §7.348)

cmds/skill/xue.lpc 在解析参数、找老师之前就判断 ob->query_temp("no_kill") || userp(ob),ob 是 0,进来就抛错(lpcc_bare.py:HEAD Bad argument 1 to EFUN call_other(),改后正常;游戏里 xue 这个动词在 hy5 上走的是 learn,敲 xue 看不出来,直接调用文件才暴露)。同一段在 learn.lpc 里没有 ob。加 objectp(ob) &&,效果和 learn.lpc 一样(这一段在解析之前永远不触发)。改动:/cmds/skill/xue.lpc。

haiyang2 enter_world():自动二转/三转写给了没赋值的 obj(2026-10-21,KB 06 §7.348)

实战经验到 5000 万(三转 6000 万)还没转生的角色登录时,obj->set("guard/flag", 1); obj->set("tforceok", 2); obj->set("zhuanshen", 1); 抛错,登录失败;同一个文件后面几处同样的写法都是 user->set(..),obj 声明了没赋过值。5 处 obj->set 改成 user->set,去掉 object obj;。改动:/adm/daemons/logind.lpc。

妙手回春 valid_learn():先读 ob->query_temp("weapon") 再给 ob 赋值(2026-10-21,KB 06 §7.348)

学这门暗器技能时 valid_learn() 第一个武器检查读的是没赋值的 ob,xue / learn 在这里抛错;后面 ob = me->query_temp("weapon") 的检查才是对的。有两种写法:if (!ob->query_temp("weapon")) 改成 me->query_temp("weapon");if ((string)ob->query_temp("weapon") != "银针")(拿一个对象和字符串比较,永远不等,补上 ob 之后这门技能也学不成)改成后一种写法里的 !objectp(ob = me->query_temp("weapon")) || !(ob->id("yin zhen") || ob->id("yinzhen"))。改动:/d/pingan/pingan/shenzhen/skills/miaoshouhuichun.lpc、/d/pingan/shenzhen/skills/miaoshouhuichun.lpc、/kungfu/skill/miaoshouhuichun.lpc。

守卫 clone/guard/baoer.lpc:do_send() 里丢了收尾引号,整个文件从来没编译过(2026-10-21,KB 03 §4.6 第五轮、KB 06 §7.349)

new_id=ob->query("id")+"'s bao er; 少了字符串结尾的引号:字符串一直读到下一行的引号,后面的汉字全成了 Illegal character 0xe5(几千行错误,批量验证器里的表现是“没有答案”)。补成 "'s bao er";。文件第一次能编译后带出 11 个 Unused local variable 警告(host_id、i、obj、shape、emotes、inv、msg、same_place、couple_id、couple_name、couple),按 KB 04 §6.10 删掉。文件里的 do_heji / do_lian / do_order / do_title / do_ticks / do_sha / do_shape / do_hulian / do_quit 等处理函数都有定义(静态扫描因为引号没闭合把它们当成字符串里的文字,补引号后运行时 function_exists() 全部证实存在),所以这个文件的 add_action 不属于死登记,没有动。改动:/clone/guard/baoer.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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 70 条注册(58 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/clone/medicine/nostrum/gaoyao.lpc(do_eat)、/d/baling/npc/zhou.lpc(do_redeem)、/d/beijing/npc/guanfuzi.lpc(do_join)、/d/beijing/npc/xutianchuan.lpc(do_skills)、/d/changsha/npc/qiao.lpc(do_redeem)、/d/chengdu/npc/obj/nostrum/gaoyao.lpc(do_eat)、/d/city/leitai.lpc(do_quit)、/d/city/npc/ake.lpc(do_pawn)、/d/city/weiqishi.lpc(do_pass、do_resign)、/d/city4/qishi.lpc(do_pass、do_resign) 等共 58 个文件。最多的函数名:do_send 15 处、do_redeem 12 处、do_qiecuo 11 处、do_quit 9 处、do_vendor_list 4 处。

基类的 add_action 登记:处理函数只在后代里定义,基类自己被加载时报错(2026-10-21,KB 06 §7.349)

房间、NPC、公告板这类基类,init() 里登记的 add_action("函数", "动词") 的函数只由继承它的后代定义;基类对象自己被直接加载(房间可以走进去、NPC 可以克隆)时,访客输入这个动词,驱动报 *Function for verb 'x' not found.(第一轮扫描把这些当成“有后代定义,是活的”放过了,scripts/lpc_verify_handlers.py 在基类对象上跑 function_exists() 才看出来)。登记前加 if (function_exists("函数", this_object(), 1))(第三个参数 1:默认 function_exists() 把 protected / private 函数当作没有定义),后代照常登记。共 6 处、1 个文件(scripts/lpc_fix_dead_add_action.py --guard):/d/bwdh/kantai.lpc(do_abandon、do_practice、do_study)。

对象路径里的 ..:new(__DIR__ "../obj/x") 报 Read access denied(2026-10-22,KB 06 §7.351)

__DIR__ 是当前文件所在目录(带结尾斜杠),__DIR__ "../obj/x" 就是 /d/zhiye/npc/../obj/x;驱动的 legal_path() 不接受带 .. 的路径,new() / load_object() 直接抛 *Read access denied.(实测,find_object() 对它答 0)。走库自己的 carry_object() / 房间 objects 表的路径,库的基类会先规范化,能用;直接把路径交给 new() / clone_object() / load_object() / move() 的不行,调用到那一步才报错。改成规范化后的绝对路径(scripts/lpc_fix_dotdot_dir.py --direct):/d/wudang/npc/xiaocui.lpc(__DIR__ "../obj/nuerxiang" → "/d/wudang/obj/nuerxiang")。

商人的 list 命令:登记的是另一个血统的函数名(2026-10-22,KB 06 §7.349)

第 42 批把 add_action("do_vendor_list", "list"); 当成死登记删掉了(这些文件里没有 do_vendor_list,输入 list 只会得到 *Function for verb 'list' not found.),但它们的商人基类里同一个工作的函数叫 do_list,list 动词就此没了:/d/death/npc/waiter.lpc, /d/jinling/npc/armorseller.lpc。按真正存在的函数名放回 add_action("do_list", "list");;用库的管理员账号克隆 NPC 再输入 list 实测(/d/death/npc/waiter.lpc:您想看哪一种货物(armor, book, misc, weapon)? ⊙),每个文件都列出商品或问要看哪一类。同一批里放回去后仍答 什麼?、没有商品或者列表时报错(货品文件缺失)的商人没有动。

缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

new(路径)->move(...); / carry_object(宏("x"))->wear(); 这类语句,路径指向的文件在这个压缩包里没有:new() 返回 0,->move() 在 0 上报 Bad argument 1 to EFUN call_other()。在 create() 里这个对象永远加载不了,在 reset() 里第一个缺失物品之后的刷新每次 reset 都被跳过。2 个文件里 3 处(new)改成 { object new_ob = new(路径); if (new_ob) new_ob->move(...); }(scripts/lpc_guard_carry_object.py --new:路径按这个文件自己看到的宏解析,宏名冲突、虚拟对象路径一律跳过)。 对这 2 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 2 个,现在 2 个(新增可加载 0 个,回归 0 个)。

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

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

(: 函数 :) 指针指向对象自己没有定义的函数(2026-10-22,KB 06 §7.355)

item_desc / inquiry / chat_msg_combat 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:编译不报错,用到时驱动报 *Undefined lfun pointer called: 函数名(look 某物、ask NPC about 话题 直接出错,战斗 / 闲聊里的指针在 NPC 的 chat() 里出错,这一拍心跳的后半段被跳过)。scripts/lpc_scan_callout_targets.py --kinds=lfun。 已补回 /d/tanggu/kongchangdi.lpc 的 look_leitai()(原型还在、函数体丢了;bxsj 里同一文件有完整函数,原样补到文件末尾,scripts/lpc_port_function.py --lfun;真机会话里 look leitai:HEAD 报上面的错误,改后正常输出)。 还剩 23 个函数没有补(inquiry 13, item_desc 6, other 3, chat_msg 1):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/cmds/imm/help2.lpc:confirm_l_choice、/d/beijing/npc/yi.lpc:ask_jiaobiao、/d/city/shuyuan2new.lpc:look_shelf、/d/city/shuyuan3.lpc:look_table、/d/city/ximenroad.lpc:look_gaoshi、/d/dingxiang/npc/chunyuwei.lpc:do_action1、/d/dingxiang/npc/ke.lpc:do_action1、/d/dingxiang/npc/kehan.lpc:do_action1、/d/gaochang/zoulang2.lpc:look_writing、/d/gaochang/zoulang_1.lpc:look_writing 等另外 13 个。

读不到的留言板:"/clone/board/x"->foo(); 在 create() 里报错(2026-10-22,KB 06 §7.357)

xkx 系的房间在 setup() 之后写 "/clone/board/gaibang_r"->foo(); 来加载留言板(foo() 本来就没有定义,只为了触发加载)。这个库没有这个文件,call_other() 报 *call_other() couldn't find object '/clone/board/gaibang_r',对象留在内存里、load_object() 却抛错:玩家第一次走进房间就看到错误,写在 create() 中间的还会切掉后半段。scripts/lpc_fix_missing_foo.py 把 1 条单独成行的语句(/clone/board/jail_b;/d/city2/jail.lpc)注释掉并写明原因(别的房间里同样的语句本来就是作者自己注释掉的)。证明和 45 个库的整体加载检查见 KB 06 §7.357。

log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)

这个库的代码往 log/ 的 2 个子目录写日志:log/html(clone/npc/robot-judge.lpc)、log/test(d/hangzhou/obj/jiasha.lpc)。压缩包、git clone 和站点包里都没有这些目录(本地启动没有写过它们,gen_keep_dirs.py 没记),write_file() 对不存在的目录抛 *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory",调用它的函数在写日志那一行中断,后面的奖励、标志、步骤都没有执行。本库的 log_file() 会自己建目录(实测,或读源码:nitan 系的 assure_file()),所以这里只记了直接用 write_file() / save_object() 写的目录。把 log/html、log/test 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。

(: 函数名 :) 指针指向本库没有的函数:只含这个指针的行加了注释(2026-10-22,KB 06 §7.355 补充)

inquiry / item_desc / chat_msg* 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:用到时(ask、look、NPC 闲聊)驱动抛 *Undefined lfun pointer called: 函数名,这一拍心跳的后半段被跳过。这些函数没有可以补回的来源(别的库同一路径的文件里没有,或者它还要用本库没有的东西),所以 15 个文件里 23 行只含这个指针的行("交镖": (: ask_jiaobiao :), "finish": (: ask_jiaobiao :), "shelf": (: look_shelf :),)加了注释,原文保留并写明原因,这一项当作没有这个话题 / 物品 / 闲聊条目(scripts/lpc_fix_dangling_lfun.py;#include 的文件在库里缺失的对象、指针不在整行上的写法没动)。 前提(function_exists())和加载检查(HEAD 与工作区对比,本库改动的 .lpc 文件)的结果见 KB 06 §7.355:没有一个回归。

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

原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:拜入少林 / 武当 / 峨嵋等派的玩家被改成错误的法名(二字名 张三 变成一个字的 澄,更长的名字取到第三个字);各派掌门的辈分判断(name[0..1] == "澄")永远不成立。

本库 37 处(31 个文件):拼名字的赋值 26 处、与汉字常量比较 9 处、对切片赋值 2 处。例:name[2..3] → name[1..1](/d/biwu/da-mo.lpc:152);name[0..1] → name[0..0](/d/mr/npc/bo.h:17);old_name[0..3] → old_name[0..1](/kungfu/class/taohua/huang.lpc:241)。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,不要重跑。

长度检查按字节写,旁边的提示按汉字写:24 处(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 你好吗)。本库 24 处(12 个文件):if( (strlen(name) < 8) || (strlen(name) > 20 ) ) {(/clone/guard/baoer.lpc:1769);if ((strlen(name) < 8) || (strlen(name) > 20)) {(/clone/guard/long2.lpc:1723)。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 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

血条 / 表情标题按字节算显示宽度(2026-10-23,KB 06 §7.365)

老档把汉字当两字节:bar_string[0..(val * N / max) * 2 - 1] 把「格数」换成字节下标,半格血条在本驱动(按字符算区间)上会整条画满;巫师表情 edemote 的 repeat_string("─", 13 - (strlen(emotion) / 2)) 也会少减汉字宽度。scripts/lpc_fix_display_pad.py:去掉 CJK 血条 / 空白条上的 * 2(jings/qis 赋值和 blank_string3[0..(23-a)] 的 23→11 一并改),表情标题去掉 / 2;ASCII 空格的 none_string / empty_string 仍保留 * 2(一格两个空格)。本库 2 处(1 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。

死巫师出口 gold/dock、sunrise、mei(2026-10-23,KB 06 §7.370)

/d/wanjiegu/road4 东向 /u/g/gold/dock、峨眉寿星居南向 /u/xinyue/sunrise_room、玩家卧室通往 /u/mei/… 均无对应房间。scripts/lpc_fix_drop_dead_u_exits.py 删除这些出口行,并去掉同方向的 create_door(否则 create_door 无出口会炸)。本库 1 处。证明:zxty08nxgbb 的 road4 HEAD 有东向,改后没有;mei_room1 可加载。

family 映射门残留(2026-10-24,KB 05 §7.30 Also)

kungfu/skill/kunlun-zhang.lpc valid_learn:无门派时 fam["family_name"] 报错。同 §7.30 Also 守卫。

深度功能测试(2026-10-27,§6.25 ZHAOSHI 截断玩家技能)

玩家自创武功在 // ZHAOSHI : 0 处截断(EOF),无法加载。按同系完整 moming-blade 补一招 + valid_*/query_*/practice(1):kungfu/skill/rulaizhang-unarmed.lpc。

深度功能测试(2026-10-27,§6.25 zhongling tell_object 引号错位)

d/wanjiegu/npc/zhongling(及同路径副本)的 tell_object("who, 钟灵脱下一双绣花鞋。\n") 把对象参数写进了字符串;改为 tell_object(who, "…")。证明:原文错位;改后加载,语料残渣 0。

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

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

深度功能测试(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… 行。