info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
本游戏自称 海洋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
内容亮点
- 世界观以江湖门派、拜师学艺、行侠仗义为主线,登录后即可看到"江南 水乡""明教""高昌""日本"等多个地图分区。
- 游戏规模较大(工作目录内近三万个文件),涵盖武功、门派任务、婚姻、 帮派等一整套传统武侠 MUD 玩法,内容相当丰富完整。
enable_player()每次重新触发都会叠加一次同样的指令绑定——这一点 在深度测试中被发现并修复,此前会导致失败指令的副作用被悄悄重复 执行。
在线试玩
https://mudlibs.fluffos.info/haiyang2/
管理员账号 / Admin account
- 账号 id:
fluffos密码:Mud@2026中文名:浮浮 - 权限:
(admin)(本 mudlib 的最高巫师等级),通过adm/daemons/securd.lpc中与原作者预留的hxsd相同的"硬编码后门" 机制授予,即使securd.o存档被重置也依然有效。
警告: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
- §4 (lazy security-daemon load recursion in valid_read/valid_write):
master.lpc'svalid_read/valid_writeonly ever dofind_object(SECURITY_D), neverload_object()— confirmed by reading the actual source before touching anything. Not applicable. - §7 (missing get_root_uid/get_bb_uid): both already present in
master.lpc, returningROOT_UID/BACKBONE_UID— confirmed present, no stub needed. - §8c (this_player()-override footgun in valid_read/valid_write):
securd.lpc'svalid_read/valid_writeuse the passed-inuserargument directly, never callthis_player()to override it — confirmed by reading the actual function bodies. Not applicable. - §15l (master.lpc destructing SIMUL_EFUN_OB in create()):
master.lpc'screate()is justwrite(...)— nodestruct()call anywhere in it. Confirmed via direct read. Not applicable. - §15 (nitan-family shared-simul_efun-dbase architecture bug): this lib's
feature/dbase.lpcalready implements real localset/query/delete, and agrepforefun::set(/efun::query(/efun::delete(/efun::addn(across the entire ~26,000-file tree came up completely empty. Confirmed via source read + grep, not assumed from lineage alone. - §8h (convertd.lpc Greek-table backslash typo):
grepfor the\"(,)?$-shaped stray-backslash pattern inadm/daemons/convertd.lpccame up empty. Not present in this copy. - named.lpc-style deep PATH()/sliding-window macro: this lib has no separate
named.lpcat all (checked:find -iname "named*"came up empty) — the equivalentcheck_legal_name/is_chineselogic lives directly inlogind.lpc/chinese.lpc, fixed there per §15h above. uid.h/priv.h/mimi.h/renwu.h/baredaemons.h: genuinely missing headers, but confirmed (viagrep -rl) referenced ONLY byadm/daemons/network/*(an unused/never-preloaded network-daemon family —te.c,pingd.c,pingtcp.c,telnetd.c,ms.c,mail_serv.c,userid.c,userid1.c, none reachable from registration/boot) and by exactly 3 room/NPC files (d/feihu/midong.lpc,d/gaochang/entrance.lpcformimi.h;d/japan/npc/li.lpcforrenwu.h) whose headers genuinely don't exist anywhere in the archive (a real content gap per AGENTS.md §13, not a bug — not fabricated).
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:
- ~40+ failures, ONE shared root cause (§8g):
d/mingjiao/npc/xkxnpc/(5 NPC files:zhuangzheng,yanyuan,wujincao,tangyang,wencangsong) all#include mingjiao_npc.lpc, which doesn't exist anywhere in the archive — a genuine content gap (§13), not a typo (the cascading "Undefined function query" / "== always false because of incompatible types" errors inmingjiao_job.hare downstream fallout from the same missing include, not separate bugs). One zone's NPC content, not on the registration/boot path. - ~21 files,
Illegal character 0xNN(multi-byte UTF-8 fragments): cross-checked against the Encoding section above — these are exactly the files already flaggedLOSSY conversion (invalid bytes dropped)duringconvert_lib.sh's pass (kungfu/skill/qingyi-jian.lpc,kungfu/skill/zhuihun-biao.lpc/diwangquan.lpc/qxxy-shou.lpcand others) — pre-existing corrupted/truncated multi-byte sequences in the original archive's GBK bytes (confirmed via the same "LOSSY conversion" cross-reference technique documented in AGENTS.md's Encoding section), not a fresh bug from this pass. Decorative kung-fu skill-table content, not reachable from registration. - 8 failures,
Bad type for argument 1 of is_killing (string vs object): a handful of NPC files (clone/demogorgon.lpc(in 2 locations on disk),d/city/npc/guidao.lpc,d/xiangyang/npc/ xiaosong.lpc) callis_killing(this_player())(an object) whereis_killing()is declared to take astringid — the mirror-image of theis_killing()type-mismatch bug documented fornitan_ceshi(archive #60), but affecting only decorative NPC combat-dialogue files here, not the player body class (confirmed:grepforclone/user/ userin the failure log comes up empty — the actual body class compiles clean, matching the successful interactive test). Pre-existing content bug, not fixed (out of scope per the "breadth over depth" policy — these are scattered one-off NPC files, not a registration-path blocker). - ~6 files,
Cannot #include uid.h/priv.h/baredaemons.h: confirmed (see "checked and confirmed NOT needed" above) referenced only by the unusedadm/daemons/network/daemon family (never preloaded, never reachable). - ~60 files, scattered
syntax error/Illegal LHS/Undefined variable/ etc, no shared root: a long tail of pre-existing authoring typos spread across kung-fu skill files, quest files, and several wizards' personal sandbox directories (u/yaodm/,u/hxsd/,u/lubinh/,u/tod/) plus one zone's NPC cluster (d/quanzhou/npc/ tang{,1,2,3}.lpc) — not chased individually given diminishing returns on a single archive among ~100; none appear anywhere in either clean interactive test transcript.
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)
- LPC reformat: ran
format-corpus.mjsover all ofwork/(26,182.lpcfiles) — 25,931 written, 19 already-idempotent/unchanged, 232 refused (self-checked token/byte-safety failures on messy legacy syntax, expected and not chased per the tool's own contract). - Native retest against the freshly-rebuilt driver (
build-debug/src/ driverrebuilt from latest upstream master today): booted clean on port 40057, zero fatal preload errors. Full registration flow run with a fresh real Chinese name ("秦岭丁"/qinlingding) through the complete wizard (encoding select → id → confirm → Chinese name → password → gift stat roll → email → gender) all the way into the actual game world;look,score, andquitall produced correct output and the character saved cleanly on quit. New (non-blocking) finding:quittriggers a caught runtime error,*Value being indexed is zeroin/clone/topten/magic-rice.lpc'ssimple_sort_skill()(line 223, called viasavetopten()'spks_topten/club_toptenentries) — root-caused to the shippeddata/topten.osave file predating thepks_topten/club_toptenfields added later in this leaderboard code (grepofdata/topten.oconfirms both keys are absent from the saved mapping, soquery("pks_topten")returns0afterrestore(), overwriting the good in-source default array with a bare0, and indexing0[i]throws). This is a pre-existing content/save-data bug, unrelated to today's reformat or driver rebuild (same shipped.ofile, same source logic, just never previously exercised by aquitat this exact leaderboard). It's non-fatal — caught by the driver's error handler,quit's save-and-disconnect completes normally regardless — so left undocumented-as-fixed rather than patched, consistent with this project's "breadth over depth" policy for scattered non-blocking content bugs. No regressions from the reformat or the new driver binary itself. - WASM test (
scripts/wasm_client.jsagainstbuild-wasm/src): boots cleanly, same preload warnings as native (no fatal preload errors). Registration is blocked, but not by the documentedquery_ip_number()limitation — instead by a distinct WASM-harness gap:wasm_client.js'scopyDir()onlymkdirs the top-levelwork/logdirectory when it decides to skip copying "runtime churn" contents, but never recreates the directory *structure* beneath it. This lib'sadm/daemons/logind.lpccallsset_visitor()→write_file("/log/mud/MUDVISITOR", ...)unconditionally on every connection's very first successful GB/Big5 encoding selection (before the "英文名字" prompt); under WASM themud/subdirectory was never created, so the write throwsWrong permissions...No such file or directory, which abortsgb_big5()mid-function — it never reachesinput_to("get_id", ob), so the connection is left with no input handler and every subsequent line (login id, etc.) is misinterpreted as an unrecognized command. Confirmed via direct source read, not guessed; confirmed not a mudlib bug — the identicalwrite_file()call succeeds natively becausework/log/mud/already exists on disk from prior test runs (both are gitignored runtime-churn paths per this repo's.gitignore, so a truly fresh checkout would need it created either way — the gap is that the harness'smkdiris not recursive into pre-existing subdirectories underlog/). Not patched (out of scope — a shared test-harness limitation, not this mudlib's code); reported upstream to the orchestrating session as a generalwasm_client.jsgap likely affecting other libs that keep real data under a nestedwork/log/<subdir>/path. - Test character (
qinlingding) save files removed after testing; driver (native, PID 2126338) killed by exact PID; no scratch files left behind.
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):
adm/daemons/band.lpc:39is_banned(): short-circuitreturn 0for loopback/empty/malformed site strings before the regexp ban-list scan.adm/daemons/logind.lpc:153new helperis_loopback_conn()(forward declared at :19); used to exempt loopback from:
- :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).
- No
uptime()startup-grace gate exists in this lib (checked; alluptime()uses are content timers/randomness — left alone).
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)
- Native (driver rebuilt from master, port 40057): fresh registration
ceshier/秦风 end-to-end (encoding g → id → y → name → password ×2 → gift 0/y → email → gender m) into the starting room; look/score/quit all correct.fluffos/Mud@2026admin login shows目前权限:(admin)andupdate /cmds/wiz/updaterecompiles successfully. debug.log: compile warnings only, zero runtime errors. Driver killed by exact PID. (The login-time notice "有新文件update错误,立即查看 /log/log" is the lib's own wizard file-size-change monitor firing because /log/log is absent — cosmetic, pre-existing, not an error.) - WASM (build-wasm with query_ip_number/resolve fixes + recursive log/ dir harness fix): the previously-documented harness gap is GONE. Fresh registration
wasmceshi/秦风 end-to-end + look/score/quit all correct; second invocation:fluffosadmin login(admin)+updatesucceeds. Verdict: native OK + wasm OK. No mudlib changes needed this pass. Test character saves removed; runtime churn (topten.o/fluffos.o timestamps) reverted.
深度功能测试 / 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:
- First attempt (fresh newbie,
combat_exp== 0): abrupt socket close (noquitsent) while sitting in西练武场, reconnected ~3s later. Landed back in the welcome room, not the room where the disconnect happened. Root-caused via source read (clone/user/ user.lpc'snet_dead()): for any player withcombat_exp < 100the lib deliberately schedulesuser_dump(DUMP_NET_DEAD)after just 1 second (call_out("user_dump", 1, DUMP_NET_DEAD)), instead of the normalNET_DEAD_TIMEOUT(900s) grace period used for everyone else — i.e. a genuine, immediate force-quit-and-save, not a stall or a void-park. Since西练武场never setsvalid_startroom, the force-quit correctly leftstartroomunchanged (still the welcome room) rather than saving a bogus location — so this is not the §7.20 void-stranding shape (no void room, no permanently-corrupted save field, exits all present) — it's a real, working quit-and-save, just triggered unusually fast for brand-new characters. Flagged as an observation, not fixed: this looks like a deliberate design choice (an aggressive newbie-idle-connection reaper, maybe to keep player slots/objects from lingering for barely-started characters) rather than a missing-guard/typo shape; genuinely ambiguous whether it's intentional, so left untouched per the scope note. - Second attempt (same character,
combat_expbumped to 5000 via the admincallcommand specifically to exercise the *normal* 900-second-grace-period branch instead): killed the connection process abruptly again while in长廊, reconnected ~a few seconds later.logind.lpccorrectly detected the still-live, still-netdead-flagged body viafind_body(), calleduser->reconnect()(confirmed reachable —grep -n "\->reconnect(" adm/daemons/logind.lpcfinds a real call site, unlike the §7.20 "zero hits" tell), printed "重新连线完毕。", and the very nextlookshowed长廊again — the exact pre-disconnect room, not the welcome room and not a void. This is the correctly-implemented case and confirms the lib'snet_dead()/reconnect()pair works as intended for any player past the first few minutes of play (the overwhelming majority of real sessions). No §7.20/§7.21-shaped bug present here.
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:
feature/damage.lpc:194(revive(), called after a player is knocked unconscious in a real fight — i.e. any newbie losing a real, non-spar fight against an NPC, an extremely common event per the newbie guide's own "低水平玩家稍有不甚就会送命" warning), andcmds/std/sleep.lpc:169/:196(wakeup()/wakeup2(), called after ordinarysleep— available to ANY player in asleep_room-flagged inn bedroom, not sect-gated; sleeping to regen is routine, repeatable play, not an edge case).
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):
- Baseline on a freshly-logged-in character:
query_commands()shows exactly onecommand_hooksentence. call qixiazi->enable_player()invoked twice more (simulating whatrevive()/wakeup()do) →query_commands()now shows threecommand_hooksentences on the same object — direct, unambiguous proof of the stacking, independent of chasing a specific double-charge reproduction through the full apprentice/skill-purchase chain (thecmds/skill/xue.lpclearn-from-teacher command has the exact same "unconditionalprintf()-before-a-later-notify_fail()" shape asdtsl's originally-reproduced case — see its"你向%s请教了...疑问"message printed before three later possible failure returns — but actually reproducing it live needs a real sect master relationship, out of this pass's time budget; the directcommands()proof above stands on its own).
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 countVerified 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 崩溃)
BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 61 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
深度功能测试(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(无需改动)
win_times修复确认存在且正确:d/city2/npc/refereew.lpc:177/d/beijing/npc/refereew.lpc:177均已用(string)(to_int(query("win_times")) % 5)。- 未发现
message()simul_efun 包装函数——不适用 message()-missing-varargs 这一类 bug。 assure_file()本身末尾不做seteuid(getuid())重置,不属于 euid-reset 那一类形状。
实测过程
以已提交的管理员账号 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
- §7.111 (
file_name(error["object"])unguarded instandard_trace()): the *live* master file (master file : /adm/single/masterinconfig.fluffos) never callsfile_name(error["object"])at all — itsstandard_trace()has that argument commented out entirely (dead/inert code, predates this pass). A strayadm/obj/master.lpc(already has the §7.111-shaped guard applied from some earlier pass) and an unusedadm/obj/masterold.lpc(has the true unguarded shape) both exist on disk but are not the file the driver actually loads as master — dead duplicates, confirmed viaconfig.fluffosandgrep -rl masteroldfinding zero real references. Nothing to fix. logind.lpcenter_world()missing->save(): present and not commented out —user->save(); ob->save();right afteruser->setup()(around line 1256). 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:
- Death corridor: after the real
init()broadcast fromsummon,callouts death_stageshowed exactly 1 entry (/d/death/npc/wgargoyle#... death_stage) andqintongshan->query_temp("death_stage_active")was1. Calledqintongshan->enable_commands()three times via admincall(the exact driver-level effect a real reconnect has) —callouts death_stagestill showed exactly 1 entry throughout (delay ticking down naturally, not reset), confirming no duplicate stacking. Let the chain run to completion:callouts death_stagewent to 0 entries andquery_temp("death_stage_active")correctly dropped back to0— the guard clears itself on both the abort and completion paths, not just blocking re-entry. - PK-arena room: teleported admin into
west_xiangfang(which, correctly, also scheduled admin's owndeath_stageentry — this room has no wizard exclusion by design) thensummonedqintongshanin —callouts death_stageshowed 2 entries (one per distinctprevious_object(), i.e. admin and the player each get their own, which is correct — the guard is per-target, not global). Three moreenable_commands()calls onqintongshanleft the count at exactly 2 throughout, confirming the fix works for the room-init shape too. debug.lognever came into existence during any of this (fresh driver, full registration + death-corridor stress test + board/shop/combat testing below, all in one session) — zero runtime errors of any kind.
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)
- Board post/read:
客店留言板(clone/board/kedian_b.lpc,inherit BULLETIN_BOARD) at 客店 (kedian).post <title>→ editor prompt → message body →.→ "留言完毕。", confirmed successful;readwith no argument correctly prompts "您要读哪一封信?" instead of crashing. No errors. - Real combat + kill (not just the training-dummy spar rounds one/two already covered): fought and killed
小混混(d/city/npc/hunhun.lpc,combat_exp200, 十里长街/nandajie1) with the default starting character build — full multi-round fight resolved correctly,杀死人数incremented from 零 to 一 onscore, no errors. - Shop mechanics:
药铺(d/city/npc/huoji.lpc,inherit F_DEALER) —listcorrectly prices and lists allvendor_goods;buy <id>correctly rejects an unmatched Chinese display-name argument (is_vendor_good()matches against the goods object'sid()keyword list, not its Chinese short name — expected/correct dealer behavior, not a bug, confirmed by readingfeature/dealer.lpc). - Death/reincarnation full-cycle completion: covered above as part of the §7.112 verification — the
wgargoylechain was allowed to run to natural completion (not just stress-tested for duplication), ending with the player correctly reincarnated and the guard flag cleared. - Sect-joining (
bai) not re-attempted this round — round two already confirmed the design-gate behavior (flavor recruiter NPC correctly rejects, real sect masters are out-of-reach without a lengthy walk); nothing new to add without an unreasonable time investment for a single data point already established as "working as designed."
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:
kungfu/skill/bingxue-xinfa.lpc:12—sname = keys(skl);with zero guard, unconditionally executed at the very top ofvalid_learn()— any player with zero skills learned so far attempting to learn this skill would crash immediately withBad argument 1 to keys() Expected: mapping Got: 0, directly in thexueskill-learn path this very task item exercises.kungfu/class/mingjiao/yangxiao.lpc:123,kungfu/class/shaolin/ qing-le.lpc:88and:116— three sect-reward-command functions (明教 tie-token / 少林 达摩令·手谕), unguardedkeys(skl)on a freshly-recruited sect member's ownquery_skills().kungfu/condition/lyjob.lpc:47-48— a different but related shape:learned = me->query_learned(); if (undefinedp(learned["buddhism"])) learned["buddhism"] = 0;indexes the rawquery_learned()return directly with no guard — crashes for any never-trained player reciting scripture (诵经) at 灵隐寺 (a generically reachable training spot, not sect-gated).cmds/wiz/wizlian.lpc:177— unguardedkeys(me->query_skills())in a wizard "reset to origin" admin command; lower severity (wizard-only) but a real crash risk on a target with zero skills.
(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)
- §7.90 (
config.fluffoseval cost):maximum evaluation cost : 30000000— already well above the documented5000000remedy floor. Not applicable. - §7.100 (live
replace_program(ROOM);): 385 raw-text hits across the tree, but every single one is either a non-.lpcdead/backup file (.C/.bak/.old/.txt/.java/.vns/.jas, a straylog/editentry) or, within real.lpcfiles (256 hits), already commented out (//) — confirmed via a targeted follow-up grep excluding//-prefixed lines, zero live matches. Already fully closed by the 2026-08-19 cross-lib sweep documented above. - §7.111 (
file_name(error["object"])unguarded in the real master'sstandard_trace()): re-confirmed still not applicable, matching round three's own finding — the live master (/adm/single/master) never calls that pattern at all. - §7.112 (
death_stage()reentrancy): manually re-read all 7 death- corridor/shaolin NPC files' fulldeath_stage()bodies (not just grepped for the guard string) confirming every exit branch clearsdeath_stage_active, and all 6bwdh/*_xiangfang.lpcPK-arena room files clear the guard unconditionally at the top of the function before any branching — all 13 originally-fixed files verified structurally sound, then re-confirmed live end-to-end via the real combat death above (see item 3). - §7.79 (bare 2-arg
addn()/addn_temp()): zero hits anywhere in the tree. - §7.108 (
reconnect()callingenable_commands()): re-confirmed present —clone/user/user.lpc'sreconnect()still callsenable_commands()first thing, matching round three's finding. - §7.30 (uninitialized-mapping accessor crashing
keys()): found and fixed a real, 3rd-corpus-wide-confirmed instance — see item 1 above.
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)
- §8.3a (
private nomask command_hook): the livefeature/command.lpcalready hasprivatecommented out (fixed in round two's own §7.28 pass);feature/commandhell.lpchas the rawprivateform but zeroinheritreferences anywhere in the tree (confirmed dead, same as the establishedcommandbak.lpc/command2.lpcprecedent elsewhere in this project). Nothing live to fix. - §7.123 (bare file-scope
IDENT = (...);killing a compile): grepped for the exact shape tree-wide; the only 3 hits are legitimate multi-declarator statements (mapping tiles = allocate_mapping(2), cchar = ([...]);andprivate mapping B2G_Cache = ([]), G2B_Cache = ([]);) — real declarations with a type keyword earlier in the same statement, not the buggy bare-reassignment shape. Zero real instances. - §7.126 (stale pre-
.lpcextension in AREA save data via a__DIR__-style macro placeholder): this lib has no coordinate-gridAREA/std/area/map.lpc-style world framework at all — ordinary discrete room files throughout. Not applicable. - §7.129 (
tell_room()wrapper forwarding omittedexcludeas literal0): already fixed pre-session per this lib's own§15scode comment inadm/simul_efun/message.lpc(bothtell_room()andshout()), matching AGENTS.md's own "ES2-family corpus sweep (2026-08-27)" note that listshaiyang2among the libs already fixed before this pass. Re-confirmed by reading the live file. - §7.130 (net-dead heart_beat calling
query_idle()unconditionally after already-detected non-interactivity):inherit/char/char.lpc'sheart_beat()correctly guards withif (!interactive(this_object())) return;immediately before its onlyquery_idle()call. Not applicable. - §7.131 (
find_living()/find_player()needingset_living_name()): the livefeature/command.lpc'senable_player()already callsset_living_name(query("id"))/set_living_name(query("name")). Not applicable. - §7.132 (
map()-over-a-mapping bound to the wrong argument): zeromap()-over-a-mapping call sites anywhere in the tree (confirmed viagrep -rn "= map("— no hits at all). Not applicable. - §7.133 (
net_dead()apply never defined):clone/user/user.lpcdefinesprivate void net_dead()and it is reachable (confirmed live via the abrupt-disconnect test above, which exercised this exact code path). Not applicable. - §7.134 (uninitialized "extra descriptions from present items" accumulator crashing every room's
long()): this lib has noadd_my_desc()/room_descs-style mechanism at all. Not applicable. - §7.135: see the
feature/dbase.lpcfix above — the one real instance found this round.
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;新进程登录(输入名字)正常。
- 2026-10-11:编译警告第二轮(按驱动报告逐条修):按位 & 改逻辑运算 0 处,返回类型/原型 49 处,NPC+F_UNIQUE 的 init() 0 个,带初始化的未用局部变量 6 个,只在 #if 分支用的声明移进分支 0 个文件;改动 47 个文件,HEAD 与工作树各载入一次无回退(47 objects; HEAD PASS 42, tree PASS 42; regress 0; newly loading 0),这些文件的警告 59 -> 4。
深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查
adm/daemons/event/qiantang.lpc钱塘潮:"…十分壮观。\n"; NOR;分号把NOR隔成独立语句,颜色不复位;改为"…" NOR;(hy5、xiakexing100、xkx100 同码一并修;xkx100utf8 走上游)。kungfu/skill/wudoumi-shengong/perform/gui.lpc多余的独立NOR;删去;kungfu/skill/liangyi-jian.bak.lpcint i; level;去掉无用的level;。载入无回退。
- 2026-10-14:新驱动(FluffOS 970d9f09)报
Float value truncated to int(#1426)。23 个文件 23 处:赋值、初始化、return 改写成to_int(...)(值不变,只是写明截断);x op= 浮点先把浮点转成整数再运算(系数小于 1 时 x 直接变 0),改为x = to_int(x op ...):/kungfu/skill/fengyun-shou/foguang.lpc(damage *=)、/kungfu/skill/taiji-quan/zhenup.lpc(damage *=)。改动的对象新进程各载入一次,无回退(scripts/lpc_fix_float_trunc.py,KB 04 §6.10)。 - 2026-10-14:技能文件里 37 处
int power_point()/int hard_point()返回 1.3、1.75 之类的浮点,经 int 返回被截断;本库没有调用者,改成float返回值,保留原数值(float_hooks.py,KB 04 §6.10)。 - 2026-10-14(同一次重扫,手工部分,与 hy5 同源同修):
- 四份 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-14:
kungfu/skill/liu-unarmed.lpc的query_action()里取等级与for循环两行被注释掉,剩下if (level > action[i - 1]["lvl"]),i 为 0,每次出招都报Array index must be positive or zero;恢复这两行(与其他武功相同的写法)。逐个武功调用 query_action/valid_learn/practice_skill 的运行期探针发现。
负数伤害当作消耗(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… 行。