Dead Souls II

✅ 可玩

dsII

更新 3e5694f 2026-10-27 源码 下载 ZIP

▶ 开始游玩 · Play Now

The real, self-identified 'Dead Souls Mud Library II,' released as public domain by Cratylus in November 2006 -- a distinct middle generation of the Dead Souls object library, between the earlier Nightmare-IV-era snapshot and the later, much larger 3.8.6 generation. A brand-new install walks the very first connecting player through a genuine first-boot admin-setup wizard (name, password, gender, display name, email, real name, and a race pick from seven options: halfling, elf, orc, half-orc, human, half-elf, or dwarf) before the mud restarts into its normal, ongoing registration flow. Four domains ship with the archive: a small default starting area, a town, an example town called Ylsrim with a pub, an armoury, a weaponsmith, a jar-and-lightning-bug minigame object, and a couple of deliberately 'broken' disease-item teaching examples, plus an LPC-University-themed campus area complete with a live greeter-bot NPC. The mud preloads a real Intermud-3 client and makes a genuine outbound connection to a live I3 nameserver a few seconds after every boot.

README

The real, self-identified "Dead Souls Mud Library II" -- public domain, released November 2006 by Cratylus. A genuinely distinct middle generation of the Dead Souls object library, sitting between the Nightmare-IV-era snapshot this collection already hosts as dsI (1996-98, 2 domains) and the later 3.8.6-era copies ds386/ deadsouls_fluffos (8 domains, much larger feature set). Confirmed distinct by a structural and byte-level diff -- see NOTES.md \S1.

Source: a ZIP archive from mudbytes.net, a now-dead site. Recovered via the Wayback Machine (<https://web.archive.org/web/20190802021627id_/http://mudbytes.net/files/download/843/>) since the original site is down. Slug dsII, number 181, port 40228.

Highlights

Registration flow

name -> "Do you really wish to be known as <Name>? (y/n)" -> password (5+ letters) -> confirm password -> gender (male/female) -> display name (blank = default) -> email (required, must look like user@host) -> real name (optional, blank OK) -> race selection (list to see options, pick <race> to choose) -> news screens (<return> to continue) -> lands in /domains/campus/room/reception (the LPC University reception area).

The very first account ever created goes through a separate, one-time admin-installation wizard instead (see above) -- the mud shuts down after that wizard completes, and every account after the restart uses the normal flow described here.

Admin account

Created through the mud's own first-boot installation wizard. One recovery needed: the shipped archive's secure/cfg/groups.cfg and secure/include/config.h already had the original author's name ("cratylus") hardcoded instead of the ADMIN/DEBUG_NAME placeholder tokens the installer's own code searches for and replaces -- a pre-existing archive bug (present on any driver, not FluffOS-specific) that would have silently left the new admin with no real group membership at all. Fixed by hand-editing both files to say fluffos instead of cratylus; see NOTES.md \S5 for the full analysis.

Verified live: who lists Arch: First Admin Fluffos, login shows general/creator/admin news sections, and update (this project's canonical ACL/compile-permission smoke test) succeeds.

Status

Boots clean, zero fatal errors. Verified live with a real driver session and a raw socket client: the first-boot admin installation wizard (including the mud's self-shutdown-after-install), a restart, admin login, a brand-new ordinary character's full registration (including race selection), and look/score/who/update/a clean quit all producing correct output for both accounts.

1401 of 1407 .lpc files pass a full lpcc_check.sh batch compile sweep (99.6%); the 6 that don't are all non-preloaded, pre-existing archive content gaps (a deliberately-removed stargate system per the archive's own release notes, a never-shipped podium object, a missing library component, a foreign-codebase remote-admin daemon, and two wizard debug scratch files) -- see NOTES.md \S7 for the full breakdown.

WASM status: playable. Shared WASM driver (sockets already guarded). Admin login (fluffos / fluffwiz123) through the news pager into Test Labs Corridor, plus score / quit, verified with scripts/wasm_client.js. Play: https://mudlibs.fluffos.info/dsII/

Local run

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

Game port: 40228. Note the Intermud-3 outbound-connection caveat above before repeated/automated boots.

NOTES · 移植与修复记录

Dead Souls II -- porting notes

Source: a 4,015,339-byte ZIP archive (ds2.zip) from mudbytes.net, a now-dead site. Recovered via the Wayback Machine (<https://web.archive.org/web/20190802021627id_/http://mudbytes.net/files/download/843/>), re-verified live (HTTP 200, application/zip, real ZIP magic bytes, size matching) at the start of this session before extracting. The zip bundles v22.2b14/ (a MudOS driver source snapshot), bin/, win32/, extra/ build/install scaffolding -- all ignored, this project uses its own driver. The real mudlib root is the zip's lib/ directory. Slug dsII, number 181, port 40228.

1. Duplicate check -- genuinely distinct, not a duplicate

This collection already has three Dead Souls entries: ds386 and deadsouls_fluffos (both 3.8.6-era, confirmed identical codebase generation to each other), and dsI (an early Nightmare-IV-era snapshot, ~1996-98, self-identifying as "Dead Souls V"). Before building out a full pipeline, checked whether this "Dead Souls II" archive is a repackaging of any of the three:

This is consistent with dsII being a genuinely distinct, real middle-generation release of the Dead Souls codebase -- historically, the actual "II" release that came between the Nightmare-IV-derived early snapshot (dsI, retroactively so named by this collection) and the later, much larger 3.8.6 generation. Proceeded with the full pipeline. duplicate_of is null in meta.json.

2. Conversion

English-language archive (public domain, from mudbytes.net) -- scripts/convert_lib.sh against raw/dsII/lib confirmed this: 1924/1937 files already valid UTF-8, only 2 lossy conversions, 11 binary files skipped. 531 .c files renamed to .lpc, 317 literal .c" references fixed, 28 local angle-bracket #includes converted to quotes, 1081 files touched by the static->nosave sweep.

2.1 Two source files were genuinely Latin-1, not GB18030

secure/sefun/translate.lpc and secure/sefun/ascii.lpc both lost real content under the default GB18030 decode (bytes dropped as invalid). Inspecting the raw bytes showed why:

Re-decoded both files individually with iconv -f ISO-8859-1 -t UTF-8 (verified line counts match the raw originals exactly, and the resulting UTF-8 is valid) rather than re-running the whole-lib conversion with a different global encoding, since every other file in the archive was correctly GB18030/ASCII.

3. Compile-time driver-compat fixes

3.1 Missing get_root_uid()/get_bb_uid()/creator_file() master applies

Same PACKAGE_UIDS requirement as dsI/foundation2/merentha/ lpuni -- this driver build calls exit(-1) at boot without these. Added to secure/daemon/master.lpc, byte-identical implementation to dsI's fix (which itself reuses this codebase's own secure/sefun/security.lpc:file_privs() classification, with the same sscanf(str, "%s#%d", base, ext) != 2 -> base = str clone-suffix guard needed to avoid passing an unmatched-%s's int-0 default into file_privs()'s string indexing).

3.2 ref is a reserved keyword on this driver

Same bug class as dsI/foundation2/nightmare3. Here it hit both secure/daemon/master.h's load_access() prototype *and* the real implementation's parameter name in master.lpc itself (dsI's copy only had the header-side collision; this codebase's master.lpc uses mapping ref directly in the function body too, including ref[fl] = ... inside). Renamed to refmap in both files.

3.3 Old-MudOS TYPE array NAME declaration dialect (432 sites)

Same bug class as dsI (370+ sites there). Wrote a small Python tokenizer-based fixer (handles the same shapes dsI's session documented: single names, = default values with balanced-paren tracking, multi-name comma lists correctly distinguished from function parameter lists by peeking whether the token after a comma is itself a type keyword, (TYPE array) cast forms, class TYPENAME array NAME, and a bare array used alone as a type). Caught and fixed one real bug in the first draft of the fixer itself before trusting it on the whole tree: the multi-name-list parser advanced past trailing whitespace even when a name wasn't followed by a comma (i.e. when the declaration ended in something other than ;), which silently concatenated the declared name with the next token when there was no punctuation between them -- e.g. foreach(string array list in ua) became foreach(string *listin ua) (in swallowed into the identifier). Fixed by only consuming trailing whitespace when a comma is actually consumed; re-ran clean afterward and spot-checked every foreach(string *list in ...) site by hand.

432 sites across 130 files via the main sweep, plus 1 more found only via lpcc --batch actually loading secure/sefun/dump_socket_status.lpc (foreach (array item in socket_status()) -- the exact recurring "dump_socket_status-in-simul_efun" pattern flagged in dsI's own WASM notes as common across sibling libs; fixed to foreach (mixed *item in socket_status())), 1 more in lib/std/bane.lpc's int SetBane(array arr) parameter (fixed to mixed *arr), and 2 more in secure/cmds/admins/admintool.lpc (array *classes; / array *races; -- a MudOS-dialect variant this codebase happened to write with the pointer star already present; fixed to mixed *), plus 3 more found only in secure/lib/connect.real once it became live (see \S4 below) -- 439 total.

3.4 .c-to-.lpc rename side effect: fixed-width filename-slice arithmetic

Same bug class as dsI/discworld/foundation2/skylib/lpuni. Checked every slice-comparison-against-a-literal-suffix pattern in the tree:

All fixed to the 4-character-aware form ([0..<5] / [<4..]). Verified live: update <path> (both with and without an explicit .lpc suffix) correctly reports Ok/File not found.

3.5 secure/cmds/creators/update.lpc: stray leading space (same bug as dsI)

cmd()'s argument-reconstruction loop (default: args += " " + foo; for every surviving non-flag token) always left a stray leading space, which made the downstream explode(args, " ") produce a spurious leading empty-string entry -- update <path> with an explicit filename argument would have produced no output at all. Fixed with args = trim(args) after the loop, byte-identical fix to dsI's.

3.6 OLD_ED: driver build vs. archive's new-style ed API

Same gap as dsI/foundation2/finalrealms: this driver build defines OLD_ED, but lib/editor.lpc and secure/cmds/creators/lsed.lpc were written against the newer ed_start()/ed_cmd()/query_ed_mode() API. Fixed identically to dsI:

3.7 Missing #include <virtual.h> in lib/virtual/virt_sky.lpc

inherit LIB_VIRT_LAND; with LIB_VIRT_LAND genuinely defined (in secure/include/virtual.h) but never #included by this one file -- the other 3 files in the tree that reference LIB_VIRT_LAND/ LIB_VIRT_MAP all correctly #include <virtual.h>. This is a one-off omission, not a systemic lib.h gap like dsI's LIB_UNDEAD/ LIB_GERM (every other LIB_* macro this codebase references -- checked exhaustively against every header in the tree -- is defined somewhere and reachable). Fixed by adding the missing #include.

3.8 #define __DIR__ "..." collides with this driver's native builtin

4 files (domains/town/virtual/{forest,server}.lpc, domains/Ylsrim/virtual/{desert,server}.lpc) manually #define __DIR__ to a hardcoded string matching their own directory -- a common old-MudOS idiom predating driver-native __DIR__ support. This driver's compiler now predefines __DIR__ natively (directory of the current file, with trailing slash -- confirmed by reading lexer_rules_pp.cc: exactly the same value these files were hardcoding), and rejects any attempt to #define it ("Illegal to redefine a predefined value."). Removed the four redundant #defines; the native builtin produces an identical value.

3.9 Two broken #includes (unrelated one-off path bugs)

3.10 SetLong/SetExternalDesc/SetInternalDesc declared too narrowly (severe -- 11+18 sites)

lib/events/look.lpc's GetExternalDesc() and lib/events/look_in.lpc's GetInternalDesc() both already support function-valued and array-valued "long description" callbacks (see their functionp()/arrayp() branches -- day/night-variant and dynamic-description objects are a real, widely-used feature of this codebase: 17 real objects across obj/ and domains/* call SetLong((: SomeFunction :))). But the *setter* side -- SetLong(string str), SetExternalDesc(string desc), SetInternalDesc(string str), plus forward declarations in lib/include/room.h and lib/include/interactive.h -- were all declared string-only. This driver's stricter argument type-checking rejected every one of those 17 real call sites (18 total compile failures counting one indirect path), the single most numerous compile-failure category after \S3.11 below. Widened all of them to mixed/mixed * to match what the getter side already supported -- not a new feature, just correcting a too-narrow declared type that happened to never matter on a looser-checking driver.

3.11 lib/comp/seal.lpc's eventOpen() called eventPick() with the wrong argument (severe -- ~195 files, the single highest-impact fix in this port)

eventPick() is declared varargs mixed eventPick(object who, string id, object tool) (lib/events/lock.lpc) -- the 2nd positional argument is always the lock id (a string), with tool 3rd and optional; every other real caller in the codebase (e.g. verbs/items/pick.lpc) follows this correctly. But lib/comp/seal.lpc's eventOpen(object who, object tool) called eventPick(who, tool) -- passing tool (an object) positionally as id (a string), a genuine original-archive bug. This driver's argument type-checking caught it as "Bad type for argument 2 of eventPick ( string vs object )" -- and since lib/comp/seal.lpc (the LIB_CLOSE+LIB_LOCK "seal" composite) is inherited transitively by lib/std/storage.lpc and therefore by essentially every closeable, lockable container/door/chest in the mudlib, this single bug broke 195 of the 232 total compile failures found in the first full sweep. Fixed by passing an explicit unset id (0) and moving tool to its real 3rd position: eventPick(who, 0, tool).

4. secure/lib/connect.real: the same by-hand treatment dsI needed

Same situation as dsI's connect.real/foundation2's login.real: secure/lib/connect.real is the mudlib's real, ongoing login/registration object -- secure/lib/connect.lpc (the one-time first-boot admin-installer) cp()'s the just-consumed installer into connect.first and swaps connect.real into connect.lpc's place once the first account finishes registering. Because it's shipped without a .c extension, the mechanical conversion sweep skips it entirely. Needed, by hand:

No slice-arithmetic or literal .c" issues in this file. Verified live end-to-end (see \S6).

5. secure/cfg/groups.cfg / secure/include/config.h: broken placeholder scrubbing (pre-existing archive bug, present on any driver)

secure/lib/connect.real's InputEmail() (the installer's final step) tries to promote the newly-created admin into the SECURE/ ASSIST/ELDER/TEST/INTERMUD groups and the debug-name config by doing a literal text substitution: replace_string(read_file(CFG_GROUPS), "ADMIN", Name) and replace_string(read_file(".../config.h"), "DEBUG_NAME", Name). But the archive as shipped by mudbytes.net already has the *original author's own name* hardcoded in both places (secure/cfg/groups.cfg: (SECURE) cratylus etc.; secure/include/config.h: #define DEBUGGER "cratylus") rather than the ADMIN/DEBUG_NAME placeholder tokens the installer code is actually searching for -- so both replace_string() calls are silent no-ops on this exact shipped copy, on any driver. Confirmed live: after running the installer as fluffos, the admin's save file correctly landed under secure/save/creators/f/fluffos.o and ADMIN_EMAIL (a *separate*, working mechanism via admintool.lpc's eventChangeEmail()) correctly updated -- but groups.cfg and DEBUGGER still said cratylus, meaning the new admin would have had no real group membership at all despite going through the installer successfully.

Fixed by hand-editing both files to replace cratylus with fluffos, completing what the installer's own code already intended to do. Verified live afterward: who shows Arch: First Admin Fluffos, and update (both with no argument and none needed for a re-test) reports Ok.

6. Live verification

Booted clean (Accepting telnet connections on 0.0.0.0:40228.), with a confirmed real outbound Intermud-3 connection (ss -tnp showed an ESTABLISHED connection to 149.152.218.102:23 a few seconds after boot -- do not sweep this lib into high-frequency automated re-boot loops, same caveat as dsI/imud/foundation2).

Tested with a raw Python socket client, both required flows:

1. First-boot admin installation wizard: fluffos / fluffwiz123 / male / default display name / blank real name / [email protected] -- completed successfully, the mud self-shut-down as designed (connect.real's own shutdown() + destruct(master()) -- this codebase's installer, unlike dsI's, requires a full driver restart before the new admin can actually log in and play). Restarted the driver, logged in as fluffos: arrived in /realms/fluffos/workroom with wizard robe+hat, all three news tiers (general/creator/admin) paged correctly, who showed Arch: First Admin Fluffos, update (current-room form) reported Ok, look/score/quit all produced correct output. 2. Ordinary new-player registration (a second character, Qintwo): full flow -- name, confirm, password, confirm password, gender, display name (default), email, real name (blank), list (see races: halfling/elf/orc/half-orc/human/half-elf/dwarf), pick human -- landed in LPC University Reception (the campus domain's starting area), with a live NPC greeting ("Jennybot ... I'm Jenny, the LPC University automated guide bot"). look/score/quit all produced correct output (score: "You are Qintwo the unaccomplished (neutral). You are a level 1 Human Explorer...").

1401 of 1407 .lpc files pass a full lpcc_check.sh batch compile sweep (99.6%); the 6 that don't are all non-preloaded, pre-existing content gaps (see \S7).

7. Known pre-existing gaps (not fixed -- content/archive design, not driver-compat)

8. Skipped/no-fix bug-class checks that turned up nothing

9. WASM pass

Measured 2026-09-03 against the shared ~/src/fluffos/build-wasm. Sockets were already guarded (this archive's earlier dump_socket_status / foreach (mixed *item in socket_status()) fix). No further mudlib-side compile fix was needed. Verified with scripts/wasm_client.js: fluffos / fluffwiz123 through the news pager into Test Labs Corridor, score showed "First Admin Fluffos", clean quit. I3 still tries an outbound connect at boot — do not loop-reboot. Shop/combat/death were not exercised this pass.

10. Sibling sweep of the dsIII §7.121 currency-float bug — confirmed present, fixed

AGENTS.md §7.121 documents a currency/economy bug found in dsIII (the Dead Souls 3.x lineage's shared secure/sefun/economy.lpc): query_base_rate(), query_player_money(), query_base_value(), and query_value() do real floating-point exchange-rate math internally but are declared to return int with no to_int() on the actual return, silently corrupting player currency into a float on every buy/sell/exchange (a declared int return type is compile-time only on this driver and never coerces a runtime float). This lib's own, independently-evolved secure/sefun/economy.lpc has the identical logic shape (same 4 functions, same missing to_int()s), plus the same two sibling misses dsIII found: lib/teller.lpc's eventExchange() (i = val / currency_rate(str2);, with an earlier correctly-to_int()-wrapped line even commented out right above it) and lib/props/value.lpc's SetBaseCost() (Cost = i * rate;).

Fixed by wrapping all four secure/sefun/economy.lpc returns in to_int() (matching the convention already used by the file's own currency_mass()/currency_value()), plus the two call sites, exactly mirroring dsIII's fix. Verified live: registered a fresh test character (Qintestdsb), navigated from the campus starting area (LPC University Reception -> corridor -> corridor3 -> foyer -> University Square -> north path x2 -> South/main Saquivor Road) to the First Village Bank, opened an account with Zoe and did ask zoe to exchange 20 silver for gold — printed a clean 2 gold with no decimal, and the raw save file after save showed Currency (["silver":59,"gold":2,]) — both integers, no float. log/debug.log stayed clean through the transaction.

11. Deep functional test (round two, per AGENTS.md §10.7)

One continuous session, ~/src/fluffos/build-debug/src/driver config.fluffos from libs/dsII/, a raw Python socket client (two concurrent connections: a fresh non-admin character for the main playthrough, plus the seeded admin for the safe-sparring/death test — see §10.7's own hy2000 precedent for why an admin-account death sequence can be a false negative). Admin credential note: the project-standard fluffos/Mud@2026 login does NOT work on this lib's seeded account — the working password is the one actually used at onboarding (§6 above), fluffwiz123. Confirmed the account's own password policy (secure/lib/connect.real's CreatePassword(), strlen(pass) < 5) would have accepted Mud@2026 fine; this is ordinary credential drift (same class as xkm in AGENTS.md's §7.7x credential-drift sweep), not a bug — left the account as originally seeded rather than resetting it.

Registered a fresh English-named character (Ericlang, human, male) through the full flow: name → confirm → password → confirm → gender → display name (default) → email → real name (blank) → list → pick human. Landed at LPC University Reception (campus domain) with a starting t-shirt, jeans, and a Player's Handbook item. look/score/i all correct after registration, after the first north move (through a closed door — open door then north worked correctly), and after activate bot/next tip (Jennybot's newbie orientation script, plays cleanly).

help newbie is broken (pre-existing archive content gap, not a porting bug — left unfixed): cmds/players/newbie.lpc's eventNoob() does this_player()->eventPage("/doc/help/players/handbook"), but doc/help/players/ is a genuinely empty directory in the raw archive itself (raw/dsII/lib/doc/help/players/, dated Nov 2006, confirmed via ls — not a conversion-sweep casualty). eventPage() (lib/pager.lpc) correctly returns the string "File not found." on a missing path, but the call site discards the return value (as does nearly every other direct-path eventPage() caller across this codebase, e.g. realms/*/workroom.lpc's /news/hints.txt calls) — consistent, codebase-wide convention, not a one-off typo. Net visible effect: typing help newbie prints only the red "WHAT A NEW PLAYER SHOULD KNOW" header and then silently does nothing. The physical "Player's Handbook" item every new character carries (domains/default/obj/handbook.lpc) is unaffected and works correctly (read index in handbook, read chapter N in handbook). Since fixing this would mean either writing the missing handbook content (a content/design call, out of scope) or guessing at a different intended source file, documented here rather than touched.

Safe-sparring mechanism: domains/default/npc/dummy.lpc / domains/campus/npc/dummy.lpc — a SetPacifist(1), 9000-HP "training dummy" whose eventReceiveDamage() narrates the exact damage event (agent/type/raw damage/limb) then immediately heals back the damage just taken (AddHealthPoints(damdiff+1)), so it never dies and never fights back. Lives in /domains/default/room/arena off the Creators'-Hall-only wiz_corr1 test-labs corridor — not reachable by an ordinary player's normal overworld navigation (this is the project's own admin/creator combat-testing fixture, same idea as bxsj's training dummy but gated behind wizard-only rooms here). Tested live as admin fluffos (goto /domains/default/room/arena, kill dummy): several rounds of real melee (GetHealthClass proc-text varied naturally — "totally miss", "completely miss", "swing... connect with thin air", one real hit landing "raw damage is 2... Actual damage done: 2"), the dummy's damage-report/self-heal logic fired every single hit with zero errors in log/errors/* or log/runtime. Confirmed clean.

Class/skill acquisition: only one class-join path exists in this generation (no admin shortcut command found in secure/cmds/admins/admintool.lpc or elsewhere) — the organic lib/leader.lpc "ask LEADER to join" mechanism. Walked Ericlang from campus reception to the town Mages' Guild (corridor → west → north → north → north → north → west → south) and did ask herkimer to join: correctly quest-gated ("Help my brother Leo find the Orcslayer..." — internally consistent content design, not a bug, left alone). Spell purchase path (lib/teacher.lpc's commercial teaching, via ask herkimer to teach buffer) correctly reported insufficient silver ("Buffer costs 200 silver and you only have 119") and correctly pointed at the bank for currency exchange — this is the same teach/training-point/commercial-fee logic already §7.121-fixed at onboarding; re-confirmed no float leakage anywhere in this path either.

Economy / currency-fix re-verification (different test character than the original §10 fix, confirms the fix generalizes): opened a First Village Bank account (request account from zoe, auto-deposited 5 silver), then ask zoe to exchange 20 silver for gold → "You exchange 20 silver for 2 gold" (clean integer). Raw save afterward: Currency (["silver":94,"gold":2,]) — both plain integers, no float, matching the §10 fix.

Combat + death/respawn (the subsystem sibling Dead-Souls-lineage libs have broken before) — confirmed CLEAN. Used eval on the admin connection (object p = find_player("ericlang"); p->eventDie(this_player());) to force a real death with a genuine living agent (avoiding a test-artifact false read from an agent-less kill): "SYSTEM Fluffos has slain Ericlang", clean NewBody()/corpse-drop/ROOM_DEATH move sequence, landed in the single shared /domains/default/room/death room ("Off the mortal coil... YOU ARE DEAD!"), inventory and carried currency correctly transferred out (death drops your stuff — internally consistent design, not a bug). score showed the expected "the ghost", "level 1 undead". Typing regenerate in the death room correctly called eventRevive() + eventMoveLiving(ROOM_START): rematerialized at half max HP/MP/SP at LPC University Reception, score back to a normal (non-ghost) state. Zero errors in any log/errors/* file or log/runtime through the whole sequence — this generation's simple "die → death room → type regenerate → revive at start" flow (no NPC-driven death_stage() chain at all) has no reentrancy surface for the §7.112-class bug to live in.

quit → debug.log grep → reconnect after a real ~100s wall-clock gap — confirmed clean. quit produced the normal "Please come back another time!" and a real connection close (not a silent/net-dead retention skip — secure/lib/connect.real's reconnect logic only special-cases an actual net-dead find_player() hit, which a real quit clears). Reconnecting after the gap required the full name+password prompt sequence again (not a resumed session), and landed back in the same room with the same (post-death, pre-quit) character state: level 1, human, broke, at LPC University Reception. No new entries in any per-directory log/errors/* file or log/runtime from either the quit or the reconnect.

Cross-cutting bug-pattern checklist (§7.121/§8.3a/§7.112/§7.122- §7.140), checked explicitly, all confirmed clean or not applicable:

Overall: clean pass. No new programming bugs found beyond the one pre-existing content gap documented above (help newbie, left unfixed per the content/programming scope boundary). The §7.121 currency fix re-verified live with a second, independent character. Test characters Ericlang/Testcarlin (an aborted first registration attempt merged into a real account mid-session — see the messy gender-prompt retry in this session's own history) and three older leftover test saves from the original §10 currency-fix session (Qintwo/Qintestdsb/Qintest) were all deleted before commit, leaving only the seeded fluffos admin account.

Sibling-sweep check for ds386's round-two eventRevive() float bug (2026-08-27)

ds386/NOTES.md's round-two pass found a NEW AGENTS.md §7.121-class bug not covered by this lib's own earlier currency-float fix (§10 above): eventRevive() feeding float arithmetic (`GetMaxHealthPoints()

AddMagicPoints()'s int parameters. Checked this lib's own lib/player.lpc:eventRevive() specifically for that shape: not present. All three heal-back calls here (AddMagicPoints, AddStaminaPoints, AddHealthPoints) use plain integer division (-(GetMaxMagicPoints()/2) etc.) — no PERCENT_MP/PERCENT_HP float #defines exist anywhere in this file. No fix needed; confirmed clean by reading the code.

Shop (2026-09-04 librarian slice)

Otik's general store (/domains/town/room/shop) is live. Seeded admin fluffos / fluffwiz123 (76 silver already on the creator save) goto'd the store; list is a creator command here (prints the "help creator commands" hint), not the later-DS shop-stock verb — the room's own help still documents ask otik to browse. buy wooden torch from otik completed a paid sale: Otik said "Here is an old wooden torch for 60 silver!", inventory showed the torch, money 76→16 silver. Purse math is integer. Do not use bare buy torch — /domains/town/obj/rayovac.lpc still carries "torch" in its id list (10 silver flashlight), same trap as deadsouls_fluffos.

Bug found and fixed (heartbeat hobbled(this_player())): same shape as deadsouls_fluffos/brassring. lib/body.lpc heart_beat called hobbled(this_player()); during heartbeat this_player() is 0, so disable.lpc did 0->GetMissingLimbs() every 2s and !stringp(0) fired eventFall() on every idle beat. Changed to hobbled(this_object()) and added if(!objectp(ob)) return "No missing limbs."; after the decls in secure/sefun/disable.lpc. After this boot, look/goto/buy worked (no mid-news What? collapse). 拜师 analogue is ask herkimer to join (quest-gated), already covered in §11 (2026-08-27); not re-run. Same unfixed this_player() line still on dsIII/dshakkard/riftsds (dsI is an older generation and has no hobbled() at all).

深度功能测试(§10.7,2026-10-03)— compile warnings

Scope and method as in dsI/NOTES.md (same date): scripts/lpc_warnings.py dsII --fix over 1420 .lpc files, hand fixes for what the tool lists, a golden-master comparison of the 163 lib/ programs against HEAD (scripts/ds_oracle.py, KB 08 §10.12), pristine-tree live boot with the admin fluffos/fluffwiz123 (login, 95-command battery, shop buy/sell, elevator button, NPC clone, container/armour round trips).

Result: 5773 distinct diagnostics → 6, all in secure/daemon/remote.lpc (see "Not done" below). The 3956 that survived the mechanical pass were one structural family, solved in a second pass (below).

Mechanical: 45 pragma lines deleted; 1450 nosave functions → protected, 206 left public (called through ->); 78 unused locals; 6 unknown escapes; 36 varargs; 6 bare return; in value-returning functions. The oracle: 212 distinct functions flipped public → protected (the static semantics the archive had), nothing else changed in the 163 programs; none of the flipped names has a cross-object caller.

Real bugs found while doing it (all fixed):

Not done, deliberately: secure/daemon/remote.lpc is the Foundation II Remote Creator Protocol daemon (single object handling raw fds); Dead Souls' LIB_SERVER hands each connection to a per-connection socket object, so a port is a redesign of a feature that secure/cfg/preload.cfg already has commented out. It stays documented, not loaded.

help was dead in a fresh clone and on the site (secure/cmds/common, verbs/spells, verbs/undead ship empty in the archive): KB 06 §7.212; 24 directories are now in scripts/wasm_keep_dirs.txt. Verified on a pristine tree: help, help look print.

Structural pass (same day): one file reached through two inherit paths (KB 06 §7.213 has the mechanism). 3256 Redeclaration of global variable + 675 inherited from both X and X warnings came from 66 source files; their descendants (485 files) only repeated them. Fixes, by kind: container.lpc no longer inherits LIB_RADIANCE (the classes that are both object and container add object::GetRadiantLight(); the old own-radiance branch divided by zero at ambient 0), living.lpc lost LIB_SMELL and its pass-through, corpse.lpc inherits LIB_SURFACE only (LIB_STORAGE/LIB_SMELL were already in it); npc.lpc lost the second LIB_CRAWL and renames its private Level to NpcLevel; the parser applies that exist on both sides are spelled out in npc.lpc (object side) and player.lpc (living side); CanSell existed twice with the same body (value.lpc and events/sell.lpc), the value.lpc copy is gone (secure/obj/staff.lpc's override takes the same argument now); GetSave() of lock/close/bait is resolved in seal.lpc (both lists), storage.lpc and the two pole leaves (item::GetSave()); exits.lpc renames its nosave Obvious (room.lpc has a saved int of the same name), nmsh.lpc lost an unused second CommandFail, fish.lpc its Mass (FishMass), bot.lpc two unused copies of body's Dying/LastHeal, pwatch.lpc, drone.lpc and the door module a variable declared twice in the same file; the nine creator modules under secure/modules/ each declared their own globalstr/globaltmp/ func/target/... scratch variables, which glasses.lpc and staff.lpc (25-35 warnings each time a creator loaded one) inherit together, now prefixed per module, and GetValue() of the mapping module (it replaced the item's value getter) is GetMappingValue(). Thirty-three leaf files inherited something their other inherit already contained (LIB_READ after LIB_ITEM, "/lib/props/ambiance" after LIB_ROOM, LIB_STORAGE/ LIB_SURFACE/LIB_SMELL after LIB_BED, ...): scripts/lpc_diamonds.py --fix-redundant removed the line, and bed.lpc, chair.lpc, table.lpc, clay.lpc, pistol.lpc, dining_table.lpc, pool_table.lpc were reduced by hand the way dsIII's are (no LIB_ITEM next to LIB_SURFACE, surface::create() instead of ::create(), surface::CanGet() for item::CanGet()). The seven backpack leaves (LIB_STORAGE + LIB_ARMOR, 96 duplicate variables each) now inherit LIB_WORN_STORAGE, the class the lib itself uses for wearable containers (combat_pack, robe) and dsIII uses for these files; the 35 functions they lose (throw, bless, curse, judge, compare, balance, sacrifice, use, wield family) are item and weapon verbs that no other armor here has either.

What the golden-master oracle (213 programs, KB 08 §10.12) showed beyond "warnings gone": real behaviour changes, all in the direction the code was written for. The 20 campus/town rooms that inherited "/lib/props/ambiance" after LIB_ROOM ignored lit lamps (the plain ambiance getter replaced room.lpc's GetAmbientLight(); a lamp of strength 5 now adds 5). Furniture and packs saved the Mass, Cost and DamagePoints of the dead first copy (bed saved Mass 0 while it weighs 1700; the live copy was forced nosave), now the live values. Chairs never ran storage::create() (::create() took LIB_ITEM, the first inherit). worn_storage with its own radiance divided by zero at ambient 0. The pistol uses item's eventDeteriorate() (which halves the value and marks it destroy-on-sell, as for every other weapon) instead of the bare weapon one. GetSave() of a chest lists the persist variables instead of the lock's three; storage now saves Closed (seal's list was missing it).

Live (pristine tree): clean boot (no warning on the console), help works, 100-command admin battery without a runtime error, fresh mortal registered, packs and the rucksack worn/opened/filled/emptied/dropped/thrown, a fight in the arena (damage both ways).

Not done, deliberately: the six remote.lpc errors above; domains/Ylsrim/room/tower, campus/room/conf2 and Ylsrim/broken/jar still fail to load for the missing content already described in §7 (their SetInventory() targets or LIB_CAPTURE are not in the archive).

深度功能测试(§10.7,2026-10-05)— 主控 author_file()/domain_file()

驱动的 mudlib_stats 包在主控载入时各调用一次 author_file(<主控文件>) 和 domain_file("/"),要求返回字符串。本库这两个 apply 只为 domains / realms 目录下的文件返回名字,其余返回 0,所以每次启动都打印 author_file() in the master file does not work, using root_uid as fallback 和 domain_file() in the master file does not work, using bb_ui as fallback 两行,网页版每位访客的终端里都看得到。secure/daemon/master.lpc 改成与 fluffos 组织维护的 Dead Souls 上游(deadsouls_fluffos)相同的写法:其余文件的域返回 "BACKBONE",作者返回 "NONAME"。域等于 backbone 域时驱动让对象沿用创建者的域,与返回 0 时相同;作者统计(author_stats())多出一行 NONAME,汇总 realms 目录以外的对象(KB 06 §7.222)。用 lpcc config.fluffos /nonexistent.lpc 前后对比,主控载入段只少了这两行。

2026-10-05 — 新玩家首次存档失败(KB 06 §7.227)

secure/daemon/master.lpc 的 compile_object() 用 else if 先建 players 目录、再建字母目录:父目录不存在时只建父目录,这位玩家第一次存档(设密码时)打开 players/<字母>/<名字>.o.tmp 失败。git 不跟踪这个目录下的文件,全新检出与网页版每位访客都会遇到。第二个 else if 改为 if(与 dsI 相同)。nightmare4 上用只含被跟踪文件的树实测注册→重登通过;本库主控编译通过。

x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)

if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。改动:/lib/interface.lpc(1 处)。

if (x = 常量):条件里写成赋值的比较(2026-10-20,KB 06 §7.290)

if (arg = "button" || arg = "按钮")、if (!arg || arg = "") return 0;、else if (check = 0 && tempB != ""):把 == 写成了 =,条件的值就是那个常量,分支对所有非零数和任何字符串(连 "" 都算)永远成立、对 0 永远不成立,变量还被覆盖;驱动对此没有任何警告。每一处 = 改成 ==(scripts/lpc_fix_cond_assign.py,共 4 处)。改动:/domains/campus/npc/jennybot.lpc(1 处)、/domains/campus/weap/grenade.lpc(1 处)、/lib/lead.lpc(1 处)、/verbs/creators/reload.lpc(1 处)。

登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)

驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/secure/sefun/sefun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。

help <话题> 在直接 clone 的树上、help 菜单的列宽(2026-10-21,KB 06 §7.212、§7.337)

help <话题> 会加载 /daemon/help,它的 LoadIndices() 把一串 get_dir() 的结果用 + 相加:secure/cmds/common、verbs/spells、verbs/undead 是存档里自带的空目录,git 不保存空目录,直接 git clone 出来的树里没有(站点靠 scripts/wasm_keep_dirs.txt 重建)。没有这些目录时第一次 help <话题> 就是 A runtime error occurred(Bad type argument to +. Had array and int),之后每个话题都"找不到"。现在每个目录都经 DirList() 读取(get_dir(pattern) || ({})),去掉这四个目录后 help look 正常。help 的编号菜单也被截坏(help races 每格只剩 [1RESET%):/daemon/terminal 的 no_colours() 把 RESET 映射为空串,FluffOS 的 terminal_colour() 会把空的 RESET 换成 ANSI 复位序列(15 字节)并写回映射,strip_colours() 因此带着转义序列,arrange_string() 把每格截短。no_colours() 现在用退格符代替空 RESET 再删掉(与 Dead Souls 3.x 的 strip_colours() 相同做法),help races 与 skills 的列都对齐了。修改:/daemon/help.lpc、/daemon/terminal.lpc。

注册到不存在函数的 add_action,第二轮:宏路径父类和静态解析不了的对象(2026-10-21,KB 06 §7.349)

第一轮的静态解析读不了经宏路径继承父类的对象(#define ROOM STD_DIR "room/room" 这类定义的正文以另一个宏开头)和它解析不了的继承,这些对象当作 UNSURE 没有扫;第二轮修了解析器,重扫并对这些对象在运行时问 function_exists()。add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 5 条注册(3 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/domains/campus/npc/jennybot.lpc(get_p)、/secure/obj/machine.lpc(check_it)、/secure/obj/memo.lpc(clean_memo、erase_memo、help_memo)。最多的函数名:get_p 1 处、check_it 1 处、clean_memo 1 处、erase_memo 1 处、help_memo 1 处。

战斗探针(管理员克隆 NPC,2026-10-22):干净

scripts/play_combat_probe.py dsII --account ... --clone ...:用库里预置的管理员账号把 /domains/default/npc/quarantine/edmund, /domains/Ylsrim/npc/balrog, /domains/default/npc/fighter, /domains/town/npc/orc, /domains/campus/npc/charly, /shadows/drone 逐个克隆到自己所在的房间并各攻击 20 秒(读了 6 场战斗):驱动没有输出,战斗画面里没有错误文字。(KB 08 §10.16)

缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

new(路径)->move(...); / carry_object(宏("x"))->wear(); 这类语句,路径指向的文件在这个压缩包里没有:new() 返回 0,->move() 在 0 上报 Bad argument 1 to EFUN call_other()。在 create() 里这个对象永远加载不了,在 reset() 里第一个缺失物品之后的刷新每次 reset 都被跳过。1 个文件里 1 处(new)改成 { object new_ob = new(路径); if (new_ob) new_ob->move(...); }(scripts/lpc_guard_carry_object.py --new:路径按这个文件自己看到的宏解析,宏名冲突、虚拟对象路径一律跳过)。 对这 1 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 1 个,现在 1 个(新增可加载 0 个,回归 0 个)。

(: 函数 :) 指针指向对象自己没有定义的函数(2026-10-22,KB 06 §7.355)

item_desc / inquiry / chat_msg_combat 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:编译不报错,用到时驱动报 *Undefined lfun pointer called: 函数名(look 某物、ask NPC about 话题 直接出错,战斗 / 闲聊里的指针在 NPC 的 chat() 里出错,这一拍心跳的后半段被跳过)。scripts/lpc_scan_callout_targets.py --kinds=lfun。 还剩 2 个函数没有补(other 2):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/secure/cmds/admins/admintool.lpc:eventBanishUser、/secure/lib/net/ftp.lpc:eventCmdPass。

log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)

这个库的代码往 log/ 的 1 个子目录写日志:log/harass(lib/interface.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 的实测表。

Bare wizard command with a 0 argument (2026-10-27, KB 06 §7.336 Also)

After the permission gate, the command still handed 0 to sscanf / read_file / find_living. lpc_fix_bare_arg.py adds if (!arg) (or help(me) / GetHelp()) before the first use. See this commit for the files touched in this lib.