info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
一个脱胎于经典《西游记》题材 MUD 的中文武侠/仙侠游戏,登录横幅另称 "翱翔天际之擎日挽月"。新角色从"南城客栈"起步,先培养力量、悟性、根骨、 敏捷、灵性五项天赋,再从光明磊落、狡黠多变、心狠手辣、阴险奸诈四种天性中选定初始气质,随后沿着呼应《西游记》原著的"大闹天宫""西行取经"等关卡主线闯荡江湖,个人档案面板还会同步显示道行、武学、法力、内力四项境界评价。游戏保留完整的门派与联盟体系,适合长线培养角色成长。
English
A Chinese wuxia/xianxia MUD descended from a classic Journey to the West codebase; its login banner styles it "Soaring Through the Heavens: Grasping the Sun and Drawing the Moon." Players start at the South City Inn, develop five innate attributes, and choose one of four temperaments, then progress through storyline chapters echoing Journey to the West ("Havoc in Heaven," "The Journey for Scriptures") alongside a full sect and alliance system built for long-term character growth.
README
内容亮点
- 玩家从"南城客栈"起步,培养力量、悟性、根骨、敏捷、灵性五项天赋,并 在光明磊落/狡黠多变/心狠手辣/阴险奸诈四种天性中择一,塑造角色初始 气质。
- 保留了呼应《西游记》原始题材的关卡系统:"大闹天宫""西行取经"等,
score个人档案面板会专门列出这两条主线的进度,以及"道行境界/ 武学境界/法力修为/内力修为"四项境界评价。 - 进入游戏世界前有一幅像素风格的电脑显示器 ASCII 画,配一句"你是 不是也象我一样。。。"的感伤台词——比常见的"侠客上路"开场多了一 分怀旧网吧年代的气氛。
- 门派与联盟体系完整,适合长线成长型玩法。
- 界面上仍能看到少量"西游记"旧题材遗留的系统提示文字——这是该库从老 西游记底本二次改造而来的历史痕迹,不影响"翱翔天际"本身的实际内容。
- 巫师/管理账号有独立的二次密码校验机制:首次登录会要求额外设置一个 专用巫师密码,此后每次登录都需要在常规密码之后再验证一次。
在线试玩
https://mudlibs.fluffos.info/aoxiangtianji/
管理员账号 / Admin account
- 账号 id:
fluffos登录密码:Mud@2026中文名:浮浮 - 权限:
(admin)(本 mudlib 的"主管天神"级别),通过adm/etc/wizlist数据文件授予。 - 注意:本游戏对巫师账号有额外的"巫师专用密码"二次校验(已预设为
WizMud2026!),登录时先验证普通密码,再验证这个巫师专用密码。管理 密码(找回密码用)为AdmMud2026x。
警告:这是为本地/浏览器单机游玩预置的公开默认密码,如要对外公网开
服,请务必先修改此账号的所有密码,或从 wizlist 中删除该账号。
本地运行
cd libs/aoxiangtianji
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40101。请在 libs/aoxiangtianji 目录下启动(保证日志写入
同目录)。
NOTES · 移植与修复记录
aoxiangtianji — 翱翔天际 (a Xiyouji/"Journey to the West"-lineage codebase,
fluffos2019-era config)
Archive: 翱翔天际utf8.7z. Port: 40101. Status: done (boots clean,
full registration flow verified end-to-end with a real Chinese name,
admin account verified in both native and WASM modes).
Encoding verdict (per dispatch note: verify, don't assume from filename)
The archive's filename claims "utf8", and this is confirmed true —
this is the one archive in this batch that did NOT need any BIG5/GBK
conversion pass. raw/axtj2019/config.cfg: file(1) reports "Unicode
text, UTF-8 text" outright, and the whole raw tree (10876 files) was
already UTF-8 when extracted — a prior session (this lib's
libs/aoxiangtianji/{raw,work}/ already existed with the .c→.lpc
rename done and 9446 real .lpc files, but config.fluffos/NOTES.md/
README.md were never written) had evidently already done the mechanical
convert_lib.sh-style pass and found nothing to convert. This session
independently re-confirmed the UTF-8 verdict by spot-decoding
config.cfg's name : 翱翔天际 line directly and by the fact that the
lpcc sweep (below) shows essentially zero encoding-shaped failures.
No BIG5 trail-byte-doubling issue applies to this archive at all —
that bug (see libs/niaoren/NOTES.md for the full writeup) is specific
to BIG5's 0x5C trail byte; a UTF-8-native archive was never at risk of it
in the first place.
What this is
Root: raw/axtj2019/ (one level). config.cfg is a modern
fluffos-2019-style config (the archive's own header says "MudOS v21" but
also literally comments "fluffos v2019 运行时配置文件" and defines
external_port_1 : telnet 6666 / external_port_3 : websocket 8888
rather than the older single port number line — this project's
convention is to collapse that to a single port number in
config.fluffos, which is what's done here). master file : /adm/obj/
master, simulated efun file : /adm/obj/simul_efun, mudlib directory :
. (relative in the original, absolute in ours).
The mudlib is clearly a customized fork of a much older "西游记"
(Journey to the West) codebase: the login banner, several system
messages, and even the ASCII-art logo's ALT text still literally say
"西游记"/Welcome to Xi You Ji! in multiple places (adm/daemons/
logind.lpc), while the actual configured name, in-game banners, and the
vertically-read ASCII logo (翱/擎, 翔/日, 之/挽, 天/挽, 际/月 — reads
"翱翔天际" down the left column) are all genuinely 翱翔天际-branded. This
is cosmetic legacy from whoever rebranded the old Xiyouji base into this
new game and didn't update every string — left as-is (harmless, not
gameplay-breaking, and correcting decades of one author's in-jokes/
half-renames is out of scope here).
Large archive: 10876 raw files, 9446 real LPC (.c→.lpc, already done
before this session) source files. No bundled driver binary/mega-lib
concerns.
Fixes applied this session
1. .c→.lpc rename fallout: string-slice extension-stripping bugs
(a new variant of AGENTS.md §2's ".c"-length-assumption family — this
project's catalog documents the sscanf-based version of this bug;
this is the first slice-arithmetic version seen)
adm/daemons/eventd.lpc's create() does:
event_name = get_dir(EVENT_DIR + "*.lpc");
event_name = map_array(event_name, (: $1[0..<3] :));x[0..<3] in LPC means "every character except the last 3". This was
written back when the archive's real extension was .c (2 characters):
for "emei.c" (6 chars), [0..<3] keeps indices 0..3 = "emei" —
correctly strips a 2-char extension. After this project's mechanical
.c→.lpc rename, the same slice against "emei.lpc" (8 chars) keeps
indices 0..5 = "emei.l" — a stray trailing "l", not the bare name.
Confirmed exactly this symptom at boot: Preloading : /adm/daemons/
eventd] -> Error *call_other() couldn't find object '/adm/daemons/event/
emei.l'. (the file emei.lpc genuinely exists; the constructed lookup
path was simply wrong). Fixed by changing <3 to <5 (strip 4 chars,
matching .lpc's actual length) with a comment explaining why. The
identical bug, same fix, was also present in adm/daemons/storyd.lpc
(same get_dir(STORY_DIR+"*.lpc") + map_array($1[0..<3]) shape — this
one didn't throw a boot-time error since nothing preload-triggers a
lookup-by-stripped-name before a player interacts with the story system,
but would have silently broken every story-by-name lookup) and in
cmds/wiz/update.lpc's backup-file-naming logic (new_name=
file[0..<3]+uptime()+".lpc" — cosmetic-only impact, produces a working
but oddly-named backup file like foo.l172023.lpc instead of
foo172023.lpc; fixed anyway since it's the same one-line change).
Worth checking any other lib in this fluffos2019/xiyouji lineage for
the same [0..<3] shape — grep \[0\.\.<3\] and check whether it's
adjacent to a get_dir(...*.lpc) or .lpc string-building context (a
few unrelated [0..<3] uses elsewhere in the tree are legitimate
independent slices, not this bug — verified each hit individually rather
than blind-replacing).
2. Stray debug printf("%O\n", ob) leaking to the connecting player
adm/daemons/logind.lpc's get_name() (Chinese-name registration step)
had a leftover printf("%O\n", ob); immediately before ob->set("name",
arg) — this is a raw LPC object-reference dump (driver's %O
formatter), and since logind.lpc's write/printf calls all target
the connecting (still-registering) player, EVERY new registration was
shown a line like /obj/user/login#3 ("0(axtjqin)") in the middle of
the "your name has been set to..." confirmation — clearly leftover
debugging output from whoever last touched this function, not intended
player-facing text. Removed the one line; re-verified registration is
now clean (no more object-reference leak) with a fresh account.
Confirmed NOT applicable / no action needed
- §4-style
load_object(SECURITY_D)recursion:master.lpc'svalid_read/valid_writedon't lazilyload_objecta security daemon at all in this lineage (securityd.lpcis a normal preloaded daemon, referenced only via the already-loadedSECURITY_Ddefine) — not applicable. - Hardcoded
MUD_PORT-styleconnect()dispatch:master.lpc'sconnect()(grepped) has no literal-portswitch/ifgating the connection — this codebase's ownexternal_port_Nconfig style + singleport numberoverride inconfig.fluffoswas already enough; first-connection tests worked immediately with no silent-reject symptom. uptime()startup gate: none found (greppedmaster.lpcandlogind.lpc); the only startup-adjacent gate isREBOOT_CMD-> is_rebooting(), a manually-toggled wizard shutdown-countdown flag, not auptime()-based grace period.- BIG5 mixed-encoding config gotcha (huoying-style): not applicable, archive is genuinely all-UTF-8 (see above).
lpcc sweep
scripts/lpcc_check.sh libs/aoxiangtianji/config.fluffos libs/aoxiangtianji/work:
9439/9446 pass (99.93%) even before this session's fixes (the eventd/
storyd bugs above are create()-time runtime issues, not compile
failures, so they didn't show up in the lpcc count either way). The
remaining 7 failures are ALL "Fail to load object" with no compile error
text — sampled and confirmed these are abstract templates never meant to
be load_object()'d bare (only clone_object()'d with caller-supplied
state): std/phome/phome_{one,two,three,four} (player-home templates,
cloned by adm/daemons/playerhomed.lpc with a master_name property
pre-set before create()'s restore() call ever runs), obj/self/
animal, adm/daemons/story/{pangtong,mengzi} (story-daemon-driven
NPCs). None of these are real bugs; the generic lpcc harness simply has
no way to supply the context these files require.
Registration flow (read from adm/daemons/logind.lpc's actual
input_to chain — notably different shape from other libs in this
project)
logon→encoding (GBK/utf-8 choice, pick utf-8)→get_id (type new
for a new character)→get_new_id (English id, 3-8 pure letters, no
digits)→confirm_id (auto, no y/n)→get_name (Chinese name, 2-4
chars)→new_ad_password/confirm_ad_password (a separate "management
password", ≥5 chars)→new_password/confirm_password (the regular
login password, ≥5 chars, must differ from the management password)→
get_gift (accept the random stat roll, y/n)→get_QQ (a QQ number,
4-13 digits — required, not skippable)→get_email→get_gender(m/f)→
selete_char (pick a "personality", 1-4)→world entry. This lib requires
BOTH a management password and a regular password up front (unlike
niaoren/huoying's single-password flow) — worth checking per-lib
rather than assuming a fixed shape.
Existing (i.e. already-registered) wizard accounts have an ADDITIONAL
gate: on reconnect, after the regular password, if password_w (the
separate "wizard-only password") isn't set yet, the game forces setting
one immediately (new_passwd_w/confirm_pass_w) — must be ≥6 chars,
contain at least one uppercase AND one lowercase letter, and must not
equal the id, the Chinese name, or the regular password. Once set, every
future wizard login asks for it (get_passwd_wiz) as a second factor
after the regular password.
Boot + registration-flow verification (native)
Booted ~/src/fluffos/build-debug/src/driver config.fluffos from
libs/aoxiangtianji/. Clean boot, only cosmetic warnings, Accepting
telnet connections on 0.0.0.0:40101. / Initializations complete..
Full transcript (scripts/mudclient.py), fresh id axtjqin, real Chinese
name 秦风:
--send "" # banner
--send "utf-8" # encoding choice
--send "new" # new character
--send "axtjqin" # English id
--send "秦风" # REAL Chinese name (2 chars)
--send "adminpass123" # management password
--send "adminpass123" # confirm
--send "test12345" # regular password
--send "test12345" # confirm
--send "y" # accept stat roll
--send "123456" # QQ number
--send "[email protected]" # email
--send "m" # gender
--send "1" # personality (光明磊落)
--send "look" / "score" / "quit" / "y" # (quit on a <30min-old
# account asks to confirm
# deletion -- game's own
# policy, not a bug)Result: name "秦风" accepted with zero rejection, entered the world at
"南城客栈" (South-city Inn) with full scene/NPC/board text; score
showed the correct bracket-quoted title (【 少 年 】) and a complete,
correctly-rendered Traditional-charset-free Simplified Chinese character
sheet (attributes, combat stats, sect/alliance fields, PK record, etc.);
quit on the fresh (<30-min-old) account triggered the game's own
"delete this new account?" confirmation, answered y, account cleanly
removed. A second run with a 3-character name (王小虎, gender f,
personality 2) also registered without any name-rejection (save file
data/user/w/wangxh.o confirmed created) — deleted after testing since
it was just a throwaway verification account.
Admin account
id fluffos, regular login password Mud@2026 (used for the actual
per-task-spec admin credential), Chinese name 浮浮, management password
AdmMud2026x, wizard-only password WizMud2026! (this lib's own extra
security layer — see "Registration flow" above; not part of the task's
single-password spec, but required by this codebase's own design to
reach a working wizard session, so documented here for whoever logs in
next). Granted (admin) via adm/etc/wizlist (plain-text, one line per
wizard: appended fluffos (admin) next to the archive's original
jinning (admin)) — note: securityd.lpc caches wizlist in memory
at daemon preload time (create()), so an edit to the wizlist file only
takes effect on the NEXT driver boot, not immediately (a distinct gotcha
from AGENTS.md's already-cataloged patterns — worth adding to the
general checklist: any wiz_status/permission table read once in
create() and cached needs a reboot, not just a file edit, before a
grant/revoke is visible). Save files (must be committed — gitignored by
default): work/data/user/f/fluffos.o, work/data/login/f/fluffos.o.
Verified via the real flow after a fresh reboot: login as fluffos /
Mud@2026 → prompted for (and, first time, required to set) the wizard
password → 目前权限:主管天神(admin) shown; ran the actual wizard
commands update /adm/daemons/rankd and update /adm/daemons/band →
both → 文件顺利更新成功! (recompiled successfully); wizlist command
shows both fluffos and jinning as 『 主管天神 』 (admin-tier).
WASM-enablement pass (fail-closed loopback gates)
Found and patched THREE distinct IP/site gates, none previously
loopback-aware (this lib had no prior loopback pass — unlike niaoren,
this archive's config.fluffos/pipeline work never got to this step
before this session):
adm/daemons/band.lpc'sis_banned()/create_char_banned()/is_strict_banned()— added a sharedis_loopback_site()helper (loopback ⇒ exactly127.0.0.1/::1/localhost/a127.prefix; anything else, including unparseable input, still goes through the real ban-list regexp check — fail-closed from the start, matching the corrected project-wide pattern, not the now-retired "malformed IP = trust it" shape).adm/daemons/logind.lpc'slogon()same-IP concurrent-connection cap (logon_cnt > 8→ reject) — added a loopback exemption (query_ip_number(ob) != "127.0.0.1" && != "::1") so repeated local/ WASM test connections can never trip it.- NOT patched, and confirmed why it doesn't need to be: the 100-slot
MAX_LOGIN/allow_multi_login()per-IP cap (include/login.h) is already high enough (100) that it can't practically block any of this project's local/automated testing; left as the archive's own default rather than special-casing a gate that was never going to fire. - NOT an IP gate, so not touched: the 120-second post-quit reconnect cooldown (
logind.lpcget_passwd(),time()-user->query( "last_on") < 120) is keyed on the ACCOUNT, not the connecting IP — it throttles the same character reconnecting too fast regardless of origin, so a loopback exemption would change actual game balance/ anti-abuse behavior rather than just unblocking local testing. Left as designed; testing simply waited it out / used fresh accounts. - No
uptime()startup gate exists (see above). - No per-IP registration-count throttle (the
huoying-style "创造的人物 太多" pattern) exists in this lib.
Recompiled both changed daemons live via update and reverified: loop
back reconnect still succeeds without hitting either patched gate.
WASM verification (scripts/wasm_client.js against build-wasm/src)
Important scripting note for future testers of this specific
registration flow: the WASM harness boots a fresh, ephemeral in-memory
driver instance per invocation (copying the HOST's work/ at boot, not
persisting writes back) — a full registration AND its subsequent login/
look/score/quit verification must happen in ONE single wasm_client.js
invocation's --send list, not two separate invocations (a save made in
one process's memory is invisible to the next). Learned this the hard
way on the first attempt (a wasmqin registration that looked like it
completed, followed by a separate --send list to reconnect and test
look/score, failed with "没有这个玩家" — no such player — because the
second invocation was a brand-new ephemeral instance that never saw the
first one's in-memory-only save).
Single-invocation full flow (id wasmqf, real Chinese name 秦风):
registration (same field order as native above) → entered 南城客栈 →
look showed the room correctly → score showed the correct 【 少 年
】 bracket-quoted title and full character sheet → quit on the fresh
account triggered the same new-account-deletion confirmation as native,
confirmed y. Separately, WASM admin login (fluffos/Mud@2026 +
wizard password) showed 目前权限:主管天神(admin), and wizlist showed
both admin accounts correctly.
Verdict: native OK + WASM OK.
Process/cleanup notes
- Killed this session's own driver processes by exact PID (verified via
/proc/<pid>/cwdpointing at this lib'swork/) before every restart; never touched any other lib's driver process running concurrently on this shared host. - Deleted the two throwaway test characters' save files created during this session's native testing (
wangxh—axtjqinself-deleted via the game's own new-account-quit policy, no manual cleanup needed for that one). Thefluffosadmin account's saves ARE kept/committed.
深度功能测试(第二轮,2026-08-03)
此前的验证只做到浅层冒烟测试。本轮启动前先主动排查了本次会话已经
反复确认过的四类高价值 bug 模式(private command_hook、
logind.lpc 里未加保护的 MESSAGE_D-> 呼叫、stat/water 对
stat/drink 键名不一致、printf("%O", ob) 调试信息泄漏),全部
未命中——这份档案这一轮没有发现新的编程 bug,是一次干净的复核。
完整验证:从注册到探索
用全新账号在原生驱动上完整走通:GBK/utf-8 编码选择 → new → 英文
id(3-8 个英文字母)→ 中文名字(二到四个字,重名会被拒绝并要求重
新输入)→ 管理密码 + 确认 → 普通密码 + 确认 → 天赋摇点(y/n 确认)
→ QQ 号码 → 电子邮件地址 → 性别 → 四种天性择一(①光明磊落 ②狡黠多
变 ③心狠手辣 ④阴险奸诈)→ 进入"南城客栈"。入口场景带有一幅像素风
格的电脑显示器 ASCII 画和一句"你是不是也象我一样。。。"的感伤台
词,气氛渲染和常见的"侠客上路"开场很不一样。店小二 NPC 主动搭话
("这位小兄弟,进来歇歇脚,喝两盅吧"),确认 NPC 的 command()
自呼叫对话链路工作正常。score(个人档案)显示完整信息面板,其中
"关卡信息"一节明确列出"大闹天宫"和"西行取经"两条主线关卡进度(均
显示"你尚未开始/你尚未西行"),"道行境界/武学境界/法力修为/内力修
为"四项境界评价,和 README 已经记载的"保留了呼应《西游记》原始题
材的关卡系统"完全吻合。i(背包)显示起始装备(皮靴、短打劲装)。
已确认属于设计、不是 bug 的观察(复核既有记录)
quit 在新账号未连续在线满 30 分钟时会触发确认提示——"你的账号是
新建的...退出游戏将删除你的账号...你确定要放弃该帐号而退出吗?
(y/n)",确认后干净完成删除("您选择了放弃账号退出西游记,档案被删
除。。。。。。"),没有崩溃或残留错误。这是这份档案自己的防止小号
滥用设计,NOTES.md 更早的记录里已经提到过,本轮只是重新验证了一遍
该流程本身在这个驱动上运行正常、不会崩溃。debug.log 除了驱动启动
期噪音外没有来自本次实际游玩会话的运行时错误。
未覆盖范围(诚实说明)
预算集中在验证注册全流程、天性选择、以及关卡系统的存在与显示,没 有走到:拜师、战斗、门派/联盟系统的实际互动。这些留给下一轮,目前 的验证边界如上所述。
深度功能测试(第二轮,2026-08-18)——补完战斗,深入排查拜师系统(非 bug 的设计限制),经济系统未定位到
补完上一轮留下的拜师、战斗、经济系统测试。战斗验证成功,拜师系统 深入排查后确认是设计限制(不是 bug),经济系统(真正的 buy/sell 商 店)本轮时间预算内未能定位。
- 战斗验证:
help newbie/combat-basic文档明确安全切磋指令是fight(非致命)。用巫师账号goto+summon把测试角色带到"朱 雀街",对"鬼见愁 钟馗"使用fight zhong kui,触发真实攻防交 换(命中部位描述、伤害数字、气血百分比),角色落败后自动"认输" (surrender机制自动触发,"这场比试算我输了,佩服,佩服!"), 紧接着仍被打晕("你的眼前一黑,接著什么也不知道了...."),一段 真实时间后自动恢复知觉。全程符合help combat文档描述,debug.log全程零报错。 - 拜师系统深入排查:
bai <NPC>(cmds/skill/apprentice.lpc) 对 NPC 目标会调用ob->attempt_apprentice(me)。用巫师账号goto+summon依次尝试了南海普陀山(观音道场)的知客僧、剃度 僧,两次都被拒绝("小兄弟非我南海普陀山弟子,贫僧/老衲所教恐怕 难以理解")。逐行读了attempt_apprentice()的完整实现(观音本 尊d/nanhai/npc/guanyin.lpc、知客僧、剃度僧三份档案的attempt_apprentice()函式体完全一致):先检查family/family_name == "南海普陀山"(不是就直接回绝这条消 息),然后检查class == "bonze",然后level > 32,然后obstacle/number >= 34(已完成的西游取经关卡 数)。这不是一个可以让全新角色直接加入的入门门派——它的验收 门槛(32 级以上、完成 34 个以上取经关卡、必须已经是"bonze"职 业)说明这是一个高等级、任务线深度绑定的终局门派,本轮用的是一 个全新 1 级角色,从一开始就不是这个门派的合理测试目标,不代表拜 师系统本身有 bug。bai.lpc自己的指令逻辑(cmds/skill/ apprentice.lpc)读起来是完整、合理的(正确处理 cancel、已是弟 子、跨门派转投、玩家间不能收徒等边界情况)。 - 经济系统未定位到:起始客栈本身
no_fight/应该也没有 buy/ sell(该房间没有add_action "buy");相邻的"朱雀街"、"南城钱 庄"都探过——"南城钱庄"是钱庄(票号),NPC "铁公鸡" 没有add_action "buy",list返回"什么?",猜测这里走的是存取款 (deposit/withdraw)而不是 buy/sell 商品;真正的杂货铺/兵器店本 轮在附近没有找到,未继续深入城内其他区域寻找。newbie指令(新 手武器/装备)需要具体物品名参数,本轮未继续深挖。 - 本轮结论:战斗系统验证正常工作,无发现问题。拜师系统的"每次都被 拒绝"现象经代码走查确认是设计使然(南海普陀山是一个高等级终 局门派,不接受新手),不是 bug——这次没有像 cctx 那样只是"没找到 地图路径",而是找到了、试了、并且确认了拒绝的具体原因,比单 纯"留给下一轮"更进一步。真正意义上适合新手加入的门派(如果存 在)在哪里,以及经济系统的真正商店位置,仍留给下一轮寻找。
§7.86 跨库扫描修复(留言板 post 崩溃)
BBS_BOARD、BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 24 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
深度功能测试(第三轮,2026-08-18)——真实死亡→断线→重连→复活全流程,发现并修复一例 §7.112 同类 bug
本轮延续第二轮留下的问题(经济系统定位、拜师系统、战斗),重点补上前两轮从未走到的:真实死亡(非 fight 切磋)→断线模拟→重连→复活的完整链路,以及留言板、当铺(拍当/卖断)经济系统。开始前先按标准清单核对了本次会话已固化的五类高价值 bug 模式。
标准清单核对结果
- §7.111(
master.lpcstandard_trace()的file_name(error["object"])空指针):adm/obj/master.lpc内两处调用点(行 416、552)均已带三元判空保护(error["object"] ? file_name(...) : "0"及(undefinedp(...)||!...) ? "(none)" : file_name(...)),无需再动。 - §7.112(gargoyle 血统
wgargoyle.lpc/bgargoyle.lpc):本档案没有这两个文件名(find -iname "*gargoyle*"零命中)。但没有止步于按文件名判断——按 AGENTS.md §7.112 "标准清单"要求,对全档案init()内call_out()做了针对性检查,在d/death/npc/pang.lpc(本档案自己的地府判官 NPC,功能上与dtsl的yanluo.lpc、其他血统的wgargoyle.lpc完全等价:五段式death_stage()引导亡魂复活)中找到了本档案自己的、未被任何已有 sweep 命中过的真实实例,详见下方"发现并修复"。 - §7.113(netdead 重连不恢复 heart_beat):读代码 + 实测双重确认不存在。
adm/daemons/logind.lpc的logon()(约行 754:user = find_body(...); if (!interactive(user)) { reconnect(ob, user); return; })是驱动实际调用的重连入口;其reconnect(object ob, object user, int silent)(约行 1976)会调用user->reconnect(),也就是obj/user/user.lpc的真正实现(非死代码),该实现正确地set_heart_beat(1); net_dead = 0;。用真实断线+重连实测复核:管理员账号在整个死亡链路中用info观察未见异常(虽然info命令本身对在线角色用 id 字符串查找会返回"没有这样物件"——info.lpc只支持present()/find_object()风格查找,不支持find_player(),这是info命令自身查找逻辑的局限,不是本次任务范围内的 bug,未处理),但整个死亡→断线→重连→复活序列的行为本身(见下)已经足以证明 heart_beat 链路没有卡死。 - §7.90(eval-cost 过低):
config.fluffos的maximum evaluation cost : 30000000,早已是本项目的高位修正值,非默认低值,无需再动。 - §7.11 类(
log_file()/write_file()写入未随包的运行时目录):审查了adm/simul_efun/file.lpc的log_file()(确认无assure_file()保护,属于潜在风险形状)以及全档案约 50 处调用点涉及的目标子目录(static/、wizcmds/、quest_xyj/等),逐一核对work/log/下这些子目录全部随包存在(ls work/log/确认static、wizcmds、quest_xyj、channel、cmds、dig、player、quest、user均已随包创建)。本档案未命中 §7.11。 logind.lpcenter_world()的ob->save():确认存在且非注释掉(约行 1551-1553:user->save(); ... ob->save();),无需处理。
发现并修复:d/death/npc/pang.lpc 的 §7.112 同类 bug(未被此前的 gargoyle 专项 sweep 命中)
d/death/npc/pang.lpc(阴阳界的"崔判官"NPC,玩家死亡后被送到这里走完五段式复活引导)的 init() 原样:
void init()
{
::init();
if( !previous_object()
|| !userp(previous_object()) )
return;
call_out( "death_stage", 5, previous_object(), 0 );
}没有任何重入保护。obj/user/user.lpc 的 reconnect()(第 349 行起)无条件调用 enable_commands(),而 FluffOS 会在 enable_commands() 时把房间内每个物件的 init() 重新广播一次——也就是说,一个正处在"阴阳界"五段对话/复活流程中的鬼魂玩家,只要恰好断线重连一次(哪怕只是网络抖动的几秒钟),就会在原有链条之外额外堆叠一条完全独立的 death_stage() call_out 链。第二条链最终仍会各自跑到 ob->reincarnate()(该函数默认 wakeup=0 分支会把玩家身上所有物品 unequip 并 command("drop ...") 全部丢弃)以及 ob->move(REVIVE_ROOM),于是玩家在已经复活、正常游玩一段时间之后,会在某个随机的后续时刻被无声地二次强制传送回复活点,并把当时正穿戴的所有装备原地丢在地上——没有任何报错,纯粹的静默破坏性 bug,和 AGENTS.md §7.112 原始发现(dtsl 的 yanluo.lpc)在功能形状上完全一致,只是这里的 NPC 换了名字和文案("崔判官"而非"阎罗王"/"石像鬼"),因此没有被基于 wgargoyle.lpc/bgargoyle.lpc 文件名的原始 corpus sweep 覆盖到。
修复(d/death/npc/pang.lpc,纯 LF 文件,无 CRLF 顾虑):给 init() 加了按受害者(ob)区分的 set_temp/query_temp/delete_temp("death_stage_running") 重入锁,在链条正常终点(reincarnate()+move(REVIVE_ROOM) 之后)和提前退出分支(!ob->is_ghost())都清除该标记,写法与 AGENTS.md §7.112 记录的标准修复形状一致。
实测验证(修复后重启驱动,双连线编排:管理员连线 A + 测试角色连线 B/C):
1. 用全新注册角色 axtjrsan(阮三),管理员执行 call axtjrsan->die() 制造真实死亡(非 fight 切磋),角色正常死亡、被送入"阴阳界",崔判官 init() 触发,第一、二段对话按 5 秒间隔正常出现。
2. 在第二段对话后直接关闭 socket B(不发送 quit,模拟断线),2 秒后用新 socket C 以同一账号重新连线——logind.lpc 立即判定 !interactive(user) 走 reconnect() 分支,显示"重新连线完毕",没有走"是否踢掉旧连线"的确认分支(证明是真断线而非仍在线的旧连线冲突)。
3. C 连线上收到的后续对话只完整播放了一次(第三、四段对话 + 最终"崔判官伸手向你一指...魂魄又回到了自己身上"+复活传送到"荒郊小店"),reconnect 触发的 init() 重新广播被新加的重入锁正确挡住,没有出现重复的复活消息或二次强制传送——在 reconnect 后又等待了完整 25 秒宽裕窗口以捕捉可能的"迟到"第二条链,未见任何异常。
4. work/log/debug.log(本次会话从空文件开始)全程只有驱动启动噪音(211 行),没有任何来自这次死亡/断线/重连/复活序列的运行时错误。
经济系统:确认定位,两条真实可用路径,此前"未找到真正商店"的疑问已解决
- **
obj/shop/*.lpc("百宝斋"等 9 个跨城市同款商店,通过如/d/city/majiu的up出口进入)全部在create()里硬编码set("shop_type", 0)(adm/daemons/shopd.lpc里shop_type的语义明确写着"0 表示关闭",且do_list/do_buy/do_sell/do_pawn/do_retrieve全部有if (!room->query("shop_type")) return "对不起,该店铺目前已经被巫师关闭。\n";的门禁)。9 处实例全部是 0,没有一个例外,且开关本身就是标准巫师指令(shopd.lpc里的open_shop/close_shop)能操作的运营状态位——这是这份档案"商店需要巫师上架/开张才能营业"的运营设计(游戏管理员的常规工作,不是玩家能触发的路径),不是编程 bug**,未做任何改动。这与第二轮"起始客栈附近没找到杂货铺/兵器店"的观察吻合并给出了确切原因。 - 真正对普通玩家开放、随开随用的经济路径是当铺(
std/room/hockshop.lpc,如/d/city/dangpu_e古记当铺):value <物品>(估价)、sell <物品>(卖断,一次性换钱)、pawn <物品>(典当,可赎回)均已实测正常工作——管理员账号把起始装备"锦缎"用sell jinduan卖给当铺,正确收到"二两白银又四十文钱",i确认物品消失、Coin/Silvercorrectly 入包。这条路径此前两轮均未测试到,本轮补完。 - 留言板(
std/item/bboard.lpc):在南城客栈自带的留言板上post <标题>→ 输入正文 →.结束(不是@,编辑器提示"结束离开用 '.'")→read 1正确显示刚发的帖子(标题、作者、时间、正文全部正确)。普通玩家发帖需要"读书识字"技能 ≥30(do_post()里的门槛判定),这是设计门槛(新手还没学识字技能),未作为 bug 处理。
本轮结论
发现并修复一例真实、跨会话已证明高价值的 §7.112 同类静默复活重复 bug(d/death/npc/pang.lpc),已实测验证死亡→断线→重连→复活全链路在修复后正确无重复;经济系统的两条真实路径(当铺 sell/pawn、留言板 post/read)均已实测确认可用,商店"全部关闭"确认是巫师运营设计而非 bug;标准五项检查清单(§7.90/§7.11/§7.111/§7.112/§7.113)逐项复核,除本轮新修复的 pang.lpc 外均确认此前记录有效或本档案不适用。**AGENTS.md §7.112 的通用教训在此再次成立:文件名匹配(wgargoyle.lpc)的机械 sweep 无法覆盖所有同功能异名 NPC,任何后续档案的深度测试都应该独立按"init() 内无保护 call_out()"这一行为特征去查,而不是只看文件名。
深度功能测试(第四轮,2026-08-19)——用全新角色独立复核经济系统,发现并修复一例真实 give/ask 崩溃 bug
本轮任务书的前提("经济系统仍未定位")实际上已经过时:第三轮(同为
2026-08-18)已经找到并实测了当铺 sell/pawn 与留言板两条真实经济路径
(见上一节),只是驱动本轮任务的记忆索引条目没有跟上第三轮的进度。
没有因此跳过任务——本轮改用一个全新注册、从未被任何前几轮账号触
碰过的测试角色,独立地把"卖东西换钱"这条链路完整走了一遍作为二
次确认,并且在走查 give/ask 命令时发现了一例此前三轮测试都没有
覆盖到的真实运行时崩溃 bug,已修复并实测验证。
经济系统:用全新角色独立复核 sell 交易,确认可正常工作
全新注册账号 axtjshi(中文名"李四"),走完完整注册流程后进入"南
城客栈"。该角色出生自带装备为"皮靴(pi xue)"和"蓝马褂(cloth)"——分别
读代码确认为 work/obj/cloth/male-shoe.lpc 和 work/obj/cloth/
male6-cloth.lpc(两份文件的注释都明确写着"This is the basic equip
for players just login."),两者 create() 里都显式 set("value",
0)。用巫师账号 goto+summon 把这个全新角色带到"古记当铺"
(/d/city/dangpu_e)后,value pi xue/sell cloth 均返回"一文不
值"——这不是经济系统坏了,是这两件新手起始装备本来就被设计成 0 价
值(防止用重复注册小号刷钱的常见 MUD 设计),与经济系统本身能否
正常运作是两回事。
为了排除"起始装备恰好都不值钱"这个巧合,进一步用巫师账号 clone
/obj/cloth/jinduan(value=800 的锦缎,第三轮用的也是这件)后
give jinduan to axtjshi 把一件真正有价值的物品转移给这个全新角
色,再切回该角色执行完整交易:value jinduan 正确显示"锦缎价值八
两白银……如果卖断(sell),可以拿到二两白银又四十文钱",sell
jinduan 正确扣除物品、正确发放"四十文铜钱(Coin)"+"二两白银
(Silver)"到背包,i/score 确认到账无误。结论:经济系统(当铺
sell 路径)在一个完全独立、未被前几轮任何账号触碰过的全新角色上再
次验证工作正常,不是"账号级偶然",是系统级可用。全档案 9 处
obj/shop/*.lpc 商店仍确认全部 shop_type=0(巫师未上架,运营设
计而非 bug,与第三轮记录一致,本轮 grep 复核未变)。
新发现并修复:give/ask 命令的 QUEST 宏路径错误,导致运行时崩溃
在准备"把物品转移给测试角色"这一步时,give jinduan to axtjshi
第一次尝试直接在玩家的转移路径上崩溃:
执行时段错误:*call_other() couldn't find object '/std/quest'.
程式:/cmds/std/give.lpc 第 108 行work/cmds/std/give.lpc 第 108 行:if (QUEST->quest_give (me, who,
obj));work/include/globals.h 第 152 行:#define QUEST
"/std/quest"。全档案里根本不存在 /std/quest.lpc 这个文件——最接近
的同名文件是 work/std/misc/quest.lpc,但读了它的内容后确认这是
"所有玩家任务继承此对象"的任务基类模板(供各个具体任务对象
inherit,本身没有 quest_give/quest_ask 这两个全局钩子函数),
不是 QUEST 宏原本想指向的那种"通用任务完成检测守护进程"。也就是
说这不是简单的路径打错字("缺一段 misc/"那么简单),而是这个全局
QUEST 守护进程本身在这份档案里从未被真正实现/交付过——是一个从祖
先代码库遗留下来、一直没写完的钩子。
影响范围:grep -rn "QUEST->" work --include="*.lpc" 全档案只
有三处:
work/cmds/std/give.lpc第 108 行(活代码,任何一次成功的give <物品> to <目标>都会触发——只要present()/playerp()等前置检查都通过,必然执行到这一行,等于说这个玩家最基础的社交/ 交易命令,长期以来对任何"给某人某东西"的合法用例都会崩溃)。work/cmds/std/ask.lpc第 87 行(活代码,ask <NPC> about <话 题>分支——只要目标不是玩家(!userp(ob)),即绝大多数"向 NPC 打听消息"的正常用法都会触发同样的崩溃)。work/std/char/char.lpc第 113 行:QUEST->quest_kill(...)整 行被注释掉,死代码,未处理。
修复:给两处活的 QUEST-> 调用点各加一个house-style的文件存在
性守卫(本档案里 file_size(path+".lpc") > 0 这个写法在
cmds/std/go.lpc/cmds/imm/goto.lpc/cmds/adm/restore.lpc 等多处
已经是既有习惯用法),而不是瞎编一个"最小实现"或者把宏改指到语义
完全不符的 std/misc/quest.lpc 模板类去凑合:
// give.lpc 第108行
if (file_size(QUEST + ".lpc") > 0 && QUEST->quest_give (me, who, obj))
// ask.lpc 第87行
if ( file_size(QUEST + ".lpc") > 0 && (msg = QUEST->quest_ask(me, ob, topic)) )效果:这个从未交付的全局任务钩子被安全跳过(give/ask 命令的其
余逻辑完全不受影响,只是不会再触发这个本来就不存在的钩子),不会再
崩溃。
实测验证:update /cmds/std/give + update /cmds/std/ask 热更
新成功后,重新执行 give jinduan to axtjshi("你给李四一件锦
缎。",无崩溃)与 ask gu guiyou about jinduan("古贵有疑惑地看着
你,摇了摇头。"——正常的 NPC 不认识话题回复,无崩溃),两条命令均
恢复正常。work/log/debug.log(本次会话从空文件开始)全程只捕捉到
这一条修复前的报错,修复并热更新之后的所有后续操作(含上面完整
的 sell 交易)零报错。
未做的事:没有去写一个真正的 /std/quest.lpc 全局任务守护进
程——这份档案里从来没有交付过这个功能,凭空发明一套任务完成检测逻
辑属于新增游戏功能而不是修 bug,超出本轮范围。也没有把这个发现扩大
成跨库 sweep——本轮只确认了这一份档案里的实例,QUEST 这个宏名字
本身很通用,其他血统家族即使同名宏也未必是同一份 globals.h/同样
的路径错误,需要每个库各自读代码确认,不建议不经验证就批量替换。
标准清单复核(快速确认,非重新深挖)
- §7.111(
master.lpc的file_name(error["object"])空指针 防护):work/adm/obj/master.lpc第 416、552 行两处调用点仍然带 三元判空保护,与第三轮记录一致,无需再动。 - §7.112(
init()内无保护call_out()死亡引导链):d/death/npc/pang.lpc第三轮已修复的重入锁仍在(本轮未改动该文 件,快速grep "death_stage_running"确认锁仍存在),无需再动。 - §7.113(netdead 重连不恢复 heart_beat):
work/adm/daemons/ logind.lpc的logon()→reconnect()路径与第三轮读到的一致, 本轮亲自靠find_body+"重新连线完毕" 的实测重连(多次,见上文两 处后台连线技巧)反复验证了重连后角色仍能正常收发命令、正常交易, 没有观察到任何 heart_beat 卡死的迹象,维持"不适用"结论。 - §7.114(
private的input_to()回调经由继承 mixin 静默失 效):grep -rn "private.*input_line\|private.*input_to" work --include="*.lpc"全档案零命中;本档案唯一的编辑器 mixinwork/feature/edit.lpc的input_line()本身不带private,不 是这个 bug 的 shape,不适用。 - §7.90(eval-cost 过低):
config.fluffos的maximum evaluation cost : 30000000,与第三轮记录一致,早已是高位修正 值,无需再动。
本轮结论
经济系统(当铺 sell/pawn)经一个全新、未被任何前几轮账号触碰过的测
试角色独立复核,确认系统级可用,不是账号偶然;顺带发现并修复一例此
前三轮都未覆盖到的真实 bug——give/ask 命令因 QUEST 宏指向一个
从未交付的全局任务守护进程路径而在几乎所有正常用例下崩溃,已加存在
性守卫修复并实测验证(give、ask 均恢复正常,交易全程 debug.
log 零报错)。标准五项检查清单(§7.90/§7.111/§7.112/§7.113/
§7.114)逐项复核均确认此前结论有效或本档案不适用,没有发现新的异
常。测试用留存证据账号:axtjshi(含一次完整、已验证到账的 sell
交易)。会话中产生的一个额外遗留测试账号 qinzhan(第三轮某次未清
理干净的残留,未提交追踪)已一并清理。
§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). 10 live
replace_program(ROOM); occurrences across 10 room files
(obj/misc/void.lpc, d/kaifeng/wroad3.lpc, d/nanhai/zhulin2.lpc,
d/lanzhou/duchuan.lpc, d/lanzhou/huanghe_2.lpc,
d/emei/qfachanfang.lpc, d/emei/xiaolu2.lpc, d/chengdu/sanxia2.lpc,
d/chengdu/shudao12.lpc, d/chengdu/chaoyangdong.lpc) — same fatal
redundant-replace-after-inherit shape as the rest of the §7.100 family.
No room-building tool exists in this lib. Fixed by deleting the
redundant lines. Verified via a clean native driver boot (zero new
debug.log errors, port listening, killed by exact PID after ~8s).
§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): 5 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.
§7.19 enable_player() reentrancy -- FALSE POSITIVE, unreachable (2026-09-01)
Corpus-wide mechanical sweep (AGENTS.md §7.19, Batch F of 6). feature/command.lpc's
enable_player() does have the same structurally-ineffective
nosave int enabled = 0; guard found on ylfyxa3/longyunmeng/xajh4gkb/
xyzxyl201412/wdxtym in this batch (enabled = 1 set AFTER
enable_commands(1) returns, not before -- would NOT actually prevent the
reentrancy if it were reachable), and feature/damage.lpc's revive()
does re-invoke enable_player() while already living(). However, unlike
those 5 siblings, an exhaustive search of this entire lib (every void
init() in all ~1150 files that define one, brace-matched to isolate each
function's own body, cross-checked with a second independent line-window
heuristic, plus every file inheriting NPC individually, plus every
F_* mixin char.lpc inherits) found zero calls to setup(),
reset_me(), enable_player(), or enable_commands() from inside any
init() function anywhere in the lib -- this lib's NPC roster (e.g.
std/char/smith.lpc, d/meishan/npc/cao.lpc, d/qujing/wuji/npc/kingfake.lpc)
simply doesn't use the "leader NPC's init() unconditionally re-calls
me->setup()" idiom that the zhangmen.lpc-family sibling libs in this
batch do. The only whole-file grep hits were coincidental same-named
setup() calls in unrelated functions (a fire-hazard room's own exit
setup() in d/18hell/16/heitudi.lpc, a ranking-board item's ITEM-lineage
setup() in obj/mptop.lpc, both several lines *after* their init()'s own
closing brace, not inside it) -- same false-positive shape as the already-
documented shzs/xzyx/xiyouji2003 cases. Since enable_player() is
never reachable from any init(), the driver-side-effect reentrancy this
sweep guards against cannot occur here regardless of the enabled flag's
ordering. Left untouched, no fix applied (the mechanical fix would be
inert but harmless; skipped per this project's "don't fix what isn't
reachable" standing practice).
深度功能测试(§10.7,2026-10-04)— paths named in another case
The archive comes from a case-insensitive host: files are stored as BAGUA0.lpc, longjing.C, d/SHAOLIN/, the code names them in lower
case (__DIR__ "bagua0", "tea/longjing", "/d/shaolin/..."), so the rooms, NPCs and items behind them could not be loaded (a .C file is
not an .lpc file; KB 03 §4.5). scripts/lpc_case_paths.py --convert --apply aoxiangtianji: 1 file(s) renamed to lower case (.C -> .lpc,
.H -> .h) and 0 path reference(s) in 0 file(s) rewritten to the new spelling; 0 skipped, 0 ambiguous. Load check of the 2 objects that were renamed, edited or name a renamed path (HEAD against the working tree, an object is the same under any spelling of its path): 1 PASS -> 2 PASS, 0 that loaded before fail now, 1 more load.
1 files moved (1 .C); mostly under obj/job/teamjob (1). A rescan with scripts/lpc_warnings.py aoxiangtianji is still due: the renamed files are compiled for the first time.
深度功能测试(§10.7,2026-10-05)— room base survives a raising NPC
scripts/lpc_resilient_room.py --apply: make_inventory() of std/room/room.lpc loads each inventory object as if (catch(ob = new(file)) || !objectp(ob)) return 0;. new() of a file that is not there returns 0 (the room just lacks that NPC), but new() of a file whose create() raises (a skill file for set_skill() this archive never had, a carry_object() item that is not there, a header that never shipped) raised inside the room and the room did not load at all. The wrong-case renames of this lib (the section above) make the NPCs behind them reachable for the first time, so a room that holds one with such a content gap would have gone from "NPC missing" to "room missing"; now it loads without that NPC, as it did before. The room base compiles and loads with the change (HEAD against the working tree). KB 03 §4.5.
深度功能测试(§10.7,2026-10-05)— 任务表里的 .c 路径与任务守护进程的防护
scripts/lpc_dynamic_quest_guard.py:1 处 spread_quest() 调用(init_dynamic_quest() 的循环里)包进 catch(),缺失的任务物品或编译不过的房间只跳过那一个任务,守护进程照常加载、其余任务照常生成。
验证:改动过的 adm/daemons/taskd.lpc 在新进程里加载,HEAD 通过 1 个、工作树通过 1 个(共 1 个),无回退。
深度功能测试(§10.7,2026-10-08)— .lpc 后缀的定宽切片
.c 改名 .lpc 时只改了字符串没改切片宽度:f[<2..] == ".lpc" 拿两个字符比四个字符,永远不成立(!= 永远成立),按后缀筛文件、去后缀的代码因此从不运行或截成 x.l(KB 06 §7.238)。scripts/lpc_lpc_suffix_slice.py --apply 改了 3 处比较、0 处去后缀(如 adm/daemons/natured.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。
深度功能测试(§10.7,2026-10-11):dns_master 空 if
adm/daemons/network/dns_master.lpcquery_services():if (! (mud_svc[mud]["tell"/"finger"] & SVC_KNOWN)) {里只有#if PREF_X & SVC_TCP … #endif,选项关闭时 if 体为空(“无副作用”警告);把#if/#endif挪到 if 外面(KB 04 §6.10,2026-10-07 那次扫描漏掉了! (带空格、大括号另起一行的写法)。载入无回退。
深度功能测试(§10.7,2026-10-11):dns_master 空 if
adm/daemons/network/dns_master.lpcquery_services():if (! (mud_svc[mud]["finger"/"tell"] & SVC_KNOWN))块里只有#if PREF_X & SVC_TCP … #endif,选项关闭时 if 为空(“无副作用”警告报在下一句);把#if/#endif挪到 if 外面(KB 04,2026-10-07 那次扫描漏了! (加括号换行这种写法)。载入无回退。
- 2026-10-14:新驱动(FluffOS 970d9f09)报
Float value truncated to int(#1426)。1 个文件 1 处:赋值、初始化、return 改写成to_int(...)(值不变,只是写明截断)。改动的对象新进程各载入一次,无回退(scripts/lpc_fix_float_trunc.py,KB 04 §6.10)。
其他体系的属性名(2026-10-15,KB 06 §7.241 第三类,--any-file)
2 个文件(技能、绝招、兵器、书籍、区域 NPC 等)来自侠客行体系(qi/jing/neili),而本库人物是 gin/kee/sen/force:receive_damage("qi", ...) 报 伤害种类错误,query("neili") 永远为 0。按 qi→kee、jing→sen(本库学习/读书指令消耗的就是它)、neili→force、max_neili→max_force、jiali→force_factor 改名(scripts/lpc_port_es_props.py --any-file)。
find_player() 只认在世界里的身体(2026-10-15,KB 07 §8.26)
simul_efun 的 filter_player() 加上 environment(ob):注册中途断线留下的、不在任何房间的身体不再被当成玩家,两个以上时 find_player() 不会再报 The player(<id>) has existed in the world.(wllz3 实测过的问题)。本库若注册流程不会留下带玩家 uid 的身体,这一改动不改变行为。
技能名拼错(2026-10-15,KB 06 §7.249)
query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):sanqing-wuchen→sanqing-fuchen(qixing-sword、taixuan-whip);mantian-xing→mantianxing(/adm/daemons/event/tianchi)。
NPC 带着本库没有的物品,载入失败(2026-10-15,KB 06 §7.73)
本库 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 个,改后 1 个(0 个原来载入不了),没有退步。
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 载入不了,刷出它的房间也少一个人。本库有 1 个对象设了本库没有文件的武功(2 个名字,最多的是 mahayana、lamaism)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:1 个里 1 个通过。(scripts/lpc_guard_set_skill.py)
武功名拼错的 NPC(2026-10-19,KB 06 §7.258)
set_skill("kunlun-shnfa", …)、map_skill("dodge", "shaolin-shnfa") 这样的调用把武功名拼错了:本库没有这个武功文件,有的是拼对的那个。以前这种 NPC 在 create() 里报 No such skill 载入不了;武功缺失时跳过之后它们能载入,可是少了那门武功(set_skill 被跳过,map_skill 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 4 处、4 个文件:jienanzhi→jienan-zhi(2 处)、yange_blade→yange-blade(1 处)、ningxie-force→ningxue-force(1 处)。(scripts/lpc_fix_skill_names.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 文件。
x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)
if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。改动:/d/taishan/shiziya.lpc(1 处)。
member_array('/', str) 改为 strsrch(str, "/"):字符字面量的 member_array 按字节搜索(2026-10-20,KB 06 §7.283)
member_array('/', str) 里的 '/' 是整数,驱动按 UTF-8 字节搜索,返回字节偏移(带第三个参数时起点也是字节);最新的 FluffOS(923cdbc0)在字符串里有非 ASCII 文字时,对每个调用点警告一次(Warning: member_array(int, string) searches BYTES)。而 str[0..idx-1] 按字符切,中文字符串会在错误的位置截断。改为 strsrch(str, "/")(找不到同样返回 -1,按字符计数;scripts/lpc_fix_member_array_char.py,JSON 编码器等少数几处手改)。改动:/std/room/vrm_server.lpc(1 处)。
call_out_info() 的每一项只有 3 个元素(2026-10-20,KB 06 §7.287)
驱动返回的是 ({ 物件, 函数, 延迟 }),不带 call_out 的参数,旧代码读第 4 项 info[i][3](callouts 指令、calloutd、cod 工具)一读就报 Array index out of bounds,指令从第一行起什么也打印不出来。改为 (sizeof(info[i]) > 3 ? info[i][3] : 0)(scripts/lpc_fix_callout_info.py)。改动:/adm/daemons/calloutd.lpc(2 处)、/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,共 2 处)。改动:/cmds/wiz/dns.lpc(1 处)、/daemon/class/xueshan/dengxian-dafa/juanbi.lpc(1 处)。
callouts:sizeof(info[i]) == 4 把每一行都挡掉了(2026-10-20,KB 06 §7.287)
驱动的 call_out_info() 每项只有 3 个元素,输出行前的守卫 if (sizeof(info[i]) == 4) 永远不成立,指令只打印表头。改为 >= 3。改动:/adm/daemons/calloutd.lpc(2 处)、/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])) ...,不检查列表是否为空。本库现有的 NPC 文件里没有字面写成空 chat_msg 的(扫描只认 set("chat_msg", ({ })));守卫挡的是运行时才为空的列表和以后新增的 NPC。在 random(100) < chance 前面加 sizeof(msg) &&(scripts/lpc_fix_chat_empty.py;if (random(100) > chance) return 0; 的写法是 !sizeof(msg) ||)。改动:/std/char/npc.lpc(共 1 个文件)。
巫师命令不带参数时对 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/adm/dumpprog.lpc(new)。加载检查 HEAD 与工作树对照,没有退步。
xupdate:file[sizeof(file)] != '/' 永远为真(2026-10-21,KB 06 §7.343)
管理员扫描里 xupdate(xyj2000)从根目录 / 开始递归时报 Bad argument 2 to foreach Expected: array Got: 0.:UpdateDir() 用 file[sizeof(file)] != '/' 判断路径是否已经以 / 结尾,下标等于长度读到的是 0,条件永远成立,/ 变成 //,get_dir("//") 答 0。改成 file[<1] != '/'。同一个写法在 xrm、rm、rmdir1、rmhir 里有 55 处,没有动:对普通路径新旧写法的行为相同,唯一改变的是 xrm /,旧写法让它死在第一个 get_dir("//"),改了它就能递归删光整个游戏目录。
aoxiangtianji teamwar:领军者撤出战场的广播读没赋值的 ob(2026-10-21,KB 06 §7.348)
check_teamjob() 在领军者不是玩家或没有环境时广播 ob->name() + "从战场撤退了,宣告本次任务失败了!",ob 到后面的 foreach 才赋值;改成领军者的名字(没有就写“领军者”)。改动:/obj/quest/teamjob/teamwar.lpc。
拼错的 call_out() / input_to() 目标(KB 06 §7.349,2026-10-21)
计时器或输入提示给驱动的函数名和文件里定义的差一个字母;驱动对不存在的函数静默不理,所以对应的计时从来没有触发过。调用和配套的 remove_call_out() 改成已定义的拼法:find_seal:disapprear → disappear(法术产生的封印十分钟后从不消失)。改动:/d/jjf/obj/find_seal.lpc。
注册到不存在函数的 add_action(2026-10-21,KB 06 §7.349)
add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 7 条注册(5 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/18hell/16/songlin.lpc(do_make)、/d/changan-s/pinqiting.lpc(do_pass、do_resign)、/d/yaomo/milin.lpc(do_quit)、/obj/npc/zongguan.lpc(do_jiaochai、do_renwu)、/std/char/smith.lpc(do_buy)。最多的函数名:do_make 1 处、do_pass 1 处、do_resign 1 处、do_quit 1 处、do_jiaochai 1 处。
调用了、但这个库自己的守护进程里没有的函数(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。 还剩 4 个函数没有补(inquiry 4):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/d/18hell/1/npc/ghost2.lpc:ask_xiu、/d/18hell/1/npc/ghost2.lpc:ask_chi、/d/18hell/2/npc/ghost2.lpc:ask_xiu、/d/18hell/2/npc/ghost2.lpc:ask_chi。
运行期目录 log/nosave 不存在,写日志的 write_file() 直接报错(2026-10-22,KB 03 §4.3 补充)
这个库的 3 个文件里有 9 处 write_file("/log/nosave/...") / log_file("nosave/...")(static 改 nosave 时连字符串里的日志路径一起改了,原来是 log/static/):/log/nosave/LASTCRASH、/log/nosave/LASTTIME 等。压缩包和站点包里没有这个目录,write_file() 在目录不存在时抛 *Wrong permissions for opening file /log/nosave/... for append. "No such file or directory",调用它的函数(杀人记录、重启记录、call 日志……)当场中断。把 log/nosave 记进 scripts/wasm_keep_dirs.txt(站点和 pristine 树都按它建空目录);没有把字符串改回 static。证明(scripts/lpcc_eval.py,catch(write_file("/log/nosave/ZZTEST", ...)),fqyy2 / hy5 / bmxkx2001 的新进程):补目录前报上面的错误,补后写成功。
log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)
这个库的代码往 log/ 的 1 个子目录写日志:log/edit(cmds/adm/vi.lpc)。压缩包、git clone 和站点包里都没有这些目录(本地启动没有写过它们,gen_keep_dirs.py 没记),write_file() 对不存在的目录抛 *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory",调用它的函数在写日志那一行中断,后面的奖励、标志、步骤都没有执行。本库的 log_file() 是 write_file(LOG_DIR + file, text),目录不存在就抛同样的错误(实测)。把 log/edit 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。
(: 函数名 :) 指针指向本库没有的函数:只含这个指针的行加了注释(2026-10-22,KB 06 §7.355 补充)
inquiry / item_desc / chat_msg* 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:用到时(ask、look、NPC 闲聊)驱动抛 *Undefined lfun pointer called: 函数名,这一拍心跳的后半段被跳过。这些函数没有可以补回的来源(别的库同一路径的文件里没有,或者它还要用本库没有的东西),所以 2 个文件里 4 行只含这个指针的行("险恶" : (: ask_xiu :), "人性的弱点" : (: ask_chi :),)加了注释,原文保留并写明原因,这一项当作没有这个话题 / 物品 / 闲聊条目(scripts/lpc_fix_dangling_lfun.py;#include 的文件在库里缺失的对象、指针不在整行上的写法没动)。 前提(function_exists())和加载检查(HEAD 与工作区对比,本库改动的 .lpc 文件)的结果见 KB 06 §7.355:没有一个回归。
长度检查按字节写,旁边的提示按汉字写:1 处(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 你好吗)。本库 1 处(1 个文件):if(strlen(arg) > 28 )(/std/item/jboard.lpc:130)。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 区间 2 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。
血条 / 表情标题按字节算显示宽度(2026-10-23,KB 06 §7.365)
老档把汉字当两字节:bar_string[0..(val * N / max) * 2 - 1] 把「格数」换成字节下标,半格血条在本驱动(按字符算区间)上会整条画满;巫师表情 edemote 的 repeat_string("─", 13 - (strlen(emotion) / 2)) 也会少减汉字宽度。scripts/lpc_fix_display_pad.py:去掉 CJK 血条 / 空白条上的 * 2(jings/qis 赋值和 blank_string3[0..(23-a)] 的 23→11 一并改),表情标题去掉 / 2;ASCII 空格的 none_string / empty_string 仍保留 * 2(一格两个空格)。本库 2 处(1 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。
中文名 / 验证码仍按 GBK 取「两字节一字」(2026-10-23,KB 06 §7.373)
pname = pname[(random(sizeof(pname)) & 0xFFFE)..<1]; pname = pname[0..1]; 在按字符计下标时会取出两个汉字。scripts/lpc_fix_pname_slice.py 去掉掩码并把收尾改成 [0..0](pname/pname2/sec_name/str/str1)。本库 4 处。证明:nt1 生成名长度 2–4;HEAD 常见 3–5。
无参数时命令对 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 守卫的误报已回退;本库本批改动见该提交。
yuanwai 孤儿 call_out announce_success(2026-10-27,KB 06 §7.349 Also)
d/changan/npc/yuanwai.lpc(及 aoxiangtianji 的 d/city 副本)在 check_daughter 里 call_out("announce_success", …),但本文件从未定义该函数;同系也无实现。到点会 *Undefined function*。cry_daughter 已完成认亲与赠书,删除这对 remove_call_out/call_out。证明:扫描 HIT call_out→announce_success 且无定义;改后残渣 0。
book-qujing 缺 obstacle.h(2026-10-27,KB 04 §6.23 Also)
d/obj/books-nonskill/book-qujing.lpc #include "/d/qujing/obstacle.h",档案未带该头,驱动报 Cannot #include。从手足 sjsh 拷入同内容 mapping。证明: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… 行。