Eastern Story I

✅ 可玩

东方故事(蓝天) — Eastern Story I (ES I)

es1

🔑 fluffos / Mud@2026 更新 8e85676 2026-10-28 源码 下载 ZIP 上游 MudRen/es

▶ 开始游玩 · Play Now

这是本项目已收录的 `es2`/`haiyang2`/`demonangel`/`xkx2001` 等"东方故事Ⅱ / ES2"家族的上一代祖先引擎——`bin` 目录里自带的旧版驱动名为 `mudos.exe`,`README.md` 自述"东方故事1MUD",管理员账号约定为 `admin`/`mudren`。虽然中文名字带有武侠色彩,实际却是一个西式 D&D 奇幻世界:注册时只需要一个纯英文角色名(不含中文名机制, `is_chinese()` 一类的功能在此版本尚未出现),新角色可以从人类、 精灵、矮人、半兽人、地精、半身人、蜥蜴人、妖精、魔族、半人马、 黑暗精灵、眼魔、吸血鬼、鸟人、变形虫共 16 个种族中挑选一个,连同性别一起降生在远风镇的"冒险者公会"。

English

The original 'ES1' engine -- ancestor of this collection's es2/haiyang2/demonangel/xkx2001 family -- but despite its Chinese name ('东方故事', Eastern Story) this is actually a Western D&D-flavored fantasy world, not a wuxia setting: registration is English-name-only, with no Chinese-name mechanic in this early snapshot at all. New adventurers choose a gender and one of sixteen playable races -- human, elf, dwarf, half-orc, gnome, halfling, lizardman, imp, demon, centaur, dark elf, beholder, vampire, birdman, or shapeshifter -- before landing together at the Adventurer's Guild in Farwind Town. Rather than martial-arts sects, advancement runs through Western-style guilds scattered across the world's domains: a mage's tower, a monastery, a knights' order, and a thieves' guild sit alongside eastern-flavored zones for killers, monks, and scholars. Score displays a six-stat sheet (strength/wisdom/agility/constitution/piety/luck) alongside stamina, spirit, and social/charisma bars, and the game maintains its own kingdom map, player leaderboard, and adventurers' bulletin board right in the guild hall.

README

原始存档:git clone https://github.com/MudRen/es(commit 4c2177e)。本次上线按本项目一贯的方式完成了 GBK→UTF-8 全量转码 与驱动兼容性修复(详见 NOTES.md)。

内容亮点

在线试玩

https://mudlibs.fluffos.info/es1/

管理员账号 / Admin account

警告:公开架站前请务必修改此默认密码。

本地运行

cd libs/es1
~/src/fluffos/build-debug/src/driver config.fluffos

游戏端口:40239。

NOTES · 移植与修复记录

es1 — 东方故事(蓝天) (git clone from MudRen/es)

Source: git clone https://github.com/MudRen/es (commit 4c2177e513c9f2e57fbcca3a9239e6a5096a6b7d, cloned 2026-08-26). Port: 40239. Status: done (boots clean, full registration verified end-to-end with an English login name and full race/gender character-creation flow, look/score/who/say/relogin/ quit all confirmed working, admin update ACL confirmed).

What this is

This is the original "ES1" engine -- the direct ancestor of this project's already-onboarded es2/haiyang2/demonangel/xkx2001/ rzrmud/xo "ES2 family". README.md in the raw archive says only "使用FluffOS v2017驱动,游戏管理员账号:admin|mudren" (admin account admin/mudren); the shipped prebuilt binary is named mudos.exe (a Windows MudOS build, not FluffOS -- "FluffOS v2017" in the README refers to a *later* fork's driver, not what originally shipped here). config.cfg's name field decodes (GB18030) to 东方故事(蓝天) ("Eastern Story (Blue Sky)"), and its default fail message decodes to the classic ES-family 什麽?.

Despite the wuxia-sounding mud name, this is a Western D&D-style fantasy world, not a jianghu/wuxia setting: registration is English-login-name-only, and adm/simul_efun/chinese.lpc / adm/daemons/logind.lpc have no is_chinese()/check_legal_name() machinery at all -- that whole §8.1 bug class (documented extensively for es2/demonangel) simply doesn't exist yet in this snapshot, because the feature it patches doesn't exist yet either. This, combined with the total absence of addn() and 2-arg query(prop,ob) call sites anywhere in the live tree (both greps came back empty outside one already-dead file), confirms this archive really is an earlier *generation* of the engine, not just a differently-reskinned sibling.

New characters pick from 16 races (human/elf/dwarf/orc/gnome/halfling/ lizardman/imp/daemon/centaur/drow/beholder/vampire/hawkman/ shapeshifter) and a gender, then land at /d/adventurer/hall/adv_guild (冒险者公会, the Adventurer's Guild in "远风镇"/Farwind Town).

Layout mirrors the family: adm/ (master/simul_efun/daemons), cmds/ (wiz/arch/imm/usr/std/open/file/object/... command dirs), feature/ (only clean_up.lpc survives here -- the rest of the later family's mixin-class system, e.g. action/attack/dbase/equip/move/ name/save/skill/team, doesn't exist yet; this codebase instead puts nearly everything directly in std/body*.lpc/std/user.lpc/ std/npc*.lpc), std/ (body/living/user/npc class hierarchy, adt/ generic data-structure classes, races/ per-race mixins, weapon/, board/), d/ (game-world domains: abyss, adventurer, deathland, eastland, erosmia, god, healer, island, killer, knight, mage, magic, martial, monk, moorth, noden, scholar, swordman, thief, translate, wiz -- zero overlap with es2's domain list), u/ (wizard workspaces). mudlib directory is the repo root itself; master file : /adm/obj/ master, simulated efun file : /adm/obj/simul_efun.

Comparison against es2 (sanity check per task brief)

Confirmed genuinely different content, not a duplicate of es2 or anything else already onboarded:

However, es1 turned out to be near-identical to this project's own already-onboarded es1_win (008, sourced from a separate es1_win.rar archive, also named "东方故事(蓝天)"/"Eastern Story (Blue Sky)" in its own meta.json) -- discovered only *after* the es2 comparison came back clean, so flagged here explicitly even though it wasn't the comparison the task brief anticipated. A byte-level diff (after normalizing es1_win's CRLF line endings) shows d/ domain lists, feature/ (both just clean_up.lpc), and individual files (e.g. std/body.lpc) are identical once line endings are stripped. A broader whitespace-insensitive diff across all 6,927 .c/.lpc files common to both trees found 6,222 (89.8%) byte-for- byte identical and only 702 (10.1%) genuinely differing in content -- overwhelming confirmation this is the same underlying codebase as es1_win, just a different point-in-time snapshot (a live git history here vs. a .rar archive there), not two independently authored mudlibs that happen to share an engine. Per this project's established precedent for this exact shape (jyqxc/jyqxc2, two near-identical browser-download copies of the same archive, both onboarded independently with duplicate_of: null and the relationship documented in prose), this lib is onboarded as an independent entry rather than folded into es1_win's number, since the two are not byte-identical and this git-history snapshot has a verifiable, citable commit hash the .rar archive does not.

Conversion (GBK→UTF-8)

scripts/convert_lib.sh libs/es1/raw/es libs/es1/work (default GB18030 encoding -- confirmed correct via the config's name field decoding cleanly). Results: 9,215 files scanned, 7,545 converted, 1,570 already UTF-8, 15 lossy (invalid-byte-dropped) files, 85 binaries skipped. .c→.lpc renamed 6,929 files; 1,102 literal .c" references fixed; 1 #include <x.c> angle-bracket reference fixed; 607 local #include <x.lpc> angle-bracket includes converted to quoted form. Also removed the copied-in .git/ directory from work/ (an artifact of cp -ring the git-cloned raw checkout) -- not part of the mudlib, and left the shipped mudos.exe binary and .vscode/ project folder in place untouched since nothing in the mudlib references them.

Config / driver-compat fixes

libs/es1/config.fluffos modeled on the raw config.cfg (decoded name/messages), pointed at libs/es1/work, port 40239; dropped the directives this driver flags obsolete (address server ip/port, binary directory, swap file, reserved size) and reduced maximum local variables from the raw config's absurd (and clearly already a no-op typo on the original driver) 50000000-adjacent value down to 200, in line with the rest of this project's ES-family libs.

Followed this session's RAM-safety discipline throughout: compiled master.lpc/simul_efun.lpc/std/room.lpc/std/body.lpc/std/user.lpc and a handful of other widely-inherited base classes individually first and got them clean before ever running a full lpcc_check.sh batch sweep; watched the batch lpcc --batch process's RSS every ~15-20s for each of the 5 sweeps run this session -- peaked around 1.2GB, nowhere close to the ~10GB kill threshold.

1. adm/daemons/usage_d.lpc's AVERAGING_NUM.0 macro-pasting trick -- #define AVERAGING_NUM 12 then x / AVERAGING_NUM.0 relied on old-MudOS macro expansion re-lexing 12 immediately followed by a literal .0 into a single float token; this driver's preprocessor doesn't re-lex after substitution, producing syntax error, unexpected '.' at all 4 call sites (plus a 5th cascading "Illegal LHS"). This file is loaded very early and widely enough that its own compile failure was blocking the RAM-safety pre-check of several other base classes (std/user.lpc, std/living.lpc, std/npc.lpc, etc. all transitively reference it) -- fixed first, before any batch sweep, per this session's own stated discipline. Fixed: AVERAGING_NUM.0 → (AVERAGING_NUM*1.0) (4 sites). 2. std/npc2.lpc declared a global variable (nosave int hb_status;) before its own inherit LIVING; -- "Illegal to inherit after defining global variables." Reordered (inherit first). Five more files had the identical shape (global var, or an #include that itself declares file-scope globals, before their inherit): d/eastland/beggar/monster/master.lpc, d/eastland/ legend/monster/captain1.lpc, d/eastland/story/wiz/monster/ wiz_elder1.lpc, d/noden/hawk/monster/pontiff.lpc, obj/tools/ barney.lpc (all: moved inherit above the offending declaration) and d/mage/tower/monsters/patolas.lpc (moved the #include "detect-magic.h" that carries the global array declarations to *after* its own inherit MONSTER;, matching the correct order already used by its sibling d/mage/tower/obj/crystal_orb.lpc). 3. adm/daemons/ref_d.lpc used a local variable literally named ref -- this driver reserves ref as a keyword (its native pass-by-reference parameter feature), so string ref; inside resolv_ref() produced syntax error, unexpected L_REF -- cascading into ~120 downstream parse-recovery errors in the rest of the same (short, 83-line) file, since this daemon is inherited widely enough to appear as the "same root cause" in dozens of unrelated command files' failure traces. Renamed the local variable to refstr (3 sites) -- did not touch the file's own "ref" *string* property-name literal, which is unrelated data, not the colliding identifier. 4. class used as a variable/parameter name in ~15 files (a second reserved-keyword collision, same shape as fix #3 but for this driver's struct-like class feature) -- void receive_message(string class, string msg), void relay_message(string class, ...), void set_guild(string class), local string class; declarations, etc., all producing syntax error, unexpected L_CLASS. Renamed the colliding identifier to chr_class (character-class, matching what these variables actually hold: knight/mage/healer/etc.) throughout each affected function, using a quote-aware substitution that left ->query("class") string-literal lookups (a legitimate stored- property key, unrelated to the reserved word) untouched. Affected: d/monk/guild.backup/monsters/saulin_monk.lpc, d/eastland/ beggar/monster/donkey.lpc, d/eastland/liang_shan/monster/ chi_yu/{knight,mage,healer}.lpc, d/eastland/liang_shan/monster/ test2.lpc, u/b/boss/monk/inner_room{,1,2}.lpc, u/b/boss/god/ guild/new_guild.lpc, u/c/creative/knight/force_room.lpc, u/s/ sage/test/pet.lpc, u/s/snake/monster/healer.lpc, u/m/moon/ user.lpc, u/m/moon/new_guild.lpc. 5. std/object/ob_logic.lpc's set_name() had varargs on its forward-declared prototype but not on its real definition -- varargs void set_name(string str, string c_str); (line 22) vs. the actual void set_name(string str, string c_str) { ... } (line 135, missing varargs); this driver enforces the arity from the real definition, not the prototype, so every one-argument set_name("foo") call site (the overwhelming majority -- this codebase has no separate Chinese-name concept) failed with Wrong number of arguments to 'set_name', expected: 2, minimum: 2, got: 1. The adjacent sibling function varargs void set_short(string shortname, string c_short) two lines below already has the correct shape, confirming this was a dropped keyword, not a deliberate arity change. Fixed by adding varargs to the real definition. 6. The std/room/room.lpc→replace_room.h→replace_program(ROOM) redundant-self-replace pattern, AGENTS.md §7.100 -- every one of 17 per-domain replace_room.h header files (plus 2 inline call sites, d/noden/6,8.noden.lpc and the in-game u/s/sage/test/ roommaker.lpc room-generator's own code-template heredoc -- the exact "the bug lives in the factory" shape §7.100 also documents) contained a bare replace_program(ROOM);, called from create() in ~159 rooms across domains this file is #included into, always immediately after (or as the last statement of) that room's own inherit ROOM;-derived create(). On the FluffOS build this project uses, this doesn't produce §7.100's originally-documented "closure-crash landmine" symptom -- instead it manifests as an immediate, deterministic runtime crash on the room's very first load: *Nesting call_out(0) level limit exceeded: 1000, thrown from std/room.lpc's own create() → call_out("reset", 0) (line 28) via the room's create() → reset() → replace_program() interaction on this driver. Root-caused via a minimal single-file lpcc test on /d/eastland/tomb/square6 (the single largest affected bucket, 69 rooms) before touching anything else. Fixed by commenting out the replace_program(ROOM); line in all 17 shared replace_room.h files (one edit per file, each carrying an explanatory comment) and both inline call sites, rather than editing the ~159 individual room files that #include them -- this is the same one-line-deletion fix §7.100 already validated as safe, just applied at the shared-header level this archive happens to centralize it at, making a full, reviewable fix tractable in one pass instead of the "future sweep backlog" punt §7.100's own closing note describes for a lib where the calls aren't centralized. Verified with individual lpcc re-tests (not just the batch sweep, see the caveat below) on 5 sample rooms spanning 3 different previously-100%-failing domains (d/eastland/tomb, d/eastland/liang_shan/lake) -- all load clean post-fix. 7. Content-tree-drift class (AGENTS.md §7's "sibling copy dropped a file"/"copied from another wizard's home dir without updating the include" shapes), ~50 files individually diagnosed and fixed -- mechanically similar to, but each individually verified rather than guessed, the following sub-patterns: - Genuinely dropped local #includes: 9 files under d/noden/ hawk/monster/ (bird, bernice, boy, girl, man, older, older1, servant, woman) had zero #includes despite bare inherit MONSTER;, while 30 sibling files in the same directory all correctly carry #include <mudlib.h> + #include "../hawk.h" -- restored the same two-line header. 27 more files across a dozen other directories (cmds/{open,std}/_tune.lpc, several d/eastland/story/{,wiz/}armor/*.lpc, d/island/samurai/obj/ dagger1.lpc, d/noden/drow/square{1,2}.lpc, 7 d/noden/hawk/ armor/*.lpc, 2 d/noden/hawk/weapon/*.lpc, u/m/moon/cloth.lpc, u/s/snow/obj/{icy,shoe}.lpc) matched the exact same shape (inherit-only file, no include, in a directory where the dominant/majority sibling pattern is a single #include <mudlib.h> or a two-line domain-header pair) -- restored via an automated per-directory sibling-header-majority-vote script, each result spot-checked before applying. - inherit before its own domain #include (2 files, d/noden/hawk/monster/{crying,saw}.lpc): reordered. - Wrong relative-path depth: d/island/monster/{feller, old_feller}.lpc's #include "../hole.h" should have been "../hole/hole.h" (the header actually lives one directory deeper, at d/island/hole/hole.h) -- confirmed via old_feller. lpc's own live (non-commented) use of HOBJ"saw", a macro only hole.h defines, ruling out the alternative "doesn't need this header at all" fix. d/eastland/liang_shan/obj/fish_plate.lpc's bare #include "takeda.h" (missing the ../ every other sibling in that directory uses) got the same one-token fix. - Stray absolute path to a nonexistent/wrong wizard-home file, where the file doesn't actually need anything domain-specific: d/island/samurai/obj/{mercury_pen,weapon}.lpc (/u/t/takeda/ takeda.h, /u/o/odin/odin.h -- neither exists anywhere in the archive, and neither file references any macro either would have defined) → swapped to the dominant sibling convention, bare #include <mudlib.h>. Same fix for d/translate/cwa/ cut_tails.lpc (/u/m/maverick/maverick.h), d/scholar/scholar/ stone1.lpc (had no include at all), u/m/moon/ftpd.lpc (ditto), d/monk/gonfus/test/fon-mo.lpc + u/c/creative/fo-mo.lpc (/u/w/wind/wind.h, a copy-pasted "gonfu skill" template shared between two wizards' homes), and 7 files under d/scholar/ demand/where/ (cave31, fire_beast, fire_elephant, ohio, pawnbroker, princess, sky_guilt) which each #included a *different*, unrelated other domain's header (layuter.h, goomay.h, saulin_temple.h, oldcat.h, almuhara.h) despite using none of those headers' macros -- confirmed via grep before touching each one, then swapped all 7 to <mudlib.h>. - **Stray absolute path where the *correct* file exists elsewhere in the archive, unambiguously**: d/god/cmds/_god.lpc + u/b/ boss/god/cmds/_god.lpc + d/killer/cmds/_killer.lpc (missing #include <mudlib.h> for the DAEMON macro their inherit DAEMON; needs -- diff-confirmed near-identical to each other, all three missing the exact same line); d/god/cmds/_god.lpc's own "../god.h" needed d/god/guild/god.h to exist (it was missing -- restored by copying the byte-identical d/god/god.h, confirmed identical to u/b/boss/god/{,guild/}god.h, into place); d/noden/hawk/monster/{elder,god,god1}.lpc's "/u/s/smore/ hawk.h" (nonexistent) → "../hawk.h" (the real, local header every other sibling in that directory uses); u/s/sage/guild/ killer_guild.lpc's "../killer.h" → "killer.h" (the real file is in the *same* directory, not the parent); u/b/boss/sword.lpc's "../moon.h" → "moon.h" (ditto, same-directory not parent); u/c/creative/weapon/sword.lpc's "../moon.h" (doesn't exist under u/c/) → the absolute "/u/m/moon/moon.h" (byte-identical content to u/b/boss/moon.h, confirmed via diff, and this file genuinely needs its ARM macro); d/noden/mj/{emsd,monster/ ronardo}.lpc's "../lilia.h" (nonexistent) → the local mj.h that already exists in d/noden/mj/ and is byte-identical to d/noden/ruin/lilia.h (confirmed via diff -- mj/ is evidently the same zone as ruin/, just renamed, with the header copied over but not the two .lpc files that still referenced the old name); u/m/mnb/wenro5.lpc's "../lilia.h" → "lilia.h" (the real file, u/m/mnb/lilia.h, is in the same directory). 8. std/board/archive.lpc had no #include at all, needing SAVE_EXTENSION (from <config.h>) for its board-archive-file suffix check -- confirmed reachable (d/wiz/archiverm.lpc, a live d/ domain file, inherits it). Added #include <config.h>. 9. cmds/{open,file}/_tail.lpc and cmds/wiz/_log.lpc called a bare tail() efun that was never real on MudOS/FluffOS (same class as es2/demonangel's identical fix) -- reimplemented locally in all 3 files with read_file()+explode()+implode() (last 20 lines), plus a forward declaration in each (this driver needs one for same-file forward references, same as fixes documented in the sibling libs' own NOTES.md).

Not fixed (pre-existing content gaps / dead code, confirmed via raw/)

Interactive test result — full registration + relogin flow

Registration shape (English-only, no Chinese-name step): login name (a-z, ≤12 chars) → confirm new-character (y/n) → Chinese display name (optional, blank defaults to the capitalized English name -- note this prompt exists in the code but nothing downstream actually requires a real Chinese name, unlike es2/demonangel) → password (≥5 chars, entered twice) → gender (m/f/n) → race (English race name, or ? <race> for a help blurb) → email (user@host or $ for none) → real name (optional) → lands in /d/adventurer/hall/adv_guild (冒险者公会, Farwind Town's Adventurer's Guild).

Verified live with a raw Python socket test script end-to-end:

Admin account

fluffos / Mud@2026. This codebase's admin mechanism predates securityd.lpc's wizlist-file approach used by es2/demonangel entirely -- it doesn't exist here. Instead, adm/obj/master/groups.lpc loads a plain-text /adm/etc/groups file at boot, defining nested groups by colon-separated member lists ((root) :mudren:admin, (admin) (root) :admin, (arch) (admin), (sage) (arch), (wiz) (sage)). Added fluffos to the (root) group's member list ((root) :mudren:admin:fluffos), then registered fluffos through the normal flow. Verified via the canonical read+compile ACL check (AGENTS.md §1.5): update /adm/obj/master succeeded ("Updated and loaded.") with no permission error. Note: the login banner's cosmetic "目前权限:" line reads from a *different*, unrelated mechanism (DOMAIN_D->query_domain_level(), a domain-builder-list lookup) and still shows "player" for this account -- this is a separate, purely cosmetic display and does not reflect actual write/compile permission, which is confirmed working via the update test above.

Batch-sweep artifact caveat (important for reading lpcc_fail.log)

The final lpcc_check.sh batch sweep in this lib's lpcc_fail.log reports 943/6,929 failures (86.4% pass), but this understates the true fix rate: after applying fix #6 (the §7.100 replace_program sweep, ~159 affected rooms across a dozen domains), the raw batch number did not move at all, even though individually re-testing 5 sample rooms from the previously-100%-failing d/eastland/tomb and d/eastland/liang_shan/lake buckets with a *fresh, isolated* lpcc invocation showed all 5 loading clean with zero errors. This matches this project's own documented "lpcc sweep eval-cost artifact" gotcha: lpcc --batch runs every file through a single, long-lived VM process rather than a fresh boot per file, and something in that shared process's call_out/eval-cost accounting appears to accumulate across thousands of sequential object loads, producing spurious repeats of the exact same Nesting call_out(0) level limit exceeded signature for objects that are actually fine in isolation. The real driver boot test above (a fresh process, not the batch tool) is the trustworthy signal for whether the fix works, and it does. The remaining ~943-row failure list should be read as a conservative upper bound including this batch-mode noise, not a precise count of genuinely-broken files; most of the individually-diagnosed content- tree-drift items in the "Not fixed" section above were confirmed via single-file lpcc runs specifically because of this caveat.

Deep functional test (round two) — 2026-08-27

Full continuous playthrough per AGENTS.md §10.7: registered a fresh English-named character (Questorix, later several more throwaway characters as earlier ones got stranded testing death), full race/gender flow, look/score/i at every state change, shop visit, combat against d/noden/farwind/monster/traveller.lpc (level 7, the shop's ambient NPC — no dedicated safe-sparring/training-dummy mechanism exists in this codebase, confirmed via a accept_fight/ stat-copy-loop grep; train <skill> in d/adventurer/adv_trainer.lpc requires exploration points a fresh character doesn't have), several full deaths, quit+relogin, and the six standing cross-cutting bug patterns (§7.121/§8.3a/§7.122/§7.123/§7.124/§7.126). Found and fixed four real programming bugs, the most severe of which (a corpus-wide tell_room() crash) completely broke the death/revival system for every player from this archive's very first boot. Admin account fluffos/Mud@2026 (already seeded from the initial onboarding pass) used throughout; all throwaway test-character saves (questorix, quillbramble, diagtester, diagtwo, diagthree, willowfen) were deleted before committing, and a bank card cloned onto fluffos for the autoload test was removed and the account quit cleanly.

Bug 1 (SEVERE, new AGENTS.md §7.129) — tell_room()'s omitted exclude argument crashes as int(0), silently breaking the ENTIRE death/revival system from first boot

adm/simul_efun/tell_room.lpc forwarded its optional 3rd parameter unconditionally to message(): message("tell_room", msg, room, exclude). This driver's message() declares that 4th parameter void | object | object * — it accepts the argument being genuinely *absent*, or a real object/array, but not a literal int. Since an omitted varargs parameter in LPC defaults to int 0 (not to "no argument"), every 2-argument tell_room(room, msg) call site crashed with *Bad argument 4 to EFUN message() Expected: object, array, Got: int(0). std/body.lpc's own create_ghost() — called from EVERY player death — makes exactly this 2-arg call for its "a white shadow rises from the corpse" room announcement, so every single death in this codebase crashed, uncaught, at that exact point, from the very first boot of this archive.

The practical effect, reproduced live: the crash aborts the entire call chain (create_ghost() ← die() ← continue_attack() ← heart_beat()), so the new ghost object — already new()'d, half-initialized, and left dangling in the game world — never gets returned to die(), so the death-god NPC encounter (std/user.lpc's DEATH->start_death(ghost), /d/abyss/hell/monster/black_wuchang.lpc) and the final call_out("remove", 0) that destroys the old body NEVER RUN. Because the dying body's own ob_data["ghost"] flag was never set (only the orphaned ghost object got it), continue_attack()'s hit_points < 1 && !ob_data["ghost"] guard stays true forever — die() re-fires on the SAME already-"dead" body on every subsequent heartbeat tick, re-running corpse creation and wealth transfer each time and crashing again at the identical line. A single test death left 5-6 duplicate corpses in the shop and repeated KILLS-log entries accumulating every ~9 seconds, forever, with the player stuck looking at an inert "ghost" that never got dragged into the death-god sequence described in the game's own room flavor text. Zero visible symptom beyond "你死了" printing normally — no crash message reaches the player, debug.log/driver stdout is the only place this ever surfaced (and even that required debug_message()-based tracing rather than log_file(), since log_file() calls made from a player-body/domain- NPC euid context silently no-op with no error — see the AGENTS.md §7.129 entry for the full diagnostic trail and root-cause method).

A grep -rnE 'tell_room\([a-zA-Z_()]*, *"' across the whole tree found dozens more 2-argument call sites in ordinary content (cave-in traps in d/noden/elf/elf/elf2.lpc, a statue awakening in d/island/monster/ holy_knight.lpc, an NPC vanishing in d/thief/hall/monsters/ pickering.lpc, etc.) sharing the identical crash, each invisible until its specific story beat actually fires.

Fix: adm/simul_efun/tell_room.lpc now only forwards the 4th argument to message() when a real exclude value was given:

if( exclude )
	message( "tell_room", msg, room, exclude );
else
	message( "tell_room", msg, room );

One-file fix, transparently repairs every 2-argument call site project-wide. Verified live end-to-end (test character Willowfen): after the fix, a full death sequence played out correctly for the first time — the "黑无常" (Black Wuchang, death-god) encounter and dialogue, the drag to "奈何桥" (Naihe Bridge), the ~40-second automatic revival timer chain (death1→death2 in black_wuchang.lpc), and a full character restoration (real body, correct stats 10/30, correct starting item) at "露天英雄纪念馆" (the cemetery) — with zero runtime errors for the whole session. Both the 2-arg and 3-arg (with a real exclude, confirmed the exclude target still correctly doesn't see their own message) forms were independently re-verified via eval post-fix.

Bug 2 (SEVERE, extends AGENTS.md §7.14's __FILE__-in-fragment class) — include/compress_obj.h's clone/master default-object pattern resolves to its own header path, crashing query() for any unset property on 44 shared "simple item" files

include/compress_obj.h (#included directly inside create(), not inherited, by 44 files: bandages, torches, maps, spellbooks, bank cards, potions, weapons, etc.) does if(clonep(this_object())) { set_default_ob(__FILE__); return; }, intending to point a clone at its own un-cloned master/blueprint copy (which alone ran the item's real create() and holds the actual property values) so query() can fall back to it for any property the clone itself never sets. But __FILE__ in an #included fragment expands to the FRAGMENT's own path, not the includer's (a general trap, AGENTS.md §7.14) — every clone's default_ob ended up literally "/include/compress_obj.h", so the FIRST query() of any property the clone doesn't set locally (e.g. prevent_drop/query_auto_load(), both checked on every player death in std/user.lpc's die(); or a shop list/buy/inventory-display of any of the 44 affected items) crashed with *call_other() couldn't find object '/include/compress_obj.h'. Reproduced live via an admin-cloned /std/cards/bank_card (a compress_obj.h user): query_short()/balance both crashed pre-fix, and the trashcan/ shop's own torch/bandage/map stock items would have hit the identical crash on first purchase or death, given they use the same header.

Fix: set_default_ob(__FILE__) → set_default_ob(base_name (this_object())). Note this is a DIFFERENT correct replacement than §7.14's usual file_name(this_object()) — that form includes the clone's own #nnn suffix, which here would make default_ob point back at the clone ITSELF, infinite-looping through query(). The correct fix depends on what the fragment's __FILE__ was actually trying to name (documented generally in the AGENTS.md §7.14 update). Verified live post-fix: the same admin-cloned bank card's query_short()/balance both work correctly, and a full quit+ relogin cycle carrying it showed no crash and no duplication (also verifies Bug 4 below).

Bug 3 (confirmed 3rd instance of AGENTS.md §7.122) — std/user/autoload.lpc's unguarded load_autoload_obj() duplicates every carried marker item across abrupt-disconnect/quit-then-restore cycles

This file's own header credits "Truilkan@TMI" — es1 turns out to share this exact TMI-2-lineage save/autoload mechanism with mortremains and tmi2 (already-documented §7.122 instances), despite its completely different D&D/English-registration content and no other visible TMI-2 branding. std/user.lpc's quit path (remove()) calls save_me() (line 410, which runs compute_autoload_array() before save_object(), baking any carried marker item into ordinary inventory data) BEFORE destroy_autoload_obj() (line 414); net_dead() (abrupt disconnect) calls save_data() and never calls destroy_autoload_obj() at all. load_autoload_obj() (run from every login via std/body.lpc's init_setup()) then unconditionally clones one more copy of every auto_load entry with no check for whether a matching item is already present. Real marker items using this mechanism: std/cards/{bank_card,credit_card}.lpc, obj/amulet.lpc, obj/tools/{memopad,staff,scroll}.lpc, obj/shells/shsh.lpc, wedding rings under d/noden/farwind/wedding_obj/ and d/std/wedding_object/.

Fix: added the same inventory-snapshot idempotency guard used for mortremains/tmi2 — load_autoload_obj() now skips cloning any auto_load entry whose base_name() already matches an item already carried. Verified live on the admin test account (fluffos): cloned a /std/cards/bank_card, carried it through a real quit→relogin cycle (exercising the save_me()-before-destroy_autoload_obj() gap) and then two direct repeat eval calls to load_autoload_obj() (the tmi2-style direct-call verification) — held steady at exactly 1 copy throughout, with i/balance both correct.

Family relevance: since es1 is the direct ancestor of es2/ haiyang2/demonangel/xkx2001/rzrmud/xo/zhyx/naruto, and turns out to share TMI-2-lineage base-library code most of its ES2-era descendants likely replaced with their own feature/-mixin architecture, this specific bug is NOT expected to recur in that later family (their feature/ directories don't exist yet in this snapshot at all — see "What this is" above). The much stronger sibling candidate is es1_win, the near-byte-identical separate archive of this same pre-ES2 codebase already onboarded in this project (see the "Comparison against es2" section above) — worth a direct check.

Bug 4 (minor, obviously-wrong-macro) — a newly-created ghost is placed in the wrong room, using the login-room macro instead of the dedicated ghost-destination macro

std/body.lpc's create_ghost() moves a fresh ghost with ghost->move(START) inside an #ifdef GHOST_START_LOCATION block — but START (/d/adventurer/hall/adv_guild, the ordinary new-player login room, from config.h) is a completely different macro than GHOST_START_LOCATION (/d/noden/farwind/cemetery, from body.h, defined specifically for this purpose). The two lines directly above, commented out, already used the correct macro (ghost->move(GHOST_START_LOCATION)) — this reads as a debug-era substitution that was never reverted. Low practical impact once Bug 1 is fixed, since the death-god NPC's start_death() unconditionally drags the ghost to a third location (the Naihe Bridge) immediately afterward regardless of where it started, but still an unambiguous wrong-constant bug worth fixing on its own merits. Fix: ghost->move(START) → ghost->move(GHOST_START_LOCATION).

Minor hygiene fixes (return-type mismatches, compiler warnings only, no observed runtime effect)

While tracing Bug 1, the compiler's own warnings surfaced two forward-declaration/definition return-type mismatches predating this session, both harmless in practice (the mismatch never actually corrupted a returned value in any reproduced scenario — Bug 1's ghost=0 symptom traced to the pre-existing if(!link) return 0 early-exit on a repeated die() call, not to type coercion) but worth correcting for hygiene since they're compiler-flagged declaration mismatches, the same class as AGENTS.md §7.127: std/user.lpc's and std/npc.lpc's forward declarations of create_ghost() said int/ protected int; the real definition in std/body.lpc returns object. Similarly std/body.lpc's own forward declaration of save_data() said void; the real definition (std/user/save.lpc) and std/ghost.lpc's override both return int. Fixed all three declarations to match their real definitions. (Left u/m/moon/user.lpc — a wizard's personal home-directory fork of std/user.lpc — with its own copy of the same mismatch untouched, matching this project's usual treatment of wizard-workspace scratch content.)

Six standing cross-cutting bug patterns — checked, clean

Interactive test state left as evidence

Admin account fluffos/Mud@2026, clean (no test items carried, clean quit). No throwaway test-character saves remain (all deleted per this project's clean-up policy). The fix for Bug 1 was verified against a real, complete death-to-revival cycle on a fresh throwaway character (Willowfen) before that save was deleted; the full transcript (registration → shop → combat → death → death-god encounter → bridge → automatic revival → correct restored stats/inventory) is summarized above and was captured live via a raw Python socket test script with the connection held open across the full ~60-second revival timer window (closing the connection early, simulating an abrupt disconnect, was itself how Bug 3's net_dead()-without- destroy_autoload_obj() gap and one red herring around a link=NONE race were distinguished from the real, connection-independent Bug 1 root cause — the link field turned out to populate correctly under normal held-open-connection conditions, ruling out a suspected connection-race explanation for the stalled death sequence before the real tell_room() crash was found).

深度功能测试(§10.7,2026-10-03)— die() null-killer sibling fix

Checked the TMI-2/ES1 death path here after the tmi2 pass (see libs/tmi2/NOTES.md, 2026-10-03). Two questions, answered on a throwaway copy of the committed tree (git archive, fresh driver, test character with 500 gold granted through the admin eval and saved):

深度功能测试(§10.7,2026-10-04)— negative range ends

scripts/lpc_fix_negative_ranges.py: 19 line(s) in 10 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)— stray backslash before a closing quote

A Big5 character whose second byte is 0x5C was written with one extra backslash after it (the old driver read 5C 5C as one backslash and so completed the character) and the UTF-8 conversion kept it (KB 03 §4.4). Before a closing quote it escapes the quote, the string runs into the next line and the file does not compile. scripts/lpc_fix_big5_quote_backslash.py deletes it when the character before it encodes to cp950 with 0x5C as second byte and the quote ends an expression. Here: d/eastland/saulin_temple/obj/vege_food.lpc (set_name and set_short end in 素食大餐\"; 餐 is cp950 C0 5C), which did not compile.

深度功能测试(§10.7,2026-10-04)— compile warnings: 3163 -> 48, 31 more files load; 6049 of 6929 files load

scripts/lpc_warnings.py es1: 6018 files compile, 911 do not; 3441 distinct diagnostics (3163 warnings, 278 errors). Now 6049 / 880; 237 diagnostics: 48 warnings, all in four files that cannot compile (std/adt/body.lpc and u/s/sage/test/dragon.lpc inherit the dbase twice, d/scholar/demand/where/fire_beast.lpc includes another area's header, std/adt/command.lpc), and 189 errors in files that still do not load. lpcc --batch over every .lpc (KB 08 §10.4, scripts/lpcc_real_fails.py): 6049 load, 764 only hit the batch's call_out(0) nesting limit, 116 do not load.

Most of it is the es1_win pass of the same day (its section has the reasons): 641 files that carry the same code as es1_win's were taken over by token comparison (scripts/lpc_twin_port.py adopt), so the nosave/protected functions, unused locals, stray backslashes, #pragma save_binary, negative ranges and the hand fixes arrived together; tail() is a simul_efun here too. This lib had its own: std/npc.lpc declared a second mapping alias (1169 warnings), class/ref/new as names in mudlist_a, captain1 and four gnome NPCs, std/priv.lpc with two case : that lost PRIVATE, !where | !objectp(where) in both _goto commands (a bitwise | of two booleans), else ("...") in the IRC lobby, goomay/monster/army remove(), the gnome's die(), and the shell's merged buffer1 variables and renamed /buffer verb and prompts (obj/shells/shsh.lpc, see es1_win). Locals read only in an inactive branch are declared in it now (languages/normal.lpc l under MESSAGE_FILES, mailer_d own, mud under #if 0, taylers invs, cost, i, moon/user.lpc before, after under PROFILING, ftpdsupp.h file), three taxmen no longer inherit /std/coinvalue twice (their MONSTER already brings it; it defines no create(), so their ::create() is unchanged), the goomay bank names exchange::coinvalue / exchange::cointypes (the later inherit was already in effect), and sage's guild headers #undef the two macros they redefine. cmds/std/_bug.lpc and praise.lpc test the directory with file_size(x) == -2 (KB 06 §7.218). The stale CMD_EVAL_TMP_FILE.lpc of u/s/sage (kissyou;) is deleted. scripts/lpc_audit_removed_locals.py HEAD found nothing a function still reads.

Found by the live pair afterwards: d/noden/hawk/monster/adolph.lpc had #include "../hawk.h"inherit MONSTER; on one line (an include swallows the rest of its line, so the inherit was gone and ::create() had nothing to call), and d/noden/drow/mob/ghost.lpc had lost the ); after its set_long here-document and the () of my_tactic (the same file in es1_win and esI); both clone now. On this lib the bug/praise report path and tail work; goto reaches the erosmia bar and the Asterism sewer rooms that HEAD refused.

储藏室东西过多时清不掉(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):adm/daemons/network/netmail.lpc、include/net/ftpdsupp.h、include/net/oldftp/ftpdsupp.h。netmail 是网络邮件守护进程(本库缺 mailer.h 等头文件,改后仍载入不了)。改后用 lpcc 逐个载入这些对象:1 个通过。

区域物件的零碎修正(2026-10-19,KB 06 §7.267–§7.271)

逐个物件与 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)。

<daemons .h> 和 <money .h>:头文件名中间多了一个空格(2026-10-20,KB 04 §6.25)

cmds/std/_orglv.lpc 和 adm/daemons/network/services/gchannel.lpc 的 #include <daemons .h>,d/island/samurai/room/shop.lpc 的 #include <money .h>(那几行是 CRLF,别的行是 LF):Cannot #include daemons .h。include/daemons.h、include/money.h 都在,去掉空格。_orglv 和 gchannel(全球频道服务)现在加载;shop.lpc 还缺同目录的 farwind.h(本库只有 d/noden/farwind/farwind.h),那是内容问题,没有动。

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() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 3 个存档共 3 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/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 处)(共 3 个存档)。

当铺 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 8 条注册(7 个文件,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)、/d/noden/mj/emsd.lpc(do_gongxian)、/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(对象本身编译不过,登记留着,等对象能加载再处理)。

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:97)。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,不要重跑。

长度检查按字节写,旁边的提示按汉字写:3 处(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 你好吗)。本库 3 处(2 个文件):if( strlen( rn ) > 14 ) {(/adm/daemons/newuserd.lpc:135);if( strlen( ncname ) > 14 ) {(/cmds/std/_chfn.lpc:91)。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 在 can_read_chinese(all[i], 处截断。收成空循环体(广播原文已失)。证明:HEAD 失败;改后加载(--at /std/zzeval.lpc)。