info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
东方故事,站内活动主题为"屠龙之战"(Dragon Slaying War)。与本批次的 `es1_win`(东方故事·蓝天版)同属 ES / TMI 引擎家族,两者核心代码几乎一致,仅站点内容与分支细节不同。本档案独有一段完整的终局剧情:`eastland/dragon` 是一座三十余间房的洞穴迷宫,深处镇守着 19 级、2500 点血量的最终 boss"孽龙恨天"——传说中因过去屠戮生灵而被玉帝封印于此的恶龙,身披并会掉落一枚龙形护身符。
English
Another build of the ES/TMI "Eastern Story" engine, sharing its Western-fantasy world with sibling archive es1_win (human, elf, dwarf, half-orc, and goblin races; knight, mage, priest, monk, scholar, and thief guilds; the farwind trading house's list/buy shopping) -- but built around its own in-game storyline, the "Dragon Slaying War" (屠龙之战). The payoff is a real endgame encounter unique to this build: a 30-plus-room cave dungeon (eastland/dragon) guarding 孽龙恨天 ("Evil Dragon, Hatred of Heaven"), a level-19, 2,500-HP boss lore-bound as a beast the Jade Emperor sealed away for past slaughter, who wears and drops a dragon amulet. This particular snapshot also ships with a test-era configuration that grants new characters automatic wizard privileges -- a holdover from internal testing/load trials rather than intended public-play behavior.
README
内容亮点
- 与 es1_win 相同的多种族、多职业公会西式奇幻世界(人类、精灵、矮人、 半兽人、地精等种族;骑士、法师、牧师、武僧、学者、盗贼等公会)。
- farwind 商行支持真实的
list/buy购物交易(已实机验证完整购买 流程)。 - 本档案额外开启了"新角色自动获得巫师(管理员)身份"的测试期配置, 说明这份快照原本是用于内部测试/性能压测的版本——普通玩家体验可自行 忽略这项特权,不使用管理指令即可。
在线试玩
https://mudlibs.fluffos.info/esI/
管理员账号 / Admin account
- id:
fluffos - 密码 / password:
Mud@2026 - 中文名 / display name: 浮浮
- 权限 / level: 完整管理员(FULL ADMIN)。
/adm/etc/groups将fluffos加入(root)与(admin)组,连线档设置wizard 1与domains(primary=wiz,level=archwizard)。
警告:公开架站前请务必修改此默认密码。
本地运行
cd libs/esI
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40010。
NOTES · 移植与修复记录
esI.rar → esI
- Archive:
archives/esI.rar(10.2MB — "东方故事"/"Eastern Story", themed "屠龙之战"/"Dragon Slaying War" per the live banner; up since May 2001 per its own credits). Same 东方故事 lineage as archive #12 (es1_win), different snapshot/branch. - Mudlib root:
esI/at archive top level. Config at top-levelconfig.cfg. - Port: 40010.
Status: DONE — boots clean, playable over telnet
Full flow confirmed: banner, staff/admin credits, uptime status, name-length validation, name-not-taken confirmation prompt.
What was fixed
Applied both fixes discovered on lib #12 (same lineage) proactively,
before the first boot attempt, since a quick grep confirmed both patterns
present in the raw archive:
1. std/object/prop.lpc's #include </std/object/prop_logic.c> (angle
brackets, local directory, reached via preload) — convert_lib.sh's
automated .c>→.lpc> fix only fixes the extension, not the
bracket-vs-quote distinction, so this still needed the manual switch
to quotes (#include "/std/object/prop_logic.lpc") per AGENTS.md §8d's
preload-timing addendum. Worth teaching convert_lib.sh to convert
local angle-bracket #includes to quotes automatically the next time
this pattern recurs in enough libs to justify it (seen twice now).
2. master.lpc's domain_file() — same explode("/", "/") returning a
zero-length array, !path guard not catching it — added
|| !sizeof(path).
Both fixes applied before ever attempting a boot; booted clean on the
first real attempt. Plus the routine per-lib steps: encoding (8471
converted, 1741 already UTF-8/ASCII, 107 skipped binary, 14 lossy),
.c→.lpc (7035 files, 1009 refs + 1 angle-bracket ref auto-fixed),
static→nosave (121 files).
Known remaining issues (documented, not fixed)
- 1195 lpcc-sweep failures (of 7035) — not triaged past confirming the lib boots and plays; given the time budget across ~90 remaining archives, not chasing every failure category individually this time (same call as es1_win's NOTES.md documents for its own long tail).
How to run
cd libs/esI
~/src/fluffos/build-debug/src/driver config.fluffos
python3 ../../scripts/mudclient.py 127.0.0.1 40010 --timeout 10 --send "" --send "look" --send "quit"Re-verification pass: driver rebuild + LPC formatter + WASM build
- Formatter:
format-corpus.mjsover all 7035.lpcfiles; 6986 reformatted, 5 unchanged, 44 refused (self-checkerrors, expected). - Native retest against rebuilt driver — 2 regressions found + fixed (both pre-existing gaps from before this pass, only surfaced now by actually running
look/score/quitto completion rather than just confirming boot + registration): 1. Same stray debugwrite("aadsaaaaaaaaaaaaaaaaaaaa\n\n")instd/user.lpc'ssetup()ases1_win(same lineage/root cause, absent fromraw/) — deleted. 2.quitcrashed with*Inherited file '/std/room' does not exist!the first time, then*No program in object '/d/std/IRC/lobby'!after a partial fix — both trace back to the same §8dget_include_path()gap: this lib's TODO.md entry claims "both known fixes applied proactively" (referring toes1_win's §8d fix), butadm/obj/master.lpcwas actually *missing*get_include_path()— onlyes1_win's copy has it. Two symptoms stacked: (a)include/globals.h'sROOMmacro is stale ("/std/room", real file isstd/room/room.lpc) whileinclude/mudlib.h's is correct ("/std/room/room") — files that#include <mudlib.h>are fine, files relying only on the auto-includedglobals.h(e.g.logoutd.lpc's room lookups) got the wrong path; fixed by correctingglobals.h's value to matchmudlib.h(single point, matches the pattern of every other macro in that file). (b) once that was fixed,d/std/IRC/lobby.lpc(lazily compiled mid-connection duringlogoutd's post-quit cleanup, not during preload) still failed withCannot #include irc.h— the exact §8d/§15o symptom (angle-bracket same-dir#includeneedsmaster.lpc'sget_include_path()for compiles triggered live, not preload/bare-lpcc); fixed by adding the standardget_include_path()shape tomaster.lpc(identical toes1_win's). Re-verified: full registration +look/score/quitall clean, zero debug.log errors, with two separate real Chinese names (乔峰, 阿朱). - WASM test: boots cleanly (only the expected non-fatal
Undefined function socket_createet al. in/adm/daemons/network/cmwhod.lpcduring preload, and the same cmwhod-related*No program in objectatquit-time cleanup — both are the documented "no sockets package under WASM" limitation, caught by the existing error handling, non-cascading). Full registration with a real Chinese name (慕容),look, andquitall worked end-to-end — this lib's IP-check is non-blocking, so it is unaffected by the documentedquery_ip_number()WASM limitation.
WASM-enablement pass (loopback / admin seeding)
Identical to sibling es1_win (ES II family, 008).
- Loopback ban bypass (§1.3b):
adm/daemons/banish.lpccheck_banned_site()(~line 75) — short-circuit at top for loopback / empty / non-string /localhost/127.names (returns 0). Gate is fedquery_ip_name(), called fromlogind.lpc:242and:577. - Uptime gate: none rejecting.
logind.lpc:87uptime()<43200only grants extra user slots (content, kept). - Anti-flood throttle: none (per-session password retry cap only).
- Admin account (§1.5):
fluffos/Mud@2026, display 浮浮. Added to(root)+(admin)in/adm/etc/groups;wizard 1+domains(archwizard) indata/std/connection/f/fluffos.o. Verifiedupdate /adm/daemons/statsdloads OK. - 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/esI/work/data/std/connection/f/fluffos.o,libs/esI/work/data/std/user_ob/human/f/fluffos.o.
深度功能测试 / Deep functional test (2026-07-24)
Round-two pass (AGENTS.md §10.7) on the native driver, one continuous session per checklist item, with several deliberate net-dead disconnect/reconnect probes (both prompt and after a real ~80s wall-clock wait) plus a fresh-boot verification of every fix. Test characters (both kept, not cleaned up, as evidence):
Jiangfeng/ 江枫 (idjiangfeng), passwordTestPass456, human male adventurer. Primary walkthrough character: registered, trainedunarmedvia guildtrain, explored 远风镇, confirmedjoinat an already-joined guild replies correctly, quit cleanly. Currently parked in/d/adventurer/hall/adv_guildafter a cleanquit. Save:work/data/std/user_ob/human/j/jiangfeng.o.Muyunhe/ 穆云鹤 (idmuyunhe), passwordTestPassZ7, female elf adventurer. Created specifically to reproduce the net-dead-during- registration bug (disconnected at the "[请按 RETURN 键继续]" prompt, before ever being placed in a room) — see bug #1 below. Also used for the full combat/shop pass: fought a level-1野狗(dog) at 远风镇西门 for 40+ real combat rounds (no crash; both combatants have very low hit chance so it never concluded — fled cleanly witheast), admin-granted 50 gold + 50 silver coins (see "Not verified live"), bought atorchfor 5 silver at/d/noden/farwind/shop, confirmedwield torchcorrectly rejects a non-weapon, quit cleanly. Currently parked in/d/adventurer/hall/adv_guild. Save:work/data/std/user_ob/elf/m/muyunhe.o.
Ported from sibling es1_win (§11 lineage, byte-identical files confirmed via diff before porting)
Both fixes below were previously found+fixed on es1_win's own §10.7
pass and explicitly flagged there as "sibling esI has byte-identical
unfixed copies of both files". Confirmed via diff against es1_win's
pre-fix commit (92be845f197^) that esI's copies were indeed
byte-identical (module trivial pragma/comment differences), ported the
same fix, and live-verified both work on esI specifically.
1. Net-dead disconnect during registration permanently strands the
account (§7.21 variant) — std/user.lpc restart_heart(). Same root
cause as es1_win: net_dead() only records linkdead_room if (env),
so a player who disconnects between finishing the registration Q&A and
pressing RETURN at the MOTD prompt (i.e. before complete_setup() ever
places them in a room) has no linkdead_room to restore and gets stuck
in LINKDEAD_ROOM forever on every future login. Fix ported verbatim:
in restart_heart(), when linkroom is unset but the player is
currently sitting in LINKDEAD_ROOM, call
this_object()->complete_setup("") to finish the interrupted setup.
Live-verified on esI: registered Muyunhe through to the
"[请按 RETURN 键继续]" prompt, let the connection close without
answering it, reconnected with the correct password — landed correctly
in /d/adventurer/hall/adv_guild (the real start room), not
/d/std/rooms/netdead. look/score both correct, zero debug.log
signal either before or after the fix (matches §7.20's "zero log
signal" warning).
2. #include <compress_obj.h>'s set_default_ob(__FILE__) — §7.14
class. __FILE__ inside the pasted-in fragment always resolves to
/include/compress_obj.h itself, not whichever of the ~40 objects it
was pasted into, so every affected clone's fallback property lookup
throws *call_other() couldn't find object '/include/compress_obj.h'
the first time it's needed (e.g. a shop's list command reading an
item's price). Fix ported verbatim: base_name(this_object()) instead
of __FILE__. Live-verified on esI: pre-fix, walking into
/d/noden/farwind/shop and typing list (or the room's own first-ever
reset(), see bug #4 below) would have hit this on the very first item
lookup; post-fix, list renders the full item list with prices cleanly
(油灯/绷带/魔法地图/火把), and a real purchase (buy torch) completed
without error (see "What was tested" below).
New bugs found on esI (not present/not reproduced on es1_win)
3. Leftover developer debug checkpoints printed to every player on
every real login — adm/daemons/logind.lpc (~line 477-490). Five
tell_object(player, "ttt\n") / "ttt1\n" / ... "ttt4\n" calls,
interleaved between the real ENTER_D/TOPPLAYER_D login-chain calls,
unconditionally print literal ttt/ttt1/ttt2/ttt3/ttt4 lines to
every player's screen on every ordinary login (confirmed reproducible —
not the net-dead-reconnect path, which goes through restart_heart()
instead and doesn't show them). Confirmed present in the raw archive
(/bin/grep -a ttt raw/esI/adm/daemons/logind.c — same 5 lines,
byte-identical), i.e. original author debug instrumentation that was
never cleaned up before this snapshot shipped, not a conversion
artifact. Same class as this lib's own already-documented
"aadsaaaaaaaaaaaaaaaaaaaa" stray-debug-string bug (see "Native retest"
section above) — zero player-facing content value, unambiguously
leftover instrumentation, not a game-content/design question. Not
present on es1_win (grep ttt es1_win/.../logind.lpc — zero hits;
this snapshot's own addition, so no sibling port needed in that
direction). Fix: deleted the 5 tell_object() lines, kept the 5
original comments and the real calls they were checkpointing. Verified:
re-logged in as both Jiangfeng and Muyunhe post-fix — no more
ttt* lines anywhere in the transcript, login chain output otherwise
unchanged.
4. Uncatchable eval-cost abort on a room's cold first compile —
another instance of §7.22's general shape, config-level fix — the
farwind shop's reset(). /d/noden/farwind/shop.lpc's create()
calls reset() synchronously (this shop's own explicit choice,
matching a lib-wide convention — 1553 files across the tree call
reset() directly from create(), not just relying on the inherited
std/room/room.lpc's async call_out("reset", 0)). On this snapshot's
shop, that synchronous reset() populates FOUR objects on the room's
very first compile, one of which — "woodman": "/d/zeq/woodman" — is a
heavily-decorated level-19 guard NPC (85350 HP) that equips 10 armor
pieces plus a weapon in its own create(), each an independent
first-time compile+clone. On a fresh boot, the FIRST player to walk
east into the shop hit a real, on-screen, cascading crash:
执行时段错误:*Too long evaluation. Execution aborted.
程式:/adm/obj/master.lpc 第 349 行
...
呼叫来自:/d/noden/farwind/shop.lpc 的 create() 第 35 行,物件: /d/noden/farwind/shop
呼叫来自:/std/room/room.lpc 的 reset() 第 47 行 ...cascading into log_error()/domain_file() themselves (each hitting
the SAME already-exhausted eval budget as soon as execution resumed),
ending in *Can't catch eval cost too big error. The player's move()
into the shop simply failed (they stayed in the previous room — not
left environment-null, unlike §7.22's original START_ROOM instance),
but the crash is loud and repeats identically every time the shop's
FIRST-ever visit this boot is attempted. Confirmed first-visit-only:
after the aborted attempt, east a second time succeeded cleanly
(shop and all 4 NPCs already resident in memory from the partial first
attempt). Root cause confirmed by direct test, not guesswork: rebooting
with maximum evaluation cost raised from the shipped 300000 to
5000000 and repeating the exact same fresh-boot east into the shop
now succeeds cleanly with zero errors — i.e. this is genuinely
"legitimately a lot of synchronous first-load work", not an infinite
loop. Worth noting the archive's OWN config comment
(config.fluffos:72-73, "Because of the heavy load on the Power PC,
made it larger to pass the bad time. (By Annihilator)") shows the
original author already had to raise this exact value once, on their
own era's hardware, for what is plausibly this same class of problem.
5000000 is also already the single most common maximum evaluation
cost value across this project's other converted libs (30 of them, per
grep -rh "^maximum evaluation cost" libs/*/config.fluffos), so this is
a precedented, low-risk config fix, not a one-off value pulled from
nowhere. Fix: libs/esI/config.fluffos, maximum evaluation cost :
300000 → 5000000. Live-verified: fresh reboot, fresh character,
east into the shop on the very first attempt this boot succeeds
cleanly (全部 4 只 NPC 正确显示), zero debug.log errors.
5. §7.12 tell_room() 2-arg wrapper bug — adm/simul_efun/tell_room.lpc, hit from a self-rescheduling call_out with no enclosing catch() (the severity-escalation shape, not just cosmetic).
varargs void tell_room(mixed room, mixed msg, mixed exclude) passes
exclude straight through to the real message() efun; called with
only 2 args (the overwhelmingly common case throughout this lib),
exclude defaults to int 0, which message() rejects (Bad argument
4 to EFUN message() Expected: object, array, Got: int(0)). Reproduced
live: adm/daemons/weather_d.lpc's change_phase() — a
self-rescheduling call_out ("it call_outs to itself recursively
so there's always one version in the call_out queue", per the file's
own comment) — calls tell_room(notifications[i], msg) with only 2 args
inside a loop, with no catch() anywhere in the function. The uncaught
throw aborts change_phase() at that exact statement, meaning the
function's OWN tail —
call_out("change_phase", day_phases[current_phase]["length"]) — never
runs, so the entire day/night phase cycle daemon permanently stops
forever for the rest of that boot, with the single runtime-error line
in debug.log as the only trace (no crash, no other visible symptom;
weather/ambient broadcasts and phase-change text simply never happen
again). Trivially reachable: WEATHER_D->request_weather_notices() is
called by every outdoor room's set_outside() (std/room/room.lpc),
so registering even ONE outdoor room (any ordinary walk outside)
arms the crash for whenever the phase timer next fires — some phases
in adm/etc/daytime.data are as short as 1 second, so this fires
quickly in practice, well within a normal play session. Confirmed via
diff: es1_win's copy of tell_room.lpc is byte-identical and
carries the same unfixed bug (not caught by that lib's own §10.7 pass —
plausibly a timing/room-visited difference between sessions). Fix
(matching AGENTS.md §7.12's documented remedy exactly):
// BEFORE
message("tell_room", msg, room, exclude);
// AFTER
message("tell_room", msg, room, exclude || ({}));Live-verified on esI two ways: (a) played normally afterward with
an outdoor room registered for ~15 real seconds, zero new debug.log
errors; (b) direct proof via the fluffos admin account's eval
command — eval tell_room(environment(this_player()), "TESTBROADCAST_2ARG\n"); return 1;
(the exact 2-arg shape that crashed weather_d.lpc) broadcast cleanly
with Result = 1 and zero debug.log error, both pre-fix (reproduced the
crash directly this way too) and post-fix (clean). Ported to
es1_win (libs/es1_win/work/adm/simul_efun/tell_room.lpc, same
one-line fix, comment cites this lib's finding) and sanity-checked there
with a fresh boot + existing test-character login/look/quit (clean,
zero new errors) — not a full independent replay of es1_win's own
§10.7 checklist, since that lib was already marked done; flagged here
for whoever next touches es1_win to note in ITS OWN NOTES.md.
What was tested and confirmed working
help startread first (doc/help/c_start) — same TMI/ES house style ases1_win'sc_start, documents the guildcost/advance/train/list/joinorganic skill path (no NPC-dialogue teacher in this lineage) and the real starting layout (远风镇/冒险者公会).- Registration: full flow (English id → confirm new → Chinese name → password/confirm → gender → race → email → real name → MOTD/RETURN) reached
/d/adventurer/hall/adv_guildcorrectly, twice (Jiangfeng,Muyunhe). Both got the "自动获得巫师权限" (automatic wizard status) message this snapshot's config grants every new character — cosmetic only in practice:westfromadv_guildinto/d/wiz/wiz_hall(wizard-only gate) still correctly rejected both test characters with "对不起,只有巫师才能进去。", and theevalwizard command was only reachable as the separately-seededfluffosadmin account, not either test character — documented as an observation, not "fixed" (a gate correctly rejecting a character despite a registration banner that implies it shouldn't is at most a content/messaging inconsistency, not a crash or obviously-wrong efun call). look/score/i: correct at every state change (registration, post-training, post-combat, post-shop-purchase, post-reconnect, post-relogin).- Guild training (organic path, no NPC teacher —
cost/advance/train/listin the guild room itself,std/guild.lpc):listshowed the 7 trainable adventurer-guild skills with real costs;train unarmedcorrectly deducted exp and printed "你的空手搏斗技能现在提升到 1 了。". join: tested at the already-joined adventurer guild — correctly replied "你已经是本公会的成员了!" (matchinges1_win); no separate sect/faction system beyond the documented guilds (adventurer/mage/ healer/knight/monk/thief/scholar, each gated behind the samejoin-in-guild-room mechanic reached via real travel this pass didn't budget time for beyond the starting adventurer guild).- Safe-sparring mechanism: this lib DOES have one, unlike
es1_win— a scripted "doll" combat tutorial at/d/noden/school/basic3-doll.lpc(kill dollsets a flag, waits 3 real seconds, thencease_all_attacks()— genuinely no real combat resolution happens, confirmed by code read;grep -r accept_fightacross the whole tree is empty, same ases1_win, so nothing in this lib implements a real damage-mirroring practice dummy). Reaching it requires completing a lengthy multi-room, command-specific tutorial gated byschool1/school2flags (get/give/put/search/ask/say/hp/who/looksequences across ~14 rooms) — not traversed this pass given the time budget, AND because it wouldn't have exercised real combat resolution anyway (per the code read above). Used a real level-1野狗(dog) at 远风镇西门 instead (same choicees1_win's own pass made, for the same reason — no functioningaccept_fight()-style dummy exists in this lineage). - Combat:
consider/killboth worked; 40+ real turn-by-turn combat rounds resolved correctly (hit/miss/damage messages,hpmid-fight status,scorereflecting HP/SP loss) against the level-1 dog — both combatants have minimal stats (character starts at 1 in every attribute) so hit rates are low and neither side reliably wins; fled cleanly with a direction command (战斗结束了。). No crash, no eval-cost error, no stuck state. - Shop purchase — completed live: admin-granted
Muyunhe50 gold + 50 silver coins via thefluffosaccount'seval(see "Not verified live" for why admin-assisted, not organic), thenbuy torchat/d/noden/farwind/shopcorrectly charged 5 silver ("你花5枚银币 买下一枝火把。"), added the torch to inventory, andscore's money breakdown updated correctly (50 金币 / 45 银币).wield torchcorrectly rejected ("那并不是武器。" — not a weapon). This also re-exercises bug #2's fix (the shop'slist/item-property lookups) and bug #4's fix (the shop's first-everreset()) together in one real transaction. quit+ debug.log: everyquitin this pass (multiple, across both test characters and the admin account) was followed immediately by agrepoflibs/esI/log/debug.log— all clean except for the bug #5 crash line itself (found this way, per §10.7's own worked lesson: never trust a normal-looking on-screen quit/interaction without checking the log).- Net-dead reconnect — both edge cases tested, not just the ported one: (a) the registration-interrupt case (bug #1, above); (b) the ORDINARY case — a player already properly placed in a real room (
Muyunheinadv_guild) disconnected uncleanly (noquit) and reconnected PROMPTLY:重新连线完毕, landed back in the exact same room with exact same HP/exp state; repeated with a real ~81-second wall-clock wait (via a foreground polling loop, not a backgrounded sleep) before reconnecting — identical correct result, zero new debug.log lines either time. (Did not wait through the fullWIZ_LINKDEAD_DUMP/LINKDEAD_DUMPforce-quit timer — 20-60 real minutes perinclude/body.h— out of this pass's time budget; thees1_winsibling's own pass didn't either.) - Clean-quit relogin: quit cleanly (
Muyunhe), reconnected fresh (full login-shaped relogin, not net-dead reconnect) — correctly restored toadv_guildwith all state (HP/exp/inventory/money) intact.
Not verified live (explicitly)
- Death and respawn: not reached — the one real fight run this pass (40+ rounds vs. the level-1 dog) never concluded either way given both combatants' minimal stats; deliberately seeking a losing fight wasn't pursued within this pass's time budget. Same as
es1_win's own pass. - The shop purchase used admin-granted money, not organically-earned money:
Muyunhestarted with zero gold (身无分文, same as every new character) and no in-session path to earn any was pursued (same situationes1_win's pass documented); coins were cloned directly via thefluffosaccount'sevalcommand (new("/std/coins")/set_type()/set_number()/move()) rather than earned through gameplay. The actualbuytransaction itself — ACL, price deduction, inventory update, the shop's ownsell/valuelogic — IS real and live-verified; only the money's ORIGIN is admin-assisted rather than organic. - Other guilds (mage/healer/knight/monk/thief/scholar): not travelled to and
joined — only the starting adventurer guild'sjoin(already-a-member case) was exercised live. Code review ofstd/guild.lpcsuggests the same mechanism handles all guilds uniformly, but this is not the same as a live-verified join into a DIFFERENT guild. - Full tutorial-school traversal / the "doll" scripted sparring mechanism: not completed (see "What was tested" above for why — time budget, plus it wouldn't exercise real combat resolution anyway).
- A broader lib-wide sweep for other rooms sharing bug #4's shape (a room whose
create()synchronouslyreset()s a first-time-heavy NPC roster): 1553 files share the "directreset()increate()" convention lib-wide; only the farwind shop was actually walked into and crashed this pass. Themaximum evaluation costconfig fix is lib-wide (protects every such room, not just the shop), but no attempt was made to enumerate or pre-visit every other room that might carry an equally-heavy first-load NPC.
Deep functional test round two (2026-08-14)
Independently re-verified all 5 round-one fixes against current code and live behavior rather than trusting the writeup above. All 5 confirmed still holding; no new bugs found or fixes needed this pass.
- Fix 1 (net-dead-during-registration
restart_heart()): code-confirmed thecomplete_setup("")fallback branch is still present. Not re-walked live this pass (same reasoning ases1_win's own round-two pass — expensive relative to a small, previously thoroughly-verified conditional). - Fix 2 (
compress_obj.h§7.14base_name(this_object())) and fix 4 (§7.90 eval-cost,300000→5000000): both live-verified together in one test, matching the exact original crash scenario — walked 冒险者公会 → east → east into 商店 on this boot's genuinely first-ever visit (fresh reboot, first attempt). Landed cleanly, full item list rendered (油灯/绷带/魔法地图/火把 with prices), the level-19woodmanNPC (皇家护卫团四将 怒, the original crash's heavyweight first-load trigger) present and correct, zero newwork/log/debug.loglines (checked the line count before and after: unchanged at 2693). - Fix 3 (leftover
ttt/ttt1-ttt4debug prints inlogind.lpc): code-confirmed zerotttmatches remain, then live-confirmed by watching a full fresh admin login transcript end-to-end — no strayttt*lines anywhere. - Fix 5 (§7.12
tell_room()exclude || ({})): code-confirmed still present with its explanatory comment. Not re-triggered live via the originalweather_d.lpc/outdoor-room mechanism this pass (would require registering an outdoor room and waiting for a phase-changecall_out), but the same underlyingtell_room()code path was exercised indirectly during ordinary movement/room-entry broadcasts throughout this session with zero errors.
Also verified: real admin write access via update /adm/daemons/logind
(succeeded, "Updated and loaded.") — used as the actual privileged-action
check rather than trusting any login banner. One reconnect exercised (the
connection's own quit is gated behind okip for wizards, same as
es1_win's sibling architecture — a genuine safety feature, not a bug —
so the reconnect went through the silent net-dead body-reattach path,
landing back in the shop with identical state, which is itself valid
reconnect-stability evidence).
Verification method
Booted native build-debug driver (zero eval-cost/preload errors this
boot, confirming fix 4 holds from cold start), admin login
(fluffos/Mud@2026). Driver killed by exact PID after testing;
incidental fluffos.o/d/noden/log/d/zeq/log save churn reverted
before commit (no code changes this pass — nothing else to stage).
§7.100 sub-threshold instance (2026-08-20)
Found during the §7.100 tail-sweep (the original 166-lib corpus sweep
only surveyed libs with >=100 live occurrences; this lib was below that
threshold and never checked). 1 live replace_program(ROOM); occurrence
in d/noden/6,8.noden.lpc's create(), immediately after
inherit ROOM; — same fatal shape as the rest of the §7.100 family
(sets a permanent "pending replace" flag that crashes the object the
first time anything binds a closure to it). No roommaker.lpc-style
room-building tool exists in this lib, so no factory-template copy of
the bug. 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).
Deep functional test round four (2026-08-20)
Targeted re-test of the two things every prior round explicitly left
unverified: a real death→respawn cycle, and a real join into a
non-starting guild. Per the project's current scope rule, only pursued
actual programming bugs (crash / debug.log error / stuck state) — not
content or balance.
Method note: both tests needed a wizard-controlled second connection
alongside the test character, run as a two-socket Python script (not
tmux_mud.sh/single-connection mudclient.py) so the admin account
could eval state changes on a character that stayed connected and
interactive throughout. Two real methodology traps hit and resolved
along the way, worth flagging for whoever does this next on another lib:
1. The eval command's underlying _set() (std/object/prop_logic.lpc)
has an intentional security guard that silently no-ops any set()
call from a /open/-rooted script onto a non-wizard living object's
properties (blocks /open sandboxes from writing player state
directly) — so ob->set("hit_points", 0) from eval does nothing,
with no error, no exception, query() afterward just returns the
unchanged old value. This is by design, not a bug. The correct way
to deal admin-driven damage is ob->receive_damage(N), which is a
real gameplay API that mutates the property mapping directly from
the target's own context, bypassing that guard exactly as real
combat damage does — and it independently no-ops (also by design)
if the target is currently linkdead (receive_damage()'s own
guard), so the test character's socket must be kept open/connected
throughout, not closed between steps.
2. A genuinely fresh (non-net-dead) login stops at the
"[请按 RETURN 键继续]" MOTD gate (setup()'s
input_to("complete_setup", 2)) before complete_setup() actually
places the character in a room — a *reconnect* (net-dead resume)
skips this entirely via a different code path
(restart_heart()/"重新连线完毕"), which is why earlier rounds'
sessions never hit this. A command sent into that still-armed
input_to() slot (e.g. look) gets silently consumed as the
RETURN-prompt's answer instead of dispatching as a real command, and
complete_setup() then places the character at their *default*
start room, silently overriding anything done to them (e.g. an
admin move_player() teleport) in between. This produced two
confusing false leads before being root-caused: an apparent "teleport
didn't stick" (character 's join replied "you're already a member"
because they'd been silently routed back to the adventurer guild,
their real default start room) and an apparent unexplained second
death (almost certainly the same mechanism misfiring on a different
test character, though not re-isolated as precisely — a clean,
admin-untouched 60-second silent-watch re-run with a fresh character
never reproduced any second death, so this is recorded as a
methodology artifact, not a mudlib bug). Fix for future sessions:
after a fresh (not reconnect) login, send one more blank line and
wait for the actual room description — not just the OKIP-check
line — before sending any other command or doing any admin-side
manipulation of that character.
Real bug found and fixed: die() crashes forever (never completes) when the character has no living last attacker
std/user.lpc's die() sets killer = query("last_attacker"); if
(!killer) killer = previous_object(); and then, in the un-fixed code,
used killer in two places without checking it might still be 0:
// BEFORE
if (!wizardp(this_object()) && killer->query("npc"))
...
if (this_object()->query_level() < 5 && userp(killer) && query("last_attacker")) {last_attacker is only set by std/body/attack.lpc's
receive_damage() when the damage's previous_object() at the time of
the call is itself a living() object — i.e. real melee/spell combat
from another living creature. Any OTHER damage source (poison,
bleeding, sickness, drowning/suffocation, desert exposure — all real,
reachable status-effect conditions in std/conditions/, each calling
receive_damage() from a non-living condition-daemon caller — or an
admin-applied receive_damage() call) never sets last_attacker, and
previous_object() *at the point die() itself runs* (called from
heart_beat()→continue_attack(), with no calling object in that
chain) is also 0. killer then stays int 0, and killer->query(...)
/userp(killer) both throw a driver-level call_other/efun-argument
error on this exact line, every single time, since continue_attack()
re-invokes die() on every subsequent heart_beat() tick as long as
hit_points < 1 — the crash never lets execution reach the
link_data("dead") early-return guard further down (line 1009,
*after* the crash site), so the character never actually dies: no
corpse, no ghost, just an infinite repeating on-screen runtime-error
spam and a permanently stuck negative-HP body. Reproduced live twice
(once via the crash itself, once again confirming the SAME crash
recurred one line further down after the first fix, until both call
sites were guarded) using receive_damage(9999) via the fluffos
admin account against a real, fully-connected test character (蜘蛛精/
testdieb's predecessor characters 夜寒霜/testmage, 白骨精/
testdie — see method notes above for why an artificial admin damage
source was needed to reach this branch, since ordinary NPC combat sets
last_attacker correctly and never hits it). Confirmed byte-identical
unguarded code in sibling es1_win's std/user.lpc (same lineage,
not yet fixed there — flagged for whoever next touches that lib).
Fix: added killer && short-circuit guards to both call sites,
matching every other killer-using line in the same function (lines
1012-1014, 1084, 1089-1095, 1109-1117 were already correctly guarded
with if (killer) — only these two were missed):
// AFTER
if (!wizardp(this_object()) && killer && killer->query("npc"))
...
if (this_object()->query_level() < 5 && killer && userp(killer) && query("last_attacker")) {Live-verified end to end, twice, on a fresh driver boot with a fresh
test character each time (testdie, then testdieb — testdie's
run additionally included the admin eval diagnostic that reproduced
the raw crash text on-screen pre-fix and confirmed its absence
post-fix): receive_damage(9999) → 你死了。 → ghost creation → the
full black_wuchang/黑无常 narrative (death1 at +10s, death2 at
+30s more) → revive() restoring the character to a real living body
in /d/noden/farwind/cemetery (露天英雄纪念馆) with reduced stats
(10/30 HP, matching the documented "skills reduced on death" mechanic)
— zero crashes, zero new lines in work/log/debug.log or
work/log/log throughout. testdieb (id testdieb, password
TestPassD2, human female adventurer) is left parked alive in the
cemetery room as evidence; testdie and the earlier testmage
mage-guild character were cleaned up (redundant once testdieb/
testguild cleanly demonstrated the same things without the
methodology-artifact confusion).
Real guild join into a non-starting guild — verified twice (mage, healer)
Code review in every prior round noted std/guild.lpc's do_join() /
each guild's own join_player() looked uniform across all 7 guilds but
was never live-exercised for anything but the starting adventurer
guild's already-a-member branch. This round completed real joins into
two different non-starting guilds, both via a wizard move_player()
teleport (d/mage/tower/mage_guild, d/healer/building/healer_guild)
followed by the character's own real join command — matching the
project's established "sect-joining needs wizard goto, not on-foot
exploration" precedent (guild rooms aren't exit-reachable from the
starting hall within a reasonable test budget):
- Mage guild (
夜寒霜/testmage, human female adventurer, since cleaned up):joincorrectly triggeredd/mage/mage.lpc'sjoin_player()— race-check passed (human isn't in the lizardman-only exclusion list), spellbook token moved to the character, welcome text and guild-wide announcement both printed ("拉修帝说: 欢迎加入魔法师公会..." / "[魔法师] 拉修帝 : 欢迎 夜寒霜(testmage) 正式加入我们的行列!"), andscoreafterward correctly showed the class change ("人类魔法师", title "魔法仰慕者"). - Healer guild (
紫霞仙子/testguild, human female adventurer, passwordTestPassG1— kept, parked in the healer guild room as evidence): same mechanism, different guild —d/healer/healer.lpc'sjoin_player()(race-check against a different exclusion list: orc/lizardman/vampire/beholder, also passed for human) correctly granted membership ("欢迎加入行医者公会..." / "[行医者] 刑老爹 : 欢迎 紫霞仙子(testguild) 正式加入我们的行列!"),scoreshowed "人类行医者" / title "草包看护生", and a follow-up adminevalindependently confirmedob->query("class")returns"healer".
Both joins used the SAME std/guild.lpc→per-guild-file→std/guild_ob.lpc
mechanism the prior rounds' code review predicted (the guild token
object, moved into the player, sets owner->set("class", class_name)
via guild_ob.lpc's move hook) — this round's live tests confirm that
prediction was correct, generalizing cleanly to at least 2 of the 5
previously-unverified guilds (mage, healer). Knight/monk/thief/scholar
remain code-reviewed-only, not live-verified, though there's no reason
to expect them to differ given the shared mechanism.
Standing bug checklist sanity pass
All checked via direct code read against the CURRENT work/ tree
(no live re-trigger needed for any of these — either already fixed, or
confirmed the vulnerable shape was never present):
- §7.90 (eval-cost):
config.fluffos'smaximum evaluation costconfirmed still5000000(the round-one fix holds). - §7.100 (
replace_program(ROOM)): confirmed fixed per this file's own dedicated section above; re-confirmed no new occurrences via a fresh grep this round. - §7.111 (
standard_trace()null-objectfile_name()):adm/obj/master.lpcalready carries the guarded form (objectp(error["object"]) ? file_name(error["object"]) : "none object") — was fixed in the corpus-wide sweep, still intact. - §7.112 (
init()-scheduledcall_outduplicated by reconnect): N/A — a corpus-wide grep forcall_out("death_stage"(the pattern's signature) across this lib's wholework/tree returns zero hits. - §7.113 (netdead reconnect never restores
heart_beat): confirmed clean —adm/daemons/logind.lpc'sreconnect()callsbody->restart_heart(), andstd/user.lpc'srestart_heart()unconditionally callsset_heart_beat(1)in the correctthis_object()(player-body) context at the end of the function. - §7.114 (
privateinput_to()callback through an inherited mixin): N/A — this lib's edit-flow mixin (std/living/edit.lpc) has noprivate input_line-shaped function; a corpus grep forprivate.* input_lineacross the wholework/tree returns zero hits. - §7.115 (
QUESTmacro pointing at a non-existent file): N/A — this lib's quest daemon macro isQUEST_D(/adm/daemons/quest_d, a real, existing file), not the standalone/std/questshape that bug is specific to; confirmed viagrepthat no file in this lib references a bareQUESTmacro at all. - §7.79 (bare 2-arg
addn/addn_temp): N/A — this lib doesn't useaddn/addn_tempanywhere;grep -rn "addn_temp(\|addn("across the wholework/tree (excluding the simul_efun shim's own definition) returns zero hits.
WASM 修复摘要(迁移自 meta.json 的 group_note)
同一血统(屠龙之战)。