info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
中文MUD鼻祖——根据 GitHub `mudchina` 组织的介绍,这是现存最早的中文 LPC mudlib 之一。它不是某个既有引擎的中文化移植,而是一份从零写起的原创代码(署名 [email protected],1994-1995 年间开发,建立在经典的 Lil mudlib 与 TMI 功能混入代码之上)——本项目里反复出现的"ES2/TMI-2/Falcon 家族"这一说法,追根溯源,指的正是这一脉传承。玩家注册时需取一个真正的中文名字,新人物降生在雪亭镇的"饮风客栈",探索由京师城门街道、雪山门派、丹霞谷竹林、绿林庄园、坟场幽冥等多个 domain 构成的东方奇幻世界,门派技能修炼与精/气/神三维状态条并行;NPC 中甚至有一位来自《七龙珠》的客串角色"孙悟空",游戏内被称作 ESII 首个"自我成长型"NPC,从创建起战斗经验便持续累积。
English
One of the earliest Chinese-language LPC mudlibs -- GitHub's mudchina org describes it as "the progenitor of Chinese MUDs" (中文MUD鼻祖). A from-scratch LPMud-lineage codebase (credited to [email protected], 1994-1995, built on the Lil mudlib with TMI feature-mixin code) rather than a reskin of an existing engine -- this project's own recurring "ES2/TMI-2/Falcon family" terminology for dozens of later Chinese mudlibs traces back to this exact lineage. Players register with a real Chinese name, land at a mountain inn (饮风客栈), and explore a wuxia/xianxia world of domains (city gates and streets, snow-capped mountains, a temple, a bamboo canyon, a ghost/graveyard area) with guild-style skill training, a two-tier reputation system, and NPCs including a Dragon Ball crossover cameo (孙悟空/Son Goku, described in-game as ESII's first self-growing NPC whose combat experience permanently accumulates from the moment he's created).
README
原始存档:git clone https://github.com/mudchina/es2(commit
ae8adf6)。仓库本身是纯 GBK 编码的原始快照,本次上线按本项目一贯
的方式完成了 GBK→UTF-8 全量转码与驱动兼容性修复(详见 NOTES.md)。
内容亮点
- 注册时需要取一个真正的中文名字(如"秦风"),新人物会降生在雪亭镇 的"饮风客栈"——一处武侠气息浓厚的驿站,NPC 包括店小二、旅客, 乃至一位来自《七龙珠》的客串角色"孙悟空"。
- 游戏世界由多个"domain"组成:京师城门与街道、雪山门派、丹霞谷竹林、 绿林庄园、坟场/幽冥地带等,展现了早期中文 MUD 典型的东方奇幻地图 结构。
- 门派/师徒(
create_family)与技能练功系统,双轨的"精/气/神"三维 状态条与独立的战斗攻防评分,score指令展现完整的干支生辰、门派、 潜能百分比等信息。 - 保留了 1994-1995 年间原始 MudOS 架构的大量历史痕迹(
adm/obj/ master.lpc、feature/*.lpc功能混入式设计等),是研究早期中文 MUD 代码演化的第一手样本。
在线试玩
https://mudlibs.fluffos.info/es2/
管理员账号 / Admin account
- id:
fluffos - 密码 / password:
Mud@2026 - 中文名 / display name: 秦风
- 权限 / level: 通过编辑
adm/etc/wizlist(securityd.lpc在create()时读入此文件填充wiz_status)加入fluffos (admin)一行,再以正常注册流程创建该账号。已用update /adm/obj/master验证成功(读取+编译 ACL 均通过)。
警告:公开架站前请务必修改此默认密码。
本地运行
cd libs/es2
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40214。
NOTES · 移植与修复记录
es2 — 东方故事Ⅱ (git clone from mudchina/es2)
Source: git clone https://github.com/mudchina/es2 (commit ae8adf6,
cloned 2026-08-25). Port: 40214. Status: done (boots clean, full
registration verified end-to-end with a real Chinese name, look/
score/quit/whoami/update all confirmed working post-login).
Note: the sibling repo mudchina/es2-utf8 (pre-converted) was
deliberately NOT used — per this project's established methodology
(AGENTS.md §4), doing the GBK→UTF-8 restoration itself, on the original
GBK snapshot, is the point.
What this is
A first-generation, from-scratch Chinese LPC mudlib, not a reskin of an
existing English codebase. bin/es2.info names it MUDNAME=ES2;
adm/obj/master.lpc's header credits "[email protected]"
(1994-11-07) with feature/treemap.lpc crediting "Buddha@TMI" and
noting it is "part of the TMI mudlib distribution" — i.e. this archive
genuinely is (part of) the historical root of the "ES2/TMI-2" lineage
name this project's AGENTS.md catalog uses for dozens of unrelated
LATER Chinese mudlibs. mudlib directory is the repo root itself (no
nested subdirectory); master file : /adm/obj/master, simulated efun
file : /adm/obj/simul_efun.
Layout: adm/ (master/simul_efun/daemons), cmds/ (wiz/arch/imm/usr/
std command dirs, Lima-esque ADM_PATH/WIZ_PATH/... dispatch by
wizhood()), feature/ (mixin classes inherited into std/char.lpc:
action, attack, command, dbase/treemap, equip, food, message, move,
name, save, skill, team, vendor, ...), d/ (game-world domains: city
gates/streets, a snow-mountain sect, a temple, a bamboo canyon, a green
forest estate, a graveyard/death area, a moon-pavilion zone, ...), u/
(individual wizard-workspace directories, e.g. u/cloud/), quest/
(experience-tier quest-list tables), daemon/class/* (character-class
skill masters). Start room /d/snow/inn (饮风客栈), death room
/d/death/gate.
Conversion (GBK→UTF-8)
scripts/convert_lib.sh libs/es2/raw libs/es2/work (default GB18030
encoding — confirmed correct via name : 东方故事Ⅱ decoding cleanly
from the raw config.ES2's mojibake bytes). Results: 1933 files
converted, 616 already UTF-8, 8 lossy (invalid-byte-dropped) files, 77
binaries skipped. Also stripped raw/'s .git/ from work/ (not part
of the mudlib) and removed the prebuilt bin/driver/bin/addr_server
binaries' influence (this project uses its own driver; left the files
in place since nothing references them).
- 8 lossy-conversion files individually audited per AGENTS.md §4.1's warning that a dropped byte can silently corrupt adjacent syntax:
d/choyin/npc/yamen_po.c,d/latemoon/upstar/upcenter.c(a@LONG/LONGheredoc room description — closing tag intact, one content character dropped),d/temple/{obj,npc/obj}/{spells_book, magic_book}.c(all 4: inside a//comment, harmless),u/cloud/obj/npc/flower_girl/guihua.c(see "Not fixed" below — independently corrupted, not a lossy-conversion artifact),cmds/std/exercise.c. All butguihua.ccompile fine; the dropped bytes inyamen_po.c/upcenter.c/exercise.cland inside string literals or comments and only cost a character or two of decades-old, already-garbled NPC dialogue/room text (pre-existing raw-byte corruption confirmed present inraw/, not introduced by this conversion pass) — left as-is per AGENTS.md's "don't fabricate content" rule. - Straggler extensionless GBK text files (
file/extension-based sweep misses these):doc/help/{weapon,quest,guild,history,theme, taoist,story,myth}— converted with a directiconv -f GB18030 -t UTF-8pass. static→nosavestring-literal collision (AGENTS.md §4.3): the blanket\bstatic\b→nosavesed rewrotelog_file("static/...")path literals inadm/obj/master.lpc,adm/daemons/securityd.lpc,cmds/std/suicide.lpc,cmds/arch/purge.lpc,cmds/wiz/call.lpc(8 hits/5 files) — reverted"nosaveback to"staticin all of them. No#define nosave static/#define protected staticcompatibility shim found (the other known collision class).
Config / driver-compat fixes
libs/es2/config.fluffos modeled on the raw adm/etc/config.ES2
(decoded name/messages), pointed at libs/es2/work, port 40214;
dropped 5 directives the driver flags obsolete (address server ip/
port, reserved size, binary directory, swap file).
1. adm/obj/master.lpc's valid_override() was 2-arg (AGENTS.md
§10.4) — simul_efun.lpc #includes 7 fragment files, needing the
3-arg (file, name, main_file) signature for efun:: calls inside
them to resolve cleanly. Widened the signature and OR'd in
main_file == SIMUL_EFUN_OB.
2. Missing runtime directories, §7.11: /log/ and /data/ were
never shipped (gitignored in the upstream repo) — mkdir -p log
data. More importantly, the shared log_file() simul_efun
(adm/simul_efun/file.lpc) had NO assure_file() guard at all
(unlike feature/save.lpc's save(), which already calls
assure_file()) — added one, with a forward declaration (this
driver doesn't resolve forward references within a file without
one — hit "Undefined function assure_file" until added). Also
guarded master.lpc's own bypass call (efun::write_file(home +
"log", ...) in log_error()) the same way. Without this, the
FIRST diagnostic/crash log write of the whole boot threw before
Accepting telnet connections.
3. feature/treemap.lpc's _query/_delete/_set were private
(AGENTS.md §7.48) — feature/dbase.lpc inherits F_TREEMAP and
calls them directly, illegal on this driver (private is
file-local only here, not "protected-like"). Changed to protected
in feature/treemap.lpc. Same class found and fixed in
feature/action.lpc's continue_action()/eval_function() and
feature/attack.lpc's attack() — all three called cross-file
from std/char.lpc, which inherits F_ACTION/F_ATTACK.
4. origin() returns a string on this driver, not an int bitmask
(AGENTS.md §7.47) — feature/team.lpc:20's
origin()==ORIGIN_LOCAL || origin()==ORIGIN_CALL_OUT is always
false because of incompatible types. This one line, inherited into
every std/char.lpc-based object, was the single largest
contributor to the initial compile-sweep failure count (1126 of
1610 raw failure lines — every NPC/player-body compile unit hit it
once). Fixed: origin()=="local" || origin()=="internal" (the
§7.47 mapping table: ORIGIN_CALL_OUT (0x10) → "internal").
5. adm/simul_efun/object.lpc overrode destruct() (AGENTS.md
§7.38, "Invalid simulated efunction override" — this driver
hard-rejects it). Deleted the wrapper (lost its ob->remove(euid)
pre-cleanup step, same documented tradeoff as every other lib that
hit this).
6. std/room.lpc's make_inventory() had no guard around
new()/move() (AGENTS.md §7.25, textbook match) — a room's
"objects" mapping referencing a missing/broken content path
crashed that room's first-ever population (*Bad argument 1 to EFUN
call_other() ... Got: int(0), /std/room.lpc in the trace). Wrapped
new() in catch(), added objectp() checks at both call sites in
reset(). Root-caused (not just guarded) 3 of the crashing rooms:
d/latemoon/{obj,npc/obj}/latebook.lpc's "objects" entry pointed
at __DIR__"npc/girl", but girl.lpc only exists under the sibling
d/latemoon/npc/ directory, not d/latemoon/{obj,npc/obj}/npc/ —
a pre-existing content-tree drift (multiple parallel copies of the
same zone), not a conversion artifact.
7. Same content-tree-drift class, but via carry_object(...)->wear()/
wield() chained straight off a possibly-0 return (no std/room.lpc
helper involved, so #6's fix didn't cover these): d/village/
flowergirl.lpc (__DIR__"obj/flower" missing, only exists at
/obj/flower.lpc or under a sibling npc/obj/), d/latemoon/room/
npc/aaa.lpc (__DIR__"houndbane", only exists under an unrelated
d/class/fighter/), d/latemoon/room/npc/fong.lpc and .../jane.lpc
(__DIR__"obj/{deer_boot,blue_dress,redbelt}", only exist under
sibling d/latemoon/{obj,npc/obj}/). Guarded each call site
(object ob = carry_object(...); if (ob) ob->wear();) rather than
guessing which sibling copy was "the" intended path.
8. adm/simul_efun/message.lpc's tell_room() passed a defaulted
int 0 as message()'s 4th (exclude) argument when called with
only 2 args (tell_room(room, str), the overwhelmingly common
call shape — 71 call sites across the tree) — this driver's
message() requires that argument be void | object | object *,
never a bare int, and throws *Bad argument 4 to EFUN message().
First surfaced via d/choyin/stove.lpc's create() crashing, but
this was a shared root cause affecting essentially every
tell_room() call in the game, not a per-file bug. Fixed once in
the simul_efun: omit the 4th argument to message() entirely when
no exclude list was passed, instead of passing a defaulted 0.
9. cmds/std/to.lpc used an old-MudOS call_other-shaped functional
literal: (: this_object(), ({ "do_to", me, arg }) :), illegal on
this driver ("Illegal to use local variable in functional"). Every
sibling ->edit() call site in this archive (std/{bboard,jboard}
.lpc, obj/mailbox.lpc) already uses the modern (: func,
bound_args... :) shape — rewrote to match: (: do_to, me, arg :),
plus a forward declaration for do_to (defined later in the same
file; same forward-reference requirement as fix #2 above).
10. cmds/wiz/tail.lpc called a bare tail() efun that was never
real on MudOS/FluffOS (not in this driver's .spec files at all).
Reimplemented with read_file()+explode()+implode() (last ~15
lines) since this driver's read_file() also has no
"negative start = from end" mode.
11. d/city/{south,east1,east2,west1,west2}door.lpc (all 5 city
gates) call create_door("out", ...) unconditionally, but each
one's own "exits" mapping has the "out" entry commented out
(its target "outskirts road" room was never shipped in this
snapshot) — create_door() hard-error()s without a matching
exits[dir] entry, crashing all 5 gate rooms' first load. Disabled
the now-inconsistent create_door() call to match the already-
disabled exit, rather than fabricating the missing target room.
12. adm/daemons/securityd.lpc's valid_write() save-file check
forgot the driver appends the save extension (AGENTS.md §7.49,
textbook match) — compared the driver's file argument (full
filename, .o included) against the bare query_save_file()
return value, so it NEVER matched and every player save threw
*Denied write permission in save_object(). This is the reason a
freshly-registered character landed with "你的四周灰蒙蒙地一片,
什麽也没有" (blank void) instead of the start room:
enter_world()'s user->save() call errors out midway through the
function, before the user->move(startroom) line ever runs — boots
clean, registers clean, only breaks on the FIRST real save (exactly
the "survives a smoke test, only shows up on the full flow"
signature §7.49 warns about). Fixed: file == qsf || file == qsf +
__SAVE_EXTENSION__. Re-verified with a full fresh registration
after the fix: character correctly lands in 饮风客栈 (start room),
look/score/quit all produce correct output, and the save
files (data/{login,user}/f/fluffos.o) are written successfully.
Author-typo fixes (pre-existing in raw/, AGENTS.md §6.6)
Confirmed against the raw GBK bytes for each before fixing (none are conversion artifacts):
u/cloud/npc/goddd.lpc: amessage_vision(...)call missing its closing quote before string concatenation ("...说:" 就凭你..., the classic §6.6 shape) -- fixed. Separately, later in the same file, an unbalanced-paren typo (num += ((...opens one extra paren versus its parallelelse-branch sibling 3 lines below, which has the correct single-paren shape) plussizeof(level)(undeclared) where every other use in the function sayssizeof(levels)-- both fixed to match the parallel/sibling code exactly, not guessed.u/cloud/obj/sword_book.lpc:set("long",\net("long", ...)— a stray duplicateet(fragment (apparent leftover from a botchedset("long",paste) breaking the call in two. Removed the stray fragment.u/cloud/npc/chess_player.lpc:name(this_player())calledname()as if it were a global "get an object's name" function; it's actuallyfeature/name.lpc's own per-instancevarargs string name(int raw)method. Fixed tothis_player()->name()(2 call sites, also affectsu/cloud/tearoom2.lpcwhich inherits this NPC).d/snow/npc/guard.lpc:is_killing(who)passed an object wherefeature/attack.lpc'sis_killing(string id)expects the string id — every other call site in the tree correctly passes->query("id"). Fixed to match.d/green/npc/master.lpc:map_skill("spells",magic-array)— missing quotes around the"magic-array"skill-name string literal (parsed as subtracting two undefined variablesmagic/arrayinstead). Fixed tomap_skill("spells","magic-array").d/village/lordhouse3.lpc: a commented-outif(...){block left its closing}NOT commented out, producing a dangling brace beforereturn 1;. Commented out the stray}too.u/cloud/duchang2.lpc:do_bet()'s source is genuinely truncated mid-statement in the raw archive (this_player->add("with nothing after it,wc -lconfirms the.cfile itself ends there) — per AGENTS.md §6.6, closed the function body as a safe no-op (notify_fail()+return) rather than fabricating the missing gambling-payout logic. Also fixed the adjacentreturn "没钱就别赌!"from a bare string return inside anint-typed function to thenotify_fail()+return 0idiom every other action-command handler in this archive uses.d/npc/oldman.lpc:kill_ob()'sset("nickname", ...)was immediately followed by a bare string literal with no enclosing function call — amessage("vision", ...)header (confirmed via the identical 4-argmessage("vision", <text>, environment(), this_object())shape used by 3 sibling files) had been dropped. Restored it. Separately in the same function, the parameterobject ob(the target being killed) was reassigned to an ARRAY (all_inventory(environment()), a "reveal boss, aggro the whole room" trigger this override repurposeskill_obfor) and then indexed — a real type collision, not a missing-quote typo. Renamed the array-holding local toobsthroughout rather than changing the parameter, preserving the original reveal-and-attack-all behavior exactly (this is a deliberate, if unusually-named, override — a siblingd/green/npc/oldman.lpcshows the "normal"kill_oboverride shape, single-target delegation to::kill_ob(); this file's author chose different semantics for this NPC and that choice was left alone, only the compile-blocking type bug was fixed).
eat_func/eat_tonic — dead-but-broken data, not fixed as content
d/snow/{obj,npc/obj}/{agaric,ginseng,ice_lotus,ebony_lotus}.lpc (all 4
tonic herbs, both obj/+npc/obj/ copies) and d/goathill/{obj,npc/obj}
/dead_leech.lpc all set("eat_func", (: eat_tonic :)) — eat_tonic is
referenced NOWHERE else in the archive (grepped the whole tree), a hard
compile error for an undefined function pointer. Even if it existed,
feature/food.lpc (the ONLY food class anything in this snapshot
actually inherits — feature/food{old,bak}.lpc are unused duplicates,
see "Not fixed" below) never reads "eat_func" at all (the one line
that would have — //if (query("eat_func")) return 1; — is itself
commented out). Dropped the dead set() call in all 10 files rather
than inventing tonic-healing logic to give eat_tonic a body.
Not fixed (pre-existing content gaps / dead code, confirmed via raw/)
adm/daemons/network/(12 files:ping_q,mudlist_a,ms,userid,pingtcp,name_server,mail_serv,netmail,telnetd,pingd,inetd, plusservices/) — an incomplete/never-finished Intermud-mail-and-services subsystem. References headers that don't exist anywhere in this archive at any path (uid.h,mailer.h,priv.h,post.h;config.h/daemons.hexist only underinclude/net/, not the bare paths these files#include), macros never defined (DAEMON,MAILDIR,POSTAL_D,THIS_MUD), and one file (mudlist_a.lpc) usesnew/oldas variable names, both driver-reserved keywords now. All of it is reachable only viadns_master.lpc's owncreate(), anddns_masteris excluded fromadm/etc/preloadin the raw archive already (AGENTS.md §7.6's own standard exclusion) — confirmed genuinely dead, not a conversion regression.u/cloud/obj/npc/flower_girl/guihua.lpc: independently corrupted in the raw archive (confirmed via direct byte inspection ofraw/, unrelated to this session's conversion pass) — the file's real// guihua.csource is preceded by ~128 bytes of binary junk, and later in the same file the content abruptly switches into what looks like a SECOND, different NPC'sset()calls (a "杂货贩"/peddler) mid- statement, including embedded non-UTF8-representable binary bytes in aset("gender", ...)call. Not a simple dropped-character case — genuinely unrecoverable without fabricating content.u/cloud/(a builder's personal workspace, containing dozens of.c.<unix- timestamp>backup/auto-save artifacts alongside the live files, e.g.d/snow/npc/rat.c.832813889) already shows other signs of interrupted/abandoned editing sessions from the original archive.d/green.n/npc/womana.lpc: genuinely truncated at the START in the raw archive too (wc -lonraw/'s copy: 4 lines, opening with a bare}) );and noinherit/create()header at all). Not referenced by any other file in the tree (grepped) — dead/orphaned, left as-is per AGENTS.md §6.6 ("don't fabricate content").d/snow/test.lpc: literally plain English prose ("test again / just a test."), not LPC source at all — a builder's leftover scratch note, not referenced anywhere.F_UNIQUEandF_MERCENARYfeature macros are referenced but never defined anywhere ininclude/globals.h(or any other header) — affectsd/snow/npc/obj/old_book.lpc,d/canyon/bamboo/obj/ {parry_book,slipcase}.lpc,d/green/obj/jade.lpc(F_UNIQUE) andd/snow/npc/mercenary.lpc(F_MERCENARY). A genuine archive content gap (theseinherits were presumably a shared feature-file that either predates this snapshot or was pulled from a different site branch) — not something to invent a base class for.feature/foodbak.lpc,obj/example/book.lpc: confirmed unreachable dead scaffolding.F_FOOD(the only macro anythinginherits) points atfeature/food.lpc;foodbak.lpc/foodold.lpcare neverinherited by anything and additionally contain their own independent typos (foodbak.lpc'sIf(capitalization).obj/ example/is explicitly example/documentation scaffolding (// An example book), and its one compile failure is an ancient 80-column hard-line-wrap landing mid-comment (// name of \nthe skill) — cosmetic, not live content.
Interactive test result — full registration flow
Registration shape: English id (3-12 letters) → confirm new-character
(y/n) → real Chinese name (2-6 characters; check_legal_name()/
is_chinese() both needed the AGENTS.md §8.1 byte-range→codepoint-range
fix, see below) → password (≥5 chars, entered twice) → email (free
text) → gender (m/f) → lands in /d/snow/inn (饮风客栈). Verified live
with a real Chinese name (秦风): look showed the full room description,
exits, board, and NPCs (店小二/旅客×3/孙悟空); score showed a fully
populated character sheet (age/gender/ganzhi birthdate, 精/气/神 bars,
combat attack/defense, food/water, kill counts, potential %, overall
score); quit produced a clean "你丢下一件布衣。...欢迎下次再来!" and
correctly wrote both save files (data/login/f/fluffos.o, data/user/
f/fluffos.o) with zero runtime errors in debug.log.
§8.1 GBK byte-range Chinese-detection fix
adm/simul_efun/chinese.lpc's is_chinese() was the length-gate
variant (strlen(str)>=2 && str[0] > 160 && str[0] < 255) and adm/
daemons/logind.lpc's check_legal_name() had the matching byte-
calibrated length bound (strlen(name) < 2 || strlen(name) > 12 for a
message that says "1 到 6 个中文字") plus an i%2==0 GBK-lead-byte loop
gate. Both fixed per the AGENTS.md §8.1 playbook: is_chinese() now
checks only the first character's CJK-Unified codepoint range (`str[0]
= 0x4e00 && str[0] <= 0x9fff`, guarded against an empty string);
check_legal_name()'s bound is now the literal 1..6 characters the
message states, and the loop checks every character (no %2 gate).
Live-verified: 秦风 (2 characters) registered cleanly on the first try.
Admin account
fluffos / Mud@2026, Chinese display name 秦风. Granted via this
lineage's own mechanism: adm/daemons/securityd.lpc's create() reads
adm/etc/wizlist (a plain <euid> <status> text file) into the
in-memory wiz_status mapping every boot — added a fluffos (admin)
line alongside the pre-existing lin (admin) entry, then registered
fluffos through the normal flow. wizhood()/get_status() both read
this mapping; (admin) status makes feature/command.lpc's
enable_player() call set_path(ADM_PATH) + the real enable_wizard()
efun. Verified: login banner shows "目前权限:(admin)", whoami shows
User ID = fluffos, and update /adm/obj/master succeeds ("master:
loaded successfully. 重新编译 /adm/obj/master.lpc:成功!") — the
canonical read+compile ACL check per AGENTS.md §1.5.
WASM status update (2026-08-25, another session)
Promoted wasm_status from "" to playable — booted clean on the
first try, zero fixes needed (unlike several sibling libs onboarded
the same day, e.g. ds386/discworld/deadsouls_fluffos, which all
hit an eager-simul_efun socket_status() compile failure specific to
the WASM build's missing sockets package). Verified with a real
scripted WASM session: login as fluffos/Mud@2026, arrival at the
documented 饮风客栈 with the 孙悟空 crossover NPC present and active,
and score producing the full character sheet (stats, 精/气/神 bars,
combat ratings) matching the native-tested output exactly. quit
wasn't distinctly captured in this transcript (孙悟空's own ambient
action lines kept the client's idle-detector from settling, the same
issue seen testing discworld's womble NPC) but is already verified
clean under native testing above and untouched by anything WASM-specific.
Deep functional test (round two, AGENTS.md §10.7, 2026-08-27)
Prior work on this lib had only ever run a basic boot/registration smoke
test (see "Interactive test result" above) plus the narrow §7.129
sibling-sweep fix (item 8 in "Config / driver-compat fixes"). This pass
is the first FULL §10.7 playthrough: registration → look/score/i at
every state change → movement → sect join (apprentice) → skill
learning (learn) → safe combat (fight) → a full death/revival cycle
→ quit + debug.log check → reconnect after a real wall-clock gap,
plus an explicit systematic grep for all thirteen standing cross-cutting
bug patterns (§7.121/§8.3a/§7.122/§7.123/§7.124/§7.126/§7.129/§7.130/
§7.131/§7.132/§7.133/§7.134/§7.135). Test character: qinfeng/秦风二
(Test@2026), joined 封山剑派 (Fengshan sword sect) under 柳淳风,
learned sword to skill level 2-3, went through a full smash-induced
death → 白无常 ghost dialogue → reincarnation → revive-room cycle, then
quit cleanly — save files removed post-test, only the seeded
fluffos/秦风 admin account remains. Four bugs found and fixed:
1. §8.3a, feature/command.lpc's command_hook — declared
private nomask int command_hook(string arg), registered via
add_action("command_hook", "", 1) in the same file's
enable_player(), but feature/command.lpc is inherited into
std/char.lpc (the base of obj/user.lpc AND every NPC) — the
textbook §8.3a shape, already confirmed broken the same way on 4
sibling ES2-lineage libs (demonangel, xuanjianlu, etc.) but never
checked on es2 itself. Fixed: dropped private, kept nomask.
2. §8.3a variant, std/item/combined.lpc's destruct_me — also
declared private, and inherited by std/money.lpc (every coin in
the game), std/medicine/{pill,powder}.lpc, std/weapon/throwing.lpc,
and several obj//d/ content items. Unlike the sibling demonangel
instance of this exact file (where the call_out("destruct_me", ...)
call site was dead/commented-out), here it is live:
set_amount() does if( v==0 ) call_out("destruct_me", 1); — reached
whenever a stacked item (most commonly money) is reduced to zero
(spending your last coin, dropping/combining stacks down to 0).
Fixed identically: dropped private, kept nomask.
3. New bug, not a prior AGENTS.md class — dbase key mismatch silently
disables the wimpy auto-flee safety net for every player, forever:
cmds/usr/wimpy.lpc (the player-facing wimpy [<percentage>]
command) reads and writes the dbase key "wimpy" — but the ONLY
consumer of the auto-flee threshold, std/char.lpc's heart_beat()
(wimpy_ratio = (int)query("env/wimpy")), and every NPC's own
create() (set("env/wimpy", N), ~30 files), all use the key
"env/wimpy" instead. These are two entirely separate dbase entries
on this driver's feature/dbase.lpc/treemap — running wimpy 30 as
a player set "wimpy"=30, which nothing ever reads, while
"env/wimpy" stayed permanently unset (0) for every player
character ever created, so heart_beat()'s
intp(wimpy_ratio = query("env/wimpy")) && wimpy_ratio > 0 guard was
always false and the auto-flee branch never ran — the player-facing
command was a complete, silent no-op from the very first player who
ever typed it. Fixed by changing both the read and write in
wimpy.lpc to use "env/wimpy", matching every other consumer.
Live-verified: after wimpy 30, a fight against a training-hall
NPC (武馆弟子) correctly triggered an auto-flee/retreat line
("你向後一纵,躬身做揖说道...") once damage pushed a stat below the
30% threshold — this did NOT happen pre-fix in code inspection terms
(the key was never read) — and the save file correctly persisted
"env":(["wimpy":30,]). Flagged for a sibling sweep: since this
is the ES2 codebase's own feature/dbase.lpc/env/ naming
convention (not something es1 shares — es1 uses a different stat
system entirely), check haiyang2/demonangel/xkx2001/rzrmud/
xo/zhyx/naruto (the later ES2-derived family) for the same
cmds/usr/wimpy.lpc vs. env/wimpy key mismatch — grep each for
query("wimpy")/set("wimpy" (no env/ prefix) outside of
std/char.lpc's own correctly-prefixed reads.
4. AGENTS.md §7.112 class, d/death/npc/{wgargoyle,bgargoyle}.lpc —
both NPCs' init() unconditionally schedules a 5-stage
call_out("death_stage", ...) narrative chain (ghost dialogue →
reincarnate() → move to REVIVE_ROOM) with no re-entry guard. This
is the exact, previously corpus-swept (150+ libs) §7.112 pattern
under its most common filenames, but es2 was never part of either
sweep wave. Fixed with the same death_stage_active set_temp()/
delete_temp() guard already applied to sibling haiyang2's copy of
this same file (see AGENTS.md §7.112's reference implementation),
adapted to each file's own exit points (bgargoyle.lpc has an extra
"not actually a ghost yet, attack them" branch that also needed the
flag cleared). Live-verified via a full smash→ghost→白无常
dialogue→reincarnate()→REVIVE_ROOM(城隍庙) cycle with zero
debug.log/driver-log errors throughout.
Confirmed clean (checked, no fix needed):
- §7.121/§7.124 (float-in-int / fraction-vs-percentage): no
int-typed economy/threshold function found doing unconverted float arithmetic; the twofloat count/count_totallocals infeature/attack.lpc'sbiwu_ob()andcmds/std/biwu.lpc'sstart_biwu()are a deliberate fractional extra-turn accumulator inside avoidfunction, not a narrow-return-type bug. - §7.122 (autoload duplication):
es2has its own from-scratchfeature/autoload.lpc(save_autoload()/restore_autoload(), NOT the TMI-2compute_autoload_array()/destroy_autoload_obj()/load_autoload_obj()lineagees1shares with TMI-2-descended libs) — this driver's plainsave_object()never serializes physical inventory contents at all, so there is no "captured twice" pathway: the autoload list is the ONLY mechanism that ever recreates a marked item, called exactly once persetup(), and ordinary reconnect (obj/user.lpc'sreconnect(),adm/daemons/logind.lpc'sreconnect()) never callssetup()/enable_commands()again — traced the full login/reconnect code path to confirm no double-restore_autoload()call site exists. - §7.123 (bare file-scope
IDENT = (...)): none found via manual inspection of everynosave TYPE ident;-then-bare-assignment candidate. - §7.126 (stale
.cextension in.osave data):es2has no AREA/coordinate-grid engine and only 3.ofiles total outside player saves (no corpus of exit-storing dbase files to be affected). - §7.130 (unconditional
query_idle()after non-interactive branch):std/char.lpc'sheart_beat()correctly guards itsquery_idle()call withif( !interactive(this_object()) ) return;one line earlier — no unconditional post-branch call found. - §7.131 (
find_living/find_playerwithoutset_living_name()):feature/command.lpc'senable_player()callsset_living_name()on every login, andadm/daemons/logind.lpc'sfind_body()usesfind_player()with achildren(USER_OB)/getuid()fallback — a modern master-object-era design, not the classic per-call-scan archetype this bug class targets. - §7.132 (
map()-over-mapping wrong-arg binding): nomap()call over a mapping with a single-parameter callback found in the tree. - §7.133 (
net_dead()never defined): bothobj/login.lpcandobj/user.lpcdefinenet_dead()(the latterprivate, butobj/user.lpcis a leaf nothing elseinherits, so the §8.3a demotion doesn't apply — confirmed via a live abrupt-disconnect reconnect test, "重新连线完毕" printed correctly every time). - §7.134/§7.135 (uninitialized-default accumulator/accessor): no new instances found beyond the
std/room.lpcmake_inventory()guard already fixed at onboarding time (item 6 in "Config / driver-compat fixes" above).
Verified via scripts/lpcc_check.sh (same 21 pre-existing FAILs as
before this pass — all previously-documented dead/gapped content, zero
new regressions) and a full native driver boot + the playthrough above,
with zero debug.log/driver-stdout errors across the whole session.
深度功能测试(2026-09-04,round three,paid shop)
新角度:补上 2026-08-27 round two 没做过的真实购买。那次已经
apprentice 封山剑派柳淳风,本轮不再拜师。
修复的 programming bug
1. log_error() 把编译警告当「编译时段错误」广播给玩家(§15w):
adm/obj/master.lpc 的 log_error() 对 this_player(1) 无条件
write("编译时段错误:" + message)。本驱动把 warning: Unused
local variable / Unknown #pragma 也送进 log_error(),第一次
登录刷了 34 条「编译时段错误」。闸门改成
this_player(1) && strsrch(message, "arning:") == -1,警告仍写入
work/log/log。修复后同路径 0 条玩家可见「编译时段错误」。
2. can_afford() 在没有白银时把任何非整两黄金的价格都判成「零钱」:
feature/finance.lpc 的 1996-10-14 改写在 silver 缺失时直接
else if (amount%10000) return 2。身上已有足够铜钱支付 15 文包子
(一百文钱 + 一两黄金)仍报「你没有足够的零钱,而对方也找不开」。
补上「无银、有铜、铜钱覆盖 amount%10000」分支。修复后
buy 包子 from waiter 成功,找零 100−15=85 文。
黄金不能拆成铜钱仍是设计(纯黄金买 15 文会走同一句零钱提示);
vendor_goods 的键是中文「包子」不是 dumpling,buy dumpling from
waiter 会「对方好像不愿意跟你交易」——list 列出的就是中文键,不是
bug。
实测过程
管理员 fluffos / Mud@2026。第一输入是「您的英文名字」,没有内码
菜单。端口 40214。落地饮风客栈 /d/snow/inn,店小二 id waiter。
孙悟空在场,ambient 不算 live clock,idle 0.45 够用。clone
/obj/money/coin + call coin->set_amount(100),list 包子十五文 /
鸡腿三十文 / 酒二十文 / 匕首五十文。修复后 buy 包子 from waiter:
「你向店小二买下一个包子。」i 包子 + 一两黄金 + 八十五文钱。
live debug.log 是 libs/es2/log/debug.log(Boot Time Fri Sep 4
03:48:47 2026),无 error: / Too deep recursion / F_FINANCE。
管理员存档未提交。