Hakkard World

✅ 可玩

dshakkard

更新 fdf581a 2026-09-12 源码 下载 ZIP 上游 hakkard-dev-team/ds-hakkard

▶ 开始游玩 · Play Now

A custom closed-alpha game -- self-titled 'Hakkard World,' banner reads 'Revision 0.0.1, Status: Closed Alpha' -- built on the Dead Souls 3.8.6 engine this collection already hosts as ds386/deadsouls_fluffos/dsIII. Its non-secure stock code (command dispatch, headers, the campus/cave/default/examples/learning/Praxis/town/Ylsrim demo domains) is close to byte-identical to ds386's own raw archive; what makes it a real game rather than another stock copy are three bespoke domains built on top. The town of Lennar centers on Everett Square, where Elma Street crosses Main Street past a statue amid 'the hustle and bustle of everyday life,' with an Adventurer's Guild ('several people mill around, with wicked-looking weapons strapped to themselves'), a Mage's Guild, and named NPCs like Clerebold Vance, a small but exceptionally fit man whose Stetson hat is 'riddled with cuts and holes.' Lennar Manor is a separate noble estate with its own reception hall, tea room, lower library, and barracks; Northern Wastes is a third, frozen area (furnace, freezer, void rooms) built on the same engine. A personal builder's workroom (realms/muirrum) holds several in-progress test rooms and abandoned editor scratch buffers -- a snapshot of a game still under active construction. Makes three real outbound network connections at boot (IMC2, Intermud-3, an automatic dead-souls.net RELEASE_NOTES fetch) -- do not sweep this lib into high-frequency automated re-boot loops.

README

A custom closed-alpha game ("Hakkard World", banner reads "Revision 0.0.1, Status: Closed Alpha") built on the Dead Souls 3.8.6 mudlib -- the same Cratylus-lineage engine this collection already hosts as ds386/deadsouls_fluffos and dsIII. Not a distinct Dead Souls release: the non-secure top-level code (command dispatch, headers, the stock campus/cave/default/examples/learning/Praxis/town/ Ylsrim domains) is close to byte-identical to ds386's own raw archive. What makes this game distinct is three bespoke domains added on top -- lennar, lennarmanor, and northernwastes, a custom fantasy setting with its own manor rooms, NPCs, and virtual-room servers -- plus a personal builder's realm (realms/muirrum) with several in-progress test rooms and abandoned editor scratch buffers.

Source: git clone https://github.com/hakkard-dev-team/ds-hakkard. Slug dshakkard, number 185, port 40230.

The interesting part of this port

The public repo's own .gitignore excludes lib/secure, lib/save, and lib/log -- the entire security/daemon engine layer (including secure/daemon/master.lpc itself) and both top-level runtime directories were never committed to this repository's git history at all. Since the rest of the codebase is confirmed byte-identical to ds386 wherever the two overlap, this port supplies the missing secure/ tree from ds386's own already driver-compat-fixed copy (verified file-by-file first) rather than inventing anything, then applied the same dialect/OLD_ED/__DIR__ fixes ds386/dsIII already catalogued to ds-hakkard's own customized files. The missing /log and /save top-level directories turned out to matter even more: their absence made the driver's own error handler crash while trying to log routine compile warnings, which silently miscategorized ~35 harmless files as hard compile failures and masked several real save-path errors entirely. See NOTES.md for the full porting log, including a subtler pitfall (ds386's own copy of secure/lib/connect.lpc had already self-consumed its one-time admin installer, which had to be restored from connect.first.lpc before this session's own install wizard could be tested for real).

Status

Boots clean, zero fatal errors. Verified live with a raw socket client: the first-boot admin installation wizard (including the mud's self-shutdown-and-restart-required design), admin login into a wizard workroom, ordinary new-player registration (age gate, 8-race selection), a distinct reconnect code path, and look/score/update/quit all producing correct output.

2432 of 2440 .lpc files pass a full lpcc_check.sh batch compile sweep (99.7%); the 8 that don't are all pre-existing non-bugs matching classes already documented on ds386/dsIII (a deliberate anti-load trap, builder-tool scaffolding templates, a missing efun, a missing library component, an abandoned editor scratch file, and one real content gap -- daemon/help.lpc crashes on get_dir() of a verbs/spells directory this repo never shipped) -- see NOTES.md §6.

Makes three real outbound network connections at boot (IMC2, Intermud-3, and an automatic dead-souls.net RELEASE_NOTES fetch) -- same caveat as ds386/dsIII: do not sweep this lib into high-frequency automated re-boot loops.

WASM status: playable. Full admin login (fluffos/Mud@2026) and a fresh new-player registration (8-race pick, age gate, email validation) both verified clean end-to-end (look/score/update/quit) via scripts/wasm_client.js. The same sockets-package-absent gap ds386's WASM pass already fixed was mostly inherited for free (shared secure/ tree); one remaining secure/daemon/imc2.lpc compile gap is identical to ds386's own current state and doesn't block boot/login/ play -- see NOTES.md's "WASM bring-up (2026-08-27)" section.

Local run

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

Game port: 40230.

NOTES · 移植与修复记录

ds-hakkard ("Hakkard World") -- porting notes

Source: git clone https://github.com/hakkard-dev-team/ds-hakkard (commit 06cb87d3cf026ec393cb40969ab14e2426df7f7b, cloned 2026-08-26). A prior research pass already confirmed this is genuinely distinct content, not a duplicate of any lib already in this collection, so this session went straight to a full onboarding pipeline. Repo top level: README.txt, UNIX_INSTRUCTIONS.txt/WINDOWS_INSTRUCTIONS.txt, License.txt, bin/ (driver-config scaffolding, ignored -- this project uses its own driver), extra/ (build scaffolding, ignored), lib/ (the real mudlib root). Slug dshakkard, number 185, port 40230.

1. The real finding: this is a ds386-based GAME, not a distinct Dead Souls release -- and its engine layer was never committed to git

The task brief (based on domain-name overlap with dsII) suggested checking dsII/dsIII's fix catalog first. That produced a more specific and more important finding: this repo's engine is ds386/deadsouls_fluffos (Dead Souls 3.8.6), not dsII, and the repo never once committed its secure/ directory, or its top-level log/save directories, to git history at all.

This is best understood as: the hakkard-dev-team built their game's custom domains and top-level content on top of a private/shared copy of the stock Dead Souls 3.8.6 engine, and only ever published the game-specific layer to this public repo -- the engine's secure/ scaffolding (and the empty runtime log/save dirs) were deliberately or incidentally excluded via .gitignore and never appear anywhere in this repository's history.

1.1 Filling the gap: sourcing secure/ from ds386, verified file-by-file

Since ds386 (already onboarded in this collection, already fully driver-compat-fixed) is confirmed byte-identical to this repo wherever they overlap, its raw secure/ tree is the correct substitute -- supplying the SAME engine version this game was actually built against, not inventing new content. Before trusting this, every /secure/* path literally referenced anywhere in ds-hakkard's own non-secure code was checked for existence in ds386's secure/: of 34 distinct references, only 4 didn't resolve, and all 4 are benign:

raw/ds-hakkard/secure was built as ds386's raw secure/ copied in wholesale, then run through this project's own convert_lib.sh alongside the rest of the tree so the whole work/ copy gets one consistent encoding/rename/static->nosave pass. The resulting work/secure was then wholesale-replaced with ds386's own already driver-compat-fixed work/secure (confirmed byte-identical raw source first via diff -rq), reusing that entire fix catalog for free rather than re-deriving it:

Two runtime-state directories under secure/ needed care: ds386's own work/secure/{save,log,tmp} are NOT pristine -- they carry ds386's own accumulated test-boot output (a populated Intermud MudList cache in save/mudinfo.o, ds386's own player/creator saves, etc.). These three subtrees were rebuilt from the pristine RAW secure/{save,log,tmp} (re-verified as pure ASCII, no encoding fixes needed) instead of copied from ds386's polluted work/ copy, so ds-hakkard's committed state reflects only this session's own fresh test boot, not ds386's.

A second, subtler instance of the same problem: secure/lib/connect.lpc had already self-swapped. ds386's own admin-install test already consumed its one-time installer -- secure/lib/connect.lpc in ds386's work/ is no longer the 6,652-byte installer, it's the 30,913-byte ongoing-login object (connect.real's content), with the original installer preserved alongside as connect.first.lpc. Copying ds386's work/secure wholesale therefore ALSO copied this post-install swap, which silently broke the first-boot admin wizard test (a first attempt produced an ordinary player account, not an admin, since the "installer" being served was actually the normal registration flow). Fixed by restoring secure/lib/connect.lpc from the still-present, still-correctly -fixed connect.first.lpc before live-testing. secure/cfg/groups.cfg and secure/include/config.h's DEBUGGER define had the same issue for the same reason (both already said fluffos, ds386's own installed admin name, instead of the ADMIN/DEBUG_NAME placeholder tokens the raw archive ships) -- restored from the pristine raw values before testing, then correctly re-populated by ds-hakkard's own, freshly-run installer.

2. Conversion

scripts/convert_lib.sh against the combined raw/ds-hakkard tree (ds-hakkard's own non-secure lib/ plus the substituted secure/, 4,656 files): 4,622/4,656 already valid UTF-8, 2 clean re-conversions, 5 lossy (all in files shared byte-identically with ds386, whose own already-fixed versions were copied in directly rather than re-deriving the same Latin-1/mixed-encoding fixes: doc/CREDITS, doc/help/players/ credits, domains/town/txt/mystery.txt, secure/sefun/translate.lpc's mixed Latin-1/UTF-8 FLUBS curse-word array, secure/save/functions.o deleted as a stale cache and regenerated fresh on first boot -- same precedent as ds386/dsIII). 2,363 .c files renamed to .lpc, 1,490 files touched by the static->nosave sweep.

A whole-tree sync pass (custom Python script, not part of convert_lib.sh) then compared every non-secure raw file against ds386's raw archive at the same relative path: 3,749 files were byte-identical and got ds386's own already-fixed work/ content copied in directly; 257 files differ from ds386 (ds-hakkard's own customizations to shared stock files) and 82 exist only in ds-hakkard (the bespoke domains plus a builder's personal realm, realms/muirrum) -- these 339 files needed the fixes below applied fresh.

3. Driver-compat fixes applied to ds-hakkard's own (non-synced) files

3.1 Old-MudOS TYPE array NAME dialect (28 files, 109 sites)

Wrote a masked-text (string/comment-aware) Python regex fixer handling the same shapes dsII/dsIII documented: single/multi-name declarators, bare array, class TYPENAME array NAME, cast forms, function return types. Caught a real bug in the first draft before trusting it on the whole tree: the multi-name-list parser didn't distinguish a genuine same-type comma list (string array a, b;) from a FUNCTION PARAMETER LIST where a comma introduces a brand-new type (string array guilds, string other_param) -- it mis-fired and produced syntactically broken output (string *guilds,*string other_param) on a test file. Fixed by peeking past each comma for a new type-keyword before treating it as a continuation name; re-verified against a battery of the exact shapes dsIII's own session had to hand-fix (function params, class TYPENAME array Func(), bare-array return types, multi-name lists with a default value) before running it for real. Ran clean: 28 files, 109 sites, including a real un-starred string oldparties = PARTY_D->GetOldParties(); bug in lib/player.lpc (should be string *oldparties -- confirmed present byte-identically in ds386's own raw archive too, fixed the same way ds386's own port fixed it) and lib/std/room.lpc's SetLong() wrapper (same too-narrow string-only declaration ds386's port widened, needed again here since this file is one of ds-hakkard's own customized copies).

3.2 OLD_ED gaps in ds-hakkard's own customized files

lib/nmsh.lpc (a "differing" file) still called the undefined query_ed_mode(); lib/interactive.lpc (also "differing") still called the undefined ed_cmd() three times in its quit-path cleanup. Both are the exact same OLD_ED-vs-split-editor-API bug ds386's own port already solved in its stock copies of these two files (confirmed via diff that the surrounding code is otherwise identical) -- applied ds386's exact fixes: nmsh.lpc's custom-prompt branch collapsed to if(0){...} dead code, interactive.lpc's three ed_cmd() calls removed (the driver's own destruct_object() already calls save_ed_buffer() automatically for a live ed_buffer under OLD_ED, so this was only ever a redundant manual save).

3.3 #define __DIR__ collisions in the bespoke domains

domains/{lennar,lennarmanor,northernwastes}/virtual/{void,server}.lpc (6 files total, all new to this repo, not present in ds386 at all) had the old manual #define __DIR__ "..." idiom this driver's native __DIR__ predefine collides with. Removed all 6; the native value is identical.

3.4 domains/Praxis/supply2.lpc

Covered in \S1.1 above -- same set_pre_exit_functions() comment-out fix as ds386, applied to ds-hakkard's own slightly-reindented copy of this file.

4. Missing runtime scaffolding: TWO gitignored directories the lpcc batch sweep exposed as cascading false failures

secure/ wasn't the only gitignored gap. This repo's .gitignore also excludes the top-level lib/log and lib/save directories (the driver's configured log directory : /log and the non-secure daemons' save location) -- neither exists anywhere in this repo's git history either, and their absence has a much nastier effect than a missing engine directory: secure/daemon/master.lpc's own log_error()/error_handler() machinery tries to write compile warnings and runtime backtraces to /log/catch//log/runtime, and when that write itself fails with "No such file or directory", the resulting SECOND error recurses through the same handler and corrupts the diagnostic output for whatever ordinary file triggered the first warning. This silently miscategorized roughly 35 files with nothing worse than a harmless "Illegal to declare nosave function" or "Redeclaration of global variable" warning (the exact same isolated-compile-warning artifact class ds386/dsIII already documented) as full batch-compile FAILures, and masked several files' REAL underlying errors entirely (daemon/stargate.lpc, obj/book.lpc, and every stargate-instance object in every domain were actually failing because daemon/stargate.lpc's create() tries to SaveObject() to /save/stargate.o, which also didn't exist).

Fixed by creating both directories (mkdir -p) with the same subdirectory skeleton ds386's own work/log/work/secure/log use (errors/<every top-level dir>, chan, router, adm, archive, watch, imc2_server, secure, reports, law/*), then re-running the full batch sweep, which dropped the failure count from 43 to 10 purely from this fix -- before touching a single line of ds-hakkard's own code. Unlike ds386's own committed repo (which commits zero files under log/, meaning a truly fresh checkout of ds386 would hit this exact same cascading-failure class again), this port commits a lightweight foo.txt placeholder (the same convention this codebase itself already uses under secure/log/{bak,network,adm,intermud}) in every otherwise-empty log subdirectory, so a fresh clone of libs/dshakkard boots and compiles cleanly without anyone needing to manually recreate this scaffolding again.

5. Live verification

Booted clean (Accepting telnet connections on 0.0.0.0:40230., Initializations complete., zero fatal errors).

Confirmed the same three real outbound network connections at boot as ds386/dsIII:

1. IMC2 (secure/daemon/imc2.lpc, preloaded) -- confirmed live (ss -tnp showed ESTABLISHED ... 97.107.133.86:8787). 2. Intermud-3 (daemon/intermud.lpc, preloaded) -- confirmed live (ss -tnp showed SYN-SENT ... 50.116.16.90:5000). 3. secure/daemon/autoexec.lpc's automatic dead-souls.net RELEASE_NOTES fetch, one second after boot, zero player interaction required -- confirmed live and reproducible on every boot (doc/RELEASE_NOTES_HTTP freshly rewritten with real, current HTTP/1.1 200 OK content each time). Reset to the pristine originally-shipped copy before every commit in this session, same as dsIII's precedent -- a future boot will regenerate it again, which is expected. Do not sweep this lib into high-frequency automated re-boot loops.

Tested with a raw Python socket client (scripts-equivalent ad hoc mudclient.py), all required flows in one session, using this project's standard admin identity (fluffos/Mud@2026):

1. First-boot admin installation wizard (secure/lib/connect.lpc, pristine installer restored per \S1.1): username -> confirm -> "13 years of age or older?" age gate -> "screen reader?" prompt (answered n) -> password/confirm -> display name (default) -> gender -> real name (blank) -> email -- completed successfully; the mud self-shut-down as designed (matching ds386/dsIII's installer behavior). secure/cfg/groups.cfg and secure/include/config.h's DEBUGGER define correctly ended up with fluffos in place of the ADMIN/DEBUG_NAME placeholders, and the account saved under secure/save/creators/f/fluffos.o (a real creator, not a player). 2. Reboot + admin login: fluffos/Mud@2026 -> arrived in /realms/fluffos/workroom with wizard robe+hat, people showed the admin present, boards reported pending board messages, score produced a correct character sheet, update (current-room form) reported Ok, look/quit both correct. 3. Ordinary new-player registration (Qinhakkard, an ASCII name -- this archive enforces the same strict A-Z a-z ' - name-charset check as ds386/dsIII, so a real Chinese name is out of scope here by the same adapted bar): name -> confirm -> age gate -> screen-reader prompt (n) -> password/confirm -> gender -> email (validated; a blank first attempt correctly rejected and re-prompted) -> race selection (list/pick human -- 8 races, identical set to ds386/dsIII) -> news pages -> landed in "The start room" (the default domain's entry point, an ASCII-art room map) with a correct HP/MP/SP status bar. look/score/quit all correct. 4. Reconnect as the same character: name -> password -> landed back in the same room with inventory intact, score/quit correct -- confirms the reconnect path is a distinct, working code path.

2432 of 2440 .lpc files pass a full lpcc_check.sh batch compile sweep (99.7%); the 8 that don't are broken down in \S6.

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

All of the following match a class already documented on ds386/ dsIII/dsII, confirmed either byte-identical to ds386's own raw archive or a clearly out-of-scope leftover:

7. WASM pass

Not attempted this session (wasm_status left ""). Given the confirmed live outbound HTTP/socket behavior (\S5) and this being the same sockets-package-dependent secure/sefun/sockets.lpc codebase as ds386, a future WASM pass should expect to need the same gut-the-function-bodies treatment ds386's own WASM notes describe before this lib can boot under WASM at all.

8. 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. ds-hakkard shares this exact file byte-for-byte with ds386/pre-fix dsIII and had the identical gap, plus the same two sibling misses: lib/teller.lpc's eventExchange() (i = val / currency_rate(str2);) and lib/props/value.lpc's SetBaseCost() (Cost = i * rate;). Fixed identically (wrapped all four economy.lpc returns plus both call sites in to_int()).

Verification here was compile-only, not a full live transaction: attempting to reach the bank (enter town from the start room, the same path used to verify ds386) hits a severe, unrelated, pre-existing bug that makes the entire town domain unreachable — see §9 below. Instead, verified the fix with lpcc --batch compiling the three touched files directly (/secure/sefun/economy, /lib/teller, /lib/props/value) against this lib's own config.fluffos: all three PASS, no new errors introduced (only pre-existing, unrelated nosave declaration warnings). secure/sefun/economy.lpc is also a simul_efun file the driver compiles unconditionally at every boot, and both the very first plain boot and the boot used for this check compiled it clean with zero errors. Given the fix is byte-for-byte the same edit already live-verified working on ds386, dsII, and deadsouls_fluffos (identical code shape, identical to_int() wrapping), this is considered sufficiently verified without a live transaction.

9. enter town infinite error-handler recursion — ROOT-CAUSED AND FIXED

Follow-up dedicated pass (2026-08-27) to the blocker flagged below (originally found while verifying §8's currency fix). Confirmed the crash is real and reproduced it directly by booting the real driver and walking an ordinary (non-creator) test character through registration into enter town: the driver logged "Error in error handler: *Object cannot be loaded during compilation." thousands of times a second and pinned a CPU core, matching the original report exactly (a stale work/log/debug.log from that repro alone grew to 8.9 million lines).

The directory-vs-file theory below was a correct but incomplete diagnosis. log/errors/daemon (and 20 sibling top-level names) really were directory-shaped where the runtime error path wants a plain file, and that *is* the event that first triggers the crash — but the actual bug that turns one failed log write into an *unbounded* crash is in secure/daemon/master.lpc's own error_handler(), at the exact line the crash trace names:

rlog += load_object("/secure/cmds/creators/dbxwhere")->cmd(this_player(1)->GetKeyName());

Full chain: some file (in the reproduced case, a file under /secure/) emits an ordinary compile warning ("Illegal to declare nosave function" — the same harmless nosave-on-a-function idiom already catalogued elsewhere in this codebase family as a non-bug) while the driver is compiling it, mid-compile, as part of a lazy load triggered by an ordinary player action. master.lpc's log_error() apply handles that warning and does catch(write_file(DIR_ERROR_LOGS "/" + nom, ...)) — if DIR_ERROR_LOGS "/" + nom is a directory (the log/errors/daemon-shape bug), write_file() throws "Wrong permissions for opening file ... Is a directory" inside that catch(). FluffOS still reports even a *caught* runtime error to master->error_handler(mp, 1) for logging purposes. error_handler() sees this_player(1) set to the ordinary player who triggered the lazy compile, and — since they're not a creator — falls into the branch that builds an admin-facing incident report, including an UNGUARDED load_object("/secure/cmds/creators/dbxwhere") (an admin debug command, essentially never already-loaded for an ordinary player's session). Since the driver is *still* mid-compile at this point, that load_object() itself throws "Object cannot be loaded during compilation." — from inside error_handler() itself, uncaught. This re-enters the same mudlib error-report path, which lands right back on the same load_object() line, which fails the same way again — the driver's own re-entrancy counter bounds any *single* burst to a handful of frames, but nothing stops the *next* triggering event (the still-uncompiled file being touched again by the next heartbeat/command) from restarting the whole cycle immediately, which is what produces the observed effectively-infinite CPU-pinning loop. This is the exact same "load-mid-compile" driver-compat class already documented in AGENTS.md §7.60 (and already fixed once, elsewhere in this very function, for WEB_SESSIONS_D, by checking find_object() before assuming an object is loaded) — dbxwhere was simply the one call in this file that hadn't gotten that same guard.

Fix (secure/daemon/master.lpc, error_handler()): wrapped the dbxwhere lookup+call in catch(), degrading to a plain placeholder string when it fails instead of letting the throw escape error_handler() itself:

if( catch(rlog += load_object("/secure/cmds/creators/dbxwhere")
        ->cmd(this_player(1)->GetKeyName())) )
    rlog += "(dbxwhere unavailable -- driver mid-compile)\n";

This is a real driver-compat/programming bug fix (an unguarded call-other that can throw during a well-known illegal window), not a content change.

Also converted every remaining directory-shaped log/errors/<name> entry to a plain empty file (the 21 named in the original diagnosis below, cfg cmds doc domains estates ftp include lib log news obj open powers realms save secure shadows std tmp verbs wwwdaemon/town/campus/ default/Praxis/Ylsrim/fluffos had already self-healed into files from earlier boots). This directory is gitignored (.gitignore:24), so this change is local-only and does not affect a fresh checkout or get committed — but it removes the *triggering* event entirely for this local checkout, on top of the real code fix which removes the ability for that trigger (or any future one shaped like it) to cascade into a crash.

Verification: killed the stale driver process, booted fresh (Initializations complete, zero fatal errors), registered a brand-new ordinary player (Bugtestchartwo, via a raw Python socket client — the existing admin account bypasses the buggy branch entirely since creators take a different, always-safe code path in the same function, so a fresh non-creator registration was required to actually exercise the fix), and walked: start room -> enter town -> look -> north (into an NPC-populated room, "a dirty beggar" who spoke and gave an item) -> look -> south -> look -> score -> quit. Every step produced correct, clean output with zero runtime-error messages and the driver's own log (driver_stdout.log, this run's actual live console/debug output — this driver's debug.log file target is unused in non-daemon mode) had zero Error in error handler/Object cannot be loaded/Wrong permissions/ runtime error lines anywhere. Confirmed the driver process stayed at single-digit-to-teens CPU (idle-normal, not pinned) throughout, and killed it cleanly by exact PID afterward. Before this fix, the identical registration-then-enter town sequence reproducibly hit the crash within one or two commands of entering town.

Test-only side effects (the ad hoc player saves, RELEASE_NOTES_HTTP's autoexec-refetched content, IMC2/vote/save-daemon state touched by the test sessions) were reverted/removed before committing, per this project's standing convention of not shipping test-session churn — the only committed change is the master.lpc fix itself.


*(Original diagnosis, kept for reference — see above for the completed root-cause and fix.)* While trying to reach the bank to verify §8 above, enter town from the start room triggers a genuine, severe bug: the driver enters an infinite "Error in error handler: *Object cannot be loaded during compilation." recursion (error_handler() calling itself via /secure/cmds/creators/dbxwhere's own load_object() attempt while already mid-compile) and pins one CPU core indefinitely until killed. Root-caused to log/errors/daemon being a directory (containing a foo.txt placeholder) rather than a plain file — the very first error the driver tries to log after any runtime error anywhere hits "Wrong permissions for opening file /log/errors/daemon for append.\n\"Is a directory\"", and logging *that* failure recurses forever. Every OTHER top-level name was, at the time, still a directory-with-foo.txt and could plausibly trigger the identical crash the first time any runtime error happened to target one of those categories.

Sibling sweep of ds386's round-two eventRevive()/room.lpc/combat.lpc/beggar.lpc bugs (2026-08-27)

Per ds386/NOTES.md's "Deep functional test (round two, 2026-08-27)" section (the reference master for this whole lineage), checked all four flagged bug shapes here. All four were present and fixed identically:

Following this lib's own standing convention noted above, only the four source-file fixes plus the admin account's post-test save state were committed -- RELEASE_NOTES_HTTP/IMC2/vote/daemon-state test churn and a stray secure/tmp/*_CMD_EVAL_TMP_FILE.lpc scratch file were reverted/removed first. Killed the test driver by exact PID when done.

WASM bring-up (2026-08-27) -- wasm_status promoted to playable

First WASM-sandbox pass for this lib (meta.json's wasm_status was still "" going in -- native verification above never touched the WASM side). ~/src/fluffos/build-wasm/src/fluffos.js/.wasm were already built on this machine; node itself needed ~/.local/opt/node/bin on PATH (not on the default PATH in this shell, unlike the ds386 session's environment which had no node at all).

As predicted by this lib's own meta.json group_note and ds386/dsIII's precedent, this is the exact same Dead Souls 3.8.6 sockets-package-absent gap ds386's own 2026-08-25 WASM pass already diagnosed and fixed -- and since this port's entire secure/ tree was copied wholesale from ds386's OWN already-WASM-fixed copy (see this lib's onboarding section above), the fix mostly came for free: secure/sefun/sockets.lpc, sefun.lpc's socket_address() wrapper, secure/daemon/instances.lpc, and secure/lib/net/server.lpc are all byte-identical to ds386's post-fix copies and loaded/compiled clean under WASM with zero changes needed here. Confirmed via node scripts/wasm_client.js ~/src/fluffos/build-wasm/src libs/dshakkard: simul_efun and the master object both load cleanly (no "must be loadable" fatal, unlike ds386's pre-fix state), so the driver boots all the way to the login prompt.

One gap remains, and it's the same one ds386 itself still has (a byte-for-byte diff of secure/daemon/imc2.lpc between the two libs returns nothing): a handful of functions inside secure/daemon/imc2.lpc (the IMC2 chat-network client) that ds386's own 2026-08-25 fix pass did not reach still call raw socket_status()/socket_close()/ socket_create()/socket_error() (lines 633-712, e.g. the router-connect routine) and fail to *compile* under WASM (Undefined function socket_status, etc.), same as daemon/intermud.lpc's own deliberate #error "You should not try and load ... with no sockets package" self-guard. Since this is identical, in the identical file, to ds386's own current committed state -- and ds386 is already playable with this exact gap present and undocumented as a per-lib issue (its own fix notes call out the incomplete sweep only for the admin-tooling files, not imc2.lpc's own remaining functions) -- this was left alone here too rather than diverging from the reference lineage's accepted baseline: both preload-time compile failures are non-fatal (the driver logs the error and simply proceeds without the daemon, exactly like any other absent-daemon case in AGENTS.md §1.3c) and neither the IMC2 nor the Intermud-3 outbound connection can possibly matter under WASM anyway, since WASM has no sockets at all -- this isn't a regression from anything native play relies on.

Verified the full flow clean end-to-end via wasm_client.js against the committed work/ tree (already past the one-time installer -- secure/lib/connect.lpc was already swapped to the real login object and fluffos/Mud@2026 already seeded per the onboarding pass above, so a WASM run reaches the normal login directly, same precedent as ds386):

1. Admin login (fluffos / Mud@2026): password prompt -> three Press <return> to continue: news pagers (general/creator/admin news, one more than ds386 hit since this lib's admin account also gets creator news) -> lands in /domains/default/room/start with the queued .profile commands (people/uptime/boards) firing correctly. Explicit look, score (correct level-1 Human Explorer sheet), and update (/domains/default/room/start: Ok) all produced correct output; quit cleanly disconnected ("Please come back another time!" + a [Fluffos quits ds-hakkard]" connections-channel message). 2. Fresh new-player registration, to also cover the first-boot-installer-flavored flow this lib is documented for natively: name (Qinwtestd, this lineage's strict A-Z a-z ' - charset) -> confirm -> age gate -> screen-reader prompt (n) -> password/confirm -> gender (male) -> email (validated; a blank first attempt correctly rejected and re-prompted, matching the native test) -> pick human (8-race list, identical set to native) -> two more news pagers -> landed in "The start room" (the default domain's ASCII-art entry point) with a correct hp/mp/sp status bar. look/score (Qinwtestd the unaccomplished, level 1 Human Explorer) both correct; quit cleanly disconnected and removed worn equipment on the way out.

Both flows produced zero uncaught runtime errors and no disconnects other than the deliberate quit. Since wasm_client.js boots a fresh in-memory MEMFS copy of work/ per run and never writes back to the host filesystem, none of this session's test characters or pager state touched the committed tree (git status after both runs: clean) -- nothing needed reverting, unlike the native-driver testing sessions above.

wasm_status promoted from "" to "playable" in meta.json: the core registration/login/play/quit loop is fully verified under WASM, matching the bar ds386/dsIII were promoted at. The remaining imc2.lpc compile gap is the same pre-existing, shared-lineage, admin/network-tooling-only limitation already accepted as out of scope for the playable/limited distinction on ds386 itself -- not something this port introduced, and not on the boot/login/play path.

Sibling sweep: become <class> SetClass -> ChangeClass fix (2026-09-01)

Ported the fix from riftsds (AGENTS.md §7.195/§7.196; commit 11216f003b1) -- see ds386/NOTES.md's identical write-up (same session, same batch) for the full bug mechanism. Summary: the stock Dead Souls "Praxis" demo guild-join rooms called SetClass() directly instead of ChangeClass(), so the multi-class privilege gate (high_mortalp(), which excludes creators too) always silently rejected a fresh explorer's first class despite a success message printing.

Confirmed present here, byte-identical to ds386/riftsds's pre-fix files: all six domains/Praxis/*_join.lpc files called SetClass(); secure/include/compat.h was missing the same three query_name/query_cap_name/query_gender mappings.

Fix applied: six SetClass() -> ChangeClass() edits, three compat.h accessor lines added -- identical to ds386.

Live-verified with the seeded fluffos admin (Class reset to "explorer" via eval between attempts): become fighter/become cleric/become mage all now print the real success text and score confirms the genuine class change ("You are a level 1 Human Fighter"/ "Cleric"/"Mage"). become monk/kataan/rogue were not tested here (the same pre-existing missing-class-data gap already confirmed on ds386/dsIII/riftsds's identical secure/cfg/classes/ directory listing -- explorer/fighter/mage/cleric/thief only, no monk/kataan/rogue data anywhere in this lineage).

Committed only the six join-file fixes and the three compat.h mappings; incidental save-file churn from this session's boot (seeded fluffos admin's Class field, player list, mudinfo, snoop, preload saves) was left uncommitted. Killed the test driver by exact PID when done.

Deep functional test (first full §10.7 pass, 2026-09-03)

First continuous playthrough of the bespoke domains (lennar, lennarmanor, northernwastes) plus stock town combat / shop / death / class-join / quit-reconnect. Admin fluffos/Mud@2026. Prior work on this lib was onboarding + sibling sweeps only — never a full §10.7 pass against the custom content.

Bug 1 (§7.11): missing /save/economy.o — every shop buy *Division by 0.0*

Onboarding rebuilt /save/ from the pristine raw tree but never seeded economy.o. Every other Dead Souls 3.x lib in this collection commits that file (ds386, dsIII, deadsouls_fluffos, …). With an empty ECONOMY_D currency map:

Fix: committed work/save/economy.o copied from ds386 (same engine, same stock currency table: copper/silver/gold/electrum/ platinum/dollars). Also hardened query_value() / query_player_money() to skip or return 0 on non-positive rates so an empty economy degrades instead of crashing. Verified live: buy ale from smort after AddCurrency("gold", 100) delivers the tankard and deducts gold (99 gold remaining).

Bug 2 (§7.11): gitignored /log and /secure/log skeleton

.gitignore has libs/*/work/**/log, so NOTES §4's "commit foo.txt placeholders" claim cannot survive a fresh checkout. Observed live:

Fix:

Verified live: wiped both log trees, rebooted — skeleton recreated with zero "Wrong permissions"; quit prints a clean Please come back another time! + connections-channel line.

Bug 3 (§7.11): STATISTICS_D->eventKill mkdir only the leaf letter dir

daemon/statistics.lpc did mkdir("/save/kills/f") without ensuring /save/kills itself exists. First kill of a rat threw Wrong permissions ... /save/kills/f/fluffos. Fixed with unguarded((: mkdir_recurse($(file)) :)) on the letter directory. Verified live: kill writes (["a rat":1,]) under /save/kills/f/fluffos.

Playthrough checklist (all green after fixes)

Side fix this cycle (not dshakkard)

Ported the !\xee\x93\xa2n!\n / !”\n string-corruption fix from qhxajh's waiter to xajhxo (d/nanyang/npc/waiter.lpc, d/city/nanyang/npc/waiter.lpc, d/menpai/shaolin/npc/huilun.lpc, system/feature/command.lpc) and the matching qhxajh system/feature/command.lpc instance.

Files touched

Heartbeat hobbled(this_player()) (2026-09-04)

Shop already verified 2026-09-03 (buy ale from smort); not re-run. Applied the same heartbeat fix as dsIII/dsII: lib/body.lpc heart_beat now calls hobbled(this_object()), and secure/sefun/disable.lpc returns "No missing limbs." when !objectp(ob). Verb-file this_player() checks left alone. Admin fluffos / Mud@2026 logged in after the edit (start room / people showed Fluffos). Save-file churn from this boot left uncommitted.