Dead Souls I

✅ 可玩

dsI

更新 1309d15 2026-10-22 源码 下载 ZIP

▶ 开始游玩 · Play Now

An early, distinct generation of the Dead Souls object library -- several in-file headers self-identify it as 'Dead Souls V'/'the Dead Souls r1/r2 Object Library' (1996-98 vintage), structurally closer to Nightmare IV than to the later 3.8.6-era Dead Souls this collection already hosts as ds386/deadsouls_fluffos (only 2 domains versus the other two copies' 8, no estates/powers/std/open directories, a roughly 1/3-the-size master.lpc). A genuine first-boot admin-installation wizard walks a fresh connection through name/password/gender/display-name/email/real-name, then a race-selection step offering 17 races (list/help RACE/pick RACE) before the mud's ordinary login/registration flow self-installs in its place. The built world is small but real: a starting domain plus one example town, Ylsrim, with a pub, an armoury, a weaponry, a jar-and-lightning-bug minigame object, and a couple of deliberately 'broken' disease-item teaching examples. Makes a genuine outbound Intermud-3 connection to a real nameserver a few seconds after boot -- don't sweep this lib into high-frequency automated re-boot loops.

README

An early, distinct generation of the Dead Souls object library. Several files in this archive self-identify as "Dead Souls V" or "the Dead Soulsr1/r2 Object Library" (circa 1996-98), and per lpmuds.net's own description this snapshot is closer to Nightmare IV than to the later 3.8.6-era Dead Souls already in this collection (ds386, deadsouls_fluffos). Confirmed genuinely distinct by a structural and byte-level diff -- see NOTES.md \S1.

Source: a maintained, already-FluffOS-adapted archive (dsI.zip) from lpmuds.net, a now-defunct LPMud archive site. Recovered via the Wayback Machine (<https://web.archive.org/web/20160306035507id_/http://lpmuds.net/files/deadsouls/dsI.zip>) since the original site is down. Slug dsI, number 178, port 40225.

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) -> two news screens (<return> to continue) -> lands in /domains/default/room/start.

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

Admin account

Created through the mud's own first-boot installation wizard (see NOTES.md), not a save-file hand-edit. Verified live: who lists Fluffos under "Arch:", login shows both Creator and Admin news sections, and update <path> (this project's canonical ACL/compile- permission smoke test) succeeds both with no argument (updates the current room) and with an explicit target file.

Status

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

576 of 585 .lpc files pass a full lpcc_check.sh batch compile sweep; the 9 that don't are all non-preloaded, non-boot-critical pre-existing archive gaps (2 truncated/corrupted source files, a missing library component, an abandoned feature, a missing header, a foreign-codebase API mismatch, and a wizard's leftover debug scratch file) -- see NOTES.md \S6 for the full breakdown.

WASM status: playable. Login as fluffos into /domains/default/room/start is verified under the shared WASM driver (NOTES.md §8). Play: https://mudlibs.fluffos.info/dsI/

Local run

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

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

NOTES · 移植与修复记录

Dead Souls I -- porting notes

Source: dsI.zip, a maintained, already-FluffOS-adapted archive from lpmuds.net (a now-defunct LPMud archive site). The live site is down; this exact archive was recovered from the Wayback Machine (<https://web.archive.org/web/20160306035507id_/http://lpmuds.net/files/deadsouls/dsI.zip>) and content-verified (valid zip, intact central directory, 1673 files, non-corrupted) before this session started. Slug dsI, number 178, port 40225. The zip bundles a FluffOS 2.7-ds2.018 driver source snapshot (fluffos-2.7-ds2.018/), plus bin/, win32/, extra/, v22.2b14/ build/install scaffolding -- all ignored, this project uses its own driver. The real mudlib root is the zip's lib/ directory.

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

This collection already has two Dead Souls 3.8.6-era copies, ds386 and deadsouls_fluffos. Before building out a full pipeline, checked whether dsI is a third copy of the same codebase:

All of this is consistent with dsI being a genuinely earlier, distinct generation of the Dead Souls codebase, not a re-packaging of the same one -- proceeded with the full pipeline. duplicate_of is null in meta.json.

2. Conversion

English-language archive (not GBK-encoded) -- scripts/convert_lib.sh against raw/dsI/lib confirmed this: 966/967 files already valid UTF-8, 0 lossy conversions, 1 binary file skipped. 583 .c files renamed to .lpc, 127 literal .c" references fixed, 26 local angle-bracket #includes converted to quotes, 511 files touched by the static->nosave sweep.

One file needed the same by-hand treatment as foundation2's login.real: secure/lib/connect.real is not .c/.lpc (skipped by the mechanical sweep) but is real, live LPC source -- the one-time first-boot admin-installation wizard (secure/lib/connect.lpc, \S5 below) rename()s it over connect.lpc itself once the first account finishes registering, and it becomes the mud's real ongoing login handler from then on. It needed both the static->nosave sweep (31 sites) and the TYPE array NAME->TYPE *NAME dialect fix (\S3 below, 1 site: string array races = (string array)RACES_D-> GetRaces(1);) applied by hand, since the mechanical sweeps both key off file extension. Caught live: a second test account's login crashed the connection outright with a bare "*No program in object '/secure/lib/connect'!" once the installer had already consumed connect.real and swapped it into place -- confirmed via log/catch/log/runtime, fixed, verified with a fresh driver boot and a full second registration.

3. Compile-time driver-compat fixes

3.1 ref is a reserved keyword on this driver

Same bug class as foundation2/nightmare3 (§7 catalog item). Only 1 site: secure/daemon/master.h's load_access() prototype used mapping ref as an (unnamed-in-effect) parameter type/name; renamed to refmap to match the actual implementation's parameter name in master.lpc (which was already correctly named resource, not ref -- only the header prototype had the reserved-word collision).

3.2 LIB_UNDEAD gated behind a garbled, always-false #ifdef

The single highest-impact fix in this port. secure/include/ lib.h had:

#ifdef Dead SoulsLPMud
#define LIB_UNDEAD         DIR_LIB        "/undead"
#endif

Dead SoulsLPMud is not a real, definable macro name (and Dead is never #defined anywhere in this codebase) -- the #ifdef always evaluated false, silently leaving LIB_UNDEAD undefined. Every other LIB_* macro in this header is defined unconditionally; there's no evidence this one was ever meant to be conditional. inherit LIB_UNDEAD; in lib/body.lpc then failed with a hard "syntax error, unexpected L_IDENTIFIER, expecting L_STRING or '('" (the driver left the bare, unexpanded identifier where a string was expected) -- and since lib/body.lpc is the base of this codebase's entire living-creature class hierarchy (body -> race -> living -> npc/sentient, plus combat, leader, mayor, teller, trainer, std/barkeep, std/vendor, obj/friend, and every NPC in domains/Ylsrim/), this one bug transitively broke ~25 files across the codebase. Fixed by defining LIB_UNDEAD unconditionally, matching every sibling macro's convention.

3.3 Missing LIB_* macros (3 more, same root cause as 3.2)

Three more LIB_* macros referenced by inherit/new() calls were simply never defined in secure/include/lib.h at all (their target .lpc files do exist -- this is a header-omission bug, not a missing- content one):

3.4 Old-MudOS TYPE array NAME declaration dialect (370+ sites)

FluffOS's grammar does natively support TYPE array NAME as a type-modifier synonym for TYPE *NAME (opt_atomic_type L_ARRAY in grammar.y, confirmed by reading the compiler source before assuming this was a bug) -- but not in every grammatical position. A function *return-type* declaration using it hits a hard parser error:

string array explode_list(string list) { ... }
// error: syntax error, unexpected L_IDENTIFIER, expecting L_ASSIGN or ';' or '(' or ','

Rather than characterize exactly which of the many possible contexts (top-level function return type, local-variable declaration, function parameter, multi-name declaration list, cast expression, class NAME array variant) are safe vs. broken, every instance was converted uniformly to the TYPE *NAME form via a small Python script (handles single names, names with = initializer default values including nested-paren/comma expressions like allocate(3) or ({ 0, 0 }), and multi-name comma-separated declaration lists like private string array Classes, Religion, Deities, DeityIds; -> private string *Classes, *Religion, *Deities, *DeityIds;, fanning * out to every name in the list). Also handled separately: (TYPE array) cast expressions -> (TYPE *) (12 sites, a different regex since there's no trailing identifier), class TYPENAME array NAME (2 sites, e.g. class MagicProtection array GetMagicProtection() -- the base-type detector only matched bare keywords like string/int, not class <Identifier>), and one bare array used alone with no type prefix at all (private nosave array History; in lib/history.lpc -> private nosave mixed *History;).

370 sites across 107 files via the main sweep, 12 more via the cast-form sweep, 2 more via the class TYPENAME array fix, 1 more for the bare-array case, plus 1 more found only later in secure/lib/connect.real once it became live (\S2 above) -- 386 total. Every file recompiled clean afterward; spot-checked a sample against the pre-fix/post-fix diff by hand (lib/chapel.lpc, lib/ magic.lpc, lib/race.lpc, verbs/items/wield.lpc, daemon/help.lpc) to confirm the comma-list fan-out and default-value paren-tracking worked correctly before trusting the full sweep.

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

arithmetic (16 sites, including the command-dispatch table itself)

Same bug class as discworld/foundation2/skylib/lpuni -- code written to strip/check a 2-character .c extension via file[0..<3] (drop last 2 chars) or compare file[<2..] == ".c" (last 2 chars) silently breaks once every file is 2 characters longer (.lpc). Checked every slice-comparison-against-a-literal-suffix pattern in the tree, not just the common one, and found instances in both directions (stripping *and* appending):

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 rather than silently matching nothing.

3.6 runtime_config.h: bundled copy is stale but harmless here

The bundled secure/include/runtime_config.h has drifted from this driver's actual get_config() index numbering in several slots (confirmed by diffing against ~/src/fluffos/src/include/ runtime_config.h: index 1 is __ADDR_SERVER_IP__ here vs. a retired slot on the real driver, index 9 is __MAX_LOCAL_VARIABLES__ here vs. retired, etc.) -- the skylib-class bug from the catalog. Checked and confirmed harmless for this archive specifically: the only get_config() calls anywhere in the tree (secure/lib/net/{ftp, server}.lpc, secure/lib/file.lpc) use exactly __MAX_BYTE_TRANSFER__ (index 16) and __MAX_READ_FILE_SIZE__ (index 17), and those two specific macro names/values are identical between the bundled header and the real driver's -- the drifted slots are simply never read. Not touched, to avoid an unnecessary risk of introducing a *new* mismatch.

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

This project's driver build defines OLD_ED (#define OLD_ED in src/local_options), which compiles out ed_start()/ed_cmd()/ query_ed_mode() (core.spec's #else branch) and leaves only the older single-efun ed(fname, exitfn, restricted) form -- but this archive's lib/editor.lpc and secure/cmds/creators/lsed.lpc were written against the newer API (same gap as foundation2/ finalrealms, opposite direction from what the AGENTS.md catalog item literally describes, which assumes the archive itself predates the new API -- here the archive is *newer* than this driver's build config).

4. Runtime (non-compile-time) bugs found and fixed

4.1 daemon/help.lpc's LoadIndices() crashed on every single boot

Not caught by lpcc_check.sh (the file compiles and loads fine standalone) -- only surfaced because lpcc --batch also *executes* each object's create(), and because this file is transitively reachable during a real boot (it's not itself preloaded, but gets loaded on first help use, and this project's batch check loads every file regardless). LoadIndices()'s "commands" index builder chains several get_dir(DIR_X + "/*.lpc") calls with +:

tmp = get_dir(...) + get_dir(...) + ... + get_dir(DIR_SPELL_VERBS + "/*.lpc") + ...;

verbs/spells/ and verbs/undead/ (DIR_SPELL_VERBS/ DIR_UNDEAD_VERBS) don't exist in this archive (the spell *objects* themselves live in /spells/ and do exist -- only their player-facing "cast"-style verb wrappers were never shipped) -- get_dir() on a missing directory returns -1 (an int), and array + int is a hard runtime error ("Bad type argument to +. Had array and int"), confirmed via log/runtime. Guarded both sites with pointerp(get_dir(...)) ? get_dir(...) : ({}). The identical pattern in lib/detect.lpc's "detect magic" skill (2 sites, map(get_dir(DIR_SPELL_VERBS + ...)), plus a spells[random(sizeof(spells))] that would also break on an empty result) was fixed the same way, falling back to a graceful "you do not detect any magic" message.

4.2 secure/lib/connect.lpc/connect.real: see \S2 above

The by-hand static->nosave + array-dialect fix on the file that only becomes live after the first successful admin registration.

5. Admin account seeding

Created live through the mud's own first-boot installation wizard (secure/lib/connect.lpc, before it self-replaces with connect.real -- see \S2), not a save-file hand-edit: id fluffos, password fluffwiz123, male, default display name, email [email protected], real name blank, race human.

One recovery needed: an earlier test run of the installer completed successfully once, but a subsequent connection then hit the connect.real static/array bug (\S2/\S3.4) and crashed before I could verify it -- in the process of restarting to retry, I deleted the already-correctly-created admin save file (secure/save/creators/f/fluffos.o) along with its intended test state, not realizing the installation had actually already fully succeeded. The *second* registration (after the real fix) went through this codebase's normal new-player flow instead of the one-time installer (which only ever fires for the very first account), landing the save file under secure/save/players/ rather than secure/save/ creators/. secure/cfg/groups.cfg already had fluffos correctly listed under (SECURE)/(ASSIST) from the first, successful installer run (a config-file edit, not a save file, so it survived). Manually moved the save file to secure/save/creators/f/fluffos.o (exactly matching what secure/lib/connect.lpc's own InputEmail() does: mkdir(DIR_CRES + "/" + Name[0..0]); rename(DIR_PLAYERS + ..., DIR_CRES + ...)) and recreated realms/fluffos/workroom.lpc (copied from the shipped realms/workroom.lpc template, matching the installer's cp() step) to match the state a clean single successful run would have produced. secure/daemon/master.lpc's player_object() picks the DIR_CRES path automatically based on file_size() at login time, so this is functionally identical to going through the installer -- verified live afterward (see \S6/README.md "Admin account").

Also found and fixed in the process: secure/cmds/creators/ update.lpc's argument-reconstruction loop (args += " " + foo for every surviving non-flag token) always left a stray leading space on the rebuilt args, which made the file-list explode(args, " ") downstream produce a spurious leading empty-string entry -- update <path> with an explicit filename argument produced no output at all (confirmed live, before the fix). update with no argument (update-current-environment) was unaffected, since it never re-explodes args. Fixed with args = trim(args) after the reconstruction loop. Verified live: update /cmds/players/inventory now correctly prints /cmds/players/inventory: Ok. (Separately, update /secure/cmds/creators/update -- updating the very command object currently executing the update -- still produces no visible "Ok" text, but this is the file destructing/reloading itself mid-execution losing its own continuation before the final print, a general LPC self-update gotcha rather than a bug specific to this port; every other target file works correctly.)

6. Known pre-existing gaps (not fixed -- content/archive corruption,

not driver-compat)

Per this project's standing rule against inventing missing game content/design, the following 9 files still fail lpcc_check.sh (576/585 pass) -- all non-preloaded, none block boot or the player/creator verification path:

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

8. WASM pass

Update (2026-08-26, another session): promoted wasm_status from "" to playable. Booted clean on the first try, zero fixes needed -- unlike most sibling libs from the recurring dump_socket_status-in- simul_efun pattern, this codebase apparently doesn't have that file (or never inherits it eagerly). Verified with a scripted WASM session: login (fluffos/fluffwiz123), the multi-page general/creator/admin news pager, and arrival in /domains/default/room/start with the correct room description. look/score/quit weren't recaptured distinctly in this transcript (absorbed by the news pager, same as the native testing initially hit -- see \S7) but are already verified clean under native testing above.

9. Sibling sweep of the dsIII §7.121 currency-float bug — NOT APPLICABLE, this lib doesn't have the affected functions

AGENTS.md §7.121 documents a currency/economy bug in dsIII (the Dead Souls 3.x lineage's shared secure/sefun/economy.lpc): query_base_rate()/query_player_money()/query_base_value()/ query_value() do real floating-point exchange-rate math but are declared int with no to_int() on the return, flagged for a sibling sweep across ds386/dsI/dsII/dshakkard/deadsouls_fluffos.

This lib's secure/sefun/economy.lpc is a much smaller, earlier Nightmare-IV-era file — it only defines currency_rate(), currency_inflation(), currency_mass(), currency_value(), and mud_currencies() (all already correctly to_int()-wrapped where they return int). The four suspect functions (query_base_rate(), query_player_money(), query_base_value(), query_value()) don't exist anywhere in this archive (grep -rn across the whole tree found zero matches), and lib/teller.lpc's eventExchange() computes its own exchange amount inline (val = amount / currency_rate(str1); ... i = to_int(val * currency_rate(str2));) with the to_int() already correctly present — no gap. lib/props/value.lpc has no SetBaseCost() or any currency-rate math at all. Checked, confirmed not applicable — this lib's economy code predates the buggy function shapes entirely, so no fix was needed or made.

10. Deep functional test (round two, §10.7)

Full continuous playthrough via a raw Python socket script against ~/src/fluffos/build-debug/src/driver, admin account fluffos/ fluffwiz123 (per §5 above). This lib had never had a round-two pass before this session. Two real programming bugs found and fixed live; everything else checked came back clean.

10.1 lib/std/room.lpc's single-inherit replace_program() "fold"

optimization crashes ANY speech (say/ask/tell) in a just-loaded room for its first ~5 minutes, driver-wide, on every simple room

create() ended with:

if( replaceable(this_object()) && !GetNoReplace() ) {
    string *tmp= inherit_list(this_object());
    if( sizeof(tmp) == 1 ) {
        replace_program(tmp[0]);
    }
}

— a classic MudOS-era memory optimization: a room that only does inherit LIB_ROOM; and adds nothing but its own create() gets its compiled program folded into the single shared base-class program once create() finishes, since nothing else in it is ever called again. This still works correctly on this driver in the end state, but the *timing* doesn't: replace_program()'s effect is deferred to this driver's periodic remove_destructed_objects()/replace_programs() sweep, which only runs every 5 minutes from boot (backend_register_tick_events() in ~/src/fluffos/src/backend.cc), not after every command the way an original-era MudOS did. Any object with a pending replace sits in a "cannot bind a functional to an object with a pending replace_program()" state (vm/internal/base/function.cc's make_functional_funp()) until that sweep fires — and lib/std/room.lpc's own eventHearTalk() (TALK_LOCAL case) builds a filter() closure ((: (int)$1->is_living() && $1 != $(who) :)) on the room object itself. Since /domains/default/room/start (the very first room every new player lands in) is exactly this shape (bare inherit LIB_ROOM;), every single say/ask/tell heard in that room during its first ~5 minutes after being loaded (i.e. essentially every fresh boot) hard-crashes with that error, caught only as a generic "A runtime error occurred, use 'bug -r' to report it" for the speaker. Confirmed live: a fresh ask <anyone> to <anything> in the start room within ~1 minute of boot reliably crashed (log/runtime), and the identical command 5+ minutes after boot worked cleanly. Fixed by dropping the replace_program() call from lib/std/room.lpc's create() entirely — it is purely a memory micro-optimization with no functional consequence once it lands, so removing it just means ordinary rooms keep their own (tiny, already-compiled) program instead of being folded into the shared one; SetNoReplace()/GetNoReplace() are left in place as harmless now-unused API. Verified live: say/ask immediately after a fresh boot (well inside the old 5-minute window) now work cleanly with zero log/runtime entries. This is a new bug shape — not a match for any existing AGENTS.md §7.* entry — added as a new entry (see AGENTS.md for the number) since replace_program()'s "fold a single-inherit object into its parent program" idiom is a generic MudOS/Nightmare/Dead-Souls-era pattern likely to recur in sibling libs that never got a §10.7 pass yet.

10.2 lib/combat.lpc: Wimpy auto-flee threshold initialized as a

0.0-1.0 fraction into a declared-int field — an exact instance of AGENTS.md §7.124 (previously found on nightmare4, a close sibling of this very lib's own "closer to Nightmare IV" lineage)

private int Wimpy; was initialized in create() with Wimpy = 0.20; — a fraction, not the 0-100 percentage that percent(), cmds/players/wimpy.lpc (wimpy on sets 23, caps user input at 30), and the runtime check itself (Wimpy < percent(hp, max) in eventReceiveDamage()) all consistently expect. This driver silently truncates a float assigned to a declared-int variable at the point of assignment (confirmed directly via eval int x; x = 0.20; return x; → 0), so Wimpy was actually 0 for every character from create() onward — and eventReceiveDamage()'s very first line, if( !Wimpy ) return x;, meant the entire auto-flee-at-low-health safety net was silently dead for every character, from the very first one ever created, with zero compile error and zero crash — exactly AGENTS.md §7.124's shape, byte-for-byte the same buggy literal (Wimpy = 0.20;) as the already-documented nightmare4 instance. SetWimpy(float wimpy)/float GetWimpy() carried the same secondary mismatch nightmare4 had too: declared to return float instead of int (matching the field's own real type and its only real callers), which meant a nonzero value round-tripped through a real float on return, corrupting the player-facing wimpy command's own percentage display ("Percentage: 20.000000%" instead of "20%", confirmed live pre-fix). Fix, identical in shape to nightmare4's: Wimpy = 0.20; → Wimpy = 20;, and float SetWimpy(float wimpy)/float GetWimpy() → int SetWimpy(int wimpy)/int GetWimpy(). Verified live end-to-end, not just the default value: a fresh character's bare wimpy command now reports "Percentage: 20%" (was "Percentage: 20.000000%" pre-fix, the exact nightmare4-shaped display corruption), and a direct test — spawn a cloned traveller NPC, cut the test character's HP to 58/390 (~15%, below the 20% threshold) via eval, then attack traveller — now correctly triggers the auto-flee: the character was moved out of the room entirely (fled from /domains/default/room/start to /domains/Ylsrim/room/bazaar via eventWimpy()'s "go out" → fallback-to-any-valid-exit logic) after taking one more hit, surviving at 49/390 hp instead of continuing to fight to death. Pre-fix, this exact sequence would never have fled at all (Wimpy being 0 short-circuits the check unconditionally). Extended AGENTS.md §7.124's "confirmed instances" note rather than adding a new entry, since this is the identical bug shape on a lineage sibling of the lib §7.124 was originally found on.

10.3 Full playthrough coverage (all confirmed clean except the two

bugs above)

- A second eventDie() while already undead flips the flag back to 0 (mortal) and takes the if(!GetUndead()) branch, which calls eventDestroyUndead(agent) — initially suspected as an undefined-function crash (same shape as the wxddym/shenmo addn() class of bug from this session's earlier sweeps), but on closer reading lib/player.lpc:116 DOES define it, just as a permanently empty stub (nosave void eventDestroyUndead(object agent) { }). Confirmed live: a second forced death produces no crash and no visible effect at all beyond the title reverting from "the ghost" to "the unaccomplished" — this looks like an intentional "permanent death" hook that was simply never implemented in this archive. Left as an observed content/design gap, not fixed (deciding what permanent death should actually do is exactly the kind of content/design judgment call this project's scope excludes) — documented here rather than guessed at. - The death room's alternate wander command (an admitted secondary path alongside regenerate) moves to /domains/campus/room/admissns, a room from a campus domain this archive never shipped (only default/Ylsrim exist here) — eventMoveLiving() handles the missing destination gracefully (eventMove()'s own find_object/catch(call_other(...)) fallback returns 0 cleanly), so this is a soft no-op ("You remain where you are") rather than a crash. Missing content, not a fix-worthy bug.

10.4 Test character cleanup

All throwaway test-character saves (Testwind, Wimpycheck, Wimpye2e — the last one never actually completed registration) were deleted before committing, per this project's standing convention — only the seeded fluffos admin account (secure/save/creators/f/ fluffos.o) remains under secure/save/.

10.5 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 §10.2/§7.124 Wimpy fix: eventRevive() feeding float arithmetic (GetMaxHealthPoints() * PERCENT_HP-style expressions) into AddHealthPoints()/AddMagicPoints()'s int parameters. Checked this lib's own lib/player.lpc:eventRevive() specifically for that shape: not present. This codebase's eventRevive() only calls eventCompleteHeal(GetMaxHealthPoints()) (a full heal, no arithmetic) for HP and AddMagicPoints(-(GetMaxMagicPoints()/2)) for MP — plain integer division, no PERCENT_MP/PERCENT_HP float #defines exist anywhere in this file. No fix needed; confirmed clean by reading the code (no live re-test required for a no-op finding).

Shop (2026-09-04 librarian slice)

No hobbled() in this generation (older Dead Souls V / Nightmare-IV shape; lib/body.lpc heart_beat has no limb check). Creators inherit LIB_INTERACTIVE only — AddCurrency() is on LIB_PLAYER/living, so the seeded admin cannot hold a purse (money prints "coders don't get salaries here!").

Bug found and fixed (compile_object letter-dir mkdir): secure/daemon/master.lpc created DIR_PLAYERS and DIR_PLAYERS/<letter>/ with a chained else if. The first mortal on a tree that has no players/ yet (this archive only shipped creators/) hits mkdir(DIR_PLAYERS) and then skips the letter bucket. SetPassword() saves immediately, before Setup()'s create_save(), so registration dies with Could not open /secure/save/players/s/<name>.o.tmp for a save and the connect object falls into a >/What? limbo. Later Dead Souls (deadsouls_fluffos) already uses independent ifs. Changed the letter-dir line from else if to if. After reboot, mortal shopdsj / Play2026x registered cleanly.

Paid buy: in-game day (SEASONS_D->GetTimeOfDay() = "day"), Max present in /domains/Ylsrim/room/armoury. Storage ships empty (intentional "vendors buy loot" design from §10.7). Admin cloned /domains/Ylsrim/armour/artrell_armour into armoury_storage, then find_player("shopdsj")->AddCurrency("electrum", 40000) and eventMove'd the mortal to the armoury. ask max to browse listed 001 a suit of leather armour. buy leather armour from max: "Here is a suit of leather armour for 29943 electrum!", inventory the armour, purse 40000→10057 electrum (40000−29943). Integer GetCost() (value 150 × bargaining markup × electrum rate ~68.83). No remaining programming bug on the buy path.

拜师 analogue: ask roshd to join fighters, already verified in §10.7 (2026-08-27). Not re-run. Throwaway shopdsj save removed before commit; shopdsi never got a save file (failed on the mkdir bug).

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

Scope: every compiler warning the driver prints for this lib, fixed at its source (AGENTS.md Conventions, 2026-10-03; KB 04 §6.10). Method: python3 scripts/lpc_warnings.py dsI --fix over all 585 .lpc files, a hand pass over what the tool lists, a golden-master comparison of the 130 lib/ programs against HEAD (scripts/ds_oracle.py, KB 08 §10.12), and a pristine-tree live boot (scripts/pristine_tree.sh; admin fluffos/fluffwiz123, a fresh mortal registered, look/score/inventory, a 26-command admin battery).

Result: 1387 distinct diagnostics → 28, all of them in the nine files that never compiled (§6 below). The 125 that survived the mechanical pass were one structural family, solved in a second pass (below).

Mechanical (tool): 34 #pragma no_warnings-style lines deleted; 875 nosave functions → protected and 199 left public because the name is called through -> or as a function pointer; 115 unused locals removed (8 by hand); 2 unknown escapes; 16 varargs on arg-count mismatches. The nosave function is the historical static→nosave port artifact: the driver ignores the modifier on a function, so those functions were public to every object; protected restores the original static. The oracle on the 130 lib/ programs: 769 pub→hid flips, no other change in function resolution; every flipped name was grepped for cross-object callers (the survivors are this_object()->f() or driver callbacks: add_action, input_to, filter(..., "fn", this_object())).

Real bugs found while doing it (all fixed):

The help command is broken in a fresh clone and on the site (get_dir() of secure/cmds/common, which the archive ships empty, returns 0): see KB 06 §7.212; the eight directories the code names are now in scripts/wasm_keep_dirs.txt.

Structural pass (same day): one file reached through two inherit paths. The 60 Redeclaration of global variable + 37 inherited from both X and X warnings came from five classes (KB 06 §7.213 has the mechanism): LIB_CONTAINER inherited LIB_RADIANCE and LIB_OBJECT inherits it too, so npc, interactive (hence player/creator) and storage (hence corpse, limb, trap, the chests) each carried two copies; LIB_LIVING inherited LIB_SMELL next to LIB_OBJECT's, and corpse inherited LIB_SMELL once more. Fixes: container.lpc no longer inherits LIB_RADIANCE (its function returns the contents' light; the classes that are both object and container already add object::GetRadiantLight(), so the totals do not change; no room, container or holder sets its own radiance, and the old branch for it divided by zero at ambient 0); living.lpc lost LIB_SMELL and its pass-through direct_smell_obj(); corpse.lpc lost the redundant inherit. The parser applies that exist on both sides (direct_look_obj, direct_look_at_obj, indirect_look_at_obj_word_obj, inventory_visible) are now spelled out in npc.lpc (object side wins, as the inherit order made it) and player.lpc (living wins); CanSell existed twice with the same body (value.lpc and events/sell.lpc), the copy in value.lpc is gone; GetSave() of lock/close/bait collided with item's: seal.lpc returns both lists, storage.lpc and the pole leaf return item::GetSave() (nobody calls GetSave() virtually here). Golden-master oracle against the first commit: only the intended differences (removed duplicate variables and functions, the explicit resolutions, the two GetSave() contents), GetSaveString() of every probed object unchanged. Live: pristine boot with no warnings, help works, a fresh mortal registered, walked, looked, smelled, listened and fought the Traveller (limb names and damage lines as before).

深度功能测试(§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 前后对比,主控载入段只少了这两行。

x != "a" || x != "b" and x == "a" && x == "b": comparison chains of one value that can never differ (2026-10-20, KB 06 7.282)

if (arg != "wan" || arg != "pill") return notify_fail(...) is true whatever arg is, dir == "south" && dir == "north" is never true: the guard under the test never opens, or never closes. The author meant "neither of these" (&&) or "either of these" (||). != chains joined by || became && (only where the statement under the test is a return, a refusal or a guard clause), == chains joined by && became || (scripts/lpc_fix_cmp_chain.py). What it was: prompt / editor messages were never marked MSG_NOWRAP (msg_class == "prompt" && msg_class == "editor"). Changed: /lib/interface.lpc (1).

if (x = CONST): a comparison written as an assignment (2026-10-20, KB 06 7.290)

if (arg = "button" || arg = "pill"), if (!arg || arg = "") return 0;, else if (check = 0 && tempB != ""): = was typed for ==, so the condition is the constant: the branch is always taken for a non-zero number or any string (even "") and never for 0, and the variable is overwritten; the driver does not warn. Every such = became == (scripts/lpc_fix_cond_assign.py, 1 terms). Changed: /lib/lead.lpc (1).

A login password over 255 bytes: crypt() raises (2026-10-20, KB 06 7.294)

The driver's crypt() raises (Error in crypt(), check your salt) for a key over 255 bytes, where the old driver hashed any length. A password of 256 bytes or more typed at the login prompt threw an error in the password check and left the connection at a bare > prompt, so the login never completed (the old-password test of password the same); found by the sweep shape of 300 x. /secure/sefun/sefun.lpc now defines a simulated efun crypt(key, salt) of the same name: only an over-long key is cut (to 255 characters, then character by character until sizeof(to_buffer(key)) <= 255: the limit is in bytes, a CJK character takes three) before it is handed to efun::crypt(); every ordinary password hashes as before and the saved hashes are untouched (scripts/lpc_add_sefun.py crypt).

help <topic> on a plain clone and the help menu's column widths (2026-10-21, KB 06 7.212, 7.337)

The help daemon guarded verbs/spells and verbs/undead by hand, but secure/cmds/common (an empty directory of the archive, missing in a git clone) was still added unguarded: without it the first help <topic> printed A runtime error occurred (Bad type argument to +. Had array and int) and every later topic "could not be found". Every directory is now read through DirList() (get_dir(pattern) || ({})), the hand guards folded in; help look answers with the four directories removed (PLAY_RMDIRS). The numbered menus were cut to nonsense (help commands: [1] a[2] a[3] b[4] b, the same for help races): /daemon/terminal no_colours() hands terminal_colour() a table whose RESET is empty, FluffOS gives an empty RESET the ANSI reset sequence (15 bytes, written back into the table), so strip_colours() kept escape codes and arrange_string() cut every cell short. no_colours() maps RESET to a backspace and deletes it afterwards (the cure of the Dead Souls 3.x sefun); help races lists [1] artrell [2] dwarf ... in columns. Changed: /daemon/help.lpc, /daemon/terminal.lpc.

Creator commands with no argument: the 0 goes straight into an efun (2026-10-21, KB 06 7.336)

Typed with no argument, a creator command gets 0 in its string parameter, and these commands hand it to an efun in their first statement: Bad argument 1 to ... at run time; nobody had run them. Each was confirmed without a login by calling main() with 0 inside one lpcc eval (scripts/lpcc_bare.py), then scripts/lpc_fix_bare_arg.py put a guard in front of the first use: a file with GetHelp() prints it and return 1;; one with no help() gets return notify_fail("...\n");. Changed (3 files, the efun that took the 0 in brackets): /cmds/creators/force.lpc (sscanf), /cmds/creators/move.lpc (sscanf), /secure/cmds/creators/banish.lpc (lower_case). Load check HEAD against the tree: no regression.

add_action() registrations of functions nobody defines, second round: objects behind a macro path or that the static scan cannot settle (2026-10-21, KB 06 §7.349)

The first round's static scan could not read objects that inherit through a macro path (a #define ROOM STD_DIR "room/room" whose body starts with another macro) or an inherit it could not resolve, and skipped them as UNSURE; the second round fixed the resolver, rescanned, and asked function_exists() of those objects in a running driver. An add_action("fn", "verb") whose function is defined neither by the object, nor by its headers, nor by its parents, nor by any object derived from it (scripts/lpc_scan_callout_targets.py, descendants checked transitively, then function_exists() in a running driver, scripts/lpc_verify_handlers.py). The driver walks the actor's sentences from the newest one: when that sentence's function does not exist it raises *Function for verb 'drop' not found. and tries no other handler of the verb (an older dead sentence is passed over silently). Removed 1 registrations in 1 files (scripts/lpc_fix_dead_add_action.py), the verbs are left to their other handlers: /lib/bboard.lpc (cmd_save). Most frequent: cmd_save x1.

Combat probe with the seeded administrator (2026-10-22): clean

scripts/play_combat_probe.py dsI --account ... --clone ...: the administrator cloned /obj/friend, /domains/Ylsrim/npc/balrog into its room and attacked each for 20 s (2 fights read): no driver line and no error text on the screen. (KB 08 §10.16)

Pointers (: name :) to functions the object never defines (2026-10-22, KB 06 §7.355)

A (: name :) in an item_desc / inquiry / chat_msg_combat names a function that neither the object, its headers nor its parents have a body for: it compiles, and the use raises *Undefined lfun pointer called: name (look THING / ask NPC about TOPIC fail on the spot, a combat or idle chat pointer fails inside the NPC's chat() and the rest of that heart beat is skipped). scripts/lpc_scan_callout_targets.py --kinds=lfun. 1 more function(s) stay missing (other 1): the prototype is there and the body is lost, or it was never written, and no sibling lib's copy of the file can be copied as it is (it lacks the function, needs helpers this lib does not have, or has another signature); nothing invented: /secure/lib/net/ftp.lpc:eventCmdPass.

log/ subdirectories nobody created: the logging write_file() / log_file() raises (2026-10-22, KB 06 §7.358)

The code of this lib writes logs into 2 subdirectories of log/: log/chan (secure/daemon/chat.lpc), log/harass (lib/interface.lpc). Neither the archive, a git clone nor the site pack has them (no local boot wrote into them, so gen_keep_dirs.py never recorded them), and write_file() raises *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory" for a missing directory: the function that logs stops on that line and what came after it (the reward, the flag, the next step) never runs. This lib's log_file() is write_file(LOG_DIR + file, text) and raises the same error for a missing directory (measured). log/chan, log/harass are now in scripts/wasm_keep_dirs.txt (scripts/lpc_keep_log_dirs.py; the site and the pristine tree create the empty directories from it). Proof (scripts/lpcc_eval.py, an eval object under /adm/daemons or similar writes /log/zzdirprobe/..., fresh processes): the measured table in KB 06 §7.358.