Dead Souls I

✅ 可玩

dsI

更新 d911539 2026-09-11 源码 下载 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).