info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
登录画面自称"A Journey to the West",版本号 2.01,最早制作于 1996-1998 年;本项目考证认为它极可能是"ES II / 西游记"这一整个引擎家族(包括 xyj2000f、mhxy、xiyouji2003、xiyouji2006、xiyouji450、shenmo 等众多同题材姊妹游戏在内)最早的祖先版本——文件时间戳最早,且没有任何后续站点加上的"破解者"署名痕迹;游戏以《西游记》原著世界观为背景,玩家从长安城出发游历天下,探索盘丝洞、五指山、南海等小说中的经典地点,作为整个西游记 MUD 世系的源头,世界内容相对朴素、原始,适合用来对比后续各分支如何演化。
English
Self-titled "A Journey to the West," version 2.01, originally created 1996-1998. This project's research concludes it is very likely the earliest ancestor of the entire "ES II / Journey to the West" engine family (which includes xyj2000f, mhxy, xiyouji2003, xiyouji2006, xiyouji450, shenmo, and other sibling games in this collection) — its files carry the earliest timestamps and show none of the "cracker" credit lines later sites added. Set in the world of the original novel, players set out from Chang'an to explore the story's iconic locations (the Silk-Web Cave, Five-Finger Mountain, the South Sea, and more). As the wellspring of the whole Journey to the West MUD lineage its world content is comparatively simple, the most primitive form in the series and a useful baseline for comparing how the later branches evolved.
README
内容亮点
- 是本项目西游记大家族中唯一没有"连线地址格式"限制问题的版本。
在线试玩
https://mudlibs.fluffos.info/xiyouji/
管理员账号 / Admin account
- id:
fluffos - 密码 / password:
Mud@2026 - 中文名 / display name: 浮浮
- 权限 / level:
(admin)—— 本 lib 最高权限(通过adm/etc/wizlist授予),可使用update、goto等全部巫师指令。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。
本地运行
cd libs/xiyouji
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40079。
NOTES · 移植与修复记录
西游记.rar → xiyouji (archive #84)
- Archive:
archives/西游记.rar(plain, ~9.9MB). Extracted root:xyj/with the actual mudlib atxyj/world/, alongside aconfig.cfg,start-xiyou/start-xiyou.BAKlaunch scripts, areadme, and prebuilt Windows binaries (mudos.exe,driver.exe) — the latter ignored per convention, our ownbuild-debugdriver used throughout. - Self-identified name:
config.cfg'sname :field (once decoded) reads西游记; the live banner also rendersA Journey to the West,Version 2.01 Copyright 1996-1998 by XYJ Wiz Group, and lists mirror sites (xiyouji.org, plus stations in 大连/北京/珠海/ 厦门/杭州/哈尔滨) — this is clearly the original/upstream "XYJ" ES II codebase, dated 1996-1998 in its own file timestamps (the archive's files are stamped 1998-05 through 1998-10), i.e. OLDER than every other 已-done 西游记-themed sibling. Kept the slugxiyouji(the archive's own title, undecorated). - Config:
xyj/config.cfg(MudOS 0.9.20 format, standard directive set, had aport number : 5555to replace). Converted to UTF-8 before any edit (§5), then edited for port/mudlib directory only. - Port: 40079 (per the task's instruction — next free after 40076-40078 reserved for archives #27/#82/#83).
Lineage: confirmed the likely ANCESTOR of the whole "ES II / XYJ" 西游记 family, not a duplicate of any already-done sibling
Per the task's explicit instruction, read libs/xyj2000f/NOTES.md
(#15), libs/mhxy/NOTES.md (#19), libs/mhxyqd/NOTES.md (#56),
libs/shenmo/NOTES.md (#73), libs/xiyouji2003/NOTES.md (#81) first, then
diffed/md5sum'd this archive's adm/obj/master.c, adm/daemons/chinesed.c
(the translator daemon; the byte-check daemon is adm/simul_efun/chinese.c
here), and adm/daemons/logind.c against the raw pre-conversion files of
all five before doing any conversion work.
Findings:
adm/obj/master.c: header comment reads verbatim// for ES II mudlib // original from Lil // rewritten by Annihilator (11/07/94)with no additional site-branding/cracker credit line at all — every other sibling's copy of this exact file adds one (xyj2000f: "cracked by vikee 2/09/2002";xiyouji2003: "Last Modified By Pkyou@xyj 2002-06-12 19:00";shenmo: "rewritten by djx (09/09/2003), xfan 3.10"). This is the earliest, least-modified copy of the family's master.c seen so far.md5sums ofmaster.c/chinesed.c/logind.cdid not match any of the five siblings byte-for-byte (this archive is not a duplicate of any of them), but a line-count/structural diff againstxyj2000fandxiyouji2003showed a large fraction of lines textually different — consistent with those two being LATER, more-modified snapshots of the same lineage (both add/change site-specific banners, extra daemons, patched bugs over the ~1998-2003 span) rather than unrelated codebases.shenmo'smaster.c(also literally rooted atxyj/in its own raw archive, being a 2003+ "神魔传说" ES II/Neolith mega-fork) is the closest structural match by line diff count, but still not identical.adm/simul_efun/chinese.chere is a tiny (16-line)is_chinese()/to_chinese()/chinese_number()wrapper delegating toadm/daemons/chinesed.c(a *translator* daemon, not a byte-range checker) — same overall shape as the rest of the ES II family, and the same GBK byte-rangeis_chinese()bug (§15h) recurs verbatim.adm/daemons/logind.c(853 lines) has the same registration-flow shape asxianlvqiyuan/xkxz2-style Century/ES-family libs (GB/BIG5 prompt → student age-gate → English id → confirm → Chinese name → password → confirm password → email → gender → gift/stat-roll confirm), but its owncheck_legal_id/check_legal_nameare defined directly inlogind.citself, not a separatenamed.c/named.lpcdaemon — so the deepernamed.lpcPATH()/sliding-window fix from §15h did not apply here (there's no such file in this archive at all).
Conclusion: this archive is a genuine, distinct member of the
"ES II / XYJ" lineage shared with xyj2000f (#15), mhxy/
mhxyqd (#19/#56), shenmo (#73), and xiyouji2003 (#81) —
almost certainly their common ancestor snapshot (oldest file
timestamps, least site-specific modification) — but NOT a byte-duplicate
of any of them. Known-lineage fixes (§15h chinese-detection, dns_master
preload exclusion, master.lpc valid_write/valid_read shape) were ported
proactively; fixes specific to a LATER snapshot's added features
(convertd.lpc's Greek-table typo, §8h) were independently re-discovered
here since this codebase's own copy of convertd.lpc has the bug too (see
below) — expected, since §8h itself is documented as recurring across the
whole family.
Flag for later cross-check: archives #82 and #83 (processed
concurrently by sibling agents, not yet available to read at the time of
this pass) are also 西游记-themed per the task description. A future pass
should diff this archive's adm/obj/master.c/adm/simul_efun/chinese.c/
adm/daemons/logind.c against whatever libs/<slug>/raw/ those two
archives extract to, to determine if either is an even-closer sibling or
duplicate of this one. (One such sibling, working under slug
xiyouji2006, was observed running concurrently on this same host during
this pass — its own separate NOTES.md should be consulted once available.)
Fixes applied (with rationale)
1. §15h — GBK byte-range Chinese-detection bug, in
adm/simul_efun/chinese.lpc's is_chinese(): replaced the byte-range
check (strlen(str)>=2 && str[0]>160 && str[0]<255) with a CJK
Unicode codepoint range check (strlen(str)>=1 && str[0]>=0x4e00 &&
str[0]<=0x9fff). Without this, every real Chinese name is silently
rejected at registration (§15h's core, most-impactful finding across
the whole project). Also fixed check_legal_name() in
adm/daemons/logind.lpc: halved the byte-calibrated length bounds
(strlen<2→strlen<1, strlen>12→strlen>6, matching what the
error message already says: "一到六个中文字" = 1 to 6 Chinese
characters) and dropped the i%2==0 && byte-alignment gate on the
per-character is_chinese() sliding check (every UTF-8 index is
already one full character, not every-other-byte).
2. §15p — proactively excluded /adm/daemons/network/dns_master from
adm/etc/preload before the first boot attempt. This lib's
preload list had it listed (confirmed via adm/daemons/network/
dns_master.c's presence and its own hardcoded remote boot-server
dependency).
3. §15ai — a dns_master-absence shutdown(1) gate in logind.lpc's
encoding() callback. Found on read-through, exactly as the task
description flagged as a known risk of the §15p exclusion: a
"mirror-IP-verification" check
(if(!find_object(DNS_MASTER) || !"/adm/daemons/band"->check_ip(...))
shutdown(1);) that unconditionally treated "daemon never loaded"
(guaranteed true here, since we exclude it) as "verification failed"
and called shutdown(1) on the entire driver process on the very
first connection. Fixed by changing the guard from !find_object(...)
to find_object(...) && ... — daemon absent now degrades to "skip the
gate" instead of "gate failed", matching the established fix pattern.
Applied proactively before ever booting (read the code first, per the
task's explicit instruction to check every catalog section against
the actual source before boot).
4. §8h — convertd.lpc's Greek/CJK-table stray-backslash typo,
recurring exactly as documented for xyj2000f/mhxy: 45
separate lines (not just the usual handful) had a stray literal
backslash immediately before the closing quote of a two-character
string entry ("α\", → "α",, "功\", →
"功",, etc — one base character + a trailing Private-Use-Area
companion codepoint that is itself completely legitimate content in
this BIG5/GB conversion table, confirmed by cross-checking ~1000 other
*unaffected* lines with the identical "char + PUA companion" shape
compiling fine). Root-caused via lpcc's exact error line + a Python
pass over the decoded file finding every line with an odd number of
trailing backslashes before the closing ",/" — not a blind
sed (this file's CRLF-preserving §8h counterexample was specifically
checked for and confirmed absent: the fix script here operated on
already-LF-normalized decoded text and verified zero remaining hits
afterward).
5. §3's blanket static→nosave sed collided with "static/CRASHES"-
and "static/PURGE"-style log-path string literals (the exact
counterexample already documented for moniHuafu, archive #57):
adm/obj/master.lpc (4 occurrences, "static/CRASHES"),
adm/daemons/securityd.lpc + adm/daemons/ss.lpc (1 each,
"static/promotion"), cmds/usr/suicide.lpc ("static/SUICIDE"),
cmds/arch/purge.lpc/purgehouse.lpc ("static/PURGE"),
cmds/wiz/call.lpc ("static/CALL_PLAYER") — all reverted from the
sed's "nosave/..." back to the original "static/...", confirmed
against the raw pre-conversion files first (cross-checked with
iconv-decoded originals to be certain these strings were genuinely
"static in the archive, not something the encoding pass introduced).
6. §15w — log_error()/APPLY_LOG_ERROR broadcasting every compile
WARNING (not just real errors) to the connected non-wizard player.
Found live during the FIRST full interactive registration test: the
default error message (你发现事情不大对了,但是又说不上来。)
spammed ~26 times during ordinary post-registration play (the
first-ever lazy compile of every never-preloaded room/feature file
reached by make_body()/enter_world() — std/char.lpc's
#pragma, feature/*.lpc's unused-local-variable warnings, etc — all
harmless, but all routed through master.lpc's log_error(), which
didn't distinguish a warning from a fatal error). Fixed by gating the
player-facing broadcast on strsrch(message, "warning:")==-1 (still
logs everything to home + "log" regardless). Re-verified with a
fresh registration after the fix and restart: zero spam.
7. Absolute angle-bracket #include, §15t pattern #1:
d/obj/books-nonskill/book-qujing.lpc had #include
</d/qujing/obstacle.h> (an absolute path inside angle brackets, which
this driver's <...> resolver never special-cases) — converted to
quoted form #include "/d/qujing/obstacle.h". Confirmed
book-qujing is real, referenced content (d/westway/npc/laoren.lpc,
d/city/npc/jieding.lpc both new()/carry_object() it), not dead
content — worth fixing, not just noting.
8. ..-relative #include, §15t pattern #2:
d/ourhome/honglou/npc/niu.lpc had #include "../honglou.h" (this
driver disallows .. in #include paths outright) — repointed to the
real absolute quoted path "/d/ourhome/honglou/honglou.h".
9. §8d-style local header one directory removed from its user: three
files under d/kaifeng/npc/old/ (shan.lpc, zhi.lpc, bei.lpc)
#include <quest_ak.h>/<reporting.h>, which exist one directory UP
at d/kaifeng/npc/ (this driver's get_include_path() fix only
prepends the COMPILING file's own directory, not its parent, so this
doesn't resolve automatically) — every OTHER file referencing these
headers already lives directly alongside them and compiles fine.
Fixed narrowly (not a generalized parent-directory search, to avoid
unintended side effects elsewhere) by copying both headers into
d/kaifeng/npc/old/ too.
10. Pre-existing typo, §10-shaped missing closing quote:
d/obj/books-nonskill/hmeng014.lpc had string name = "《红楼梦》
第十四回; (missing closing " before the ;), confirmed against
12+ sibling hmeng0NN.lpc files in the same directory which all use
the correct "..."; form — fixed to match.
11. Pre-existing typo, corrupted/dropped-byte argument: data/
armor.lpc's do_closecommand() had ob->set_alias(cmd, "); (an
unterminated string literal swallowing the next several lines,
cascading into a wall of "Illegal character" errors reported much
further down the file) — confirmed via the raw pre-conversion bytes
that this was originally set_alias(cmd, "<one genuinely-undecodable
byte>");, dropped by the lossy iconv -c recovery pass, and
confirmed via the sibling do_opencommand() function IN THE SAME
FILE (ob->set_alias(cmd,0);) what the correct call shape actually
is — fixed to ob->set_alias(cmd,0); to match.
12. Mapping-literal syntax typo: d/gao/obj/pen.lpc's is_container()
had set("objects", (["/u/bula/gao/obj/kaoji"]) ); — a mapping
literal with a bare key and no :value, which this driver rejects as
a syntax error (compared against d/changan/playerhomes/h_croc.lpc's
own, valid, set("objects", ([ path:1, path:1 ])); usage of the same
"objects" property) — added the missing :1. The referenced target
(/u/bula/gao/obj/kaoji) still doesn't exist anywhere in the archive
(genuine content gap, not fabricated), so this only takes the file
from a hard compile error to a graceful missing-content no-op.
13. Orphaned junk data mistakenly caught by the .c→.lpc rename,
§12 pattern: obj/file.c (416 bytes of literal random binary
noise, file reports "data", confirmed unreferenced anywhere via
grep -rn "/obj/file\b") — renamed to obj/file.orphaned-junk so it
no longer pollutes the lpcc sweep's pass/fail signal.
14. Corrupted pre-existing NPC-vendor save data, §15m-adjacent: four
data/npc/boss/*.o files (city_kongfang.o, city_weiluqi.o,
laosun.o, yangzhongshun.o) are genuinely random/binary garbage
(confirmed byte-identical to the raw un-touched archive via
md5sum — not something our conversion pipeline broke), which would
throw *restore_object(): Illegal file format at runtime the first
time a player actually visits the NPC that owns one (found via the
lpcc sweep compiling /d/city/bookstore in isolation, which clones
its bookseller NPC). Moved out of the way into data/npc/boss/
corrupted-original-backup/ (not deleted) so restore() finds
nothing and proceeds cleanly, matching the established zhonghua2
precedent — this was NOT triggered during the real boot/registration
test (the bookstore isn't on the path from the start room), so it
wasn't blocking anything we tested, but is a real latent crash for
normal play.
15. §14 — valid_override() given the documented 3rd main_file
parameter. master.lpc's copy was the old 2-arg form, and
adm/simul_efun/object.lpc (an #included fragment of
simul_efun.lpc, not that file itself) defines its own destruct()
override calling efun::destruct() — the 2-arg check's file ==
SIMUL_EFUN_OB test is false for this fragment (file is the physical
object.lpc, not simul_efun.lpc), which per the docs is exactly
the scenario the 3rd parameter exists to fix. Applied proactively;
per the catalog's own note this may never surface as a real boot
symptom, but the fix is free and correct.
What was checked and confirmed NOT needed
- §4 (lazy security-daemon load in
valid_read/valid_writecausing infinite recursion):master.lpc'svalid_write/valid_readonly doif (ob = find_object(SECURITY_D)) return ...->valid_write(...); return 0;— noload_object()call at all, so the recursion trap cannot occur. Confirmed by reading the source, not by hitting the bug. - §7 (missing
get_root_uid()/get_bb_uid()): both already present inmaster.lpc, returningROOT_UID/BACKBONE_UID(both defined ininclude/globals.h). - §15 (nitan-family shared simul_efun dbase architecture bug):
adm/simul_efun/wizard.lpc(18 lines) andadm/simul_efun/object.lpc(89 lines) define no bareset/query/deleteat all — every relevant object (std/char.lpc,std/room.lpc, etc)inherit F_DBASEdirectly instead, the architecturally-correct per-object pattern already confirmed safe onrzrmud/xkx2001-lineage libs. - §15l (master.lpc destructing
SIMUL_EFUN_OBincreate()):master.lpc create()is justwrite("master: loaded successfully.\n");— no destruct call at all. - §15ae (
private nomaskcommand-hook breakingadd_actiondispatch):feature/command.lpc'scommand_hook()is declared plainnomask int command_hook(string arg)(noprivate) — a commented-out// private nomask int command_hook(string arg)line directly above is a stale doc-comment, not live code. Confirmed both by grep and by the livelook/scoretest actually working. - §15ag (
ed_start/ed_cmd/query_ed_mode,__OLD_ED__): zero hits anywhere in the tree. - §15al (
crypt(str,0)random-hash-per-call footgun):crypt(pass,0)IS used (innew_password()/cmds/usr/passwd.lpc), but only ONCE, to produce the initially-STORED hash — every later comparison usescrypt(candidate, stored_hash)(a real string salt extracted from the stored hash), which is deterministic regardless of thecrypt(_,0)quirk. Verified independently with Python'scryptmodule against an actual stored hash from a real registered character (qinfeng/test1234) — reproduces byte-for-byte. No client-side challenge/response protocol exists here that would need a second, independently-computedcrypt(_,0)call to match (that's the failure shape §15al actually describes, and it doesn't apply to a plain password-login flow like this one). - §15am (
file_size()==-1truthiness trap): the onefile_size(...)bare-condition usage found (adm/simul_efun/file.lpc'sassure_file()) already correctly compares!=-1.adm/obj/master.lpc'spreload()usesfile_size(file + ".lpc") == -1(also correct — and already auto-adjusted byconvert_lib.sh's literal-.c-reference fixer from the originalfile_size(file + ".c"), confirmed). - §15ac (bare
SAVE_EXTENSIONvs__SAVE_EXTENSION__): one hit (include/net/ftpdsupp.h), but dead code behind#ifdef GUEST_WIZARD_FTP, which is explicitly#undef'd inftpdconf.h— never compiled. - §15n/§15o (custom securityd ACL blocking mid-connection lazy compiles / missing get_include_path):
securityd.lpc'svalid_readunconditionallyreturn 1;(fully open reads, the ACL logic that exists is commented out) — no read-permission gate exists at all, so §15n's specific failure mode can't occur.master.lpchas noget_include_path()override, but this lib has no#include <local.h>-next-to-its-user idiom in the boot/registration path (checked: all 58#include <x.h>angle-bracket references in the tree resolve against the globalinclude/directory, confirmed by name againstls include/*.h, except the two §15t/§8d cases fixed above) — not needed. - §15x (hardcoded
MUD_PORTmismatch):master.lpc'sconnect()doesn't reference a mudlib-side port constant at all; the driver's ownget_config(__MUD_PORT__)-style dynamic lookup is used wherever the port is needed. No fix required. - §15u (dormant "phone-home" destructive license check): grepped
securityd.lpc/master.lpcforrm(/unlink/shutdown(-on-mudlib- root shaped functions; none found beyond the already-addressed §15ai gate (which is ashutdown(1), not a mass-delete). - §15v (
LONELY_IMPROVED-style always-on legacy-driver flag gatingefun::X()calls to nonexistent functions): not a "nitan/Lonely" lineage lib; no such flag or gating pattern present. - §15af (log_error calling wizardp() during compile-time before securityd loads):
master.lpc'slog_error()only callswizardp(this_player(1))inside anif(this_player(1))guard — during preload (before any connection exists),this_player()is always 0, so the dangerous lazy-load-triggering call never fires in the vulnerable early-boot window. Confirmed by reading the code, not by hitting a crash — this file's shape differs fromshenzhou's unguarded version.
Fullwidth punctuation / mojibake escape sequences (harmless, not fixed)
daemon/class/fighter/oldstuff/champion.lpc (5 hits) and obj/npc/
garrison.lpc (2 hits) both have a stray literal backslash immediately
before a bare Chinese character inside a string ("水烟阁传功\使",
"许\多武林人物") — confirmed pre-existing in the raw archive, and
confirmed non-fatal (warning: Unknown escape sequence, not an
error) both via the lpcc sweep and by cross-referencing that these two
files are never reached by the registration/look/score test path.
Left as-is per the project's policy of fixing only what a real
error/lpcc-FAIL demonstrates matters, not every cosmetic warning.
Genuine archive-content gaps (not fixed, not fabricated)
- **Missing
/d/city/npc/obj/*directory entirely** (8 distinct NPC clothing/weapon call_other targets —choupao,cloth,sengpao,longpao,changqiang,caidao,blade) — confirmed the whole directory doesn't exist anywhere in the archive; several/d/city/ npc/*.lpcNPC files reference it increate(). /obj/longsword,/u/mes/obj/blade,/d/changan/playerhomes/npc/ {babyslimia,babycroc}— individually missingcall_other()/new()targets referenced by a handful of NPC/room files, confirmed absent anywhere in the tree.- **Several
daemon/class/oldstuff/*/master.lpcanddaemon/class/fighter/oldstuff/*files grant skills (essencemagic,perception,celestial, etc) that were never actually implemented** in this archive's skill system (*F_SKILL: No such skillat runtime) — the directory name "oldstuff" strongly suggests already-deprecated/retired class content in the *original* game, not something broken by this conversion. d/obj/fabao/EXAMPLE.lpc: a deliberate, incomplete wizard-facing code TEMPLATE (own header comment: "some files to inherit, include, and some functions to declare... notice from the declarations that the fabao can be used as a weapon alone") with real syntax errors — never referenced by any other file (grep -rlncame up empty besides itself), not live game content.- 5 genuinely TRUNCATED source files, confirmed pre-existing in the raw archive (not a conversion artifact):
cmds/imm/cast1.lpc(a wizard-only "cast a spell on someone else's behalf" admin command, cuts off mid-string insidemain(), 0 closing braces — encoding conversion of this file also needed a manual Pythonerrors='ignore'decode pass sinceconvert_lib.sh's automated iconv attempt failed outright on the truncated trailing byte),d/nanhai/obj/jingu2.lpc,d/qujing/wudidong/npc/fangzhang-jail.lpc,d/qujing/jindou/obj/zhuofake.lpc,d/qujing/jingjiling/jingji6.lpc— all cut off mid-statement with no closing braces, all confirmed viagit-free byte inspection to already be this way in the raw.cfiles. None of these are on the registration/look/scorepath (all deep zone/NPC content); left as-is, not fabricated. d/gao/obj/pen.lpc'sis_container()still references a nonexistent/u/bula/gao/obj/kaoji(only the mapping-syntax half of this was fixed, per item 12 above — the missing target itself is a content gap, not something to invent).
Re-verification pass (QA sweep, later session)
Re-tested the full flow end-to-end again this pass (still clean, zero
执行时段错误 in debug.log). Found and fixed one real, shared-lineage
bug while cross-checking against sibling xiyouji450's own re-verification
pass: adm/daemons/logind.lpc's get_name() had a stray, pre-existing
debug leftover printf("%O\n", ob); right after a new player's Chinese
name is accepted -- dumps a raw internal object reference (e.g.
/obj/login#0) straight to the connecting player, on every single
registration. Purely cosmetic (never affected registration itself --
confirmed the Chinese name still gets set and stored correctly either
way) but visibly unprofessional. The exact same leftover was found in
all three other siblings in this session's batch
(xiyouji2003 -- 2 occurrences, xiyouji2006, xiyouji450), confirming
shared lineage at the source level; removed in all four. Re-verified with
a fresh registration (qfrong/秦荣) after restarting the driver: no
stray object-reference text anywhere in the transcript, look/score/
quit all still correct.
Boot + registration + post-login command test (the actual verification)
Booted ~/src/fluffos/build-debug/src/driver config.fluffos from
libs/xiyouji/ (via the tool's run_in_background, after one earlier
attempt via nohup setsid ... & disown died to the documented
unexplained-external-SIGTERM issue — matches AGENTS.md's own note about
this exact symptom on this host). debug.log shows only compiler
WARNINGS (unused locals, #pragma notices, "Illegal to declare nosave
function" — all cosmetic/non-fatal) and ends cleanly with Accepting
telnet connections on 0.0.0.0:40079. / Initializations complete. — zero
fatal errors, zero segfaults, zero "Too deep recursion".
Full registration + post-login command transcript (via scripts/
mudclient.py 127.0.0.1 40079 --timeout 25 --idle 0.6, one continuous
connection, real Chinese name), final confirmation run with a never-before-
used id (qfengyu) after the §15w fix landed and the driver was restarted:
--send "gb" -> GB/BIG5 charset prompt accepted
--send "no" -> student age-gate declined
--send "qfengyu" -> English id (new, unregistered)
--send "y" -> confirmed new-character creation
--send "秦风" -> REAL Chinese name, ACCEPTED on the
first try (confirms §15h fix)
--send "test1234" -> password set
--send "test1234" -> password confirmed
--send "[email protected]" -> email
--send "m" -> gender: male
--send "y" -> accepted the rolled stat block
--send "look" -> full room description of 南城客栈
(South-City Inn), NPCs/board listed
--send "score" -> full character sheet: 【平民】普通
百姓 秦风(Qfengyu), age/birth date,
all 8 stats, HP/MP bars, food/water
bars, kill counts, 潜能:99
--send "quit" -> clean disconnect, "欢迎下次再来!"Zero occurrences of the log_error() default-error-message spam this
time (confirms fix #6 above); zero silent/no-op commands (confirms
add_action dispatch works, ruling out §15ae); zero missing-environment
symptom (confirms the character actually landed inside /d/city/kezhan,
ruling out §15aj). An earlier interactive test (before the §15w fix, id
qfeng/qinfeng) reached the identical successful outcome but with ~26
spurious default-error-message lines interleaved — that repro is what
found bug #6 in the first place.
debug.log after this session: only compile warnings from the lazy
first-time compile of std/char.lpc, its inherited feature/*.lpc
files, std/room.lpc, d/city/kezhan.lpc, feature/equip.lpc,
std/bboard.lpc, obj/mailbox.lpc — all cosmetic, zero errors.
lpcc_check.sh sweep results
First pass (before the fixes in items 4/7-14 above): 4921/4988 pass
(98.7%), 67 failures.
Second pass (after fixes): 4931/4987 pass (98.9%), 56 failures
(file.lpc's rename-away accounts for the total dropping by 1). Memory
stayed healthy throughout both runs (free -h never dropped below
~4.4GB free on this 23GB host) — well below the OOM-risk threshold noted
in AGENTS.md §6b, so no need to back off.
Remaining 56 failures triaged by category, all either genuine
archive-content gaps (documented above) or expected lpcc-isolation
artifacts per §6b (a room/NPC create() calling into another object or
the skill system before anything else is loaded in a bare single-file
compile — e.g. /d/qujing/pingding/shilang1's "Eval interrupted... cost
limit reached" during an isolated NPC-attack-loop compile, or /adm/
simul_efun/object.lpc failing standalone since it's an #include-only
fragment of simul_efun.lpc) — none affect the real boot or the
registration/look/score test, which is the actual completeness gate
per AGENTS.md's "Definition of done".
Directories/files created that weren't in the raw archive
data/login/{a..z}/anddata/user/{a..z}/— the raw archive only shippedc/,p/,t/,w/shards (query_save_file()in bothobj/login.lpcandobj/user.lpcshards save files bysprintf(DATA_DIR "login/%c/%s", id[0], id)/.../user/%c/%s) — created all 26 letter-shard directories proactively so registration with ANY English id doesn't hit a missing-directory write failure (§15ah's general lesson, applied preventively rather than reactively here).d/kaifeng/npc/old/quest_ak.h,d/kaifeng/npc/old/reporting.h— see fix #9.data/npc/boss/corrupted-original-backup/— see fix #14.
Standing scratch-file hygiene
No boot_stdout.log/trace_lpcc.json/similar left in libs/xiyouji/'s
top level or work/ (checked and removed a stray work/trace_lpcc.json
before finishing). lpcc_batch_raw.log and lpcc_fail.log at the
top level are the standard lpcc_check.sh output artifacts, kept per the
same convention every other already-done lib in this project follows.
Driver-rebuild retest + LPC reformat + WASM pass (this session)
- LPC formatter applied (
tools/lpc-syntax, allwork/*.lpc): 4,930 files reformatted, 53 unchanged, 4 refused (self-check failures on messy legacy code, expected). Confirmed theadm/daemons/ logind.lpcprintf("%O\n", ob);removal from the earlier re-verification pass survived reformatting untouched. - Native re-test against the freshly rebuilt driver (
~/src/fluffos/build-debug/src/driver, rebuilt from latest upstream master): boots clean, zeroFATAL/SIGSEGV/执行时段错误indebug.log. Full registration verified with real Chinese name 秦风回 (male, idqfrevb), reaching the actual starting room (南城客栈/South City Inn),look/score/quitall producing correct output. - WASM build tested (
~/src/fluffos/build-wasm/srcviascripts/wasm_client.js): boots cleanly (only expected non-fatal preload warnings, nosocketspackage built in). Full registration + login succeeded end-to-end under WASM too — real Chinese name 秦风网 (idqfwasma), through gb/age-gate/id/name/password/email/ gender/gift-accept, landed in the real starting room (南城客栈),lookrendered the actual room description,quitdisconnected cleanly. This lib'slogon()/registration path does not gate onquery_ip_number()'s format, so it is not affected by the documented WASM IP-format limitation — a genuinely clean WASM result, not just a partial one.
WASM-enablement pass (loopback-allow + admin seed)
Applied the standard WASM-first changes (AGENTS.md §1.3b/§1.3e/§1.5):
1. Loopback always allowed through ban gates —
adm/daemons/band.lpc: added is_local_ip(string ip) helper
(127.*, empty/non-string, or non-dotted-quad => local) and
short-circuited all three entry points the login flow calls:
is_banned(), create_char_banned(), is_strict_banned()
(each now return 0 immediately for local IPs). These gates were
not actively blocking (shipped ban lists are empty) but are patched
per standing policy so runtime-added bans can never lock out local/
WASM play.
2. Uptime startup gate: none in this lib (checked logind.lpc).
3. Anti-flood throttles: none per-IP in this lib. The "玩家已经太多"
gates are global player-count caps, not per-IP throttles — left
intact. SECURITY_D->match_wiz_site() returns 1 when a wizard has
no wiz_sites entry, so it does not block fluffos — left intact.
4. Admin account seeded — id fluffos, password Mud@2026,
display name 浮浮 (male), registered through the real flow
(gb → no student gate → id → y → name → password ×2 → email →
gender → accept talents). Granted (admin) via adm/etc/wizlist
(fluffos (admin)), read by securityd.lpc::create(). Verified
after restart: re-login shows 目前权限:(admin),
update /adm/daemons/band recompiled OK, goto worked. Save files:
work/data/user/f/fluffos.o + work/data/login/f/fluffos.o
(untracked, NOT gitignored — orchestrator must git add).
Retest: fresh registration (fluffos itself) reached 南城客栈 as
(player), look correct; fluffos re-login as (admin) with working
wizard commands; log/debug.log clean (0 errors).
深度功能测试 / Deep functional test (2026-07-24)
Second lineage in the project's round-two deep-playthrough pass (after
bxsj, a completely different codebase/lineage — see
libs/bxsj/NOTES.md's "深度功能测试" for the worked-example this pass
follows). Played as an ordinary new player through registration,
exploration, safe combat, organic skill-learning and sect-joining, a real
quit, and a real-wall-clock-gap reconnect, native driver
(build-debug). Read doc/help/newbie in full first (the single
highest-value planning resource, exactly as the methodology predicts —
it named the fight-vs-kill safety distinction, apprentice/learn
syntax, and the general command set up front). doc/help/ also has ~90
other topic files (combat, menpai, individual sect writeups, etc.) but no
separate general-help directory beyond it.
Test characters (both kept, not cleaned up, as representative playthrough evidence):
- id
shenqy, Chinese name 沈青云, passwordTest1234, male — the main playthrough character. State: apprenticed to 秦富 (Qin Fu, 将军府/ General's-Mansion sect, 4th generation —将军府第四代弟子), learnedforce(内功心法) to level 1 via the organic teacher-NPC path, survived afightagainst ahuojiNPC, quit once (dropping its starting 粗布衣 per this lineage's item-drop-on-quit design, see below) and relogged in successfully with all sect/skill state intact. Saves:work/data/user/s/shenqy.o,work/data/login/s/shenqy.o. - id
shenqf, Chinese name 沈青枫, passwordTest1234, male — a second, fresh-registration character used specifically to re-verify thezhangmen.lpc/room.lpcfix (below) end-to-end from a clean boot with no prior state. Saves:work/data/user/s/shenqf.o,work/data/login/s/shenqf.o.
Bug found and fixed: unbounded init()/reset() recursion crashes the FIRST visit to any of the game's 9 sect-entrance rooms
This is a genuinely new bug class for this project's catalog — not
the §7.16 rank-decay class (bxsj's bug), and not any previously-cataloged
shape. Filed as file:line d/jjf/npc/zhangmen.lpc:36/623 (the crash
site) and std/room.lpc:25 (reset(), the structural root).
- Symptom: the very first time any player walks into
/d/jjf/front_yard2(练武场, the courtyard housing 将军府's "senior disciple" NPC, directly on the only path from the sect's gate to its main hall where the sect's actual teacher NPC lives) the connection gets a caught-but-real crash:你发现事情不大对了,但是又说不上来。(this lib's generic non-wizard error message), andlog/debug.logshowsToo deep recursion.— the driver's call-stack-depth safety limit (CFG_MAX_CALL_DEPTH, a compile-time constant, currently150in this build; see "false lead" note below). The exact blamed file:line varies run to run (sometimeszhangmen.lpc:36+:623, sometimes deeper insidereset_me(),feature/dbase.lpc'squery_temp(), orstd/char.lpc'ssetup()) — a strong signal, in hindsight, that the recursion is a genuine unbounded *reentrant call* cycle rather than one specific buggy line: whichever mudlib function happens to be executing at the moment the stack finally tips over gets blamed, and that moment is inherently timing-sensitive. Player-visible impact: the room usually still gets entered (the crash is caught) but the NPCs inside render with a corrupted title (0掌门大师兄 大弟子(Zhang men)— a stray"0"from afamily/ family_nameproperty read that resolved to0/undefined mid-crash), and on unlucky timing the move itself can silently fail entirely (command produces no visible room description at all). Once the room has been visited once and its objects are in memory, subsequent visits are clean — this is a first-visit-only, fresh-boot bug, invisible to any verification pass that doesn't specifically walk a brand-new character through content beyond the start room, and easy to miss even in manual testing because the crash is silently caught (exactly the §10.7 lesson: check debug.log, don't just eyeball the screen). - Root cause (two contributing mechanisms, both fixed): 1.
std/room.lpc'ssetup()callsreset()synchronously (this_object()->reset();), and every room'screate()ends withsetup()— soreset()runs twice for a brand-new room: once explicitly as the last step of its owncreate(), and once more from the driver's own standard "give a freshly-loaded object a reset pass" behavior, which can fire for the same object on the same underlying trigger (a player'smove()into it).reset()populates the room's NPCs viamake_inventory()→new(file)→move(this_object()), and only marks that population done at the very last line (set_temp("objects", ob)). If a second call toreset()starts before the first one reaches that last line (i.e. is genuinely reentrant, not merely called-again-later), it seesquery_temp("objects")still empty and clones a second full set of NPCs — and cloning+moving an NPC synchronously fires that NPC'sinit(), which for this lineage'szhangmen.lpc(below) can itself touch the room again, closing a cycle that repeats until the driver's call-depth limit aborts it. 2.d/jjf/npc/zhangmen.lpc'sinit()self-initializes viacreate_identity(master, where), called withwhereas a bare STRING path to the NPC's own room ("/d/jjf/front_yard2", always — every call site in every one of this lineage's 9 sect-zhangmencopies passes its own home room's path this same way).create_identity()'s original body, whenwhereisn't already an object, did an unconditionalcall_other(where, "???"); where_ob = find_object(where);purely to force the room to be a resolvable object — a force-load-by-path idiom that is fine in isolation but, called from *inside* that very room's own first-everreset()(per #1 above), the room may not yet be registered as "loaded" fromfind_object()'s point of view, so the forcedcall_other()can itself trigger the driver to treat the room as needing (re)compiling — which runscreate()/setup()/reset()on it again, cloningzhangmenagain, callinginit()/create_identity()again. This is the second half of the same cycle, and (being *this* lineage's own NPC code, not shared driver plumbing) is the more surprising, mudlib-specific half. Neither mechanism alone reliably reproduced the crash in isolated testing (each was independently disabled and retested — see "false leads" below); both together, under this driver's real reset/init timing, do. The fix therefore closes both doors rather than betting on which one is "the" cause. - Fix (both parts; each is a defensive reentrancy guard, not a behavior change for the normal, non-reentrant case): 1.
std/room.lpc: added anosave int resetting_now;flag, checked at the top ofreset()(if (resetting_now) return;before doing anything else) and cleared at every return path. A reentrant call toreset()on the same room object is now always a safe no-op; the original (outer) call still runs to completion exactly once. This is a lib-wide change (every room in the game inheritsstd/room.lpc) but is provably inert for the overwhelmingly common non-reentrant case — confirmed by re-testing several already-known good multi-NPC rooms (d/city/kezhanwith its 2 NPCs,d/city/ center,d/jjf/front_yard/front_yard2themselves) after the fix and seeing identical, correct NPC population every time. 2.d/jjf/npc/zhangmen.lpc(and its 7 sibling per-sect copies, see below): added anosave int in_init_now;flag with the same guard-at-entry/clear-at-every-return shape around the ENTIRE body ofinit().init()is pure one-time setup with no reason to ever be legitimately reentrant on the same live object, so skipping a nested call is always safe. 3. Also hardenedcreate_identity()'swhere-resolution itself (belt-and-suspenders, on top of #1/#2, not a substitute for them): sincewhereis, at every real call site in this lineage, always the room the NPC was just cloned into, resolve it viaenvironment(this_object())first and only fall back to the oldcall_other(where, "???")/find_object(where)force-load dance if that doesn't already give the right object. This removes one avenue of the room re-touching itself but, per the "false leads" note below, was NOT sufficient by itself. Scope: this lineage ships the identicalcreate_identity()/init()shape in all 9 of its sect-entrance "senior disciple" NPCs (one per sect, matchinghelp menpai's 9-sect list) —d/jjf/npc/zhangmen.lpc(将军府, live-reproduced and fixed), plusd/nanhai/,d/sea/,d/xueshan/,d/death/,d/lingtai/,d/moon/,d/qujing/wudidong/(all fixed identically, same guard shape, verified each file still diffs cleanly against its siblings aside from the expected per-sect content). Only/d/jjf/front_yard2was live-reproduced and live-verified fixed — the other 8 sects are scattered across the map (方寸山, 昆仑山月宫, 南海普陀山, 阴曹地府, 大唐将军府 done, 龙宫, 陷空山无底洞, 大雪山, 五庄观) and a live round-trip to each was not pursued given the time budget; they are fixed proactively by code-shape match, per this project's own standing practice (AGENTS.md's "port the sibling's proven fix" pattern, §2.1), but this specific fix's live verification is honestly one-out-of-nine. A future pass reaching any of the other 8 sects should spot-check its ownzhangmen.lpc's entrance room on first visit. - False leads worth recording (spent real time on these; ruling them out is itself useful for the next person who hits this shape):
- maximum call depth : 30 in config.fluffos is a dead setting on
this driver build — raising it to 150 (the driver's own
hardcoded default) had zero effect on the crash. Checked the
actual driver source (~/src/fluffos/src/vm/internal/base/
interpret.cc): the enforced limit is the compile-time constant
CFG_MAX_CALL_DEPTH (150); the config key is registered in rc.cc
but never read by the interpreter. The archive's own "(unused
currently)" comment on this key is, unusually, accurate for this
driver build (most of this file's "(unused currently)" comments are
stale — see logind.lpc's call-depth-adjacent settings elsewhere in
this project's other libs — so don't generalize this either way
without checking the specific key). Left the config value unchanged
(30, the archive default) with a comment recording this so a future
agent doesn't re-spend the time. If a lib's crash trace shows "Too
deep recursion" at a depth that looks legitimately-deep-but-finite
(not cyclic), the fix has to be an actual code change, not a config
tweak, on this driver.
- Disabling *only* create_identity()'s call from init() still
crashed (blamed reset_me() / feature/dbase.lpc's query_temp()
instead). Disabling reset_me()/restore()/fully_recover() too
(leaving init() almost empty) *still* crashed, now blamed on
me->setup()/std/char.lpc's setup(). This is what established
that the recursion isn't really "inside" any one of zhangmen.lpc's
own functions — it's that init() itself was being re-entered on the
same object, so whatever code happened to be running when the
call-depth limit hit varied by exact timing.
- Applying *only* the std/room.lpc reentrancy guard (without the
zhangmen.lpc init() guard) still crashed. Applying *only* the
zhangmen.lpc init() guard (confirmed via git stash isolation,
without the room.lpc guard) was the point at which live retesting
stopped reproducing the crash across several repeated fresh-boot
attempts — but std/room.lpc's setup() calling reset()
synchronously (see root cause #1) is real, structural, and shared by
every room in the game, so the guard was kept anyway as defense in
depth even though the zhangmen.lpc-side guard alone was sufficient
in this specific reproduction.
- Verified: reproduced live pre-fix, multiple times, across fresh driver restarts (not just one lucky/unlucky run — the timing-dependent nature of the bug means a single run either way isn't conclusive). Post-fix: re-tested via
shenqy(existing character) and a brand new registration (shenqf/沈青枫) from a freshly restarted driver each time, walking the full南城客栈→朱雀大街→十字街头→青龙大街×2 →answer 拜师→将军府→front_yard(练武场/sandbags) →front_yard2(练武场/木桩+沙坑, the crash site) →正厅(main hall, 秦琼) route four separate times post-fix with zero recurrence and a cleanlog/debug.log(grep -i "fatal\|too deep\|error:"empty) every time.zhangmen's displayed title also changed from the corrupted0掌门大师兄 大弟子to the correct将军府掌门大师兄 大弟子post-fix, confirming the crash was also silently corrupting its own state, not just producing a scary-looking but harmless message.
What was tested and confirmed working
- Registration: two independent real Chinese names (沈青云, 沈青枫), full flow (gb/big5 charset choice → student age-gate → English id → confirm new character → Chinese name → password → confirm → email → gender → accept rolled talents), landing in
南城客栈(South City Inn) both times. - Movement/exploration: walked
南城客栈→朱雀大街→十字街头(city center, 4-way hub) →青龙大街×2 →将军府(将军府总管 秦安's gate, gated behindanswer 拜师) →练武场(front_yard, sandbags) →练武场(front_yard2, wooden posts/sand pits — the crash site, now fixed) →正厅(keting, main hall). Room descriptions, exits, and day/night flavor text all correct throughout; read the underlying.lpcroom files to plan the route rather than guessing (§10.7's explicit instruction) — this is how/d/jjf/gate'sanswer 拜师gate (implemented ind/jjf/npc/qinan.lpc'sdo_answer()) was discovered before blindly walking into a "你胡说什么呀?" rejection loop. - Character info:
score(title/attributes/HP-MP bars/kill count/潜能) andhp(气/神/食物/饮水/内力/法力 bars) both correct at every stage (fresh, post-apprentice, post-skill-learn).icorrect throughout, including the□equipped-item marker convention (same as other ES2- family libs in this project). - Combat: this lib's own
help combatdocumentsfight(notkill) as the intended sparring verb — battles fought withfightstop at unconsciousness/surrender/flight and don't carry grudges, matchingbxsj's "safe sparring" pattern but implemented as a command *contract* here rather than a dedicated stat-mirroring training-dummy object. Note: a genuinemuren.lpc(木人/training dummy) object with a stat-mirroringaccept_fight()DOES exist in this codebase (d/city/obj/muren.lpc,d/obj/misc/muren.lpc— byte-identical duplicates) but neither copy is placed in any room's"objects"mapping anywhere in the archive (confirmed viagrep -rn "obj/ muren\|muren.lpc"across the whole tree) — it's dead/orphaned content, not a live safe-sparring mechanism in this particular archive, despite the exact same idiom being wired up and reachable in sibling lineages (bxsj). Usedfight huoji(a peaceful, low-combat_expshop-clerk NPC ind/city/zahuohang.lpc, matchinghelp newbie's own advice to start with the weakest-looking NPCs) instead — produced a normal turn-by-turn exchange, both fighters ended at full HP after disconnecting mid-fight (confirmingfight's non-lethal auto-resolution), no crash. - Skills: the organic path is
learn <skill> from <teacher>, gated onis_apprentice_of()/family match (cmds/std/learn.lpc) — used after apprenticing (below);learn force from qin fusucceeded,skillscorrectly listed the new内功心法 (force)entry at level 1.skills <teacher>(e.g.skills qin fu) correctly lists the teacher's own full skill roster with□marking their enabled specials. - Menpai/sect: the organic path is
apprentice/bai <target>(cmds/std/apprentice.lpc), which for an NPC withrecruit/attempt_apprenticesupport auto-completes in one step. Two sect NPCs were checked in将军府:d/jjf/npc/qinqiong.lpc(秦琼, the sect's actual generation-2 master) gates its ownattempt_apprentice()oncombat_exp >= 100000— correctly out of reach for a fresh character, and NOT a bug (matcheshelp jjf's own text: "先需寻找...", i.e. the game's documented design is to start under a LOWER-ranked teacher and work up) — whiled/jjf/npc/qinfu.lpc(秦富, the steward, generation-3) has no such threshold and accepted immediately:apprentice qin fu→你想要拜秦富为师→ auto-recruit→恭喜您成为将军府的第四代弟子—score's title line updated to将军府第四代弟子 沈青云,师父line correctly showed 秦富. A direct/shortcut sect-join path (a newbie-gift NPC or admin command) was not found in this archive — unlikebxsj's explicitshizhe.lpcgift-envoy, this lib's only join mechanism is the organicapprenticepath; the closest thing,attempt_apprentice()'s auto-recruit, still requires physically finding and interacting with a real sect NPC. Not flagged as a gap — it's simply this lineage's design, and the organic path alone fully satisfies §10.7 item 4's "test at least one path." - Persistence: confirmed at both layers present in this lineage — (a) *silent reconnect* (disconnecting mid-session without
quitand reconnecting with the same id/password mid-scene shows重新连线完毕and resumes exactly where the character was, inventory/HP/location all intact, confirmed repeatedly across this session's many reconnects used for isolating the recursion bug above); (b) *full quit → real wall-clock wait (35s via a backgroundedsleep, per the standing methodology note about not idling a live connection) → relogin*: title (将军府第四代弟子), 师父 (秦富), and the learnedforceskill (初学乍练 1/0) all round-tripped correctly. Location resets to南城客栈on a full relogin regardless of wherequitwas called from (confirmed: quit happened infront_yard/练武场, relogin landed back at the inn) — this lineage's own designed behavior (logind.lpcdoesn't restorestartroomposition across a real login the way the silent-reconnect path does), not a bug. Item-drop-on-quit, same class asbxsj's documented behavior but a DIFFERENT underlying mechanism:cmds/usr/quit.lpc'smain()drops every non-query_autoload()carried item on a realquit(confirmed live:你丢下一件粗布衣— the starting linen robe — printed atquit), yetiafter the relogin showed the SAME item back in inventory. Root cause (read, not guessed):feature/autoload.lpc'srestore_autoload()unconditionally grants a fresh/obj/loginload/ linen.lpc(or skirt+shoes for a female character) at the end of every restore — the guardingif (count==0)check that would have made this conditional is commented out, so it fires every time regardless of whether the player already had starting clothes. (adm/daemons/ logind.lpcalso has an equivalent gender-based cloth-grant block, but it's fully commented out in THIS archive and not what's actually firing —feature/autoload.lpc's unconditional grant is the real mechanism. Don't assume which of two candidate code paths is live without checking both.) Deliberate original-archive design (anti- hoarding for un-flagged gear, masked for the specific case of default starting clothes), not a persistence defect — noted here per the same "don't let a future tester misdiagnose this" reasoning asbxsj's NOTES.md. - Shops/economy:
d/city/kezhan.lpc's 店小二 (Xiao er) sells a documented menu perhelp newbie(buy jiudai from xiao eretc.) — attempted live with a fresh character's starting money (0) and correctly rejected with你的钱不够(insufficient funds) — this IS a real, correct economy-code exercise (the gate fired correctly), but no successful purchase was completed live since a fresh character starts with no money and no income source was pursued within the time budget. Flagged honestly as an incomplete verification, not silently skipped. - Death/respawn: not live-tested.
feature/damage.lpc'sdie()was read (environmentalternative_diehook, wizard-immortal guard, self-crafted-item cleanup,COMBAT_D->announce/killer_rewardcalls) and looks structurally sound, but no character was actually brought to death — thefight huojitest intentionally used the lib's own non-lethalfightverb rather thankill, and nosuicide-equivalent or genuinely-outmatchedkillfight was attempted. Concrete to-do for a future pass on this lib.
Methodology notes (for the broader pass this seeds)
- This is the project's SECOND deep-playthrough pass, and it found a DIFFERENT bug class than the first (
bxsj's §7.16 stale-timestamp decay-loop) — exactly the point of doing a second, lineage-diverse pilot before deciding whether to scale the approach: the two bugs share almost nothing (one is data-driven and time-triggered, this one is a structuralinit()/reset()reentrancy hazard triggered purely by first-visit timing) but both were invisible to every prior verification layer (compile sweep, boot-log watch, registration smoke test) and both were only caught by a real, multi-room playthrough plus a disciplined debug.log grep. - "The room loads fine and the crash message looks vague/generic" is not proof of a harmless bug. This crash's player-visible symptom (
你发现事情不大对了...) is IDENTICAL to the generic warning-spam message this lib's ownlog_error()fix (item 6 in this file's earlier "Fixes applied" section) was built to suppress — meaning a less careful pass could easily have assumed this was "just" a stray compile warning being (correctly, per that earlier fix) shown to the player, and moved on without checkinglog/debug.logfor what kind of error it actually was. Always read the actual debug.log line, not just the player-facing message's general shape. - A crash that reproduces "most of the time but not always" from an otherwise-identical fresh boot is still a real, fixable bug — don't let non-100%-reproducibility be a reason to file it as "flaky/ unexplained" (AGENTS.md §1.4 item 5 documents two such genuinely- unexplained WASM oddities elsewhere in this project, but this one WAS explained, with a concrete structural mechanism, once enough isolation testing was done). The isolation technique that worked here: bisect by disabling chunks of the suspected function's body (via commenting out code, not adding print statements first) and re-testing against a freshly restarted driver each time — adding instrumentation (
efun::write_file) INSIDE the suspect function turned out to contaminate the experiment (the diagnostic write call itself has a call chain, adding stack depth and occasionally becoming the last straw that tipped the crash) before a cleaner "does removing this code entirely change the symptom" bisection cut through the noise. efun::write_file()for diagnostic writes should targetLOG_DIR(/log/), not an arbitrary root path. An early diagnostic attempt wrote to/DEBUG.logand produced its own tangled secondary ACL/logging trace instead of the clean data expected — this lineage'ssecurityd.lpc'svalid_write()unconditionally allows anyLOG_DIR-prefixedwrite_file(), which is both simpler AND avoids the ACL-check code path becoming an unwanted extra variable in the experiment.
WASM 修复摘要(迁移自 meta.json 的 group_note)
西游记/xiyouji.org 家族的祖先快照(1996-98)。
§7.86 跨库扫描修复(留言板 post 崩溃)
BBS_BOARD、BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 23 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
深度功能测试第二轮 / Deep functional test round two (2026-08-15, post driver-upgrade re-test)
驱动于 2026-08-12 升级后的重测。标准检查清单发现并修复三处问题:
1. config.fluffos:maximum evaluation cost 从 400000(已知
风险区间)提升到 5000000。
2. adm/simul_efun/file.lpc:log_file() 没有 assure_file()
目录预建保护,补上调用及前向声明;cat() 补上
read_file() || "" 空值防护。
3. obj/user.lpc::reconnect()(AGENTS.md §7.108,第十三条独立确
认的血统——本档案是 xiyouji.org 大家族本身的祖先快照,§7.17/
§7.19 等多条既有条目的血统源头,这次在它自己身上也确认了同一类
bug):adm/daemons/logind.lpc 有同款 exec(old_link, user);
踢掉重复登录写法,reconnect() 缺少 enable_commands()。按
§7.108 记录的写法预防性修复,现场用两个真实连线复现"保持第一个
连线不断开→第二个连线登录→答 y 踢掉旧连线"验证:score 修复后
立即正常显示完整角色档案。
cmds/wiz/update.lpc(§7.106)与 master.lpc::log_error()(§7.10
的 "arning:" 大小写无关写法)均已是正确写法,均无需改动;本档案无
adm/daemons/closed.lpc,不受 §7.107 影响。
现场验证摘要
驱动干净启动,管理员 fluffos/Mud@2026 登录(GB/BIG5 选择→未成
年人关卡"no"→id+密码)确认 目前权限:(admin),update
/adm/daemons/logind 成功验证真实写入权限。踢掉重复登录重连路径现
场验证通过(见上)。debug.log 全程干净(387 行,无真实错误)。
本轮修改的文件
config.fluffoswork/adm/simul_efun/file.lpcwork/obj/user.lpc
§7.100 sweep (2026-08-19)
Fixed the corpus-wide inherit ROOM; ... replace_program(ROOM); redundant-replace bug (AGENTS.md §7.100). 172 live occurrences deleted: 171 via scripted sweep (fix_710_room.py), plus 1 hand-fixed roommaker-tool template (obj/roommaker.lpc, simple string-builder variant). 2 already-commented-out instances left untouched. No real .lpc source found under work/data/. Verified via build-debug driver boot: clean compile, zero new "cannot replace"/"cannot bind" debug.log lines; confirmed serving via raw-socket connect on port 40079.
§7.30 uninitialized-mapping accessor sweep (2026-08-20)
Corpus-wide mechanical sweep of the feature/skill.lpc shared-lineage
bug (confirmed independently on xiakexing2017/jqxz2015/haiyang2
via round-four testing): 3 accessor(s) in this file returned a raw
never-initialized mapping instance variable (defaults to int 0,
not ([]), until first assigned), crashing any unguarded
keys()/sizeof()/indexing caller for a fresh/untrained character.
Fixed at the accessor level (mapp(x) ? x : ([])) per the documented
remedy. Verified via lpcc --batch static compile check only (not a
live boot) as part of a large mechanical sweep; not individually
functionally re-tested live on this lib.
深度功能测试第三轮 / Deep functional test round three-plus-four (2026-08-21) — CLEAN PASS, no new bugs
Round-two (2026-08-15, above) had explicitly flagged two gaps: no
successful shop purchase completed live, and death/respawn not
live-tested at all. This pass closes both, plus adds a first-ever live
test of the mail system, and re-confirms the §7.108 reconnect fix and
the corpus-wide §7.112 death_stage() reentrancy guard (already
present in d/death/npc/pang.lpc via the earlier 7ff1b78860d sweep
commit — not previously exercised live on THIS lib). Native
build-debug driver, existing character shenqy/沈青云 (still
apprenticed to 秦富, 将军府第四代弟子) plus admin fluffos.
Economy/shop purchase (round-two's gap #1): the player-side economy
is not a wallet integer — money is a physical carried item
(/obj/money/{coin,silver,gold,thousand-cash}, feature/finance.lpc's
can_afford()/pay_money() scan present("coin_money", me) etc.).
Granted shenqy 5000 coin via clone /obj/money/coin +
call coin->set_amount(5000) + give coin to shenqy (admin). buy
huasheng from xiao er then succeeded (你向店小二买下一碟花生豆),
confirming the full economy code path (money-item detection, price
deduction, item creation) works correctly. Note for future testers:
std/char/npc.lpc's add_money(string type, int amount) only exists
on the NPC mixin, not the player one — call shenqy->add_money(...)
returns 0 (no-op) on a real player object; the clone+give path above
is the correct way to grant a player money live.
Death/respawn (round-two's gap #2, now fully closed): call
shenqy->die() (admin) triggered the complete cycle cleanly — corpse
left behind, die()'s DEATH_ROOM->start_death() call is a benign
undefined-function no-op (same shared-lineage artifact noted elsewhere
in this project, confirmed harmless again here), player moved to
/d/death/gate (阴阳界) as a ghost. d/death/npc/pang.lpc's
death_stage() ran its full 5-message, 25-second judge dialogue
(崔判官...) via the already-guarded death_stage_active temp-flag
pattern (§7.112 fix, present since the earlier corpus sweep) with zero
reentrancy issues, then called reincarnate() and moved the player to
REVIVE_ROOM (荒郊小店). debug.log clean throughout (grepped for
non-warning lines across the whole session: zero hits). Potential
(潜能) dropped from 99 to 50 as an intended death penalty
(COMBAT_D->victim_penalty()); HP shown at 35/100 post-revive is
correspondingly partial, not a crash artifact.
Mail system (new — not covered by any prior pass on this lib):
obj/mailbox.lpc itself is dead/orphaned content (not placed in any
room's objects mapping, same class as round-two's noted muren.lpc
gap) — the *live* entry point is d/ourhome/npc/bigeye.lpc (邮差
千里眼/Bigeye, the NPC standing in the start room), whose inquiry
mapping wires "mail"/"信"/"收信" etc. to receive_mail(), which
clones a personal MAILBOX_OB into the player only when the player is
physically standing in their own startroom. ask bigeye about mail →
received a personal mailbox → from correctly reported empty → mail
fluffos (compose flow: title → body → . to end → save-copy y/n) sent
successfully, and admin fluffos got a real live notification
(千里眼跟您说:有您的信!快来一下!). Full round-trip confirmed
working, zero errors.
Reconnect (spot-check re-verification of round-two's §7.108 fix,
unmodified since b24f99518c4): disconnected shenqy's telnet session
uncleanly (killed the tmux pane) and reconnected fresh with the same
id/password — 重新连线完毕, score immediately showed full correct
state (title, 潜能 50 matching the post-death value from this session).
Verdict: no new bugs found this round — a clean confirmatory pass
that closes both gaps round-two left open. Test character shenqy's
save state (money, death/revive location, mailbox) was left as-is per
this project's standing convention of keeping test characters as
representative playthrough evidence, not reset.
AGENTS.md §7.19: enable_player() reentrancy guard (2026-09-01)
Same corpus-wide bug class as mhxy/wuhanzhan: feature/command.lpc's
enable_player() wraps enable_commands() and is unconditionally
reachable from an NPC's init() via setup()/reset_me() (confirmed
on this lib's own d/*/npc/zhangmen*.lpc-family NPCs, matching
mhxy's originally-documented d/xueshan/npc/zhangmen.lpc pattern).
Calling enable_commands() on an object that's already living()
makes the driver re-invoke that object's init() as a side effect;
since init() calls back into enable_player(), that is genuine
same-call-stack reentrancy that repeats until "Too deep recursion"
aborts a room's first-ever visit.
Fixed with a true reentrancy flag (nosave private int
in_enable_player_now;), NOT a bare if (living(this_object()))
return; guard — this lib's feature/damage.lpc revive() and
cmds/std/sleep.lpc wakeup()/wakeup2() all legitimately
re-invoke enable_player() while the object is still living()
(that's how a fainted/asleep character gets commands back), so a
living()-gated guard would silently break every one of those real
re-enables. enable_player()'s single body has no early return
statements, so the flag is set at entry and cleared once, before the
function's fall-through end. Verified with a single-file lpcc
compile check (exit 0, no errors) against feature/command.lpc.
深度功能测试(§10.7,2026-10-04)— negative range ends
scripts/lpc_fix_negative_ranges.py: 4 line(s) in 1 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: 1 here-document block(s) repaired (1 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/moon/bedroom.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-05)— compile-warning pass, truncated files completed
scripts/lpc_warnings.py xiyouji (4987 files; the oldest ancestor of the Journey-to-the-West / ES II family, see the English description): 659 distinct diagnostics at the
start (492 unused locals in 273 files, 39 unknown escapes, 25 prototypes with another argument count, 14 nosave functions ...), 78 at the end, 4937 files compile
and 50 do not before the repairs below. 329 files changed. Tools: scripts/lpc_warnings.py --fix, scripts/lpc_family_pass.py's sequence (redundant inherits, void
overrides, macro redefinitions, prototype return types, clean_up() diamonds named after a rescan, forward declarations); this lib has no wrong-case paths.
Truncated files completed from a sibling (four files, each checked byte for byte). The archive's copies of d/qujing/jindou/obj/zhuofake.lpc (1137 bytes), d/qujing/jingjiling/jingji1.lpc
(1089) and d/qujing/wudidong/npc/fangzhang-jail.lpc (1150) stop in the middle of a function; each is exactly the first bytes of the same file in xiyouji2006 (4546 / 2293 /
10490 bytes), whose remainder was appended (End of file in all three before). d/nanhai/obj/jingu2.lpc lacks only the closing } of free_jingu() that the sibling's copy
has. d/qujing/jingjiling/jingji6.lpc is cut the same way but the sibling's copy is a reformatted version, so it was left.
What is left (19 files with a compile error). Dead or damaged: d/kaifeng/npc/old/* (headers quest_*.h, reporting.h never shipped), cmds/imm/cast1.lpc, d/obj/fabao/EXAMPLE.lpc, d/moon/obj/poem.lpc,
/obj/toy/* (message.h), d/city/obj/dami.lpc and d/obj/food/dami.lpc (a weapon class UNARMED this lib does not have), d/city/npc/duguoyin.lpc (::buy_object() in an NPC with no
dealer base), cmds/wiz/tail.lpc (no tail() simul_efun, as in 92 other libs).
深度功能测试(§10.7,2026-10-05)— room base survives a raising NPC
scripts/lpc_resilient_room.py --apply: make_inventory() of std/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. This lib has no wrong-case paths; the change is the one made in the libs whose renames made such NPCs reachable (KB 03 §4.5), and keeps a room loadable when an NPC in its inventory has a content gap: it loads without that NPC. The room base compiles and loads with the change (HEAD against the working tree).
深度功能测试(§10.7,2026-10-05)— compile-warning pass, second round (29 warnings -> 0)
The first round (above) left 29 warnings that the tools did not take; each is fixed at its source now, so the lib prints no compiler warning when it boots. 17 files changed.
adm/daemons/network/dns_master.lpcquery_services():if (!(mud_svc[mud]["tell"] & SVC_KNOWN)) {held only#if PREF_TELL & SVC_TCP ... #endif, so withPREF_TELLset to UDP theifwas empty and its test an expression with no effect. The#if/#endifpair now sits outside the braces (the same forPREF_FINGER); with the option on, the code is the same text as before (scripts/lpc_fix_no_effect.py, KB 04 §6.10).daemon/skill/xuanyuan-archery.lpc(and the two copies inskill_junk/,skill_wiz/archery/):(: throw_weapon :);as a statement ofpractice_skill()made a function pointer and threw it away, as in the original; deleted.- Unused locals whose initializer is a getter call (
me->query_kar(),RANK_D->query_rude(me),allocate_mapping(3)):d/jjf/npc/yuchigong.lpc,d/obj/fabao/{huntian-ling,qiankun-quan}.lpc,d/qujing/wudidong/npc/{bishu,tianshu}.lpc,d/city/piggy.lpc,d/qujing/jindou/obj/zhuofake.lpc(compiled for the first time with the repair above), andcmds/std/find.lpc(string failnext to a functionfail():fail(me)calls the function, the local was never read). Four unusedthis_room()->query("match/...")locals ofd/kaifeng/ground{1,2,3}.lpcwere deleted by hand:this_room()loads the arena room and each of those functions calls it again. d/kaifeng/ground.h: three tables (modes,mode_hints,families) are defined by every program that includes it, andground.lpc->ground3->ground2->ground1->ground0inherit one another, so each derived program redeclared the variables it inherits (12Redeclaration of global variable). The derived programs#define GROUND_TABLES_INHERITEDbefore the include and the header skips the tables then;ground0.lpcand theguanli.hrooms (east,north,south,west) keep them.- Left:
adm/simul_efun/object.lpcreportsInvalid simulated efunction override+no side effectswhen it is compiled alone (it is#included byadm/obj/simul_efun.lpc, where master'svalid_override()accepts it; the scan drops that pair now), and the 14 files with a compile error listed above (their content is missing or damaged;cmds/wiz/tail.lpcwaits for thetail()simul_efun). - Checked: the 16 changed objects plus
d/kaifeng/{east,north,ground0}load with no diagnostic (scan tree, HEAD against the working tree).
深度功能测试(§10.7,2026-10-05)— tail() simul_efun
scripts/lpc_add_tail.py --apply: cmds/wiz/tail.lpc calls tail(file), an efun of the MudOS this archive ran on that FluffOS does not have (Undefined function tail: the command did not compile). adm/simul_efun/file.lpc now defines void tail(string file): the last 10 lines of the file, written to the player. Load check (HEAD against the working tree): 1 of the 1 tail command file(s) failed to load at HEAD, all load now, and the simul_efun file loads. KB 04 §6.2.
深度功能测试(§10.7,2026-10-05)— 房间列出的物件缺档时房间照常加载
房间基类 std/room.lpc: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。
NPC 带着本库没有的物品,载入失败(2026-10-15,KB 06 §7.73)
15 个文件、18 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()),多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/d/city/npc/obj/choupao、/d/city/npc/obj/bingfu、/d/city/npc/obj/cloth)。本库 NPC 基类 std/char/npc.lpc 的 carry_object() 第一句是 if (file->query_unique()),对不存在的文件直接 call_other,驱动在这里报 call_other() couldn't find object,new() 来不及返回 0,所以只要 NPC 带着一件本库没有的物品就载入不了(调用处加判断对它无效)。在它前面补 if (!objectp(load_object(file))) return 0;(--base),缺的物品就跳过,NPC 照常载入;上面那些调用处同样改成带判断的块(基类以后改回去也不怕)。载入检查:改动的文件改前能载入 1 个,改后 15 个(14 个原来载入不了),没有退步;其余 1 个另有原因(多为 set_skill() 了本库没有的武功)。
去掉的预定义宏 SAVE_EXTENSION(2026-10-18,KB 04 §6.11)
老版 MudOS 给每个 LPC 对象预先定义了 SAVE_EXTENSION(".o");FluffOS 的 ChangeLog(COMPAT_BUSTER 一条)把它删了,改用 __SAVE_EXTENSION__。本库没有自己 #define 这个宏,用到它的文件一律 Undefined variable 'SAVE_EXTENSION',整个对象载入不了。改成 __SAVE_EXTENSION__(scripts/lpc_fix_predefined_macro.py):include/net/ftpdsupp.h。
d/moon/obj/poem 两句诗丢了收尾的引号(2026-10-18,KB 03 §4.6)
string *poem 数组里 "不放玉花飞堕地,留在广寒宫阙。, 和 "把酒送春春不语,黄昏却下潇潇雨。, 的收尾引号没了(转码时跟半个汉字一起丢了),下一行的开引号把字符串合上,后面的诗句成了代码:Illegal character 0xe6,整个物件载入不了。补回 ",。载入:通过。
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 载入不了,刷出它的房间也少一个人。本库有 12 个对象设了本库没有文件的武功(19 个名字,最多的是 perception、celestial、celestrike)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:12 个里 12 个通过。(scripts/lpc_guard_set_skill.py)
补回本库没有的基类文件(2026-10-18,KB 04 §6.15)
下面这些基类本库没有,继承它们的对象(房间、NPC、物品)整批载入不了(inherit XXX; 的宏没定义,或者 inherit "/std/…"; 的文件不在)。同系的库里有同名文件,两库有大量逐字节相同的文件(亲缘关系),所以从那边原样拷来,缺的宏补进全局头文件 include/globals.h:/std/weapon/unarmed(宏 UNARMED)←sjsh,1 个对象继承它,抽样 1/1 载入。每个基类补回后单独载入、再抽样载入使用它的对象,基类载不进来或者一个用户也载不进来的就撤掉(本库撤掉了它们,不再补)。(scripts/lpc_restore_bases.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 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 3 处、3 个文件:yange_blade→yange-blade(1 处)、dragon-step→dragonstep(1 处)、huntian-qiong→huntian-qigong(1 处)。(scripts/lpc_fix_skill_names.py)
调用了父类没有的函数的对象(2026-10-19,KB 06 §7.259)
::create();、::init();、return ::buy_object(who, item); 这类调用写的是父类里有这个函数的版本;本库的父类没有,旧驱动把它当空操作(返回 0),现在的驱动在编译时就报 Unable to find the inherited function,整个对象载入不了。单独成句的调用删掉,return ::f(...) 改成 return 0;(void 函数里是 return;),与旧驱动的行为一致。共 1 处、1 个文件:::buy_object()(1 处)。只改了出错原因全是这一类的文件;还带着别的错误(多半是父类本身没了)的文件没动,等父类补回后再看。(scripts/lpc_fix_dangling_super.py)
武功基类缺少 NewRandom()(2026-10-19,KB 06 §7.261)
基本武功(boxing、houquan、westsword、taiji-quan 等)用 action[NewRandom(i, 20, level/5)] 挑招式;NewRandom() 和它调用的 sum() 在别的库的武功基类里有,本库的 /std/skill.lpc 是更精简的版本,没有,于是这些武功全都报 Undefined function NewRandom 编译不过,有这门武功的人物只能空手。把这一对函数(115 个库共用的同一份文字)补进武功基类,受益武功 2 个:/daemon/skill/jueqingbian.lpc、/daemon/skill_wiz/whip/jueqingbian.lpc。(scripts/lpc_restore_skill_helpers.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 文件。
被注释吞掉的 inherit / include(2026-10-19,KB 06 §7.274)
/daemon/class/moon/moonshentong/shiyue.lpc:inherit SSERVER;。这些指令接在上一行 // 注释的末尾(档案里丢了换行),成了注释的一部分,驱动看不到:缺 inherit 的技能报 Undefined function offensive_target,缺 #include <weapon.h> 的兵器报语法错误。scripts/lpc_fix_swallowed_directive.py 把换行放回去。载入检查 1 个文件,1 个原来编译不过、现在载入,没有退步。
call_out_info() 的每一项只有 3 个元素(2026-10-20,KB 06 §7.287)
驱动返回的是 ({ 物件, 函数, 延迟 }),不带 call_out 的参数,旧代码读第 4 项 info[i][3](callouts 指令、calloutd、cod 工具)一读就报 Array index out of bounds,指令从第一行起什么也打印不出来。改为 (sizeof(info[i]) > 3 ? info[i][3] : 0)(scripts/lpc_fix_callout_info.py)。改动:/cmds/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,共 5 处)。改动:/cmds/wiz/dns.lpc(1 处)、/d/city/puke-room.lpc(4 处)。
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 处)。
登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)
驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/adm/obj/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。
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])) ...,不检查列表是否为空。本库有 1 个 NPC 文件写成 set("chat_msg", ({ }))(或 chat_msg_combat)并带正的 chat_chance(例如 /daemon/class/oldstuff/ronin/master.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) ||)。改动:/std/char/npc.lpc(共 1 个文件)。
巫师命令不带参数时对 0 调用 efun(2026-10-21,KB 06 §7.336)
巫师(管理员)把命令敲成不带参数时,驱动把 0 传给字符串参数;下面的命令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...,这条指令从来没有用过。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py 在第一处使用参数的语句前加守卫:文件自带 int help(object me) 的,守卫是 if (!arg) return help(me);(help() 定义在 main() 之后的 1 个文件补了原型 int help(object me);,否则编译报 Undefined function help)。改动(1 个文件,括号里是接住 0 的 efun):/cmds/wiz/test.lpc(present)。加载检查 HEAD 与工作树对照,没有退步。
cast1:宏 SYNTAX 带着分号(2026-10-21,KB 04 §6.28)
#define SYNTAX "指令格式:cast <法术> [<参数>| on <目标>]\n"; 的 ; 属于宏展开,notify_fail(SYNTAX); 变成 notify_fail("..." ;);,整个指令编译不了(unexpected ';')。去掉宏里的分号,并删掉没用到的局部变量 dest(编译警告)。这份文件在 return notify_fail("对方正在断线中,不能对其施法 这句字符串中间就断了(档案丢了后半截),后半截(。\n"); 之后的 main() 余下部分、destruct_flag()、help())按 xyj42 的同一份文字补全。
拼错的 call_out() / input_to() 目标(KB 06 §7.349,2026-10-21)
计时器或输入提示给驱动的函数名和文件里定义的差一个字母;驱动对不存在的函数静默不理,所以对应的计时从来没有触发过。调用和配套的 remove_call_out() 改成已定义的拼法:find_seal:disapprear → disappear(法术产生的封印十分钟后从不消失)。改动:/d/jjf/obj/find_seal.lpc。
注册到不存在函数的 add_action(2026-10-21,KB 06 §7.349)
add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 8 条注册(5 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/changan/pinqiting.lpc(do_pass、do_resign)、/d/city/npc/duguoyin.lpc(do_vendor_list)、/d/lingtai/npc/kancai.lpc(do_vendor_list)、/d/qujing/jisaiguo/obj/luche.lpc(do_get、do_put)、/d/qujing/wudidong/cave2.lpc(do_push)。最多的函数名:do_vendor_list 2 处、do_push 2 处、do_pass 1 处、do_resign 1 处、do_get 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 处函数体被作者整段注释掉(指针没跟着删),是有意关掉的,没动。
(: 函数名 :) 指针指向本库没有的函数:只含这个指针的行加了注释(2026-10-22,KB 06 §7.355 补充)
inquiry / item_desc / chat_msg* 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:用到时(ask、look、NPC 闲聊)驱动抛 *Undefined lfun pointer called: 函数名,这一拍心跳的后半段被跳过。这些函数没有可以补回的来源(别的库同一路径的文件里没有,或者它还要用本库没有的东西),所以 1 个文件里 2 行只含这个指针的行("剃度": (: ask_for_join :), "出家": (: ask_for_join :),)加了注释,原文保留并写明原因,这一项当作没有这个话题 / 物品 / 闲聊条目(scripts/lpc_fix_dangling_lfun.py;#include 的文件在库里缺失的对象、指针不在整行上的写法没动)。 前提(function_exists())和加载检查(HEAD 与工作区对比,本库改动的 .lpc 文件)的结果见 KB 06 §7.355:没有一个回归。
汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 3 处(2026-10-22,KB 06 §7.360)
原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:拜入少林 / 武当 / 峨嵋等派的玩家被改成错误的法名(二字名 张三 变成一个字的 澄,更长的名字取到第三个字)。
本库 3 处(2 个文件):拼名字的赋值 3 处。例:name[0..1] → name[0..0](/d/nanhai/npc/master.lpc:102)。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,不要重跑。
按字节写的汉字循环、茶馆猜诗和同盟频道标题(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 区间 4 个文件;茶馆猜诗 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(一格两个空格)。本库 4 处(2 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。
洗衣盆 is_container 映射缺值编不过(2026-10-27,KB 04 §6.28 Also)
d/gao/obj/pen.lpc(或 d/qujing/gao/obj/pen)的 is_container() 写成 set("objects", ([ "/u/bula/gao/obj/kaoji" ]));:映射只有键没有值(unexpected ']'),对象无程序。同族 hy5 / xyj2000 已把该函数注释掉。改成标准的 return 1;。证明:HEAD 加载失败;改后加载成功。
toy 旁缺 message.h(2026-10-27,KB 04 §6.23 Also)
obj/toy/*.lpc(及少数 obj/obj/toy、嵌套副本)写 #include "message.h",引号 include 只解析同目录;本 lib 的 d/obj/misc/message.h 有同内容副本,toy 目录没有。玩具对象无法编译(Cannot #include message.h)。从本 lib 的 d/obj/misc/message.h 复制到缺失的 toy 目录旁。证明:HEAD buwawa 加载失败;改后加载成功。
悬空 ::init / unique::init(2026-10-27,KB 06 §7.259 Also)
kugu-1/2/3、kunxian-suo 写 ::init();,tianyi 写 unique::init();,而 F_UNIQUE / ITEM / feature/unique 没有 init;本驱动编译报 Unable to find the inherited function 'init'(tianyi 带 in file 'unique'),物件无法加载。老驱动把空父调用当返回 0。lpc_fix_dangling_super.py 删掉这些语句;cloth::init() 保留。证明:HEAD 加载失败;改后加载成功。
dami 误调 init_unarmed(2026-10-27,KB 04 §6.25 Also)
d/obj/food/dami.lpc 继承 ITEM+F_FOOD 却调用 init_unarmed(5)(并 #include <weapon.h>),未继承空手兵器基类,驱动报 Undefined function init_unarmed。手足 hymud 已注释该行。删掉该调用与无用的 weapon 头。证明:HEAD 加载失败;改后加载成功。
fabao EXAMPLE 缺 ) 与 !(2026-10-27,KB 04 §6.25 Also)
d/obj/fabao/EXAMPLE.lpc 写 if (objectp(victim = present(target, environment())),丢了 ! 和右括号,驱动报 unexpected L_RETURN。按手足 kunxian-suo 改为 if (!objectp(victim = present(target, environment(me))))。证明:HEAD 加载失败;改后加载成功。
深度功能测试(2026-10-27,§6.25 jingji6 message_vision 截断)
d/qujing/jingjiling/jingji6 在 message_vision("$N想 处截断。用 xlqy_new2007 完好本覆盖。证明:HEAD 失败;改后加载。
深度功能测试(2026-10-27,§6.25 tell_object 参数顺序)
tell_object("…", me/who) 参数颠倒(消息当对象);改为 tell_object(me/who, "…")(guest.lpc)。证明:残渣 0;抽样 guest/rukou/forest_c 加载。
深度功能测试(2026-10-27,§6.25 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… 行。