info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
"东方故事"(ES)系列 MUD 的一个早期基础版本,属于 ES / TMI 引擎家族——尽管名字听起来像武侠题材,实际内容是一个西式奇幻/DND 风格的冒险世界。与本批次的 `esI`(屠龙之战)同源,共享同一套核心引擎代码。新角色降生在远风镇的冒险者公会大厅(`/d/adventurer/hall/adv_guild`),通过 `cost`/`advance`/`train`/`list`/`join` 等公会内部指令自行训练升级——没有 NPC 教师,以骑士、法师、牧师、武僧、学者、盗贼等公会体系取代门派玩法;farwind 商行的 `list`/`buy` 交易是出镇后的第一站。角色创建菜单本身即提供十余种可选种族:人类、精灵、矮人、半兽人、地精、半身人、蜥蜴人等。
English
An early foundational build of the "Eastern Story" series and part of the ES/TMI engine family — despite the wuxia-sounding name, it's actually a Western-style fantasy world in the D&D tradition, sharing its core TMI-lineage engine with sibling esI (this project's "Dragon Slaying War" build). New characters land in 远风镇's adventurer's guild hall (`/d/adventurer/hall/adv_guild`) and level through a guild-room-only training loop (`cost`/`advance`/`train`/`list`/`join`, no NPC teacher) across knight/mage/healer/monk/thief/scholar/swordman-style guild domains rather than sect-based jianghu factions, with a working shop (farwind, `list`/`buy`) as the first stop out of town. Over a dozen playable races are offered (humans, elves, dwarves, orcs, goblins, halflings, lizardfolk, and more per its own character-creation menu).
README
内容亮点
- 十余种可选种族:人类、精灵、矮人、半兽人、地精、半身人、蜥蜴人、 妖精、魔族、半人马、黑暗精灵、眼魔、吸血鬼、鸟人、变形虫。
- 通过加入不同的职业公会(骑士团、法师塔、牧师团、武僧院、学者会、 盗贼公会等)来学习技能、提升等级——公会制而非门派制,是它与本项目 其他"风云"系列武侠 MUD 最大的不同之处。
- 冒险者公会大厅(
/d/adventurer/hall/adv_guild)是新手枢纽,商店 系统(如 farwind 商行)支持真实的list/buy交易。 - 整体风格更接近传统欧美奇幻 MUD,而非江湖武侠。
在线试玩
https://mudlibs.fluffos.info/es1_win/
管理员账号 / Admin account
- id:
fluffos - 密码 / password:
Mud@2026 - 中文名 / display name: 浮浮
- 权限 / level: 完整管理员(FULL ADMIN)。通过
/adm/etc/groups将fluffos加入(root)与(admin)组(/adm/etc/access授予(admin)对/的全局读写),并在连线档设置wizard 1与domains(primary=wiz,level=archwizard)。
警告:公开架站前请务必修改此默认密码。
本地运行
cd libs/es1_win
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40009。
NOTES · 移植与修复记录
es1_win.rar → es1_win
- Archive:
archives/es1_win.rar(10MB — "东方故事(蓝天)"/"Eastern Story (Blue Sky)". 东方故事 (Dongfang Gushi / "Eastern Story") is one of the foundational Chinese wuxia mudlib bases — several other archives in this collection reference/derive from it (东方故事二.raris a direct sequel; lib #1's includes mention it too). - Mudlib root:
es1/at archive top level. Config at top-levelconfig.cfg. - Port: 40009.
Status: DONE — boots clean, playable over telnet
Full flow confirmed: banner, "东方故事现在没人." (no one online) status line, name-length validation, name re-prompt.
What was fixed
1. Encoding: 8238 converted, 1674 already UTF-8/ASCII, 82 skipped binary,
17 lossy. .c→.lpc: 6933 files, 1101 refs + 2 angle-bracket
#include<x.c> refs auto-fixed.
2. master.lpc's domain_file(): explode("/", "/") (called by the
DRIVER ITSELF during its own bootstrap domain-assignment, per
mudlib_stats.cc) returns a zero-length array in this driver
rather than ({ "", "" }), and the existing if(!path) return
"NONAME"; guard doesn't catch a non-null-but-empty array — path[0]
threw "Array index out of bounds", failing master's own compile.
Added || !sizeof(path) to the guard.
3. std/object/prop.lpc had #include </std/object/prop_logic.lpc>
(angle brackets, file in the SAME directory) — the exact §8d pattern
from lib #3, but with an important addition to that lesson: the
master::get_include_path() fix does NOT help here, because this
particular file gets compiled during PRELOAD, before
compiler_vm_context is set (confirmed via the driver source,
lexer_utils.cc's init_include_path(): "No VM context: keep the
config-file include path as-is -- there is no master object to ask").
get_include_path() silently never gets consulted for preload-time
compiles. Fixed by changing the include to quotes instead
(#include "/std/object/prop_logic.lpc") — quote-style local-directory
resolution doesn't depend on any master apply/VM context at all, so
it's the more robust fix regardless of when a file happens to compile.
Also added the get_include_path() apply to this lib's master.lpc
anyway (harmless, helps genuinely-dynamic compiles), but the REAL fix
for this specific case was the quote change.
This failed to reach the login prompt with NO error output at all
until traced with temporary write("DEBUG X\n") statements inserted
into logind.lpc's logon() — the actual error (_query/_delete/
_set undefined, cascading from the broken include, inherited all the
way from /std/room/room down to /d/std/rooms/netdead) was being
thrown during the very first connection's active_users() call, and
the DRIVER's own new_conn_handler catches ANY error escaping
logon() and just silently disconnects the user with zero message —
so a real compile-cascade bug during first-connect can look exactly
like a hung/dead server with nothing in reach except the debug.log.
Known remaining issues (documented, not fixed)
- 1260 lpcc-sweep failures (of 6933). A meaningful chunk (
inherit ROOM;failing with "unexpected L_IDENTIFIER" ind/noden/drow/*,d/noden/hawk/*,std/adt/iqueue.lpc, and others) trace to files that rely on the auto-includedglobal include file(globals.h) for macros likeROOM, butglobals.h's ownROOMdefinition is commented out with a DIFFERENT (stale/wrong) value than the real one ininclude/mudlib.h("/std/room"vs"/std/room/room") — so uncommenting it isn't a safe fix; the affected files are missing an explicit#include <mudlib.h>instead. Zone names (noden,drow,hawk) suggest legacy/template filler content possibly never used in the live wuxia game, not something actively maintained — not fixed given the scale (dozens of files, no single-point fix) and that it's off the critical boot/login path. - Not otherwise triaged past a category scan given time constraints.
How to run
cd libs/es1_win
~/src/fluffos/build-debug/src/driver config.fluffos
python3 ../../scripts/mudclient.py 127.0.0.1 40009 --timeout 10 --send "" --send "look" --send "quit"Re-verification pass: driver rebuild + LPC formatter + WASM build
- Formatter: ran
format-corpus.mjsover all 6933.lpcfiles; 6772 reformatted in place, 5 already-clean/unchanged, 156 refused (errors, expected on messy legacy code per the tool's own token/byte-identity self-check) — not chased down individually. - Native retest against rebuilt driver: booted clean (zero fatal errors, "Initializations complete") against the freshly-rebuilt
build-debug/src/driver. Full registration verified with a real Chinese name ("令狐冲"/Linghuchong, human male) all the way into the starting room (远风镇冒险者公会);look,score,quitall produced correct, real output (score showed the right race/gender/age line, quit produced the real farewell/drop-inventory text). Regression found + fixed (pre-existing since the original conversion commit, not caused by the reformat or driver rebuild, but only now caught by this pass's post-login-output check):std/user.lpc'ssetup()unconditionallywrite()s a literal debug/garbage string"aadsaaaaaaaaaaaaaaaaaaaa\n\n"to every player on every login, right before the real "last logon" message. Confirmed absent fromraw/es1/std/user.cunder any encoding, so it was accidentally introduced (not original content) at some point before or during the initial conversion commit. Fixed by deleting the straywrite()call; re-verified clean (no more garbage line) on a second full registration + login run, zero debug.log errors either run. - WASM test: booted cleanly under
build-wasm(the only errors are the expected non-fatalUndefined function socket_create/socket_error/socket_bindin/adm/daemons/network/cmwhod.lpcduringmaster.lpc'ssocket_preload()— caught by the master's own error handler, same as a missing daemon natively, no cascade). Logged into the existing test character over WASM, reached the game room, andlookproduced the correct room description. This lib's own IP-check step ([OKIP]) merely warns ("你没有设定任何 IP Address 检查") rather than gating login, so it is not affected by the documentedquery_ip_number()WASM limitation — full WASM login + play works.
WASM-enablement pass (loopback / admin seeding)
- Loopback ban bypass (§1.3b):
adm/daemons/banish.lpccheck_banned_site()(~line 75) — short-circuit at top: loopback / empty / non-string /localhost/127.names return 0 (not banned). Gate is fedquery_ip_name()(hostname), called fromlogind.lpc:242and:573. Original ban logic unchanged below. - Uptime gate: none that reject/destruct. Only
uptime()in login daemons (logind.lpc:87) grants extra user slots first 12h — content, kept. - Anti-flood throttle: none. "试太多次" is a per-session password retry cap, not a per-IP throttle.
- Admin account (§1.5):
fluffos/Mud@2026, display 浮浮. Granted via/adm/etc/groups(addedfluffosto(root)and(admin)) plus connection savedata/std/connection/f/fluffos.o(wizard 1triggersenable_wizard()instd/body.lpc:199;domainslevelarchwizard). Verifiedupdate /adm/daemons/statsdloads OK as fluffos. - Fail-closed retrofit (2026-07-24 security correction): the loopback check(s) above originally also treated an empty/non-string IP as loopback (defensive fallback for the then-broken
query_ip_number()). Since the driver's IP-reporting bug is now fixed upstream (WASM reports a clean127.0.0.1like native), that fallback was removed — loopback is now strictlystringp(ip) && (ip=="127.0.0.1" || ip=="::1" || ip[0..3]=="127."); anything unparseable/empty is untrusted/remote and goes through the original gate logic. Retested: fluffos login +look/quitstill clean over loopback. - Save files to force-add (untracked, NOT gitignored):
libs/es1_win/work/data/std/connection/f/fluffos.o,libs/es1_win/work/data/std/user_ob/human/f/fluffos.o.
深度功能测试 / Deep functional test (2026-07-24)
First real *playthrough* pass (AGENTS.md §10.7) — every prior pass on
this lib had only verified registration + look/score/quit + admin
login, never a continuous real playthrough. Native driver
(build-debug), one continuous session per checklist item, with
several deliberate net-dead disconnect/reconnect probes. Test
characters (both kept, not cleaned up, as evidence):
Shenqingxue/ 沈清雪 (idshenqingxue), passwordTestPass123, human female, adventurer class. Primary playthrough character: fought and killed a野狗(dog) at 远风镇西门, ended with 695 exp, 14/30 HP, currently sitting in/d/adventurer/hall/adv_guildafter a cleanquit. Save files:work/data/std/connection/s/shenqingxue.o,work/data/std/user_ob/human/s/shenqingxue.o.Muyunlan/ 沐云岚 (idmuyunlan), passwordTestPassX9, human male. Created specifically to reproduce the net-dead-during- registration bug below (see "Bugs found and fixed" #1); currently parked normally in/d/adventurer/hall/adv_guild. Save files:work/data/std/connection/m/muyunlan.o,work/data/std/user_ob/human/m/muyunlan.o.
Correction to this file's own earlier "How to run" section: log
directory : /log in config.fluffos resolves against the driver's
LAUNCH cwd, not the mudlib root (AGENTS.md §5.2) — with the documented
cd libs/es1_win && driver config.fluffos invocation, debug.log lands
at libs/es1_win/log/debug.log, not
libs/es1_win/work/log/debug.log (a long-stale file left over from an
earlier era/invocation convention that this pass initially — and
wrongly — kept checking for several rounds before noticing it never
changes). Use the top-level log/ path when grepping for errors on
this lib.
Bugs found and fixed
1. Net-dead disconnect during the registration Q&A permanently
strands the account in the net-dead holding room on every future
login — std/user.lpc:902 restart_heart().
- Symptom: a player who disconnects (network drop, not
quit) at ANY point betweennewuserd.lpc'sget_real_name()(which callsbody->setup(), printing the MOTD news and the "[请按 RETURN 键继续]" prompt) and that prompt's owncomplete_setup()callback actually placing them in the world — i.e. anywhere in the ordinary gap between finishing the registration questions and pressing RETURN to continue — reconnects, on every future login, directly into/d/std/rooms/netdead("时间的缝隙", the lib's net-dead holding room/portal hub), permanently. Nothing else in the login flow ever routes them anywhere else again —get_password()'s "existing body found, not interactive" branch (adm/daemons/logind.lpc:323-328) just callsbody->restart_heart()unconditionally. Reproduced live end-to-end with a fresh character (Muyunlan): registered through the "[请按 RETURN 键继续]" prompt, let the connection close without answering it (a realistic disconnect point — the exact same "one more screen before real gameplay" moment any client crash or network drop could hit), reconnected with the correct password, and landed straight in "时间的缝隙" — confirmed via the room's ownd/std/rooms/netdead.lpcset_short()string, and via the player's own.osave file having no"linkdead_room"key at all (only ever written whennet_dead()'senvis non-null). The room does have an undocumented escape (enter door), but it's a random teleport to one of six hardcoded coordinate rooms that ALSO immediately deducts 10% of the player's current HP on "landing" — actively harmful for a fresh, zero-inventory, zero-experience character, and undiscoverable without reading source. - Root cause:
std/user.lpc'snet_dead()(~line 843) always callsmove_player(LINKDEAD_ROOM, "SLIENCE")as a fallback holding pen, but only recordsset("linkdead_room", env)if (env)— i.e. only if the player already had a real environment when they went net-dead. A player who disconnects during the registration Q&A/MOTD gap has never been placed in ANY room yet (complete_setup(), the function that actually doesmove(start_room), hasn't run), soenvis null,linkdead_roomnever gets set, andrestart_heart()(std/user.lpc:902) — the function that's supposed to restore a net-dead player's pre-disconnect location — has nothing to restore to and just leaves them sitting inLINKDEAD_ROOMwith no further recovery path. Same general shape as AGENTS.md §7.21 ("reconnecting mid a mandatory pre-gameplay wizard permanently strands a player" —input_to()chains don't survivenet_dead()/reconnect, and nothing else re-triggers the interrupted flow), but a different lineage and a meaningfully different concrete manifestation: §7.21's instance blocks EVERY command behind a catch-all after reconnect; this one doesn't block anything — the player can move, talk, fight — they're just permanently misplaced in a hazardous portal room instead of their intended start room, with no error or warning ever shown. - Fix (
std/user.lpc:921-932, insiderestart_heart()): whenlinkroomis unset, check whether the player is currently sitting inLINKDEAD_ROOManyway (the only way that combination can happen is the never-placed-yet case above) and, if so, callthis_object()->complete_setup("")— the exact same function the original "press RETURN" prompt would have called — to finish the interrupted placement. Before/after:
```lpc // BEFORE linkroom = (object)this_object()->query("linkdead_room"); if (linkroom) { this_object()->move_player(linkroom, "SNEAK"); tell_room(linkroom, ...); }
// AFTER linkroom = (object)this_object()->query("linkdead_room"); if (linkroom) { this_object()->move_player(linkroom, "SNEAK"); tell_room(linkroom, ...); } else if (base_name(environment(this_object())) == LINKDEAD_ROOM) { this_object()->complete_setup(""); } ```
- Verified: reproduced pre-fix exactly as described above (character
Muyunlan, first reconnect landed in "时间的缝隙" with the debug.log showing no error at all — this is a genuinely silent stranding, zero log signal, matching the AGENTS.md §7.20 detection warning). Restarted the driver with the fix, re-registeredMuyunlanfresh, stopped again at the same "[请按 RETURN 键继续]" prompt without answering, waited for the driver to notice the disconnect, reconnected with the correct password: now lands correctly in/d/adventurer/hall/adv_guild(the realSTARTroom),目前权限:playerprints as on a normal fresh login,look/scoreboth correct. Also re-verified the NORMAL net-dead path (disconnect/reconnect while ALREADY placed in a real room, both promptly and after a real 75-second wall-clock wait) is unaffected and continues to restore the exact pre-disconnect room — see "Net-dead and reconnect testing" below. - Lineage note:
esI(this lib's documented sibling, AGENTS.md §11) shares the identicalnet_dead()/restart_heart()/LINKDEAD_ROOMshape (same TMI/ES ancestry) and was not checked/fixed here — flagged for the orchestrating session to port if it does the same lineage sweep it's done for other bugs found this round.
2. #include <compress_obj.h>'s set_default_ob(__FILE__) points
every affected clone at the wrong file, crashing on any property
lookup that falls through to it — already-cataloged AGENTS.md class
§7.14 ("__FILE__ in an #included fragment expands to the
FRAGMENT's path, not the includer"), previously seen on xlqy_early/
longyunmeng; this is a new (fixed) instance of the same class.
- Symptom, reproduced live: a fresh character standing in the one shop in the game (
/d/noden/farwind/shop, reachable straight from the start room) typing the ordinarylistcommand to see what's for sale crashed with a real, on-screen, uncaught-looking runtime error (driver's default handler did catch it, but the trace printed straight to the player, not silently swallowed):
``
执行时段错误:*call_other() couldn't find object '/include/compress_obj.h'.
程式:/std/object/ob.lpc 第 127 行
物件: /obj/bandage#40
`
Every item in this lib that inherits only the lightweight
/std/object/ob base (checked: obj/bandage.lpc, obj/bowl.lpc,
obj/torch.lpc, obj/map.lpc, obj/bag.lpc, obj/light_ball.lpc,
and ~35 more across d//u/, one grep -rl compress_obj away) is
affected, since set_default_ob()'s stored value is consulted
(std/object/prop.lpc:126-127, value = default_ob->query(label))
on ANY property read that isn't already set directly on the specific
clone — trivially reachable any time such an item's price/weight/etc.
needs to be displayed, which is exactly what a shop's list` does for
every item it sells.
- Root cause:
include/compress_obj.his a bare code FRAGMENT (no function wrapper) meant to be pasted, via#include, into the very first line of ~40 different objects'create()bodies, to make each one register itself as its own memory-saving "default value" fallback target. Itsset_default_ob(__FILE__)line assumes__FILE__resolves to whichever OUTER file it was pasted into (e.g./obj/bandage) — but on this driver__FILE__tracks the PHYSICAL file currently being lexed, which while processing this fragment's own text is the fragment itself,/include/compress_obj.h, regardless of where it was#included from. Every one of the ~40 affected objects' blueprints therefore silently self-registers as "/include/compress_obj.h" (a header, never a loadable/compilable LPC object) instead of their own real path — inert until the first property lookup that actually needs the fallback, at which pointcall_other("/include/compress_obj.h", "query", label)throws. - Fix (
include/compress_obj.h:6-15), matching AGENTS.md §7.14's documented remedy (there: swap infile_name(this_object()); here:base_name(this_object()), sinceset_default_obwants the BLUEPRINT's bare path forcall_other(), notfile_name()'s clone-numbered variant):
```lpc // BEFORE if ( clonep(this_object()) ) { set_default_ob(__FILE__); return; }
// AFTER
if ( clonep(this_object()) ) {
set_default_ob(base_name(this_object()));
return;
}
``
base_name(this_object()) is evaluated at RUNTIME inside the actual
compiling object's own create(), so it always correctly resolves to
whichever real file (e.g. /obj/bandage`) the fragment was pasted
into, regardless of the header's own physical path.
- Verified: reproduced the crash live pre-fix exactly as above (shop
listwith abandagein stock). Applied the one-line header fix, killed and rebooted the native driver, re-ran the exact sameShenqingxue→ walk to shop →listsequence: full item list now renders cleanly (油灯/绷带/魔法地图/火把 with prices), no error, no new debug.log line. Also exercisedbuy torch/value torch(correct "you don't have money"/"you don't have that" responses, no crash) — a genuine purchase wasn't completed since the character has no starting money and none was farmed for this pass; see "Not verified live" below. - Lineage note:
esI'sinclude/compress_obj.his byte-identical and unfixed (checked, not touched) — same flag as bug #1 above, for the orchestrating session's lineage sweep.
What was tested and confirmed working
Full continuous playthrough as Shenqingxue (real Chinese name, human
female adventurer), covering the whole §10.7 checklist:
help start(doc/help/c_start) read first — this lib's own newbie guide, matches the classic TMI/Discworld-family help-file house style (c_about,c_rules,c_help_screen, etc., all real, substantial Chinese content, not filler). Correctly documents the guild-roomcost/advance/train/list/joincommand set as the organic skill/attribute path (no NPC-dialogue teacher in this lineage — the "teacher" IS the guild room itself) and gives the real starting-zone layout (远风镇/冒险者公会).- Registration: full flow (English login id → confirm new name → Chinese display name → password/confirm → gender → race → email → real name → MOTD/RETURN) reached the real start room,
/d/adventurer/hall/adv_guild, correctly. look/score/i: all correct at every state change (fresh registration, post-training, post-combat, post-reconnect).- Guild skill/attribute training (this lineage's organic path — no NPC teacher, the guild room's own
cost/advance/train/listcommands, perstd/guild.lpc):listshowed the adventurer guild's 7 trainable skills with real costs;costshowed the real attribute/skill upgrade table;advance strcorrectly rejected for insufficient "usable" exp (678 total vs 389 needed from a smaller advance-pool) whiletrain unarmedsucceeded, correctly deducting exp and printing "你的空手搏斗技能现在提升到 1 了。" — the two-tier exp economy (total exp vs. per-level advance-pool) behaves as documented. join: tested at the already-joined adventurer guild — correctly replied "你已经是本公会的成员了!" rather than silently no-opping; no separate sect/faction system exists beyond the four documented guilds (adventurer/mage/healer/knight/monk perhelp start), each gated behind the samejoin-in-guild-room mechanic.- Exploration: walked 远风镇冒险者公会 → 大街 → 商店/广场/微风路/ 西门, reading room
.lpcsource to resolve exits/navigation puzzles (notably the smithy's boxes→ladder→climb sequence, which needs a light source the character can't afford — see "Not verified live"). This lib's virtual-object convention (_smain.lpc/_nmain.lpcstyle underscore-prefixed real files served under their unprefixed name via each zone's ownvirtual/server.lpc, a genuine TMI feature, not a bug) confirmed working correctly throughout. - Combat: no dedicated safe-sparring dummy exists in this lib (
grep -r accept_fightreturns zero hits anywhere in the tree, anddoc/wizhelp/c_combatis pure mechanics documentation, not a practice-target pointer) — used a real level-1野狗(dog,d/noden/farwind/monster/dog.lpc,set_level(1), low stats) at 远风镇西门 instead, per the checklist's fallback of picking a deliberately weak wild target.consider/killboth worked; full turn-by-turn combat resolved correctly (accuracy/miss/damage/gore messages,hpmid-fight status line), character took real damage (26→14 of 30 HP) and the dog died correctly ("狗死了。"), leaving a corpse and awarding exp (605→695). No crash, no over-death, no eval errors. - Net-dead and reconnect testing (AGENTS.md §7.20/§7.21 checklist item): beyond the bug #1 repro above, separately confirmed the NORMAL case is healthy — disconnected
Shenqingxueuncleanly (socket close, noquit) while she was properly placed in/d/adventurer/hall/adv_guildwith real HP/exp state, reconnected PROMPTLY:重新连线完毕, landed back in the exact same room with the exact same HP (14/30) and exp (695). Repeated with a real 75-second wall-clock wait before reconnecting (via a foreground timed wait, not a backgrounded one): identical correct result, zero new debug.log lines either time. - Quit + debug.log: every
quitin this pass (multiple, across different session states) was followed immediately by a grep of the CORRECTlibs/es1_win/log/debug.log(see the correction note above) — all clean, no newerror:/Too deep recursion/fatal lines, in clear contrast to the compress_obj.h crash, which WAS visibly loud on screen (a good reminder that "on-screen crash" and "logged crash" are two independent signals, not synonyms — neither implies the other on this lib). - Clean-quit relogin: quit cleanly, reconnected fresh (full registration-shaped relogin path, not the net-dead reconnect path) — correctly restored to the guild room with all state (HP/exp/skills) intact, confirming
complete_setup()'sstart_room = getenv("START")-then-guild-room design intentionally always restarts a fresh session at the player's guild rather than their exact last room (distinct from, and not to be confused with, the net-dead reconnect path's exact-room restore).
Addendum (2026-07-24): §7.12 tell_room() fix ported from sibling esI
esI's own §10.7 pass found that adm/simul_efun/tell_room.lpc's 2-arg
call shape (exclude defaulting to int 0, rejected by message())
crashes adm/daemons/weather_d.lpc's self-rescheduling change_phase()
call_out — no enclosing catch(), so the uncaught throw aborts the
function before it reaches its own tail-end reschedule, permanently
killing the day/night phase daemon for the rest of that boot the first
time any outdoor room is visited (set_outside() registers it, some
phases in adm/etc/daytime.data are as short as 1 second). Confirmed
via diff that this lib's tell_room.lpc is byte-identical to esI's
pre-fix copy, so it carries the same bug (not independently reproduced
live on THIS lib during either pass — plausibly a session-timing/
room-visited difference). Ported the same one-line fix:
message("tell_room", msg, room, exclude || ({}));Sanity-checked only (fresh boot + existing test character
shenqingxue login/look/quit, clean, zero new debug.log errors) —
not a full independent §10.7 replay, since this lib was already marked
done. See libs/esI/NOTES.md's own "深度功能测试" section (bug #5) for
the full live-reproduction detail (including a direct eval-command
proof of the crash and the fix).
Not verified live (explicitly)
- A completed shop purchase:
Shenqingxuestarts with zero money (身无分文) and no in-session way to earn any was pursued within this pass's time budget;buy/valuewere exercised and behave correctly (proper rejection messages, no crash) but no item was actually bought. Code review ofstd/seller.lpc/shop.lpcsuggests the buy path is otherwise ordinary and low-risk, but this is not the same as a live-verified purchase. - Death and respawn: the one real fight run to completion was survivable (14/30 HP remaining at the end); deliberately losing a fight to reach the death/ghost/respawn flow was not attempted this pass — reason: time budget, not because it looked risky or was avoided for any code-quality concern.
- An UNRESOLVED, likely-real but NOT root-caused timing anomaly: on a handful of repro attempts, the FIRST movement command issued immediately (within 1.5-3 real seconds) after moving into a room being compiled for the very first time this boot (e.g.
eastfrom/d/adventurer/hall/adv_guildinto/d/noden/farwind/_smain.lpcright after a fresh driver boot) sometimes failed silently (the command never appears to reachstd/user/tsh.lpc'spush_cmd()at all — confirmed once via temporary instrumentation, then reverted) or produced the driver's generic default-fail message instead of actually moving. The SAME sequence against an already-warm room (one any player has visited since boot) was reliable across every attempt (7+), and a longer 3-second gap between commands did NOT reliably fix it, which argues against this being purely amudclient.pysend-pacing artifact. This has the same general "first-ever compile this boot only" timing-sensitivity shape as AGENTS.md §7.17/§7.19/ §7.22, but a different concrete symptom (an apparently-dropped queued player command, not a crash or corrupted state) that doesn't cleanly match any of those three, and repeated attempts to get a clean instrumented trace of an actual FAILING run were unsuccessful (addingwrite()instrumentation itself changes the timing enough that the instrumented runs happened not to reproduce it). Given the real-world impact is narrow — only the very first player to explore a given room after each server restart, and only if they move unusually fast for the first couple of commands after arriving — this was left UNFIXED and is reported here rather than silently dropped, for whoever next has time to pin it down with a cleaner reproduction setup (a raw packet-capture of the two client sends plus driver-side socket-read logging would likely settle whether this is input coalescing during a slow synchronous first-compile, which was this pass's leading but unconfirmed hypothesis).
Deep functional test round two (2026-08-14)
Independently re-verified against current code rather than trusting the
round-one writeup above. Confirmed all 3 prior fixes still hold (the
net-dead-during-registration restart_heart() fix, the
compress_obj.h __FILE__/§7.14 fix, and the ported §7.12
tell_room() fix), and found one new §7.90-class issue: a real preload
crash, not just a theoretical risk.
New fix: §7.90-class eval-cost abort during boot preload — chinese_d's dictionary build genuinely failed, not just at-risk
config.fluffos had maximum evaluation cost : 300000, below even this
project's common 700000 template default. Unlike some other libs this
session where a low eval-cost config was flagged as a latent risk without
a confirmed failure, THIS lib's boot log showed a real, live preload
crash:
Eval interrupted: object adm/daemons/chinese_d cost limit reached, limit: 300000 usec.
执行时段错误:*Too long evaluation. Execution aborted.
程式:/adm/daemons/chinese_d.lpc 第 60 行
呼叫来自:/adm/daemons/chinese_d.lpc 的 make_dictionary() 第 60 行
执行时段错误:*Can't catch eval cost too big error.chinese_d's make_dictionary() (called from its own create(), itself
called from master.lpc's preload()) never finished building its
dictionary — every boot, unconditionally, not an occasional flake. Fixed
with the established §7.90 remedy: maximum evaluation cost → 5000000.
Verified: fresh reboot, zero cost limit/Too long evaluation/Can't
catch hits anywhere in the boot log.
Re-verified live: all 3 round-one fixes still hold
compress_obj.h§7.14 fix: code-confirmedset_default_ob(base_name(this_object()))still present (with its explanatory comment), then live-verified by walking the exact original crash route as admin (远风镇冒险者公会 → east → east → 商店) and runninglist— full item list rendered cleanly (油灯/绷带/魔法地图/火把 with prices), zero newwork/log/debug.loglines (checked the file's line count before and after the whole session: unchanged at 1035).- §7.12
tell_room()fix: code-confirmedexclude || ({})still present inadm/simul_efun/tell_room.lpc. - Net-dead-during-registration
restart_heart()fix: code-confirmed theelse if (base_name(environment(this_object())) == LINKDEAD_ROOM) { ... complete_setup(""); }branch is still present. Not re-walked live this pass (requires a fresh registration interrupted mid-MOTD, then a real disconnect/reconnect cycle — expensive relative to this pass's time budget given the fix is a small, previously-thoroughly- verified, self-contained conditional) — noted honestly per this lib's own established "not verified live" convention rather than silently re-claimed as freshly tested.
Other checklist items: mostly not applicable, different lineage architecture
This lib's TMI/Discworld-family codebase doesn't share the ES2/大唐
lineage's adm/simul_efun/file.lpc/cmds/wiz/update.lpc shapes this
session's checklist was tuned around — no update.lpc exists at all (the
wizard reload command is _update.lpc, invoked as update, a different
implementation entirely; tested live, works correctly). log_error()
(adm/obj/master.lpc:348) never broadcasts compile diagnostics to the
connected player at all (only writes to a log file, with a fallback
write() only on write FAILURE) — confirmed this lineage simply doesn't
have the §7.10-class leak risk by construction, not because it's
correctly gated. work/log/ is a real, git-tracked, already-existing
directory (not gitignored, unlike some other libs), so no
assure_file()-class gap applies to its own log writes either.
A cosmetic finding, not a bug: fluffos's displayed rank shows "apprentice", not an admin-sounding title
First login this pass printed 目前权限:apprentice, which looked
concerning at a glance. Investigated rather than assumed broken: this
label comes from DOMAIN_D->query_domain_level(player), a separate
"domain" title-display system, NOT from the actual ACL groups that
control real permissions. /adm/etc/groups still correctly lists
fluffos under both (root) and (admin). Verified REAL admin access
directly rather than trusting either label: update /adm/daemons/logind
succeeded ("Updated and loaded."). Left the cosmetic domain-title as-is —
this project's own "don't fabricate content" principle applies to
game-facing labels too, and actual access is unaffected.
Verification method
Booted native build-debug driver, admin login (fluffos/Mud@2026),
update /adm/daemons/logind as the real privileged-action check
(succeeded despite the cosmetic "apprentice" display). One rapid
reconnect (the connection's own quit is gated behind okip for
wizards — a genuine safety feature, not a bug — so the reconnect
exercised the silent net-dead body-reattach path instead, landing back
in the exact same room/state, which is itself valid reconnect-stability
evidence). Driver killed by exact PID after testing; incidental
fluffos.o save churn and a stray .tmp save file reverted/removed
before commit.
§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). 2 live
replace_program(ROOM); occurrences: d/noden/6,8.noden.lpc's
create() (same shape/location as sibling esI), and this lib's own
room-building tool u/s/sage/test/roommaker.lpc's heredoc
ROOM_CODE template — so newly-built rooms were inheriting the bug too.
Both fixed by deleting the redundant line. Verified via a clean native
driver boot (zero new debug.log errors, port listening, killed by
exact PID after ~8s).
die() null-killer crash — ported from sibling esI (2026-08-20)
esI's round-four §10.7 deep test found a real crash in
std/user.lpc's die(): killer (set from query("last_attacker"),
falling back to previous_object()) can end up int 0 whenever a
character dies from a source other than direct living-creature combat
(poison/bleeding/sickness/drowning conditions, or an admin-applied
receive_damage()) — two lines then call killer->query("npc") /
userp(killer) unconditionally, throwing a driver-level error on
every heart_beat() tick for as long as hit_points < 1, which means
the character never actually finishes dying (no corpse, no ghost,
stuck forever). Confirmed via diff that this lib's copy of the same
two lines is byte-identical/unfixed. Ported the same fix verbatim
(killer && guards on both call sites, matching every other
killer-using line in the function which was already correctly
guarded):
// BEFORE
if (!wizardp(this_object()) && killer->query("npc"))
if (this_object()->query_level() < 5 && userp(killer) && query("last_attacker")) {
// AFTER
if (!wizardp(this_object()) && killer && killer->query("npc"))
if (this_object()->query_level() < 5 && killer && userp(killer) && query("last_attacker")) {Sanity-checked here with a fresh native driver boot + look/quit
smoke test (clean, zero new debug.log lines, killed by exact PID
after) — not a full independent replay of this lib's own death/respawn
flow, since esI's pass already live-verified the fix's actual
behavior end-to-end twice. Flagged here for whoever next does a
round-four (or deeper) pass on this lib specifically, to do that full
live verification in its own right.
Round-four (§10.7) deep test: net-dead-during-registration gap closed live (2026-08-21)
Closed the one remaining explicitly-flagged gap from this lib's own
NOTES.md: the restart_heart() net-dead-during-registration fix
(std/user.lpc:921-932, the else if (base_name(environment(...)) ==
LINKDEAD_ROOM) { ... complete_setup(""); } branch) had been
code-confirmed present across two prior passes but never re-walked live
end-to-end since the original fix/verification. Actually walked it this
pass, with a fresh, full-budget session:
- Booted native
build-debugdriver against this lib's ownconfig.fluffos(eval cost already at the fixed5000000, log cleared first for a clean signal). Boots clean — only pre-existing compile warnings, no fatal errors, "Initializations complete." - Registered a brand-new character (
caiyunshu/蔡云舒, human female, idcaiyunshu, password set but redacted here per this project's password-leak-prevention rule) via a raw Python socket script, validating each server prompt before sending the next answer (English login id → confirm-new-name → Chinese name → password/confirm → gender → race → email → real name → MOTD), and closed the connection without answering the final "[请按 RETURN 键继续]" prompt — a real, clean TCP close simulating exactly the disconnect point bug #1 originally described, not aquit. - Confirmed the pre-placement state directly from the save file:
work/data/std/user_ob/human/c/caiyunshu.ohas zero occurrences oflinkdead_room(grepped) — i.e. this reproduces the exact never-placed-yet precondition the fix targets, matching the original bug report's root-cause analysis precisely. - Checked
log/debug.log: zero new lines from the whole registration-and-drop sequence (no crash, no error trace — a silent drop, as expected). - Reconnected with a fresh socket, same login id + correct password: landed correctly and immediately in
/d/adventurer/hall/adv_guild(冒险者公会, the realSTARTroom) with the normal "重新连线完毕. 检查 IP Address 中... [OKIP]" reconnect banner — not/d/std/rooms/netdead("时间的缝隙").lookandquitboth produced correct, real output afterward. Zero newdebug.loglines from the entire reconnect-and-play sequence. - This is a genuine live pass/fail result, not a repeat of the original reproduction: this run used a brand-new character never touched by any prior pass, a fresh driver boot, and the current on-disk code — the fix holds. Gap closed; no code change was needed since the fix (present since the original 2026-07-24 pass) works correctly.
- Driver killed by exact PID (confirmed
readlink -f /proc/<pid>/cwd== this lib'swork/dir first) after testing. Only new artifact:caiyunshu's two save files (connection + user_ob), kept as evidence per this lib's existing convention for prior test characters.
Fast checklist pass: three newer session-wide findings, not applicable to this lib
- combatd.lpc
bounce/division-by-zero (found on 4 sibling libs this session):find libs/es1_win -iname "combatd.lpc"— no such file exists anywhere in this lib. This lineage (TMI/Discworld family, per this lib's own already-documented "different lineage architecture" note above) doesn't share the ES2/大唐-family combat daemon shape the bug was found in. Not applicable. - chacha.lpc
death_stage()reentry-guard leak (found on 4 more sibling libs):find libs/es1_win -iname "chacha.lpc"— no such file exists anywhere in this lib either. Same lineage-mismatch reasoning; not applicable. - §7.30 mapping-accessor
mapp(x) ? x : ([])guard: this lib's actual skill-accessor equivalents,std/living/stats.lpcandstd/living/stats_npc.lpc, both declaremapping query_skills(). Checked both directly — both already correctly guarded (a slightly different but equivalent shape to the documented fix pattern):
``lpc
mapping query_skills() {
if (!skills) return ([]);
else return copy(skills);
}
``
identical in both files. No unguarded raw-variable return found.
Confirmed clean, no fix needed.
§7.122 sibling-sweep fix: unconditional autoload-marker reclone on net_dead (2026-08-28)
Flagged in AGENTS.md's §7.122 entry as a "near-certain candidate" — es1
is es1_win's literal direct ancestor/near-byte-identical sibling
(shared "东方故事" lineage), and es1 itself was already confirmed and
fixed for this exact bug on its own §10.7 round-two pass. Checked
directly rather than assumed: work/std/user/autoload.lpc's
load_autoload_obj() was indeed byte-for-byte the same vulnerable
TMI-2-lineage code (// autoload.c: adapted from 2.4.5 code by
Truilkan@TMI) — unconditionally clones every entry in auto_load[] on
login with no presence check. Confirmed the same triggering ordering as
es1: std/user.lpc's remove() (clean quit path) calls save_me()
*before* destroy_autoload_obj(), and net_dead() (abrupt-disconnect
path) calls save_data() but never calls destroy_autoload_obj() at
all — so an abrupt disconnect bakes every carried autoload-marker item
(bank cards, rings, disease/guild markers, etc.) into the ordinary save
data AND leaves it physically in inventory, and the next login's
load_autoload_obj() then clones a second copy on top with no guard,
duplicating without bound across repeated net_dead cycles.
Fixed by porting es1's exact proven remedy verbatim (adapted only to
this file's own K&R brace style): load_autoload_obj() now snapshots
all_inventory(this_object()) once, and skips cloning any auto_load
entry whose base_name() already matches something already carried,
appending each freshly-cloned object to that same tracking array so
duplicate entries within one auto_load array can't double-clone
either. Verified via a clean native driver boot (Initializations
complete, port 40009 listening, zero new compile errors/warnings
introduced) — not independently live-reproduced with a real net_dead
cycle on this specific lib (the fix is a verbatim port of an already
live-verified remedy from the literal ancestor codebase, not a novel
fix needing its own from-scratch reproduction).
WASM 修复摘要(迁移自 meta.json 的 group_note)
东方故事基础版(蓝天)。
深度功能测试(§10.7,2026-10-04)— negative range ends
scripts/lpc_fix_negative_ranges.py: 16 line(s) in 9 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-04)— compile warnings: 2583 -> 46, and 191 more files load
scripts/lpc_warnings.py es1_win: 5764 files compile, 1169 do not; 3282 distinct diagnostics (2583 warnings, 699 errors). Now
5955 / 977; 358 diagnostics: 46 warnings, all in two files that cannot compile (std/adt/body.lpc inherits the dbase twice,
u/s/sage/test/dragon.lpc is two files pasted one after the other), and 312 errors in files that still do not load. 929 files
edited.
183 files under d/ did not load: #include <../hole.h>. A header named relative to the including file, in angle
brackets (71 x ../hole.h, 30 ../goomay.h, 23 ../replace_room.h, 18 ../mumar.h, 10 ../island.h, 9 ../zeus.h, 9
../takeda.h ...). The driver refuses .. in a <...> name and resolves only a quoted include against the including file, so
every NPC, weapon and armour of those areas died with Cannot #include and each user of the macros it defines with a second
error (218 files). A wizard's goto /d/deathland/dwarf/center answered No program in object, as did the Deathland fog,
palace and troll rooms, cook_master, the island vampire and the dragon-bone amulet. adm/obj/master.lpc now answers
include_file() with "./" + path for a name that starts ../ (a different string makes the driver resolve it the way a
quoted include is, through merge(), which understands ..; KB 04 §6.1). 177 of the 184 files then load through lpcc
--batch (it runs create()), and a live session against HEAD and the working tree shows the same rooms and clones working
where HEAD printed the error. The 7 that still fail are content gaps, left: liang_shan/monster/guard2.lpc wields
gurad_sword and guard1.lpc guard_dagger, neither of which exists in the area (no room places either guard);
mumar/monster/temp.lpc is a draft of cook_master with a broken if; d/island/monster/{feller,old_feller}.lpc include
../hole.h where the header lives in d/island/hole/; d/scholar/demand/where/{fire_beast,princess}.lpc include the headers
of two other areas that are not there. d/noden/drow/mob/ghost.lpc had lost the ); after its set_long here-document and the
() of my_tactic; both are back.
Mechanical (--fix): 429 nosave functions made protected (21 left public because another object calls them), 305 stray
backslashes after the Big5 characters whose second byte was 0x5C (KB 03 §4.4), 809 unused locals. The esI hand fixes carried
over (see its section of 2026-10-04): the second mapping alias of std/npc.lpc (1116 of the warnings), class -> cls
(16 files), ref -> refl, new -> new_mud, == typed for = in _bribe.lpc (13), _who.lpc, the acupuncture and
jousting files and the two shield spells, the leaf NPCs whose two parents both define init() or reset() (chained), the
prototypes that disagreed with their definitions, levels -> sort_levels in the five guild books that redeclared the
guild object's table.
Found here, not in esI:
obj/shells/shsh.lpchad been converted frombuffer(a type name in this driver) tobuffer1with a global search-and-replace, which also renamed the unrelatedBUFFERmessage list, so one name declared two variables (the colour pair that is saved, the list that is not). The list isBUFFER1again.std/priv.lpc: bothcase PRIVATE:labels ofvalid_read()/valid_write()had lost the word (case:), a syntax error that kept the file from loading.- Return types:
std/ghost.lpcsave_data()isvoidlike the body's,cmds/ghost/_look.lpcprototypescontent_matchasstring,u/m/moon/user.lpcprototypescreate_ghostasobject,gnome_archelder1.lpcoverridesdie()with the body'svarargs void die(int silent),goomay/monster/army.lpcremove()isvoid. u/s/sage/CMD_EVAL_TMP_FILE.lpc(the scratch file theevalcommand writes into a wizard's home; this one heldkissyou;) is deleted; the other wizard scratch file,u/m/moon/CMD_EVAL_TMP_FILE.lpc, compiles and stays.
Checked: scripts/lpc_audit_removed_locals.py HEAD over 2526 removed declarators found nothing a function still reads, and a
live pair (HEAD and working tree, fluffos admin) logged in, walked look / goto / clone through the Deathland and
island files that HEAD refused with no new error in the driver output.
(2026-10-04, later the same day) obj/shells/shsh.lpc's blind buffer -> buffer1 / cursor -> cursor1 conversion had also
changed text the player sees: the /buffer verb itself, the Cursor [ / Buffer [ prompts, the buffer view's banner and its
[Top of Buffer] / [End of Buffer] / [Buffer view closed] lines and the recall message. The strings read as in esI again; the
function-name strings ("open_buffer1", "cursor1_colour", ...) keep their renamed targets.
(2026-10-04, same pass) d/eastland/saulin_temple/obj/vege_food.lpc had the stray backslash before the closing quote of set_name and set_short (素食大餐\"; 餐 is cp950 C0 5C, KB 03 §4.4): the file did not compile. Fixed with scripts/lpc_fix_big5_quote_backslash.py.
深度功能测试(§10.7,2026-10-04)— the files that still did not load, read one by one: 124 of 6933 left
lpcc --batch over every .lpc (KB 08 §10.4; scripts/lpcc_real_fails.py lists the failures): 6048 load, 761 only hit the batch's call_out(0) nesting limit (they compile and load alone), 124 do not load. After the <../x.h> fix these were read file by file:
- Lost includes: 39 files (
d/noden/hawk/{armor,monster,weapon},d/eastland/story/{,wiz/}armor,d/island/samurai/obj/dagger1, the Snow/Moon/Boss wizard files ...) areinherit MONSTER;/inherit ARMOR;and nothing else;es1carries them with the#include <mudlib.h>(and"../hawk.h") restored, so they were copied from it by token comparison. 39 more (adm/daemons/network/services/{gchannel,tcp},netmailandstd/board/archivethat nameSAVE_EXTENSIONwithout<config.h>, island and erosmia files ...) came fromscripts/lpc_add_missing_include.py, which finds the one header that defines the missing macro. Three hawk monsters (adolph,crying,saw) had their includes below theinherit. inheritbelow a global variable (7 files: the beggar master,captain1,wiz_elder1,pontiff,obj/tools/barney,std/npc2, andpatolas, whose headerdetect-magic.hdefines globals): moved up.- Stale paths left when an area moved: the five island war mages inherit
/d/eastland/island/war/i_warvalue(now/d/island/war/),erosmia/ekravia/quest/girlquest_d,saulin_temple/workshop/std/shop(/std/room/shop),d/wiz/hallwaystd/room,d/island/monster/{feller,old_feller}include../hole.h(the header is ind/island/hole/, ases1found), and four IRC rooms still sayIRC "..."for the macroirc.hnow callsRM. - Typos:
string compared = ({forstring *in 8 sewer rooms,if (!me->query_attacker(); ) {in_yubi,set_save_dir(/d/eastland/data);without quotes,if (s = lower_case(s) != "menu")in the erosmia bar and tavern (the assignment took the comparison),if (8 (int)...inflamen,[BBvoidinold_feller,object *victimwhere the code stores one attacker (island war mages),voidfunctions that return a value (fire_hammer,shopkeeper,bubble_metal_slime),statusandstring *voidin two files nobody loads,refas a parameter in_changelog. set_nameand the prototype:std/object/ob_logic.lpcprototypesvarargs void set_name(...)and defines it withoutvarargs, so five objects that pass one argument (silver_card,tavern_key,crimson_scepter,pig,wanderthief) could not load (KB 06 §7.219).es1has it right.get_dir(x) == -2incmds/std/_bug.lpcandpraise.lpcnever wrote a report to the domain (KB 06 §7.218):file_size(x) == -2.tail()was an efun of the old driver;/adm/simul_efun/tail.lpcgives thetailandlogcommands (andopen/_tail) something to call. The erosmia bar's two parents both definereset()(chained).
Left, as content gaps, not invented: 16 inherits of files that are not in the archive (/u/e/elon/drink, /u/m/mercury/obj/special, /std/object/sec_ob, /std/map, ...), 11 NPCs that wield or wear files that were never shipped (gurad_sword, guard_dagger, ARM "earing" ...), headers of wizard homes (/u/s/smore/hawk.h, ../lilia.h, ...), the Megatower area whose header says /d/noden/megatower while the files are in backarea/megatower, draft files (temp.lpc, temp2.lpc, jin.lpc, _bed2.lpc is three copies of a room one after the other), functions of another driver (opcprof, function_profile, set_limbs, setup_race_body), and the commands saved without the leading underscore (flee, praise, typo, idea, query: only _name.lpc files become commands). std/adt/body.lpc and u/s/sage/test/dragon.lpc hold the last 46 warnings and cannot compile.
深度功能测试(§10.7,2026-10-05)— 主控 author_file()
驱动的 mudlib_stats 包在主控载入时调用一次 author_file(<主控文件>),要求返回字符串,否则每次启动都打印 author_file() in the master file does not work, using root_uid as fallback 一行,网页版每位访客的终端里都看得到。本库 adm/obj/master.lpc 的 author_file() 只为 /u/<字母>/<名字>/ 下的文件返回作者,其余返回 0。而且驱动传入的路径是 "/" 加上配置里的 /adm/obj/master,即 //adm/obj/master,sane 模式的 explode() 会保留一个开头的 ""。praise 和 find -author 直接调用 master()->author_file(),把 0 当作“没有作者”,所以没有把 0 改成字符串。现在先去掉空段(explode(...) - ({ "" })),/adm 下的文件返回 ROOT_UID(与同文件 domain_file() 的 adm 分支一致),这样也回答了开机探测;/u 分支加了长度检查(原来 /u/<x> 这样的短路径取 path[2] 会越界出错,而 author_file() 在每个对象载入时都会调用)(KB 06 §7.222)。用 lpcc config.fluffos /nonexistent.lpc 前后对比(主控和 preload 全程),只少了这一行。
编译警告收尾(2026-10-13)
scripts/toolkit_fix.sh 一轮:d/eastland/liang_shan/obj/fish_plate.lpc 没用的局部变量 i 删去。能编译的文件警告清零,regress 0。(同轮报的 6 处“需人工”的重叠继承都在不能编译的文件里,未动。)
- 2026-10-14:新驱动(FluffOS 970d9f09)报
Float value truncated to int(#1426)。6 个文件 10 处:赋值、初始化、return 改写成to_int(...)(值不变,只是写明截断);x op= 浮点先把浮点转成整数再运算(系数小于 1 时 x 直接变 0),改为x = to_int(x op ...):/d/monk/guild/train_room.lpc(exp -=)、/d/thief/hall/monsters/taylers.lpc(exp -=)、/d/thief/hall/monsters/taylers.lpc(exp -=)、/d/thief/hall/monsters/taylers.lpc(exp -=)、/u/b/boss/monk/train_room.lpc(exp -=)。改动的对象新进程各载入一次,无回退(scripts/lpc_fix_float_trunc.py,KB 04 §6.10)。 - 2026-10-14:64 个存档文件(其他 64 个;
d/adventurer/data/attic/adventurer_board_798460167.o,d/healer/data/attic/healer_board_780485145.o,d/healer/data/attic/healer_board_781540005.o…)还是原档案的 GBK 编码,没有转成 UTF-8,restore_object()报Invalid utf8 string后对象以空数据启动(表情守护进程没有表情、留言板没有帖子、玩家无法载入)。scripts/lpc_save_encoding.py逐个按 GB18030 解码,结果按存档格式解析通过才写回(KB 03 §4)。
- 2026-10-14:2 个文件因
Type of returned value doesn't match function return type ( int vs void )整份不编译:声明为void(或没写类型)的函数却返回值,旧驱动照样把值交给调用者。调用者要用返回值的(add_action 处理函数、命令、辅助函数)改为它实际返回的类型:help_cut()、report()。新进程比对 HEAD 与工作树:2 个对象,新增载入 0,回退 0(scripts/lpc_fix_void_return.py,KB 04 §6.3)。
储藏室东西过多时清不掉(2026-10-15,KB 06 §7.245)
std/room/std_storage 的 my_clean_func() 在物品超过上限一半时从最后一件往前清:for (i = number; ; --i) 从 inv[number] 开始,第一次就越界,报 Array index out of bounds,储藏室从来没有被清过;循环也没有下界。改为 for (i = number - 1; i >= 0; --i)。跳过玩家的判断写成了 ->querry("npc"),一并改为 query(原意是留下玩家、NPC 也清)。测试里六件物品、上限二:改前越界报错,改后剩两件。
去掉的预定义宏 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/oldftp/ftpdsupp.h。
区域物件的零碎修正(2026-10-19,KB 06 §7.267–§7.271)
/d/noden/hawk/monster/elder.lpc的#include "/u/s/smore/hawk.h"改成/d/noden/hawk/hawk.h(巫师目录/u/…不在档案里,头文件的唯一副本在区域里)/d/noden/hawk/monster/god.lpc的#include "/u/s/smore/hawk.h"改成/d/noden/hawk/hawk.h(巫师目录/u/…不在档案里,头文件的唯一副本在区域里)/d/noden/hawk/monster/god1.lpc的#include "/u/s/smore/hawk.h"改成/d/noden/hawk/hawk.h(巫师目录/u/…不在档案里,头文件的唯一副本在区域里)std/npc.lpc的wield_weapon()/equip_armor()在new()找不到文件时不再对 0 调move()(d/noden/hawk/monster/elder拿的武器在巫师目录里,整个 NPC 载入不了)
逐个物件与 HEAD 对比载入,没有退步。
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/xtra/_callouts.lpc(1 处)、/obj/tools/cod.lpc(3 处)。
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,共 3 处)。改动:/cmds/xtra/_graph.lpc(1 处)、/d/adventurer/hall/obj/salt_bottle.lpc(1 处)、/d/adventurer/hall/obj/test.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)。
advance:5 级以上时 check_explore() 除以零(2026-10-21,KB 06 §7.331)
公会的 advance 在等级 5 以上要问 EXPLORE_D->check_explore(me),它对每个区域算 探险点 * 100 / query_total_explore(区域)。区域总数是巫师用命令 explored 登记的探险点数,登记数据(存档)不在归档里;公会自己的 explore 指令对 max < 1 的区域写了 continue,check_explore() 没有,所以新树里只要有一个区域没有登记点数,advance 就抛 Division by zero。先取总数,总数小于 1 的区域当作没有可探索的东西(mark++; continue;),与 explore 指令一致。三个库(es1、es1_win、esI)的 explore_d.lpc 逐 token 相同,同一处改法。只做了加载检查(没有 5 级的角色可测)。
CMD_PRAISE 指向不存在的 _praise(2026-10-21,KB 06 §7.325)
include/commands.h 的 CMD_PRAISE "/cmds/std/_praise",实际文件是 praise.lpc(归档里就没有下划线);idea.lpc、typo.lpc 都 inherit CMD_PRAISE; 编译失败。这两个文件也没有下划线,CMD_D->find_cmd() 找的是 /_ + 命令名,所以 idea、typo 在这个库里本来就是 "什麽?",不报错。宏改成 "/cmds/std/praise",两个文件加载(HEAD 1、树 3),命令仍然不可达(与归档一致)。esI 的同一处改法见 esI 的 NOTES(那里 _idea.lpc 有下划线,命令可达,错误可见)。
存档里的 .c 文件名改成 .lpc(2026-10-21,KB 03 §4.2 第 9 项)
存档里写着 "/d/city/npc/wei.c" 这样的文件名;extract 时源文件已经全部改成 .lpc,而驱动对带扩展名的路径只认字面,new() / load_object() / move() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 4 个存档共 4 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/adm/daemons/languages/normal.o(1 处)、/data/std/user_ob/elf/c/cach.o(1 处)、/data/std/user_ob/gnome/a/aha.o(1 处)、/data/std/user_ob/orc/c/creative.o(1 处)(共 4 个存档)。
当铺 hochshop:赎回物品后“记住的物品”往前挪的循环用错了下标(2026-10-21,KB 06 §7.347)
当铺在玩家身上记着最多十件当出去的东西(REM_ITEM_PROP_ + DIGITS[n]),赎回一件之后后面的往前挪一位:for (j = i; j < my_item_num - 1; j++) me->set(REM_ITEM_PROP_+DIGITS[i], me->query(REM_ITEM_PROP_+DIGITS[i+1]));,循环变量 j 没用上,一直在写第 i 格,后面的格子根本没动,记录就乱了。改成 DIGITS[j] / DIGITS[j+1]。
工匠 artisan.lpc:my_tactic(int damage) 里的 victim 没赋值(2026-10-21,KB 06 §7.348)
attack_d 每回合调用 tactic_function(victim)(call_other(this_object(), tactic, victim))。工匠的 my_tactic(int damage) 把对手声明成了 int,局部的 object victim 从未赋值,random(20)>10 的那几回合 victim->query("armor/shield") 抛错。改成 int my_tactic(object victim),开头 if (!(victim = query_attacker())) return 0;(本库 teacher.lpc、mob_special.lpc 的写法)。改动:/d/eastland/mumar/monster/artisan.lpc。
拼错的 call_out() / input_to() 目标(KB 06 §7.349,2026-10-21)
计时器或输入提示给驱动的函数名和文件里定义的差一个字母;驱动对不存在的函数静默不理,所以对应的计时从来没有触发过。调用和配套的 remove_call_out() 改成已定义的拼法:idle_teme → idle_time。改动:/d/monk/guild/inner_room.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 条注册(6 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/eastland/liang_shan/monster/fish11.lpc(kill_me)、/d/eastland/saulin_temple/brass/zeng7.lpc(do_auction)、/d/eastland/story/monster/elder1.lpc(do_embattle)、/d/eastland/story/weapon/elder1.lpc(do_embattle)、/d/noden/hawk/obj/tape.lpc(do_cast、do_tear)、/std/npc2.lpc(quit)。最多的函数名:do_embattle 2 处、kill_me 1 处、do_auction 1 处、do_cast 1 处、do_tear 1 处。没有动(读过):/d/erosmia/ekravia/object/chain_fw.lpc 的 do_light(对象本身编译不过,登记留着,等对象能加载再处理)、/d/erosmia/ekravia/object/chain_fw.lpc 的 write_message(对象本身编译不过,登记留着,等对象能加载再处理)、/d/noden/mj/emsd.lpc 的 do_gongxian(对象本身编译不过,登记留着,等对象能加载再处理)。
log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)
这个库的代码往 log/ 的 1 个子目录写日志:log/harass(std/body.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/harass 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。
汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 2 处(2026-10-22,KB 06 §7.360)
原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:NPC 的称呼和提示里取到两个字而不是一个字。
本库 2 处(2 个文件):query("name")[..] 2 处。例:(string)ob->query("c_name"))[0..1] → (string)ob->query("c_name"))[0..0](/d/eastland/easta/monster/taxman.lpc:96)。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,不要重跑。
长度检查按字节写,旁边的提示按汉字写:4 处(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 你好吗)。本库 4 处(3 个文件):if (strlen(rn) > 14) {(/adm/daemons/newuserd.lpc:132);if (strlen(ncname) > 14) {(/cmds/std/_chfn.lpc:86)。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 退步。
samurai dagger MSPECIAL 指向缺失 /u/(2026-10-27,KB 06 §7.359 Also)
d/island/samurai/obj/dagger.lpc 写 #define MSPECIAL "/u/m/mercury/obj/special",档案无该巫师路径,驱动报 Inherited file ... does not exist。改为本库已有的特殊攻击基类 /d/eastland/goomay/obj/special(同文件头注释所述用途)。证明:HEAD 加载失败;改后加载成功。
深度功能测试(2026-10-27,§6.25 death_echoall 截断)
std/fun/death_echoall 同 es1 截断;同样收成空循环。证明:改后加载。