info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
重庆电信 ShuJian MUD Wizard Group 自 1997 年起开发维护的经典武侠 MUD,与 bxsj1《书剑·经典》同源,共享大部分核心引擎代码。新人从"武馆前院"起步,沿东练武场、设有木人陪练的东武场、兵器库一路探索,是一套完整成型的新手引导区;木人会原样复制对手自身的属性技能来对练,是真正意义上的安全陪练。拜师系统有两条路:既可以循序渐进地拜入武当宋远桥等正统门派掌门门下,也可以直接找游戏内建的"礼物使者"报出门派名,瞬间加入十五个门派中的任意一个并领取满级绝技全套装备——这是官方明示的新手鼓励捷径。`ask <npc> about name/rumors/here` 等江湖打探指令几乎对所有 NPC 都有效,`rumor` 频道匿名传话、`chat` 频道显示真实姓名,社交玩法比较成熟。
English
A classic wuxia MUD developed and maintained since 1997 by the ShuJian MUD Wizard Group at Chongqing Telecom. New players train in a full beginner region at the martial hall, complete with a training dummy that mirrors an attacker's own stats for safe sparring, and can join any of fifteen sects either through formal apprenticeship or a built-in "gift envoy" shortcut for newcomers.
README
内容亮点
- 新人从"武馆前院"起步,沿东练武场、东武场(设有木人陪练)、兵器库一路 探索,是一套完整成型的新手引导区。
- 木人(
d/wuguan/wuchang4)会原样复制攻击者自身属性/技能来对练,fight时到五成资源自动收手认输,是真正意义上的安全陪练。 - 拜师系统有两条路:正统的门派掌门(如武当宋远桥)循序渐进拜师,或找 游戏内建的"礼物使者"NPC 直接问"拜师"/"门派名",瞬间加入 15 个门派中 的任意一个并获得满级绝技全套装备——后者是官方明示的"新手鼓励"捷径, 非破解漏洞。
- 有一处值得留意的原始设计:非巫师角色
quit退出时,未标记 autoload/unique 的随身物品(包括初始布衣布履)会被直接清空——这是 ES2 谱系一以贯之的"防屯物"机制,重新连线(非完整退出)不受影响。 - 江湖社交三件套
ask <npc> about name/rumors/here对几乎所有 NPC 都 有效,rumor频道会匿名化发言者,chat频道则显示真实姓名。
在线试玩
https://mudlibs.fluffos.info/bxsj/
管理员账号 / Admin account
- ID:
fluffos - 密码 / Password:
Mud@2026(8 位,含大写字母与数字,符合本站密码规则) - 中文名 / Display name:浮浮
- 权限 / Level:
(admin)(adm/etc/wizlist中授予的最高等级) - 授权方式:在
adm/etc/wizlist加入fluffos (admin)一行。
警告:Mud@2026 是本地游玩用的公开默认密码。若要正式对外开放主机,请先修改此密码。
本地运行
cd libs/bxsj
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40004。
NOTES · 移植与修复记录
bxsj.rar → bxsj
- Archive:
archives/bxsj.rar(15.4MB — "书剑天下"/"ShuJian MUD", a substantial, actively-maintained-looking codebase, MudOS-lineage,Copyright 1997-2004 ShuJian MUD Wizard Group). - Mudlib root in archive:
shujian/(config lives one level up inshujian/mudos/config.txt, alongside a bundledmudos/driverWindows binary — ignored, we use our own driver).mudlib directory : /txin the shipped config is the ORIGINAL developer's absolute server path, unrelated to our layout — always override, never trust this value. - Port: 40004. 9936
.lpcfiles after conversion — second-biggest lib so far. - Note: this lib's
name :is also "书剑天下", the SAME name as another archive in this collection (书剑天下.rar, TODO #36) — likely a different snapshot/version of the same underlying mud. Not the same archive (not a byte-duplicate), so processed separately per the standard pipeline; cross-reference when that archive's turn comes.
Status: DONE — boots clean, playable over telnet
Reached name entry, length validation, existing-ID detection, and the
password prompt — full login flow confirmed working. This lib surfaced
three genuinely new (to this project) driver-interaction bugs, all
generalizable patterns now cataloged in AGENTS.md.
What was fixed
1. Encoding: 10865 converted, 737 already UTF-8/ASCII, 75 skipped binary,
36 lossy. Found the convert_lib.sh binary-detection bug here
(AGENTS.md, "Encoding — file's text/binary guess is not reliable
enough to gate on"): file misclassified several genuine GBK source
files as data (unusual CRCRLF line endings apparently confuse its
heuristic) and the script silently skipped converting them, leaving raw
GBK bytes in work/. Fixed the shared script (forces known text
extensions regardless of file's guess) AND manually converted the
9 affected files found here (feature/save.lpc, clone/user/
olduser{,2}.lpc, adm/daemons/bbsd.lpc, cmds/std/testsjmud{1,2}.lpc,
cmds/arch/ucp.lpc, cmds/wiz/movedata{2,cq}.lpc) plus re-scanned
libs #1-3 for the same issue (clean, 0 hits — this bug's impact was
specific to whatever tool/era produced this particular archive).
2. .c → .lpc: 9936 files. 834 literal .c" refs auto-fixed, 0 left over.
3. static → nosave: 199 files.
4. No §4 (load_object in valid_read) issue in this master — checked,
valid_write/valid_read call SECURITY_D via find_object only
(no lazy load_object), so no recursion risk there.
5. New bug (AGENTS.md §8, extended): master.lpc's log_error() —
the driver's own error-reporting callback — calls
CHANNEL_D->do_channel(...) unconditionally. If CHANNEL_D isn't
actually loaded at the moment a PRELOADED file fails to compile (which
is exactly when log_error() runs), that call needs a fresh compile,
which is forbidden mid-compile → Object cannot be loaded during
compilation → which re-enters log_error() → cascades. This fired
repeatedly during boot for multiple different failing preload entries.
Fixed with the same reentrancy-guard + catch() shape as AGENTS.md §4.
6. New bug (AGENTS.md §14 cousin): master.lpc's valid_override()
was 2-arg (missing the main_file parameter needed for efun:: calls
inside #included simul_efun fragments) AND defaulted to deny
(return 0) rather than allow for anything not explicitly listed —
stricter than lib #1's version. Added the 3rd parameter, plus an
explicit allow-list for the simul_efun message-formatting wrappers
(shout/write/tell_object/tell_room/say) that cmds/adm/ad.lpc
needed via efun::shout(...).
7. New bug, the real blocker for this lib (AGENTS.md §8c) — took the
most effort to find: securityd.lpc's valid_read() had
if (this_player()) user = this_player(); unconditionally, which
wrongly attributed a SYSTEM-initiated lazy load_object() (master
loading BAN_D/band and UPTIME_CMD/cmds/usr/uptime, neither
preloaded, both first touched from inside a brand-new connection's own
login sequence) to that connecting player's own unprivileged
permissions instead of the real (root-euid) caller — an exclude_read
rule protecting /adm from ordinary players then denied it, which
permanently stranded every single new connection at the login banner
(no compile errors anywhere — looked like a total, silent hang).
Diagnosed via a temporary instrumented master.lpc valid_read()
(logged file/user/func/result to a scratch file) rather than guessing
from the generic *Read access denied. message — found the exact two
denied load_object calls in minutes. Fixed: only override with
this_player() when the passed-in user doesn't already have a
resolvable euid/uid.
Known remaining issues (documented, not fixed)
- 78 lpcc-sweep failures (post-fixes; was many more before). Notable categories left un-triaged:
Bad type for argument 1 of is_killing(9 files),Undefined function set/set_temp(10 files combined), optional network/mail daemon cluster (member_group,data_netmail_file— same missing-headers shape as libs #1/#2), a handful of scatteredUndefined function/variablecontent gaps. clone/misc/tang.lpc: one string literal built entirely from octal byte escapes ("\377\371\377\376\042\377\374\005..."— raw telnet protocol/control bytes, not text) that has no valid representation as a UTF-8 LPC string in this driver (old drivers had raw 8-bit strings). Would need converting to abufferliteral to fix properly; one obscure clone item, not fixed.
How to run
cd libs/bxsj
~/src/fluffos/build-debug/src/driver config.fluffos
python3 ../../scripts/mudclient.py 127.0.0.1 40004 --timeout 10 --send "n" --send "testuser" --send "quit"Post-hoc fix: UTF8-native is_chinese/registration (AGENTS.md §15h)
Applied in a later batch pass across the whole project: is_chinese/is_chinese2
in the shared chinese.lpc simul_efun fragment used GBK byte-range checks that
silently never match real Chinese text once strings are UTF-8 (this driver's
str[i] returns a Unicode codepoint, not a GBK byte). This broke character
registration specifically -- any real Chinese name was rejected. Fixed the
range check to test the CJK Unicode block instead, and halved the
GBK-byte-calibrated length bounds in check_legal_name to match. See
AGENTS.md §15h for the full writeup; confirmed via a real interactive
registration test (Chinese surname + given name reaching the next prompt).
Retroactive fix (found via archive #90, jinyongwenzi): this lib was completely command-dead after registration (AGENTS.md §15ae + a new commandd.lpc variant)
Archive #90 (金庸文字版.exe) turned out to be the literal same codebase as
this lib (byte-identical master.c/securityd.c/chinese.c after CRLF
normalization -- confirmed via diff, not assumed) processed much later in
the project, after §15ae (the private nomask command-hook bug) had
already been discovered and fixed elsewhere. That later pass found this
exact lib had TWO independent bugs compounding into the same symptom --
every single post-login command (even look) silently doing nothing at
all, with zero error anywhere:
1. feature/command.lpc's command_hook() was private nomask --
unreachable via add_action's external dispatch on this driver (§15ae).
2. adm/daemons/commandd.lpc's rehash() filtered get_dir() output
via sscanf(cmds[i]+"$", "%s.c$", cmds[i]) -- a live sscanf pattern
invisible to both the .c→.lpc rename's quoted-string fixer and the
bare-data-file fixer, since it's neither. After the rename this matched
zero files forever, so commandd's command-search table was never
populated and find_command() always returned 0 -- a second,
independent cause of the exact same symptom. Fixing only one of the two
would still have left every command dead.
Both fixed here (dropped private; changed the sscanf pattern to
"%s.lpc$"), then re-verified with a full fresh registration (real name,
one of "秦风"/"秦岭"/"秦河" tested across a few runs) followed by look,
score, and quit all producing correct real output, and debug.log
confirmed clean (0 error: lines). This lib's ORIGINAL testing pass (see
above) never verified a post-login command -- exactly the blind spot §15ae
warns about -- so this had been silently broken since this lib was first
marked "done," undetected until the sibling codebase surfaced it. See
AGENTS.md §15ae and the new commandd.lpc sscanf-pattern addition for the
general writeup.
Re-verification pass (2026-07-23): driver rebuild + LPC formatter + WASM build
- Formatter: ran
format-corpus.mjsover all ofwork/(9,936 files, 9,681 written/reformatted, 102 already-clean, 153 refused with an error — expected on legacy code, not chased individually). - Native retest against rebuilt driver (
build-debug/src/driver, rebuilt from latest upstream master): clean boot, zero fatal errors indebug.log. Full registration re-verified end-to-end on the now-reformatted source with a fresh real Chinese name (秦叔, IDqinshu) through the complete flow (ID → password, which must contain both a digit and an uppercase letter and not be "too simple"/derived from the ID → Chinese name → talent roll → email → gender), reaching the actual game world (武馆前院 starting room) with the welcome-NPC escort dialog (狄云) firing correctly;look/score/quitall produced correct output (the full 个人资料卡 stat card rendered correctly), zero real errors indebug.log. No regressions from the reformat or the fresh driver build. - WASM build: preload completes with only the expected non-fatal
sockets-package gap (adm/daemons/network/dns_master.lpc'ssocket_create/socket_close→Undefined function, caught bymaster.lpc's own error handler exactly as for a missing daemon natively —Initializations complete.still printed). Registration itself is blocked by the exact documentedquery_ip_number()WASM-mode limitation this project's tooling docs call out by name for this lib:adm/daemons/sited.lpc's site-restriction check rejects every attempted ID with "对不起,这个英文名字不能从当前地址登录。" ("sorry, this name cannot log in from the current address") because the wasm loopback connection'squery_ip_number()doesn't format as a real dotted-quad. Not a mudlib bug — this is the documented driver-side gap, not something to patch. Verdict: boots cleanly under WASM; registration cannot complete due to the driver's IP-check limitation.
WASM-enablement pass (2026-07 standard: loopback-allow, throttle exempt, admin seed)
Gates patched (all short-circuit loopback / malformed-empty IP first, original logic intact below):
adm/daemons/band.lpcis_banned()(~line 40): loopback/127.-prefix/ non-string/malformed-IP →return 0(never banned).adm/daemons/sited.lpcis_valid()(~line 37): loopback →return 1(site always valid).is_multi()(~line 65): loopback →return 0(multi-login throttle exempt; movedIP_D->ip2namebelow the guard so a garbage IP never reaches it).adm/daemons/logind.lpclogon()(~line 95): per-IP concurrent-connection cap (login_cnt > 3) now only destructs a real remote dotted-quad; loopback and malformed/empty IPs exempt.
Notes:
- The ACTIVE login daemon is
adm/daemons/logind.lpc(LOGIN_D, called byclone/user/login.lpc), NOT the root/logind.lpc(dead copy — left untouched).gb_big5()already wrapsBAN_D->is_bannedincatch()(fail-open), and the band.lpc guard now also returns 0 for loopback. - No
uptime()startup-grace gate rejects connections in this lineage (theuptime()calls in logind are content timers — robottest, set_temp "time" — kept). get_id()'swiz_level(arg) < wiz_lock_levelgate:WIZ_LOCK_LEVELis 0 here, so normal players pass; left as-is.
Admin account: id fluffos / Mud@2026 / 浮浮, granted (admin) via
adm/etc/wizlist. Verified update + goto work. Save files for the
orchestrator to force-add: libs/bxsj/work/data/user/f/fluffos.o and
libs/bxsj/work/data/login/f/fluffos.o (untracked new dirs; not
gitignored).
Retest: fresh registration (id fluffos, name 浮浮) reached 武馆前院,
look/score correct. Pre-existing content errors unrelated to this pass
remain in debug.log (message() bad-arg in user.lpc user_dump();
call_other() bad-arg from NPC 桃花 carry_object).
Retrofit (2026-07-24): fail-closed loopback check (security correction)
The loopback-allow gate patched above originally also treated a
non-string/empty/malformed query_ip_number() result as loopback (a
defensive stand-in for the WASM driver bug). That driver bug is now fixed
upstream, so this was tightened to fail-closed: only an exact
"127.0.0.1" / "127."-prefix / "::1" match bypasses the gate; a
malformed or non-string address now falls through to the original gate
logic (treated as untrusted/remote) instead of being auto-allowed.
Re-verified fluffos login still works after tightening.
深度功能测试 / Deep functional test (2026-07-24)
First real *playthrough* pass on this lib (all prior passes verified only
registration + look/score/quit + admin login, or watched boot output).
Played as an ordinary new player through most systems, native driver
(build-debug), with a WASM re-verification of the one fix made. Test
character: id qintest, Chinese name 秦风, password Abc12345 —
kept (not cleaned up) as a representative playthrough character; it
joined 武当派 (Wudang) via the newbie gift-envoy shortcut and has a
reasonably rich state (skills, family, rank-table entry) that's useful
evidence of a working system. Save files:
work/data/user/q/qintest.o, work/data/login/q/qintest.o.
Bug found and fixed
cmds/usr/top.lpc — unbounded per-hour rank-decay loop crashed on
literally every quit (and on top), file:line cmds/usr/top.lpc:395-401
(the live add_rank()) plus four sibling copies of the identical pattern
in add_rank_beauty() (~179), add_rank_pk() (~224), add_rank_rich()
(~271), add_rank_worker() (~338).
- Symptom: every single
quit(viacmds/usr/quit.lpc:218's unconditionalTOP_CMD->add_rank(me), called for every player regardless of rank) and everytop/top beauty/top pk/top rich/top workerinvocation triggered a caught-but-real runtime crash:执行时段错误:*Too long evaluation. Execution aborted.(eval-cost limit, 50,000,000 usec) inside the rank-decay loop. Invisible to the player (the driver's error handler catches it andquit's own visible output — "正在退出游戏……" — looks completely normal), so it was never caught by any prior pass that only checkedquit"looks right" to the player. Classic instance of the project's own §8.3/§10.1 lesson generalized one level further: a command whose visible output is correct can still be silently crashing server-side; grep debug.log after everyquit, not just after login. - Root cause: each
add_rank*()function keeps a per-entry"time"field on its saved rank-table rows and, for any currently-offline entry, runswhile (rank["time"] + 3600 < t) { rank["time"] += 3600; rank["score"] = rank["score"]*97/100; }to decay that player's score by ~3%/hour since it was last touched — fine on the original always-on server wheretonly ever drifts forward by however long the driver has been up between calls. This project's restoration ships the archive's real shipped/log/ranksave data verbatim (genuine 2008-era player rankings,"time":1219369347etc.) and boots it fresh against today's real wall clock — a ~566,000,000-second (18 real year) gap per entry, i.e. ~157,000 required loop iterations for a single stale row, times up to 99 rows. Trivially blows the eval-cost limit on the very first call. This is a new, general bug class for this project (a decay/catch-up loop keyed off a *stored* real timestamp vs. the *current* real clock, safe under continuous original operation, pathological once an old save is booted fresh against a modern clock) — not previously cataloged in AGENTS.md; written up there now. Grepped the rest of the lib for the same shape (while.*\["time"\].*< *t\b,while\s*(.*time()) — these five are the only live occurrences (a sixth copy attop.lpc:~556is inside an already-dead/* ... */-commented duplicateadd_rank(), untouched). - Fix: capped every one of the five loops at 240 iterations (10 days' worth of hourly decay, already enough to crush any stale entry's score to near zero) and unconditionally set
rank["time"] = tafterward (matching what the existingelsebranch already does for online players), so a still-stale-after-240-iterations entry just jumps its clock forward rather than looping further. Preserves the original per-hour compounding behavior for any realistic gap; only changes behavior for the pathological >240-hour-offline case, where the score was already going to be decayed to noise anyway. - Verified: reproduced live pre-fix (debug.log showed the exact "Too long evaluation" trace rooted at
add_rank()lines 399/400, called fromquit.lpc'sdo_quit()); rebuilt nothing (LPC-only change), killed and rebooted the native driver, re-ranqintestthrough several freshquits andtop/top pk/top worker— zero eval-cost errors afterward, andtop/top workernow renderqintest's real entry (综合评价 3470, worker score 200) instead of silently never getting inserted. Re-verified under WASM (node scripts/wasm_client.js ~/src/fluffos/build-wasm/src libs/bxsj, login asqintest+look+score+quit): clean, no eval-cost error in the captured stdout/stderr.
What was tested and confirmed working
Full continuous playthrough as qintest (ordinary new player), plus one
wizard-flavored digression through the game's own "newbie gift" NPC
(see below) to reach the menpai/skills systems without a long trek:
- Registration: real Chinese name (秦风), full flow (id → password with digit+uppercase validation → confirm → name → talent roll/accept → email → gender) — already known-good from prior passes, re-confirmed.
- Movement/exploration: walked the 武馆 (martial-hall) starting zone (
d/wuguan/) —武馆前院→东练武场(wuchang3)→东武场(wuchang4, training dummy)→兵器库(armory)→碎石路→门廊,look,look <item-id>(paizi/shakeng/plaque banners) all render correctly; exits, room descriptions, day/night flavor text all correct. - Character info:
score(the 个人资料卡 stat card — attributes, title, master, money, kills/deaths, etc.) andhp(精/气/精力/内力/ 食物/饮水/潜能/经验 bars) both correct at every stage (fresh, post-gift, post-sect-join).i/inventory correct throughout, including the □ "equipped" marker convention. - Combat:
d/wuguan/wuchang4's 木人 (training dummy,muren.lpc) is this lib's designated safe newbie sparring target — itsaccept_fight()literally mirrors the attacker's own stats/skills for a fair, non-lethal bout.fight murenproduced a full turn-by-turn combat log (attack/dodge/parry narration, qi/jing damage, fatigue messages) and correctly auto-halted at the ~50% resource threshold ("木人胜了这招,向后跃开三尺,笑道:承让!") exactly ashelp newbiedocumentsfight(vshit/kill) should behave. No crash, no over-death. - Skills/kungfu: the organic first-skill path is
d/wuguan/wuchang3's 武馆教头 NPC (jiaotou.lpc,recognize_apprentice()/is_apprentice_of()both hardcoded1— teaches ANY player without a formalapprenticefirst), viacha jiaotou(list his skills) →xue jiaotou <skill>(learn) — command dispatch and ACL confirmed working (cha/xuecorrectly require the teacher NPC to be physically present, correctly reject skills he doesn't teach with "这项技能你恐怕必须找别人学了"across the codebase).lian <skill>(practice) correctly refuses base skills not yetenabled as a special technique ("你只能练习用 enable 指定的特殊技能") — matcheshelp newbie's documented model (practice targetsenabled special/menpai moves, not raw base skills) — not a bug. Full skill-list/enable/practice loop verified via the sect-join path below, which grants real special skills:skillslists 职业/杂项/基本/特殊 categories correctly;enable strike taiji-quanandlian strikeboth exercised (rejected post-Wudang-join with "这个技能不能当成这种用途" because the gift-granted 太极拳/太极剑 etc. are already the intendedstrike/swordspecials and — investigated —taiji-quanmaps tocuffnotstrike, i.e. correct rejection of a mismatched base/special pairing, not a bug). - Items/equipment:
d/wuguan/bingqiku's 兵器架 (na jian from jia, one-per-character via amarks/拿temp flag) — correctly grants a real长剑weapon;wield/unwieldboth correct (wield message, weapon shown unmarked vs□-marked armor ini).remove cloth/wear clothround-trip correct. Discovered, NOT a bug — flagging per the "real design tradeoff" instruction:cmds/usr/quit.lpc:146drops (not destroys, unless valueless) every carried item that doesn't implementquery_autoload()orquery("unique")when a non-wizard quits — confirmed live (a picked-up sword, and even the starting 布衣/布履, vanished from inventory after aquit; a *silent reconnect* — driver-levelnet_dead()/find_body()re-attach without a real quit — preserves everything, only an explicitquitsweeps). This is real, deliberate ES2-lineage-family original design (present in the raw archive, not introduced by conversion) — an anti-hoarding mechanic, not a persistence bug — but it means ordinary gear a player spends time earning is NOT expected to survive aquitunless flaggedautoload/unique. The only reason default clothes don't visibly "disappear forever" is thatadm/daemons/logind.lpc:691(UPDATE_D->get_cloth()) unconditionally re-grants fresh (and, once in a sect, sect-appropriate) clothing on every *full* login — masking the loss for that one item category. Left entirely as-is (§7.14 "don't fabricate/alter deliberate content" applies to mechanics, not just rooms); noting it here because it's exactly the kind of thing a player or a future tester could easily misdiagnose as a save/persistence bug. - NPCs/social:
ask <npc> about name/rumors/here(the three standard questions every non-animal/non-mute NPC answers perhelp newbie) all produced correct, in-character dialogue on 宋远桥 (Song Yuanqiao, Wudang's hall master).say/emoteboth correct.chat/rumorchannels both correct (rumor correctly anonymizes the speaker to "某人"; chat shows the real name) — matcheshelp newbie's documented distinction.tell <self>correctly rejected ("告诉自己?"). - Quests: the newbie escort/tutorial chain (
d/wuguan/npc/diyun.lpcand its near-duplicatedizi7.lpc/冯坦, both driving the SAMEwgtask/wgmeet/wgquestionplayer temp-var state machine) gatesd/wuguan/dayuan.lpc's southward exits ("你先回答了冯坦的问题再离开也 不迟啊!") until answered — confirmed both the accept path (answer y→ move-command tutorial → directs to 孙均) and the decline path (answer n→ skips straight to the "find work" branch) work. Note:wgtask/wgmeet/etc. arenosave*temp* vars, so a low-combat_expcharacter gets re-approached and re-gated on every FULL login (not on a silent reconnect) — mildly repetitive UX but consistent/correct, not a bug (temp state resetting on reconnect is expected LPC behavior, and the gate is always answerable). - Menpai/sect: two independent paths confirmed — 1. *Organic*:
bai <master>/apprentice <master>on a real family-affiliated NPC (宋远桥) — first call ran the full accept flow ("你想要拜宋远桥为师。...你跪了下来...恭喜您成为武当派的第三 代弟子。"), second call correctly recognized the existing relationship ("你恭恭敬敬地向宋远桥磕头请安").cha songcorrectly lists his 14 skills incl.□-marked (enabled) special techniques. 2. *Newbie-gift shortcut*:d/wuguan/npc/shizhe.lpc(「书剑天下礼物 使者」, present in the starting room) —ask shizhe about newbie(wipes skills, grants a large stat/VIP boost) →ask shizhe about 拜师→ask shizhe about <门派名>— instantly joins any of 15 sects with a full max-level special-skill kit and teleports to that sect's hall. This is clearly an intentional in-game "catch-up"/GM-flavored newbie reward (explicit "为鼓励 newbie特此奖励" message), not a bug — used here deliberately as a fast path to reach and verify the skills/family systems without a long trek, then cross-checked against the organicbaipath above to confirm both converge on the same, correctly-recognizedfamily/is_apprentice_ofstate. - Persistence: confirmed at both layers this lib has — (a) *silent reconnect* (driver
net_dead()leaves the body object alive in the world; next login'sfind_body()+!interactive()re-attaches it with zero data loss, no re-look) — exact room, inventory, HP all preserved; (b) *full quit+relogin* (realsave()/restore()) — stats, skills, family/sect membership, and title all round-tripped correctly across multiple cycles (only the item-drop-on-quit mechanic above affects inventory, and that's original design, not a persistence defect). - Economy/shops: reviewed at the code level (
inherit/room/shop.lpc,clone/shop/yangzhou_shop.lpc— a player-consignment auction house requiring player-crafted "imbued" goods, not a fixed-price vendor;feature/vendor.lpc/F_VENDOR-based fixed-goods peddlers likeclone/npc/huolang.lpcandd/xiangyang/npc/niulaoban.lpc's 杂货铺 general store,buy/list/sellall implemented with correct money checks) — not live-verified end-to-end (no successful purchase completed). The nearest confirmed fixed-price vendor (d/xiangyang's 杂货铺) is a genuine trek from the startingd/wuguanzone, and the sect-join shortcut relocated the test character to a third, even more distant zone (Wudang mountain); chasing a full buy/sell round-trip across that distance was deprioritized given the time budget once the code review turned up no red flags. Free-goods interactions (武馆's ownd/wuguan/chufang.lpckitchen,get/eatfree tea/rice) were identified as reachable but likewise not live-run. Flagged as a real gap in this pass, not a "confirmed working" claim — a future pass on this lib (or the next tester following this checklist) should prioritize a live shop transaction early, before wandering into an unrelated zone. - Death/respawn: not conclusively live-tested, noted honestly rather than guessed.
feature/damage.lpc:210'sdie()was reviewed (looks structurally sound — listener hooks, wizard-immortal guard, arena-specific branch, falls through toreincarnate()) but not triggered. Themurenspar's built-in 50%-resource auto-halt (see Combat above) confirms the *combat-safety* half of this works. Two live paths were identified but not pursued:kill muren(removes the auto-halt; sincemurenmirrors the attacker's exact stats it's close to a fair coin-flip fight, so *could* produce a real, controlled player death) andsuicide -f(a real, sanctioneddie()trigger requiring either a bladed weapon in hand or force-skill ≥500 — the test character had neither at the point this was considered, having already dropped its sword per the quit-drop mechanic above). Skipped because the character was, by that point, deliberately overpowered by the newbie-gift shortcut (combat_exp 1,000,000, skills ~201) relative to any easily-reachable NPC, making genuine self-endangerment impractical without a further trek to find a dangerous opponent, and the time budget was better spent chasing thetop.lpccrash (a confirmed, live, every-single-quit bug) to a real fix. Left as a concrete to-do for whoever plays this lib next.
Methodology notes (for the broader pass this seeds)
- The
top.lpcbug is the single most important finding of this pass, methodologically: it was invisible to every previous verification because "the visible output ofquitlooked correct." New standing checklist item: after everyquitin a deep pass (not just after login/registration), re-grep debug.log for fresh error/crash lines before moving on — a caught server-side error can ride along behind a perfectly normal-looking client-visible message. - Restored archives carry real historical timestamps as data, and this driver's
time()is the real wall clock — any lib that ships saved data with a"time"/"date"/epoch-like field consumed by a *loop* (not a one-shot comparison) is a candidate for this same class of bug. A one-shotif (time() - x > N)is safe; awhile(or recursive) catch-up keyed off the same gap is not. Worth agrep -rn 'while.*time()\|while.*\["time"\]'pass on any newly-picked-up lib with old shipped rank/leaderboard/decay data. - Wandering NPCs make room-content assumptions stale mid-script. Several
chat_msg-driven NPCs here (jiaotou,huolang,zhanggui) include(: random_move :)— a scripted multi-step test that assumes "NPC X is still in room Y" three commands later can silently fail for reasons unrelated to the system under test. Prefer alookright before any NPC-dependent command in a scripted session, or accept the occasional need to re-plan a step live. - A single continuous connection is much more valuable than several short ones for this class of lib: this lineage's login flow has (a) a 30-second real-time anti-flap lock keyed off
last_on(set on BOTH clean quit and raw disconnect) that rejects a same-ID reconnect attempted too soon, and (b) a "silent reconnect" path that reuses the live in-memory body (no freshlook, noget_cloth()re-grant, exact position preserved) whenever the previous session ended without a realquit. Both are correct, deliberate behavior (§1.3e-style quit-retention timer, kept per AGENTS.md policy) but they mean a test harness that reconnects between every few commands will intermittently (a) get locked out for 30 real seconds, and (b) silently skip the full-login code path (banner,get_cloth(), quest-greeting re-fire) that a "fresh login" test actually wants to exercise. Plan a script's full command sequence in ONE session where possible; only split across reconnects when specifically testing the reconnect/persistence path itself, and expect to need real wall-clock waiting (not a instant retry) between separate connection attempts — standalonesleepis blocked in this harness; use a backgroundedsleep N && echo ready(run_in_background: true) and continue other (non-connection) work in the meantime rather than idling a live connection at the login prompt (idling at the BIG5-font prompt itself trips a *separate*, unrelated 30-secondlogin.lpctime_out()— seeclone/user/login.lpc:19-39— so "just wait inside the open connection" doesn't work either). - A "safe newbie combat area" is worth identifying by name early. This lib's is a training-dummy NPC (
muren.lpc) whoseaccept_fight()deliberately mirrors the attacker's own stats — a reusable pattern worth grepping for (accept_fight+ a stat-copy loop fromob->query_skills()) on any wuxia lib before assuming combat testing requires finding a "safe" wild NPC by trial and error. - A lib's own
help newbie/help introfile (when present) is the single highest-value source for planning a systematic test path — it directly named the exact commands (fightvshit/kill,apprentice/bai,learn/xue,practice/lian,enable/jifa,ask ... about ...,chat/rumor) and explained WHY each system behaves the way it does, turning what would otherwise be a slow trial-and-error command-guessing exercise into a directed one. Read it in full before starting the playthrough, not just when something looks wrong. - A deep-test checklist for this class of lib (menpai/kungfu/wuxia), distilled from what actually mattered here, roughly in the order that paid off fastest: 1. Read the lib's own newbie help file (if any) FIRST — it's usually a complete command reference for exactly this checklist. 2. One continuous session: register →
look/score/iat each major state change (not just once) → explore the starting zone'sexitsby reading room.lpcfiles rather than guessing, to plan an efficient route instead of bumbling into dead ends live. 3. Identify and use the lib's own "safe sparring" mechanism if one exists (grepaccept_fightfor stat-mirroring or an explicit newbie-dummy flag) before hunting for a "weak enough" wild NPC. 4. Learn ONE skill via the organic teacher-NPC path AND verify the sect-join (apprentice/bai) path separately — they can be inconsistent or one can be gated behind the other; both are cheap to test once located. 5.quit, grep debug.log, THEN reconnect (real wall-clock gap) and confirm state — do not skip the debug.log grep even though the player-visiblequitmessage looked fine. 6. Shop/economy and death/respawn are the two systems most likely to require genuine travel/setup investment (money, a dangerous opponent) — budget time for them explicitly, or explicitly flag them as unverified rather than silently skipping past them.
WASM 修复摘要(迁移自 meta.json 的 group_note)
书剑 MUD 家族的基础版本。状态已从过时的 limited 修正——这份档案自己的 NOTES.md 记录了一次完整的原生驱动深度功能测试(2026-07-24),以普通新玩家身份走完了注册、移动、人物信息、战斗(木人安全对练机制)、门派/技能系统,并在过程中发现并修复了一个真实 bug(cmds/usr/top.lpc 的 add_rank() 系列函数:一个不设上限的按小时衰减排名循环,一旦这份 2008 年代的存档数据配上现代系统时钟运行,每一次 quit/top 指令都会撑爆求值成本上限——已限制在 240 次迭代以内)。只有商店/经济系统和死亡/复活没有被实测覆盖到(是测试覆盖缺口,不是已知损坏)。这份档案自己的 README 没有记录任何缺陷,本轮也没有发现新的。本轮在当前 WASM 驱动构建下重新验证了预先播种的管理员账号登录(fluffos/Mud@2026,look/score/quit 全部正常,"您目前的权限是:(admin)")——由于 NOTES.md 里 2026-07-24 的深度测试已经完整覆盖过注册流程,本轮没有重跑注册;同一代码家族的手足档案 bxsj1 本轮则重新完整验证过一次注册流程,结果一致。
§7.86 跨库扫描修复(留言板 post 崩溃)
BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 1 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
Deep functional test round two (2026-08-14)
Independently re-verified every §10.7 checklist item against current code
rather than trusting the 2026-07-24 writeup above. One proactive hardening
fix applied; the one real bug found in round one (top.lpc's rank-decay
crash) was re-confirmed still fixed via a genuine live regression test on
the same character that originally triggered it, not just by grepping the
diff.
Fix: adm/simul_efun/file.lpc's cat() had no null-guard on read_file()
Standard proactive hardening for this session's checklist (§7.11-adjacent):
cat() called write(read_file(file)) unguarded — read_file() returns
0 for a missing/unreadable file, and write(0) is a latent throw risk.
// BEFORE:
void cat(string file) {
write(read_file(file));
}
// AFTER:
void cat(string file) {
write(read_file(file) || "");
}log_file() in the same file was already correctly hardened — it delegates
to LOG_D->log_file() (adm/daemons/logd.lpc), which unconditionally calls
assure_file(file) before every write regardless of the optional db flag
(the if (db) only guards the preceding dbquery() call — misleading
indentation, correct semantics, verified by reading the actual statement
boundaries rather than trusting the visual layout). No callers in this lib
pass a truthy db flag, so the MySQL dbquery() branch is dead code, not
exercised, not a live risk.
Checked and ruled out as not applicable to this lib: no log_error()
severity-gate case-mismatch (none found in adm/single/master.lpc), no
dbase.lpc password-write-guard shape (this lib's admin seeding doesn't use
that pattern), no %-operator-on-corrupted-float shape beyond the
already-corpus-fixed quest_times/win_times class (the only unguarded %
hits found were unrelated weight-formatting arithmetic in
d/city/weapon/weapon*.lpc, not attribute counters), no §8.9 food/water
wrong-object read, no printf debug leaks in logind.lpc.
Re-verified live: top.lpc rank-decay crash (round one's fix) still holds
Logged in as qintest (the same character whose quit originally
reproduced the crash on 2026-07-24) rather than trusting the diff was still
applied. top correctly rendered the real ranking entry (综合评价 3470),
and quit — the exact original crash trigger, since cmds/usr/quit.lpc
unconditionally calls TOP_CMD->add_rank(me) — completed with zero new
work/log/debug.log lines (checked the file's line count before and after:
unchanged). The original crash was invisible in the player-facing output
even when it was happening, so a clean-looking quit message alone isn't
sufficient evidence; the debug.log line-count check is what actually
confirms it.
Verification method
Booted native build-debug driver, admin login (fluffos/Mud@2026) with
update /adm/daemons/logind as the real privileged-action check (succeeded
— "重新编译 ... 成功!"; adm/simul_efun/file.lpc itself is #include'd into
adm/single/simul_efun.lpc, a container object, so it can't be update'd
directly — same limitation documented elsewhere this campaign). Two rapid
consecutive admin reconnects, both clean, both landing a full fresh login
(上次连线 timestamp updated each time). One login attempt as qintest timed
out mid-password-entry on the login procedure's overall timer
(clone/user/login.lpc's time_check()/time_out()) when sent via too
many separate tool calls; resolved by sending the whole
id+password sequence as a single batched tmux_mud.sh multi call with a
slightly longer per-line wait — not a mudlib bug, a test-harness pacing
issue (this lib's overall login-procedure timeout is comparatively tight).
Noted a stale FATAL ERROR: SIGTERM: Process terminated line at the tail of
work/log/debug.log predating this session's own boot — the driver
(confirmed via readlink /proc/<pid>/cwd) stayed alive and responsive
throughout testing, so this is leftover content from an earlier, unrelated
process instance sharing the same log path, not a live crash; not
investigated further since it didn't correlate with any actual failure
during this pass. Driver killed by exact PID after testing; incidental
fluffos.o/qintest.o save-timestamp churn reverted before commit.
Files modified this pass
work/adm/simul_efun/file.lpc—cat()null-guard onread_file().
Round three deep functional test (2026-08-18)
Went deeper than rounds one/two per the standing checklist: real combat
death/reincarnation (not just code review), board post/read, shop dispatch,
and a persistent-guild command smoke test — plus a project-wide sweep for
three newly-catalogued bug patterns (AGENTS.md §7.111/§7.112, and the
§8 logind.lpc enter_world() missing-save shape).
Bug found and fixed: §7.112 unguarded reincarnation-chain init() (2 live instances + 1 hardening)
Grepped every init() containing a call_out( corpus-wide (2,278 files have
init(), 543 also schedule a call_out; narrowed to 81 candidates lacking
any existing remove_call_out/query_temp guard, then to the ones whose
scheduled function recursively re-schedules itself — a genuine multi-stage
chain, not a one-shot timer). Two real hits, both in the death/reincarnation
zone (/d/death/), exactly matching the reference shape from
haiyang2/jym's wgargoyle.lpc:
1. work/d/death/npc/death.h (included by mengpo.lpc/孟婆,
yanluo.lpc/阎罗, pusa.lpc/菩萨 — the three reincarnation-guide NPCs a
dead player's ghost meets) — init() unconditionally scheduled a 5-stage
death_stage() message/teleport chain (first stage 20-40s out, then 5s
between stages) with no guard. Since the driver re-broadcasts init() to
every NPC in a room whenever a living object's commands are enabled
(including on a player reconnect), a player who disconnects and
reconnects while standing in front of one of these NPCs — very plausible
given the 20-60s window — would stack a second parallel chain: doubled
dialogue, and a second reincarnate()/move at the end.
2. work/d/shaolin/npc/yu-zu2.lpc (狱卒, a Shaolin-jail guard) — same
unguarded shape, its own private 5-stage death_stage() chain (60s
between stages) ending in ob->move("/d/shaolin/woshi1"). Grep found no
room anywhere in the corpus that currently places this NPC (a sibling
yu-zu.lpc, no "2", is the one actually used in d/shaolin/jianyu1.lpc,
and it already has its own correctly-guarded remove_call_out+call_out
pattern) — yu-zu2.lpc looks like dead/orphaned code today, but fixed it
anyway since the shape is real and harmless to guard.
Fix (matching the documented reference shape): a death_stage_active
per-player set_temp()/query_temp() guard around the call_out()
scheduling call in init(), cleared at every exit point of
death_stage() (the ghost-already-resurrected early return, the
target-left-the-room early return, and the final completion branch).
Proactive hardening in the same zone: work/d/death/gate.lpc (地门/鬼门关
— DEATH_ROOM, the very first room a dying player lands in) had the
identical unguarded shape one level up — its own init() unconditionally
scheduled a call_out("run", 1, me) that randomly routes the ghost into
gateway/mpting (yanluo/mengpo). Narrower window (1s) and more benign
outcome (a duplicated flavor-text line, and a possible unwanted second
random reroute) than the death.h chain, but same class of bug and directly
in the path being live-tested here, so hardened it with the same
gate_run_active guard shape for consistency.
Live-verified end-to-end, twice:
- As admin (
fluffos), became a ghost (ghost), thengoto'd into/d/death/mpting(mengpo) twice in immediate succession (bounce out to/d/wuguan/dayuanand back) to force twoinit()calls within the vulnerable 40-second window. Only one copy of the 5-stage message chain played (confirmed by watching for the "孟婆说道:哦!又来了个新的" opening line — appeared exactly once, not twice), and the chain completed correctly (moved back to/d/wuguan/dayuanmatchingenter_wuguan).debug.logstayed completely empty throughout. - A real, non-simulated combat death: as admin,
smash qintest(a wizard command that calls the target's realdie()) while qintest was actively logged in. Watched the full pipeline fire for real: corpse created,qintestbecame a ghost,save()d, moved toDEATH_ROOM(gate.lpc, our new guard), routed into the reincarnation NPC's room, ran the full 5-stagedeath.hchain once, and correctly returnedqintesttoREVIVE_ROOM(/d/city/chmiao). Verified via a laterscoreonqintest:死亡:一次(death count 1),上次遇害:被闪电劈死 了(matchessmash.lpc's flavor text),combat_expreduced by exactly 10,000 (the death penalty), skills all reduced accordingly — the whole death→reincarnation→revival cycle executed correctly with zerodebug.logerrors. This closes the "death/respawn not conclusively live-tested" gap flagged as a to-do in round one's notes.
Not a match — checked and ruled out: work/adm/single/master.lpc's
standard_trace() (§7.111) formats error["object"] with %O (safe,
never throws on a non-object), not an unguarded file_name(error["object"])
— this lib's error handler doesn't have the §7.111 shape at all.
adm/daemons/logind.lpc's enter_world() (the §8 logind save-guard check)
already unconditionally calls both user->save() and ob->save() right
after setup() completes — not commented out, not missing.
Live-verified: economy/shop (closes another round-one gap, partially)
Round one flagged shop transactions as reviewed-but-never-actually-run.
This pass: goto'd directly to /d/xiangyang/zahuopu (牛老板's general
store) as admin, ran list (correct item/price/stock table), then
buy deng long — item-id resolution worked correctly (vendor.lpc's
is_vendor_good()/do_buy() dispatch fired without error), and since
admin had no money, MONEY_D->player_pay()'s real insufficient-funds path
executed and returned the correct in-character rejection ("穷光蛋,一边呆
着去!"). Confirms the shop's full command-dispatch and payment-check code
path executes cleanly end-to-end; a successful purchase (money actually
changing hands) is still not verified — no in-game money-granting wizard
command exists in this lib (checked cmds/wiz/*, cmds/adm/*; no
clone/cash/give tool), and legitimately earning coin would require a
genuine trek/job. Left as a to-do, same as round one.
Live-verified: board post/read (new coverage)
As admin on the 巫师留言簿 (/d/wizard/wizard_room's wizard board):
post <title> (title must be given inline, then drops into the real
ed-style multiline editor via me->edit(...)) → wrote a body line → .
to end — got "留言完毕" (posting complete), board count went from 124 to
125, and read 125 played back the exact posted title/body/author/timestamp
correctly. Confirms bboard.lpc's post/read path (including the
§7.86-fixed inherit/replace_program shape from the earlier corpus
sweep) is still fully live-functional, not just "loads without crashing."
Smoke-tested: persistent-guild commands (cmds/group/*, new coverage)
Distinct from the menpai/sect system (bai/apprentice, tested in round
one) — this lib also has a separate persistent-faction system
(gcreate/glist/gmove/grant/gforce/abdicate/destory, dispatched
by filename via commandd.lpc, not add_action). glist rendered a
correctly-formatted (empty) faction leaderboard without error. gcreate
with a syntactically-valid Chinese faction name (测试帮, ends in a legal
suffix character) correctly rejected with "抱歉,当前只允许贵宾玩家才能创
建帮派" (VIP-only gate) — read gcreate.lpc: this is a genuine, deliberate
VIP paywall, not a bug, so left alone per scope discipline. Confirms the
command-dispatch and argument-validation paths for this whole subsystem are
alive; full faction creation remains unverified (would need a VIP account).
Methodology notes
- A wizard
ghost/gotocombo (or, better, a realsmash-triggered death) is a fast, repeatable way to reach and re-trigger reincarnation-NPCinit()chains for regression-testing §7.112-class guards without waiting on genuine risky combat —gotointo the NPC's room firesinit()on every occupant exactly like a room-entry would, so re-entering twice in a row is a faithful stand-in for the reconnect-mid-chain scenario, and is much faster to script than arranging two real concurrent socket connections. smash <target>is a real, unguarded wizard-level kill command (callsob->die()directly, bypassing combat) — useful for deterministic death/respawn testing on any character regardless of level, without needing to find a "fair fight." Requires the target to bepresent()in the admin's own room first (gotothere).- This lib has no in-game way to grant money to a test account outside earning it legitimately — checked
cmds/wiz/*andcmds/adm/*thoroughly; noclone/cash/give-style tool exists. A future full shop-purchase test needs either a real trek+job/quest income, or a save-file edit while the driver is stopped.
Flag for cross-lib awareness (not fixed elsewhere, per instructions)
The §7.112 pattern (unguarded reincarnation/staged call_out() chain in a
room or NPC's init()) was found here in the death-zone specifically. Any
other lib in this corpus with its own /d/death/-style
reincarnation-guide NPCs (or any other multi-stage init()-scheduled
process) is worth the same grep sweep
(call_out( inside init(), minus files that already remove_call_out
or check a temp flag first) — this is the third or so independent lineage
this exact shape has now been found in per today's (2026-08-18) session
notes.
Files modified this pass
work/d/death/npc/death.h—death_stage_activere-entrancy guard (§7.112).work/d/shaolin/npc/yu-zu2.lpc— same guard, likely-dead code but fixed for consistency (§7.112).work/d/death/gate.lpc—gate_run_activeguard, proactive hardening of the same class one room upstream.
§7.100 sub-threshold instance (2026-08-20)
Found during the §7.100 tail-sweep (below the original 166-lib survey's
=100-occurrence threshold, never checked). 24 live
replace_program(ROOM); occurrences across 24 files (d/wanshou/*.lpc,
data/group/groom/*.lpc, d/cangzhou/dangpu.lpc) — same fatal
redundant-replace-after-inherit shape as the rest of the §7.100 family.
This lib's room-building tools (clone/misc/roommaker.lpc,
cmds/debug/roommaker.lpc) never had the bug baked into their
templates, so no factory-side fix needed. One pre-existing
already-//-commented instance (d/xueshan/jlshan.lpc) left untouched.
Fixed by deleting the redundant lines (binary-mode script, 24/24 matched
git diff --stat). Verified via a clean native driver boot (zero new
debug.log errors, port listening, killed by exact PID after ~8s).
``§7.112`` residual-gap closure (2026-08-20)
Corpus re-scan (grep -rl 'call_out("death_stage"' ... | filter for missing guard) found unguarded init()-scheduled death_stage() call_out chain(s) in d/death/npc/death2.h that the original two-wave sweep (see AGENTS.md §7.112) missed -- same reconnect-triggered duplicate-chain bug, different filename/lineage. Added the standard query_temp("death_stage_active")/set_temp/delete_temp re-entry guard, adapted per file's own exit points. Compile-verified via lpcc --batch.
Note: the touched header here (death2.h) turned out to be dead/unreferenced code in this lib -- the live NPCs (yanluo.lpc/mengpo.lpc/pusa.lpc) actually #include a sibling death.h, which was already guarded from an earlier fix. This edit is a harmless no-op; no live vulnerability existed in this lib for this file.
§7.30 uninitialized-mapping accessor sweep (2026-08-20)
Corpus-wide mechanical sweep of the feature/skill.lpc shared-lineage
bug (confirmed independently on xiakexing2017/jqxz2015/haiyang2
via round-four testing): 4 accessor(s) in this file returned a raw
never-initialized mapping instance variable (defaults to int 0,
not ([]), until first assigned), crashing any unguarded
keys()/sizeof()/indexing caller for a fresh/untrained character.
Fixed at the accessor level (mapp(x) ? x : ([])) per the documented
remedy. Verified via lpcc --batch static compile check only (not a
live boot) as part of a large mechanical sweep; not individually
functionally re-tested live on this lib.
AGENTS.md §7.19 sweep (2026-09-01): enable_player() reentrancy from init()
Same corpus-wide bug class as mhxy/wuhanzhan (AGENTS.md §7.19): this
lib's feature/command.lpc enable_player() wrapper (around the raw
enable_commands() efun) is reachable from an NPC's init() via a
redundant create()-then-init()-calls-setup()/reset_me() chain --
confirmed via a body-aware static scan of every init() in this lib: 5
NPC files call setup()/reset_me() from init() (e.g.
data/group/npc/hufa.lpc, d/gb/npc/xixia-wushi.lpc,
d/gb/npc/xiaox-wushi.lpc), after create() already made the object
living(). Calling enable_commands() a second time on an
already-living() object makes the driver re-invoke that object's own
init() as a side effect, re-entering the same chain while the
original call is still on the stack -- genuine reentrancy, crashing
with "Too deep recursion" (most likely on an NPC's first-ever
preload/compile).
feature/damage.lpc's revive() and cmds/std/sleep.lpc's wakeup()
both call enable_player() again while the object is still living().
This confirms a bare if (living(this_object())) return; guard would
be the WRONG fix -- used the same true reentrancy-flag fix as mhxy
instead: a nosave private int in_enable_player_now; set for the
duration of the wrapper's body, guarding only genuine same-call-stack
reentrancy while leaving every legitimate re-enable (revive/wakeup)
unaffected. enable_player() had a single fall-through exit (no early
returns), so one guard-at-top + one clear-at-bottom pair was
sufficient. Verified via a single-file lpcc --batch compile check
(PASS) -- not individually live-boot-tested.
深度功能测试(§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)— 房间列出的物件缺档时房间照常加载
房间基类 inherit/room/room.lpc:make_inventory() 用 catch() 包住 unew(file)(缺档时它在 simul_efun 里对 0 调用 query("unique")),else 分支只在确有克隆时才取 [<1]。档案里没有的 NPC 或物品(或 create() 出错的物件)原先会让列出它的房间整个加载失败(Bad argument 1 to environment(),或对 0 的 call_other()),连带这个房间后面的区域;现在房间照常加载,只是少了那个物件,下次 reset() 再试(KB 05 §7.25;scripts/lpc_resilient_room.py、scripts/lpc_room_reset_guard.py)。
验证:探针(scripts/lpc_room_reset_probe.py:房间基类列出一个不存在的物件,再调用 reset())HEAD 单件 崩溃、多件 崩溃,工作树 单件 OK、多件 OK。
深度功能测试(§10.7,2026-10-07)— 编译警告整理
scripts/lpc_warnings.py bxsj --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 515 个 .lpc 和 11 个 .h(多为删去未用的局部变量)。HEAD 与工作树分别加载(新进程):505 -> 505 通过,无回退;改过的头文件的 12 个包含者 11 -> 11 通过。启动时警告 17 -> 0;新进程登录(编码选择、新人物确认)正常。
深度功能测试(§10.7,2026-10-08)— .lpc 后缀的定宽切片
.c 改名 .lpc 时只改了字符串没改切片宽度:f[<2..] == ".lpc" 拿两个字符比四个字符,永远不成立(!= 永远成立),按后缀筛文件、去后缀的代码因此从不运行或截成 x.l(KB 06 §7.238)。scripts/lpc_lpc_suffix_slice.py --apply 改了 10 处比较、0 处去后缀(如 d/xueshan/zanpugc.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。
- 2026-10-10:编译警告第二轮(按驱动报告逐条修):按位 & 改逻辑运算 0 处,返回类型/原型 19 处,NPC+F_UNIQUE 的 init() 0 个,带初始化的未用局部变量 1 个,只在 #if 分支用的声明移进分支 0 个文件;改动 18 个文件,HEAD 与工作树各载入一次无回退(18 objects; HEAD PASS 18, tree PASS 18; regress 0; newly loading 0),这些文件的警告 20 -> 0。
深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查
clone/obj/traverser.lpc:DEBUG是空宏,每次调用留下("text", n);语句;改成空函数varargs void DEBUG(string fmt, mixed arg) {}。载入无回退。
深度功能测试(§10.7,2026-10-11):全库严格 UTF-8 检查
d/city/weapon/weapon-2.lpc、weapon-4.lpc是含 NUL 的二进制垃圾(原档损坏),保留原样。
深度功能测试(§10.7,2026-10-12):编译警告收尾(一)
kungfu/skill/*zhen*.lpc等 11 个阵法技能:query_action()返回招式描述字符串,基类inherit/skill/skill.lpc声明mapping;子类改为mixed(调用方拿到的值不变,类型检查不再报警)。工具:lpc_fix_void_override.py --mixed(新增)。inherit/misc/equip.lpc:void ob_hit()改为mixed ob_hit()。combatd把护甲ob_hit()的返回值当消息字符串或伤害修正读,软猬甲等子类返回 int;基类是 void 才报警。函数体不变,落尾返回 0。d/xingxiu/obj/duchong.lpckill_ob()改为 void(无人读返回值);d/gumu/npc/mmnz.h原型int kill_ob改为与定义一致的void。- 5 处多余的 inherit(
kuihua-shengong.lpc的SKILL已由FORCE带入等,lpc_diamonds.py)。 - 77 个对象(含包含/继承改动文件的)载入无回退。剩余多为
Redeclaration of global variable(skills_pfm.h、r_name.h、huanjing2003等),按 KB 04 逐个核对后另行处理。
深度功能测试(§10.7,2026-10-12):编译警告收尾(二)
全库编译警告 89 -> 0(scripts/lpc_warnings.py bxsj),逐条按 KB 04 §6.10 核对后修:
adm/daemons/huanjing2003/的 4 个国度 NPC、4 个宝物和room_readygo_hj.lpc:一条声明里同名变量写了两遍(string me_guodu, ask_arg, …, me_guodu = "…", …),驱动给后一份初始化、前一份是空的。删去前面不带初值的那串名字,只留带初值的一份(代码原本读的就是后一份,ask_arg = baowu_name也读到初值)。- 16 个继承
FIGHTER的门派弟子/护卫 NPC(d/city/npc/laopu.lpc、d/huashan/npc/cs_npc.lpc等):自己的string *str_menpai(各自的门派列表)与inherit/char/fighter.lpc的同名变量冲突。两份各管各的(setparty()读基类的,NPC 自己的create()读自己的),叶子一侧改名npc_menpai,行为不变。 d/city/npc/r_name.h(经d/city/npc/skills_pfm.h包含进上述 NPC)的六张姓名表与include/r_name.h(fighter.lpc包含)同名但内容不同;按“两份头文件同名不同内容”处理,改名c_f_name等,叶子里的random_name()照旧读自己那份。d/city/npc/mengdie_npc{,1,2}.lpc:已继承FIGHTER又#include <skills_pfm.h>,变量和函数再来一份;删去多余的 include(函数体相同,继承来的那份照常可用)。kungfu/skill/dzxy.lpc:string *attack_action与inherit/skill/skill.lpc的同名变量本意相同(skills.h读它),删去叶子声明,改在create()里赋值(先调::create())。d/taishan/npc/runner.lpc:泰山叛徒的逃跑路线path与feature/command.lpc的命令搜索路径path撞名,叶子改名route。d/village/npc/obj/robber.lpc、robber_n.lpc:NAME_D重定义,前面加#undef NAME_D(生效的仍是本文件的定义)。cmds/usr/mygift.lpc、mygift2.lpc:?:两支 int/string,去掉无效的(int)。clone/npc/tdh/man1.h、d/shaolin/npc/h.lpc:块注释里的/*去掉(注释范围不变)。
改动的 37 个文件及包含改动头文件的对象共 40 个,HEAD 与工作树各载入一次(新进程):40 个对象载入无回退(regress 0);lpc_audit_removed_locals.py 0 处待查。
- 2026-10-14:新驱动(FluffOS 970d9f09)报
Float value truncated to int(#1426)。12 个文件 21 处:赋值、初始化、return 改写成to_int(...)(值不变,只是写明截断);x op= 浮点先把浮点转成整数再运算(系数小于 1 时 x 直接变 0),改为x = to_int(x op ...):/d/mr/npc/murong-fu.lpc(bouns *=)、/kungfu/skill/force/qi.lpc(q *=)、/kungfu/skill/force/recover.lpc(q *=)、/kungfu/skill/taiji-quan/luanhuan.lpc(p *=)。改动的对象新进程各载入一次,无回退(scripts/lpc_fix_float_trunc.py,KB 04 §6.10)。
- 2026-10-14:1 个文件用了颜色宏(
HIW、NOR)却没有#include <ansi.h>(首个错误syntax error, unexpected L_STRING);开头补上(KB 04 §6.1、§6.3)。新进程比对 HEAD 与工作树:1 个对象,新增载入 1,回退 0。
负数伤害当作消耗(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/fanchang、/kungfu/skill/hujia-daofa、/kungfu/skill/huntian-qigong/huntian、/kungfu/skill/lanhua-shou、/kungfu/skill/longxiang-boruo/fanchang、/kungfu/skill/yiyang-zhi。
无门派玩家的 family 映射(2026-10-15,KB 05 §7.30 第三种)
mapping fam = ob->query("family"); 之后直接 fam["family_name"]:没有门派(新人或叛师后)的玩家在学习、练功、拜师、进入门派房间时报 Value being indexed is zero,而不是作者写好的提示。11 处在声明行加上 if (!mapp(fam)) fam = ([ ]);(scripts/lpc_fix_family_guard.py)。
family 映射(补)(2026-10-15)
mapping fam = (mapping)me->query("family");(带类型转换的写法)同样直接取下标,2 处补上 if (!mapp(fam)) fam = ([ ]);。
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。3 处补上 if (!mapp(fam)) fam = ([ ]);(刚 recruit_apprentice() 之后的除外)。
技能按另一门武功的等级取招(2026-10-15,KB 06 §7.243)
这些技能文件是从别的武功复制改名来的,query_action() 仍用原武功的等级查招式表:只会本技能的人等级为 0,取不到招,每次出招都报错。改为读本技能自己的等级(scripts/lpc_fix_action_level.py):kuihua-shengong。
把经验、属性、内力当技能读(2026-10-15,KB 06 §7.247)
当前内力、精这类名字有时指的是内功技能,逐处读过后手改:lengquan-shengong 的 ob_hit() 用 query_skill("neili") 比较双方内力,恒为 0 对 0,只会走“化去一部分”的分支,改为 query("neili")。
技能名拼错(2026-10-15,KB 06 §7.249)
query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):jinshe-zhang→jinshe-zhangfa(jinshe-jianfa/kuangwu、kuangwu);qimen_baguazhen→qimen-baguazhen(suibo-zhuliu/wuzhuan);xixing-dafa→xixin-dafa(tianlong-shengong)。
技能名拼错:技能文件里拼错自己的名字(2026-10-15,KB 06 §7.249)
技能自己的文件(或它的绝招目录)里,query_skill() 读的是差一个字母的自己的名字,永远得 0:“基本功夫要高过本技能”之类的限制从不生效,按等级取招、计算伤害的那一项一直是 0。改为本技能的名字(scripts/lpc_fix_skill_typo.py 的 self 判据):xuantia-wuji→xuantian-wuji(xuantian-wuji/wuji)。
地图数据库 mapdb 读到只有文件头的 map.o(2026-10-15,KB 06 §7.250)
本库的 data/npc/map.o 只有一行文件头:genmap 第一次载入时 if (!restore()) save();,地图还没建就存了档,值为 0 的变量不写进去。mapdb 的 restore() 读它成功,map 却成了 0,query_room_info()、query_room_exits()、query_map() 都对 0 取下标或把 0 交出去:任务系统 job_area.h 的 get_arearoom() 在 keys(room) 报错。mapdb(/clone/obj/mapdb.lpc)加一个 restore():读档后 map 不是映射就重置为 ([]);地图为空时 query_random_room_path() 返回 0。地图本身仍需巫师用 genmap 生成。
npc_setup 的浮点截断警告(2026-10-15,KB 04 §6.10)
/clone/obj/npc_setup.lpc:level = pow(10 * exp, 0.333334); 把浮点数隐式截成整数,编译时警告;改为 to_int(...),结果不变。
燃木刀法「焚天」的效果一秒就消失(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/cloth/cloth、/d/city/obj/blade、/u/lius/wudang/obj/cloth)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 0 个(0 个原来载入不了),没有退步;其余 3 个另有原因(多为 set_skill() 了本库没有的武功)。
NPC 带着不存在的物品:simul_efun unew() 报错(2026-10-18,KB 06 §7.73)
adm/simul_efun/object.lpc 的 unew(str)(带 unique 判断的 new())先 ob = new(str),紧接着就读 ob->query("unique")。物品文件不存在时 new() 返回 0,读 0 的属性报 Bad argument 1 to EFUN call_other(),从 carry_object() 一路带出去:create() 里带着缺失物品的 NPC 载入不了(jh2006 的苗疆何铁手一类,该有 NPC 的房间少了人)。读属性之前补 if (!ob) return ob;(int i = ob->query("unique"), j, k; 拆成声明和赋值两句),缺的物品跳过,NPC 照常载入(scripts/lpc_guard_carry_object.py --unew)。载入检查:改动的文件改前能载入 1 个,改后 3 个,没有退步。
clone/spec/putao 的 long 里引号错位(2026-10-18,KB 03 §4.6)
set("long","这是一串由达摩传入中土HIG"神奇葡萄"NOR",……"):开引号落在 HIG 后面而不是前面,字符串在 HIG 处就闭合了,后面的 神奇葡萄 成了代码(Illegal character 0xe7),整个物件载入不了。把引号挪到 HIG 前面(中土"HIG"神奇葡萄"NOR",,与同系其他库的同名文件一致)。载入:通过。
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 载入不了,刷出它的房间也少一个人。本库有 2 个对象设了本库没有文件的武功(22 个名字,最多的是 kick、liangyi-dao、pishi-poyu)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:2 个里 1 个通过,其余 1 个停在别的错误上(clone/obj/npc_setup:*No program in object '/clone/obj/npc_setup'!)。(scripts/lpc_guard_set_skill.py)
通向不存在房间的出口,按对面房间的回头出口改正(2026-10-18,KB 06 §7.257)
房间出口 "west": __DIR__"xxx" 指向的文件本库没有,玩家走过去就被卡住(笔误、改过名的房间,或者没带进档案的编号房间)。对面的房间存在、并且它相反方向的出口正好指回这个房间,说明出口原本就是通向它:/d/fuzhou/nanmen 的 south:birdge → bridge;/d/shaolin/nanyang 的 south:yidao2 → hanshui2。改动后每个出口都能解析到本库的文件(scripts/lpc_fix_dead_exits.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 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 35 处、35 个文件:shaolin-shnfa→shaolin-shenfa(14 处)、kunlun-shnfa→kunlun-shenfa(11 处)、yijinjing→yijin-jing(2 处)、guiyuan-tuanfa→guiyuan-tunafa(2 处)、banrou-zhang→banruo-zhang(2 处)、qinmang-jian→qingmang-jian(1 处)、huashen-shenfa→huashan-shenfa(1 处)、weiituo-gun→weituo-gun(1 处)等共 9 个名字。(scripts/lpc_fix_skill_names.py)
全局头文件丢了 1 个宏(2026-10-19,KB 04 §6.16)
/include/globals.h 里没有定义 SHOP_DIR,用到它们的物件(名单里 1 个)编译时报 Undefined variable/Undefined function,整个物件载入不了(编译器在第一个未定义的名字上停下,后面的错误和警告都看不到)。同系的库在各自的头文件里都有这几行:照其中和本库共有文件最多的那个库,原样补到 /include/globals.h 最后一个 #define 之后,宏体里出现的路径在本库里都存在。名单里的物件除了这几个宏之外还有别的错误,暂时仍载入不了。(scripts/lpc_restore_defines.py)
区域物件的零碎修正(2026-10-19,KB 06 §7.267–§7.271)
/d/shaolin/npc/xuan-du.lpc的#include "/u/hunthu/npc/xuan-du.h"改成/kungfu/class/shaolin/xuan-du.h(巫师目录/u/…不在档案里,头文件的唯一副本在区域里)
逐个物件与 HEAD 对比载入,没有退步。
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 04 §6.15、§6.18)
宏有定义、基类文件却不在档案里:继承它的对象载入时报 Inherited file '…' does not exist!,编译扫描看不到这一类(scripts/lpc_missing_inherits.py 能列出)。同系的库里有同一个文件,原样拷来(scripts/lpc_restore_bases.py);基类和继承它的对象 include 而本库没有的头文件一起带来。每个基类补回后单独载入,再载入使用它的对象,载不进来、或一个用户也载不进来的撤掉了。
/inherit/weapon/weapon_combo←sje:2/2 个继承它的对象载入
逐个与 HEAD 对比载入,没有退步。
x == "a" || "b":后面的字符串常量永远为真(2026-10-19,KB 06 §7.281)
if (arg == "yuyou" || "yu you") 里的 "yu you" 是非空字符串,永远为真,整个条件对任何 arg 都成立;作者要的是每个字符串都和变量比较。具体是:吃任何东西都会吃掉鱼油。在每个字符串前补上变量和运算符(scripts/lpc_fix_literal_or.py;forrest2.lpc 是 dir == "north" && "south",手工改成 (dir == "north" || dir == "south") && ...):/clone/food/yuyou.lpc(4 处)。
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)。改动:/kungfu/skill/anran-zhang/jishi.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,共 16 处)。改动:/clone/npc/mj.lpc(3 处)、/cmds/group/glist_new.lpc(2 处)、/d/city/npc/budai.lpc(1 处)、/d/city/obj/budai.lpc(1 处)、/d/sld/npc/stt.lpc(1 处)、/d/sld/sanpo7.lpc(1 处)、/kungfu/skill/qiankun-danuoyi.lpc(7 处)。
socket / dumpsocket 指令:dump_socket_status() 驱动里没有(2026-10-20,KB 06 §7.291)
这些巫师指令 write(dump_socket_status());,MudOS 的 efun 在 FluffOS 里不存在,指令编译不过。在模拟 efun 文件里补了 dump_socket_status():用 socket_status() 排出同样的表(没有 sockets 包的构建,比如 WASM,只有表头;scripts/lpc_add_sefun.py dump_socket_status)。
登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)
驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/adm/single/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。
峨嵋 d/emei/npc/jing-xin.lpc 找不到 nigu.h,静心师太加载不了(2026-10-20,KB 04 §6.24)
这个 NPC 是 kungfu/class/emei/jing-xin.lpc 的副本(文件头注释还写着原来的路径),#include "nigu.h" 却没有跟着改:nigu.h 只在 kungfu/class/emei/ 里,d/emei/npc/ 下没有,所以静心师太从来加载不了(登录调查的游戏阶段控制台 5 次 /d/emei/npc/jing-xin.lpc:5: error: Cannot #include nigu.h)。改成绝对路径 #include "/kungfu/class/emei/nigu.h",不复制头文件(两份会各自漂移)。加载检查:HEAD 0 通过 / 1 失败,工作树 1 通过,无退化。同一家族的六个库(bxsj、bxsj1、shujian2008、shujian3、sjecl、sjtx2)是同一处。
exchange 指令:数据库结果还没检查大小就取下标(2026-10-20,KB 06 §7.324)
cmds/usr/exchange.lpc 的 Update_Credit() 在声明里就读 ret[0][0],下一条语句才检查 sizeof(ret) == 1:博物馆没有 MySQL,dbquery() 返回空数组,每次敲 exchange 都是 *Array index out of bounds.(指令扫描里的裸指令)。改成 int credit = (sizeof(ret) == 1 && sizeof(ret[0])) ? ret[0][0] : 0;。实测:exchange 显示 您目前可用书剑通宝为:零个……,exchange gold 显示用法,控制台无报错。同一份文件在 bxsj、bxsj1、shujian2008、shujian3、sjecl、sjtx2 六个库里逐字节相同。
巫师命令不带参数时对 0 调用 efun(2026-10-21,KB 06 §7.336)
巫师(管理员)把命令敲成不带参数时,驱动把 0 传给字符串参数;下面的命令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...,这条指令从来没有用过。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py 在第一处使用参数的语句前加守卫:文件自带 int help(object me) 的,守卫是 if (!arg) return help(me);(help() 定义在 main() 之后的 1 个文件补了原型 int help(object me);,否则编译报 Undefined function help)。改动(1 个文件,括号里是接住 0 的 efun):/cmds/wiz/test.lpc(sscanf)。加载检查 HEAD 与工作树对照,没有退步。
bwscore:比武大会的广场房间档案里没有(2026-10-21,KB 06 §7.299)
玩家指令 bwscore(cmds/usr/bwscore.lpc)不带参数时打印 /d/bwdh/sjsz/square.lpc 的描述,本库没有 d/bwdh(只有 xkm、xkx2000zxb、shenzhou 带着),new() 答 0,0->long_desc() 就是玩家看到的系统错误。改成 (square ? square->long_desc() : ""),用法提示照常。
苗疆山谷 shangu1:第二条蛇写成了 jinshe = new(..yinshe)(2026-10-21,KB 06 §7.346)
烧灵芝兰(do_shao)引出两条蛇:jinshe = new(__DIR__ "npc/jinshe"); jinshe = new(__DIR__ "npc/yinshe");,第二句把第一条覆盖了,yinshe 一直是 0,后面的 yinshe->move(...) 抛错。第二句改成 yinshe = new(...)(sje、jym 的同一份文件是对的)。
张三丰 ask_me_6:who->query("id")(2026-10-21,KB 06 §7.348)
非武当弟子问“太极道袍”时先 command("shake " + (string)who->query("id")),who 声明了没赋值,应是 this_player();抛错在拒绝的 say 之前,什么也说不出来。改成 this_player(),去掉 who。改动:/kungfu/skill/zhang.lpc。
保护任务的商人 clone/obj/job/job_protect_npc.lpc:两处笔误让整个文件从来没编译过(2026-10-21,KB 04 §6.29)
clone/obj/job/protect.h(保护商人的任务)克隆这个 NPC,但文件有两处笔误:int baohu(string age) 的函数体用的是 arg(Undefined variable 'arg'),而 init() 登记的是 do_baohu,函数名也对不上;query->("id") 多了一个 ->。改成 int do_baohu(string arg) 和 query("id")(if (!player = find_player(...)) 报的 Illegal LHS 只是前面语法错误引出来的连锁错误,这种写法本身是对的,没有动);文件第一次能编译后带出 7 个 Unused local variable 警告(sacrifice、exp_rate、skill_rate、lmt、menpai、killer、kill_num),按 KB 04 §6.10 删掉。baohu / protect 两个动词现在有处理函数,任务的商人也第一次能出现。改动:/clone/obj/job/job_protect_npc.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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 19 条注册(14 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/adm/daemons/robot-judge.lpc(do_test)、/d/city/pinqiting.lpc(do_pass、do_resign)、/d/dali/wuliang/muwu1.lpc(do_tiao)、/d/dali/wuliang/shanlin-6.lpc(do_tiao)、/d/huanghe/npc/mafu.lpc(do_rideyz)、/d/huashan/jinshe/obj/haigu.lpc(do_move)、/d/huashan/npc/yaba.lpc(do_follow、do_kill)、/d/mr/obj/chahua3.lpc(do_chu)、/d/plummanor/hall.lpc(do_answer)、/d/songshan/fengchantai.lpc(do_decide、do_jieti) 等共 14 个文件。最多的函数名:do_tiao 2 处、do_learn 2 处、do_test 1 处、do_pass 1 处、do_resign 1 处。没有动(读过):/clone/obj/job/job_protect_npc.lpc 的 do_baohu(对象本身编译不过,登记留着,等对象能加载再处理)、/cmds/leitai/dzd/dzd_flag.lpc 的 do_check(对象本身编译不过,登记留着,等对象能加载再处理)。
对象路径里的 ..: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/dali/yideng/npc/yuren.lpc(__DIR__ "../obj/boat" → "/d/dali/yideng/obj/boat"), /d/dali/yideng/npc/yuren.lpc(__DIR__ "../obj/jiang" → "/d/dali/yideng/obj/jiang"), /d/lanzhou/npc/chen.lpc(__DIR__ "../obj/jiuyin-book2" → "/d/lanzhou/obj/jiuyin-book2"), /d/wudang/npc/xiaocui.lpc(__DIR__ "../obj/nuerxiang" → "/d/wudang/obj/nuerxiang")。
缺失物品的 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 都被跳过。1 个文件里 1 处(new)改成 { object new_ob = new(路径); if (new_ob) new_ob->move(...); }(scripts/lpc_guard_carry_object.py --new:路径按这个文件自己看到的宏解析,宏名冲突、虚拟对象路径一律跳过)。 对这 1 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 1 个,现在 1 个(新增可加载 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 ← fengyun434:check_control()(1 处调用)、check_length()(4 处调用)、check_return()(1 处调用)、check_space()(1 处调用)。
(: 函数 :) 指针指向对象自己没有定义的函数(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。 还剩 1 个函数没有补(inquiry 1):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/d/mr/npc/fu.lpc:ask_dan1。
(: 函数名 :) 指针指向本库没有的函数:只含这个指针的行加了注释(2026-10-22,KB 06 §7.355 补充)
inquiry / item_desc / chat_msg* 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:用到时(ask、look、NPC 闲聊)驱动抛 *Undefined lfun pointer called: 函数名,这一拍心跳的后半段被跳过。这些函数没有可以补回的来源(别的库同一路径的文件里没有,或者它还要用本库没有的东西),所以 1 个文件里 1 行只含这个指针的行("玉露清新散": (: ask_dan1 :),)加了注释,原文保留并写明原因,这一项当作没有这个话题 / 物品 / 闲聊条目(scripts/lpc_fix_dangling_lfun.py;#include 的文件在库里缺失的对象、指针不在整行上的写法没动)。 前提(function_exists())和加载检查(HEAD 与工作区对比,本库改动的 .lpc 文件)的结果见 KB 06 §7.355:没有一个回归。
汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 91 处(2026-10-22,KB 06 §7.360)
原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:拜入少林 / 武当 / 峨嵋等派的玩家被改成错误的法名(二字名 张三 变成一个字的 澄,更长的名字取到第三个字);各派掌门的辈分判断(name[0..1] == "澄")永远不成立;改名官 zhuce 的长度检查按字节算,合法的名字一个也过不了;NPC 的称呼和提示里取到两个字而不是一个字。
本库 91 处(32 个文件):拼名字的赋值 19 处、与汉字常量比较 52 处、改名官的长度检查 14 处、对切片赋值 3 处、两个名字切片互比 2 处、query("name")[..] 1 处。例:name[2..3] → name[1..1](/d/shaolin/npc/fangsheng.lpc:150);old_name[2..3] → old_name[1..1](/d/shaolin/npc/rong.lpc:66);old_name[0..3] → old_name[0..1](/d/thd/npc/yaoshi.lpc:405)。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 处(7 个文件):if (strlen(cmd) < 4 || strlen(cmd) > 8)(/cmds/group/gforce.lpc:180);if (strlen(rname) < 4 || strlen(rname) > 16) {(/cmds/usr/irc.lpc:135)。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 区间 9 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。
从末尾按字节取汉字、书剑开帮长度(2026-10-23,KB 06 §7.364)
§7.360 只折了数字下标的 [a..b],从末尾取的 [<2..<1](GBK 下一个汉字)和 [<4..<1](两个字)还留着:开帮 gcreate 的帮助写「二至六个汉字 / 3到8个英文字母」,检查却是 (strlen%2)==1 || <4 || >12,末字对 ({ "帮","会",… }) 用 gname2[<2..<1] 永远对不上;少林 / 峨嵋法名 "明" + name[<2..<1]、死讯 killer[<2..<1] != "。"、兵器改名的 can_name 同样坏掉。scripts/lpc_fix_tail_slice.py 只改汉字语境(对照非 ASCII 字面量、can_name / valid_group_name、拼名字),ASCII 补零 / ctime / 复数词尾 / .lpc 不动;[<2..<1]→[<1..<1],[<4..<1]→[<2..<1],开帮门槛按帮助改写。本库 开帮 gcreate 1 个文件;从末尾切片 9 个文件。证明(scripts/lpcc_eval.py bxsj):HEAD 上 斧头帮[<2..<1] 对不上「帮」;改后 [<1..<1] 对上;长度放行 斧头帮/fut;"明"+"张三"[<1..<1] 得「明三」。
Beyond 出口仍指向 /u/beyond(2026-10-23,KB 06 §7.368)
宁波 / 苏州 / 慕容等区域已进 /d/ningbo、/d/suzhou、/d/mr、/d/hz,出口与任务物路径仍写 /u/beyond/…。scripts/lpc_fix_beyond_exits.py 在目标 .lpc 存在时改写(murong→mr)。本库 5 处。证明:sjplii xikou HEAD 西向 /u/beyond/ningbo/aywsi,改后 /d/ningbo/aywsi 可加载;bxsj hubian1 三向均可加载。
无参数时命令对 0 的调用(玩家/--all-dirs 残留,2026-10-27,KB 06 §7.336 Also)
无参数命令把 0 交给 present / sscanf / file_exists 等会运行时报错。lpc_fix_bare_arg.py --all-dirs 在首次使用参数前加守卫。多行 if (arg && !sscanf)(吐纳默认消耗)与已有 can_learn/get_object 守卫的误报已回退;本库本批改动见该提交。
丐帮长老 gb_nz 无法加载(2026-10-27,KB 04 §6.25 Also)
d/gb/npc/gb_nz.lpc:mixed wherei=({ 少了内层 ({(手足 xkxz2 / xkxc98sj 的 d/gaibang/npc/gb_nz 有),query_temp("promotion_target) 缺收尾引号,accept_object 在注释掉的备用奖励块前多了一个 }。补内层数组、补引号、去掉多余 },并删掉未用的 fam。/u/hunthu/ 路径未动(内容)。证明:HEAD 加载失败;改后加载成功。
深度功能测试(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… 行。