Haven

✅ 可玩

havenmud

更新 4b67bae 2026-10-27 源码 下载 ZIP 上游 brainface/havenmud

▶ 开始游玩 · Play Now

A long-running, genuinely large Dead Souls/Nightmare-lineage production game self-titled 'Haven,' set in the world of Kailie -- 13,589 source files (7,328 under domains/ alone), not a near-stock demo like this collection's sibling dshakkard. New characters begin 'in the skies above Kailie,' a staging room where reading a newbie book awards a real quest completion before descending into one of several playable hometowns. Kailie has its own pantheon (the Lord of Balance Duuktsaryth, and the Primal Gods Amelia, Balishae, Syra, Morgoth, Zaxan, and Ahriman), a creation-myth timeline, and its own calendar counting years from Haven's own founding. Playable races span humans, elves, dwarves, half-elves, orcs, half-orcs, sprites, skaven, lamrani, dark-elves, and wild-elves, with goblins, kobolds, gelflings, nosferatu, drughkor, and meuzzin appearing as lore/NPC races. 21 custom domains make up the live world -- averath, baria, crystal_reaches, estergrym, frostmarches, gurov, havenwood, innersea, islands, namatar, peninsula, southern_coast, tairi, valley, westwood, xandria, and yozrath among them -- towns, wilderness, and dungeons built up over years of individual wizards' work, plus a marriage/chapel system, player estates, a crafting subsystem, an in-game newsgroup daemon, and a wizard FTP daemon. 25 personal wizard workrooms sit under realms/ (Duuk, Zaxan, Mahkefel, Alessandra, Laoise, Torak, and others), several holding real in-progress building projects alongside old scratch files. Makes a real outbound Intermud-3 connection on boot.

README

A long-running Dead Souls-based game called Haven, set in the world of Kailie. New characters begin in "the skies above Kailie" -- a staging area where you read the newbie book (Type read book, then go down) before descending into the real world through your chosen hometown.

Source: git clone https://github.com/brainface/havenmud. Slug havenmud, number 947, port 40249.

The world

Kailie has its own pantheon (the Lord of Balance Duuktsaryth and the Primal Gods Amelia, Balishae, Syra, Morgoth, Zaxan, and Ahriman), its own creation-myth timeline, and its own calendar counting years from the opening of Haven for player testing. Playable races include humans, elves, dwarves, half-elves, orcs, half-orcs, sprites, skaven, lamrani, dark-elves, and wild-elves -- with several more (goblins, kobolds, gelflings, nosferatu, drughkor, meuzzin) appearing in the lore and as NPCs.

21 custom domains make up the live world: averath, baria, crystal_reaches, estergrym, frostmarches, gurov, havenwood, innersea, islands, namatar, peninsula, southern_coast, tairi, valley, westwood, xandria, and yozrath, among others -- towns, wilderness, dungeons, and quest lines built up over years. On top of the world content, Haven has a marriage/chapel system, player estates, a crafting subsystem (clothing, at least), an in-game newsgroup daemon, and an FTP daemon for wizards. realms/ holds 25 personal wizard workrooms (Duuk, Zaxan, Mahkefel, Alessandra, Laoise, Torak, and others), several containing real in-progress building projects alongside old scratch files and backups.

Status

Boots clean under this project's native FluffOS driver and plays through the full flow: name -> confirm -> email -> password (with confirmation) -> gender -> real name -> race (list/pick) -> town (list/pick) -> arrival in the newbie staging room -> read book (awards a real quest completion + development points) -> go down into your hometown's real content (verified reaching Haven town's "Above the Elven Charity"). look/score/quit all produce correct output, and reconnecting (name + password) restores the same character in the same room with inventory intact.

See NOTES.md for the full restoration log, including several driver- compatibility bugs found and fixed -- most notably a single off-by-one slice bug in the shared command dispatcher that silently broke every non-verb player command (including score) game-wide.

Live network note: this lib's daemon/intermud preloads and makes a real outbound connection to the public Intermud-3 network on boot (confirmed live, same router this collection's other Dead Souls-family libs connect to) -- don't sweep this lib into high-frequency automated re-boot loops.

WASM status: playable. Shared WASM driver. New-character registration (wasmhav / Mud@2026 / pick human / pick haven) into "In the skies above Kailie", plus look / score ("You are Wasmhav, a level 0 Human.") / quit, verified with scripts/wasm_client.js. FTP/socket_* compile failures are graceful preload skips. Play: https://mudlibs.fluffos.info/havenmud/

NOTES · 移植与修复记录

Haven ("havenmud") -- porting notes

Source: git clone https://github.com/brainface/havenmud (a single- commit snapshot from 2023, cloned 2026-08-28). Repo top level: bin/ (driver-config scaffolding, ignored -- this project uses its own driver), ansible/, scripts/, smb.conf, Vagrantfile, and lib/ (the real mudlib root, confirmed via bin/haven.cfg's master file : /secure/daemon/master and mudlib directory : /home/haven/haven/lib). secure/daemon/master.c's header credits "Descartes of Borg 940910" and other file headers cite "Nightmare IVr2"/"Nightmare IVr3" and "Dead Souls" lineages by name -- this is the same MudOS/FluffOS- compatible Dead Souls/Nightmare family already represented in this collection by ds386, dsI/dsII/dsIII, dshakkard, and deadsouls_fluffos. Slug havenmud, number 947, port 40249.

Unlike dshakkard (a closed-alpha game with almost no bespoke content beyond three domains) or the various near-stock Dead Souls ports, this is a large, long-lived production game: 13,589 .c files (7,328 under domains/ alone), 21 custom domains, 25 personal wizard realms under realms/, and its own pantheon/history/calendar (doc/help/players/timeline). Unlike dshakkard, this repo commits its own secure/ tree in full -- no engine-layer gap to fill from a sibling.

1. Conversion

scripts/convert_lib.sh against raw/havenmud (16,347 files copied from the repo's own lib/): the content is already almost entirely ASCII/UTF-8 (16,272 of 16,347 files needed no conversion at all; 7 clean re-conversions, 7 lossy, 61 skipped binaries), matching the imud-class precedent for an English-language source rather than the usual GBK/GB18030 corpus. 13,589 files renamed .c->.lpc, 1,188 literal .c"-reference fixups, 37 local angle-bracket includes converted to quotes, 12,234 files touched by the static->nosave sweep. Removed two genuine non-content binaries the conversion pass correctly left alone: a stray macOS .DS_Store (std/mine/) and a 999,992-byte stale secure/tmp/swap.mud.5000 runtime swap file that had been accidentally committed to the original repo.

A tree-wide Python UTF-8 decode scan (per AGENTS.md section 4.1, run against the WHOLE work/ tree, not just .lpc/.h) found the remaining 47 non-UTF-8-decodable files were all genuine binaries (a .bmp, the .DS_Store/swap file above, and ~40 real Microsoft Word .doc design documents under realms/zalbar/oldfiles/projects/ prison_tunnels/ -- a builder's abandoned project notes, left as-is, same "someone's late-night work" class this project preserves rather than discards).

2. Missing runtime scaffolding

This repo's .gitignore excludes the usual set of runtime-written directories that were never committed: /lib/log, /lib/tmp, /lib/secure/save, /lib/ftp, /lib/news, /lib/worlds, /lib/bak, and several named /lib/save/<category> subdirectories (boxes, estates, kills, phouses, pshops, shops, vehicles). All were recreated with the standard placeholder-file convention (foo.txt in otherwise-empty dirs so git tracks the directory shape), following the ds386/dshakkard precedent:

Unlike dshakkard's identical-shaped gap, this repo's own secure/tmp/ was already present and correct (just holding one stale swap file, removed per section 1 above) -- only the top-level runtime trees needed rebuilding.

3. Driver-compat fixes

3.1 secure/daemon/master.lpc -- missing PACKAGE_UIDS applies (AGENTS.md section 7.2)

No get_root_uid()/get_bb_uid()/creator_file() anywhere in this codebase (confirmed zero geteuid()/uid() call sites -- permissions are enforced entirely by the mudlib's own group/wizlist ACL, not the driver's uid system). Added the standard minimal stubs (all objects attributed to "root"), identical in shape to every other lib in this lineage that needed this fix.

3.2 secure/daemon/master.lpc:load_access() -- a local variable named ref collides with this driver's ref keyword

private nosave void load_access(string cfg, mapping ref) {
  ...
  ref[fl] = explode(ac, ":");
}

ref is a genuine lexer keyword on this driver (L_REF, used for pass-by-reference call arguments) -- using it as a plain identifier compiles the parameter declaration fine but hard-errors ("syntax error, unexpected '['") the moment it's used as an lvalue later in the same function, since the driver expects the ref-specific grammar production there instead. This is the FIRST file loaded at boot (load_access() runs from create(), called for read/write/groups ACLs), so it was a hard boot blocker. Renamed the parameter to acc; LPC mappings are reference types already, so nothing about the by-reference-mutation behavior changed.

3.3 lib/rooms/room.lpc -- MudOS-era replace_program() fold (AGENTS.md section 7.141)

The exact same fold documented across this whole lineage (ds386/dsI/dsII/dsIII/dshakkard): create() tried to fold a bare-inherit room's program into its shared parent to save memory, but this driver defers replace_program()'s effect to a periodic backend sweep rather than applying it synchronously -- so any closure built on a freshly-loaded room (this file's own eventHearTalk() TALK_LOCAL filter, reached by ordinary say/ask/tell) would crash for the first several minutes after every boot. Removed the fold entirely, leaving SetNoReplace()/GetNoReplace() in place as harmless unused API, exactly as fixed on every sibling.

3.4 OLD_ED vs. the split editor API (AGENTS.md section 6.2)

lib/users/editor.lpc, lib/users/nmsh.lpc, and secure/cmds/ creators/lsed.lpc all called ed_start()/ed_cmd()/query_ed_mode(), which don't exist under this driver's __OLD_ED__ build (only the single-entry ed() efun exists). Since editor.lpc sits in the player body's inheritance chain, this compile failure would have taken the whole player body down with it. Fixed identically to the established pattern: editor.lpc rewritten against ed(file, exit_fn, restricted); nmsh.lpc's dead custom-prompt branch (already unreachable once an ed session is open, since the driver's own comm.cc intercepts input directly) collapsed to if(0){...}; lsed.lpc's core batch-editing function replaced with a "use ed interactively instead" message, since there's no synchronous scriptable-ed entry point under OLD_ED at all.

3.5 Bare array as a type specifier -- hard parse error, not silent mis-declaration

AGENTS.md section 6.3 describes bare array x; as typically compiling without error but failing to actually declare anything. On this specific driver build it's worse: array used as a type specifier (variable, parameter, or return type) is a hard syntax error (unexpected L_IDENTIFIER), including in a cast position ((string array)expr). 13 files/19 sites fixed (-> mixed */ mixed): lib/npcs/equipper.lpc, lib/livings/race.lpc, lib/users/ history.lpc, daemon/currency.lpc, domains/havenwood/areas/ parvahosp/obj/viallimb_f.lpc (3 sites), domains/westwood/areas/ hiker/obj/fruit.lpc, secure/daemon/newsgroup.lpc (2 sites, including secure/daemon/include/newsgroup.h's matching forward declaration -- newsgroup is preloaded, so this alone blocked one boot-critical daemon), realms/melchezidek/currency.lpc, realms/melchezidek/ armours.lpc, realms/melchezidek/projects/starfire/npc/starfire.lpc (3 sites), realms/melchezidek/projects/starfire/room/room08.lpc (2 sites), cmds/players/armours.lpc.

3.6 The old-MudOS TYPE array NAME two-word dialect (AGENTS.md section 6.3)

356 files / 853 sites, fixed with a masked-text (string/comment-aware) Python regex pass covering every shape this lineage's siblings already documented (simple decl, decl-with-init, multi-name comma lists, function parameters, return types, cast expressions). Verified safe before running corpus-wide: dry-run diffed against hand-picked samples (a function whose OWN return type and OWN parameter were both in the old dialect on one line -- string array SetRestrictLimbs(string array limbs) -- correctly became string *SetRestrictLimbs(string *limbs)) and, after fixing a real bug in the first draft (naive in-place list splicing shifted every later match's offset once an earlier variable-length replacement changed the string's length -- rewritten to build the output left-to-right from a sorted, non-overlapping match list instead), a paren-balance diff between every touched file's raw source and its fixed work/ copy confirmed zero sites where the fixer introduced or removed an unmatched (/) across all 356 files.

3.7 daemon/command.lpc's GetCommand() -- the exact [0..<3] bug already found on ds386, and its wider sibling class

cmd = file[0..<3] while scanning get_dir(path+"/*.lpc") keeps len-2 characters -- correct for stripping the OLD .c MudOS extension (2 characters) but wrong for .lpc (4 characters), turning "score.lpc" into "score.l". Since this daemon is how every non-add_action() player/creator command resolves (cmds/players/*.lpc, cmds/creators/*.lpc, etc.), this broke every single command dispatched through it -- confirmed live: a fresh character's first score command silently fell through to the generic "unrecognized command" fallback with no error message at all, while movement (add_action()-based, a different dispatch path entirely) worked fine. This is the exact same bug, in the exact same file, already found and fixed on ds386 (AGENTS.md/ds386 NOTES.md: "broke the stock score command on every login... the single highest- impact instance of the whole class").

Given the hit, swept the whole corpus for ds386's documented sibling variants of this same "hardcoded old 2-char .c suffix length, should be 4 for .lpc" class and found it recurs far more widely here than on ds386 -- 24 more sites across 20 files:

Three superficially-similar [0..<3]/[<2..] sites were confirmed NOT part of this bug class and left untouched: lib/users/ history.lpc:113 (stripping a literal "/g" 2-char path fragment, unrelated to .lpc), secure/lib/net/ftp.lpc:441 (stripping a trailing "/.", unrelated), and secure/daemon/newsgroup.lpc:76 (genuinely stripping the 2-character .o save-file extension, which IS correctly 2 characters).

3.8 lib/users/player.lpc:SetUndead() -- bare-string calls into a string *-typed API

RemoveAdditionalId(GetUndeadType())/SetAdditionalId(GetUndeadType()) passed a bare string where both functions declare string *. This is the player body class itself -- every single character in the game inherits it -- so this alone hard-blocked all character loading (master.lpc's player_object() reported *No program in object '/lib/users/player'! the moment any registration or login tried to instantiate a new character). Fixed by wrapping both calls in a single-element array (({ GetUndeadType() })), matching the established convention already used correctly at dozens of other call sites in this same codebase.

3.9 SetLong()/SetShort() narrowed to string where the real implementation already accepts mixed (the Dead Souls "SetLong/SetExternalDesc/SetInternalDesc widened to mixed" class, AGENTS.md, already fixed on ds386/dsIII/dshakkard)

lib/common/look.lpc's GetExternalDesc() already handles a function pointer or a ({day, night}) array in ExternalDesc, but its own setters (SetExternalDesc(string desc), SetLong(string str)) were narrowly typed to string only -- widened both to mixed. This was the single largest shared-root-cause cluster in the compile sweep (std/obj/screen.lpc's SetLong((: eventView :)) alone cascaded into 77 reported failures across every object that inherits it). A second, narrower instance: lib/npcs/npc_interactive.lpc's pass-through SetShort(string str)/SetLong(string str) wrappers (used by every NPC) re-narrowed the already-widened base API -- widened to mixed too, fixing domains/valley/towns/jidoor/npc/sentry.lpc's SetShort((: CheckSetShort :)) and two similar NPCs.

3.10 lib/rooms/chapel.lpc -- TYPE * a, b, c; comma-list star-binding bug (AGENTS.md section 6.3)

private string *Classes, Deities, DeityIds;

The * binds to the first declarator only on this driver; Deities/ DeityIds are used as string * everywhere in the file (assigned array literals, indexed with member_array()), so both needed their own *. This file is inherited wherever a marriage/chapel room exists across the world, so the single missing * cascaded into dozens of reported compile failures.

3.11 Author-level content bugs found via the compile sweep (fixed as unambiguous, evidence-backed type/argument-shape corrections, not invented content)

4. Known pre-existing gaps (left undone -- content/archive design, not driver-compat)

5. Live verification

Booted clean under ~/src/fluffos/build-debug/src/driver (Accepting telnet connections on 0.0.0.0:40249., Initializations complete., zero fatal errors, ~290 MB RSS steady-state).

Confirmed a real outbound network connection at boot, same class already documented for ds386/dsIII/dshakkard: daemon/intermud (preloaded) connects to the public Intermud-3 router -- ss -tnp showed ESTABLISHED ... 97.107.133.86:8787 from the driver process, the exact same IMC2/I3 endpoint this lineage's other libs connect to. Do not sweep this lib into high-frequency automated re-boot loops.

Full registration -> world -> reconnect -> quit flow tested with a raw Python socket client (this project's standard admin identity wasn't set up for this lib -- see caveat below):

1. New player registration (Testchartwo, ASCII name -- this archive enforces a strict A-Z a-z name-charset with no digits, confirmed live: Testchar2 was rejected as "not valid or is banished" while Testchartwo was accepted): name -> confirm (y/n) -> email (required, validated non-blank) -> password (6-char minimum) -> confirm password -> gender (male/female) -> real name (blank -> defaults sensibly) -> race (list showed all 11 playable races; pick human) -> hometown (list showed 4 towns; pick haven) -> landed in "In the skies above Kailie", the newbie staging room, correctly wearing starter clothes (threadbare pants/ shirt/socks). 2. The newbie gate: the staging room's down exit is gated behind read book (completing the "Newbie Book" quest, which correctly awarded a real 160 development points and a quest/title credit live). Also confirmed the exit command itself is go down, not bare down (this codebase's core movement dispatch, unlike its cmds/-daemon-based commands, requires the go verb). 3. Reached real custom domain content: go down landed the character in "Above the Elven Charity" in Haven town (domains/southern_coast/towns/haven/) -- a fully-described, bespoke room (sleeping cots, a ticking clock, its own atmosphere text), not a stock/generic placeholder. 4. look/score/quit all produced correct output post-login; score in particular exercises a great deal of the player body class (level, alignment, native town/citizenship, faith, quest/ title counts, in-game birth date and age, development points, wimpy percentage, hunger/thirst/intoxication/temperature status, combat ratings, heart rate, active fears) and came back fully correct and consistent across two separate test characters. 5. Reconnect (name + password, no NetDead state from a live session): restored the SAME character in the SAME room ("Above the Elven Charity") with inventory intact and stats preserved (quest/title/development-point state correctly carried over from the prior session's read book) -- confirmed this is a genuinely distinct code path from fresh registration, not just a re-run of the same logic.

Caveat: this lib has no first-boot admin-install wizard (unlike the ds386-lineage Dead Souls forks) -- it's a live production game whose wizard hierarchy is populated by manual angel/decre promotion commands from an EXISTING admin account, and no such account exists in this fresh checkout. Admin/wizard-level testing (the fluffos/Mud@2026 identity this project normally seeds) was not attempted this session; only ordinary player registration and play were verified. A future session wanting wizard access would need to either hand-edit a save file's group membership or add a bootstrap path -- left undone here as out of scope for the native-boot verification bar.

6. Compile sweep

(ulimit -v 8388608; bash scripts/lpcc_check.sh libs/havenmud/ config.fluffos libs/havenmud/work) -- run under the 8 GB virtual-memory cap per this session's standing caution for large-corpus libs (a prior similarly-sized lib's unguarded batch compile grew to ~15 GB RSS before being killed); this lib's own batch run stayed under ~4 GB RSS throughout, well inside the cap. Iterated four times across this session as fixes landed:

| pass | pass | fail | notes | |------|------|------|-------| | 1 | 9,024 | 4,565 | before any fixes beyond the proactive checklist | | 2 | 9,268 | 4,321 | after the SetReligions/SetId(animate_*)/eventAttack reorder fixes | | 3 | 9,343 | 4,246 | after chapel.lpc/screen.lpc/SetId+SetFriends sweeps, GetInvis/SetPick/SetNoCorpse/VS_SHUTTLE/wound_hamstring | | 4 | 9,344 | 4,245 | after player.lpc, command.lpc, and the full [0..<3] sibling sweep (section 3.7-3.8) |

The remaining failures are overwhelmingly the section 4 classes above (the orphaned std/tower/ bolt-on alone accounts for ~70 files; personal-realm staging/backup copies and unfinished builder templates account for most of the rest) rather than driver-compat gaps -- the live playthrough in section 5 reached real, unrelated domain content with zero runtime errors, which is the stronger signal for this lib's actual playability than the raw batch-compile percentage (see this project's own documented lpcc-batch-vs-live-boot divergence class: a battery of daemon/-preloaded objects can report a bare "Fail to load object" with no visible error text at the point they're reached in a giant batch run, purely as a batch-mode artifact, while compiling and loading perfectly fine standalone or in a real boot -- confirmed directly on this lib for domains/valley/towns/jidoor/npc/ parcil.lpc and domains/crystal_reaches/towns/gwonish/npc/ sneak_trainer.lpc, both of which failed in-batch with no error text at all but compiled clean standalone -- and parcil in particular is a real NPC in the real Haven-adjacent town content, not orphaned or backup content, further confirming this is a batch-tooling ceiling on a 13,589-file lib rather than a real per-file defect).

7. §10.7 deep functional test (2026-08-31)

Round-two pass on the native driver (~/src/fluffos/build-debug/src/ driver, port 40249), building on top of the onboarding playthrough in section 5. Driven via scripts/tmux_mud.sh persistent telnet sessions (three sequential/parallel test characters: Deeptestor, Combattest, Finaltest), plus a ~210s idle long-sit boot watch (python3 scripts/mudclient.py --idle 210 --timeout 215) per AGENTS.md §10.0. One native driver reboot mid-session to pick up the two live fixes below; per section 5's own caution (daemon/intermud's real outbound I3 connection), reboots were kept to the minimum needed.

Two real bugs found and fixed, verified live

1. AGENTS.md §7.191 (new) -- daemon/help.lpc's LoadIndices() crashed on the very first help <topic> command any player ever ran, then stayed silently, permanently degraded for the rest of that boot. tmp = get_dir(DIR_ROOM_VERBS + "/*.lpc") + get_dir(...) + ... + get_dir(DIR_CRAFTING_VERBS + "/*.lpc") + ... -- /verbs/ crafting genuinely never existed in this archive (confirmed absent in raw/ too, not a conversion artifact), and get_dir() on a missing directory returns the driver's own bare integer 0 rather than an empty array, so array + int threw Bad type argument to +. Had array and int. uncaught. Live impact: a fresh character's help vendors (the exact topic that surfaced this) printed only "A runtime error occurred, use \"bug -r\" to report it." Because the crash happened PARTWAY THROUGH a create()-time one-shot function, every category meant to be assigned after that line (verbs, combat actions, immortal commands, player documents, immortal documents, towns, classes, races, religions, lore, library objects) was simply never populated for the rest of the boot -- confirmed by help human (a race) and help sword combat (a verb) both incorrectly returning "Help for the topic ... could not be found", and help index showing only 3 categories (commands, skill documents, stat documents) instead of the real 11. Fixed with a GD(string path) wrapper (mixed r = get_dir(path); return arrayp(r) ? r : ({});) substituted into every get_dir() call feeding a +-concatenation in LoadIndices() -- the correct guarded pattern was already sitting a few lines below in the SAME function for a different set of categories. Verified live twice: before the fix, help vendors/help human/help index all failed exactly as above on a fresh boot's first help invocation; after the fix and a clean reboot, the SAME fresh-boot-first-help sequence returned the real doc/help/players/vendors documentation text (confirmed by its own worked example, "Kelan"/"steel boots"), help human returned the real race blurb, and help index showed all 11 real categories. 2. **domains/averath/towns/averath/npc/chartercaptain.lpc -- SetFirstCommands("board ferry") passed a bare string where the real signature takes string *, so this NPC never compiled at all.** Same self-evident-typo class already catalogued in section 3.11 above (SetReligions/SetId/SetFriends bare-string calls). Impact: domains/averath/towns/averath/room/charter.lpc's SetInventory() tries to new() this NPC to run Averath's ticket-purchase travel feature ("buy a charter to anywhere you've been") -- with the NPC uncompilable, every attempt logs *No program in object '/domains/averath/towns/averath/npc/ chartercaptain'! to log/catch, and the whole charter-travel feature is permanently unusable. This also fires repeatedly and noisily during ordinary play: the stock ferry's own wander/sail logic (lib/npcs/shipcaptain.lpc) passes near/through the charter room while sailing the Haven<->Averath route, so the caught error logs on a recurring basis even for players who never try to charter anything. Fixed by wrapping the argument (({ "board ferry" })). Verified: lpcc standalone-compiles chartercaptain.lpc with zero errors after the fix (zero occurrences of the pre-fix Bad type for argument 1 of SetFirstCommands in a fresh log/errors/averath compile pass), versus the error present before -- not independently re-confirmed via a live charter purchase this session (the charter feature itself needs real travel-history state this session's time budget didn't reach), but the compile-blocking bug itself, which is the actual defect, is conclusively fixed.

Confirmed working live

Minor anomaly observed, not root-caused (flagged, not fixed)

Sibling bug-class check (per AGENTS.md §10.7 item 7)

Checked both catalogued Dead-Souls/Nightmare-lineage sibling findings this session was specifically asked to cross-reference:

Unverified / out of scope for this pass

WASM measurement (2026-09-03)

meta.json was already playable from the 2026-08-31 deploy-unblock; the README had no WASM line. Cold-boot under the shared ~/src/fluffos/build-wasm succeeded with no mudlib-side compile fix (FTP/socket_* failures in secure/lib/net/ are graceful preload skips). Verified with scripts/wasm_client.js: new character wasmhav / confirm / email / Mud@2026 / male / real name / pick human / pick haven. Landed in "In the skies above Kailie" (newbie book visible, down exit), score showed "You are Wasmhav, a level 0 Human" / "Citizen of Haven", quit printed "Please come back another time!" / "Wasmhav logs out." Shop/combat/death were not exercised this pass. The MEMFS copy does not write the throwaway character back to the host. Do not loop-reboot (I3).

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

scripts/lpc_fix_negative_ranges.py: 1 line(s) in 1 file(s) count a range from the end with < (x[a..-1] -> x[a..<1], x[-2..] -> x[<2..]; KB 06 §7.209). In this driver a negative constant end gave "" / ({ }) and a negative start the whole value, so each of these returned the wrong slice; the compile warning A negative constant as the second element of arr[x..y] is gone from the files that compile. Range lvalues (s[0..-1] = text, the prepend idiom) are left alone. A paired compile of the changed files at HEAD and in the working tree showed no new error.

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

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

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

secure/daemon/master.lpc 的 compile_object() 用 else if 先建 players 目录、再建字母目录:父目录不存在时只建父目录,这位玩家第一次存档(设密码时)打开 players/<字母>/<名字>.o.tmp 失败。git 不跟踪这个目录下的文件,全新检出与网页版每位访客都会遇到。第二个 else if 改为 if(与 dsI 相同)。nightmare4 上用只含被跟踪文件的树实测注册→重登通过;本库主控编译通过。 另:/secure/daemon/remote(远程创作者协议)从预载中注释掉。它按 Foundation II 的旧套接字接口写成,本库的 server.lpc 接口不同,每次启动编译失败;协议还需要每位创作者的 ~/adm/remote 对象,本档案没有。与 dsI、dsII、nightmare4 及 fluffos 组织的 Dead Souls 上游相同。 另:lib/verbs/jump.lpc 的 GetJumps() 只写了 Jumps;,永远返回 0,补上 return(与 nightmare4 的 lib/events/jump.lpc 同一处)。

深度功能测试(§10.7,2026-10-05)— 编译警告(机械部分)、新角色邮箱存档失败

1. 新角色的邮箱存档失败。 secure/daemon/options.lpc 的 assure_box_exists() 只建 postal/<字母> 与 postal/<字母>/<名字>,不建 DIR_POSTAL(secure/save/postal)本身;git 不跟踪这个目录下的任何文件,所以全新检出与网页版里它不存在,每个新角色进入游戏时 Could not open /secure/save/postal/q/qahaven/postalrc.o.tmp for a save。先建 DIR_POSTAL 再建下层。nightmare4、dsI、dsII、foundation2 在这个目录下有被跟踪的文件,不受影响。 2. 编译警告(机械部分,KB 04 §6.10):scripts/lpc_warnings.py havenmud --fix,12535 个 nosave 函数改 protected(被别处按名调用的 244 个保持 public)、删 562 个未用局部变量、补 173 处 varargs、75 处缺返回值的 return;、3 处无效转义;lpc_audit_removed_locals.py 复核 719 个被删声明,0 个需要处理。§9 格式化只动了 6 个文件。启动无错误;剩下的警告是结构性的:RadiantLight(2635)、CommandFail(2418)等由同一 mixin 经两条路径继承造成,与 nightmare4 同源,但本库的房间和 worn_storage 真的会用到容器那份光亮变量,不能照搬 nightmare4 的做法,留作下一步单独处理。 3. 用只含被跟踪文件的树(git archive + 本次改动)新建角色 qahaven(人类、Haven):天空房间、score、inventory、smell、who、quit 正常,驱动输出没有权限拒绝。单打 down 不是命令(本库默认别名只有 d/u 等缩写,go down 可用并提示先读新手书),属设计。 4. 编译不过的文件多为档案残留:巫师个人目录里过时的区域副本(如 realms/alessandra/malveillant 引用已搬到 domains/havenwood 的 ../mal.h)、std/tower(另一个 mudlib 的区域,引用本库没有的 mudlib.h/types.h)、domains/averath/oldareas。 5. lib/users/nmsh.lpc 声明了从未使用的 private nosave string CommandFail,与 lib/livings/command.lpc 的同名变量在玩家和 NPC 里重复,删掉后 2418 条重复声明警告消失(与 nightmare4 相同)。

深度功能测试(§10.7,2026-10-06)— 光亮变量的重复继承(KB 06 §7.213)

lib/common/container.lpc 继承 LIB_RADIANCE,而 npc、玩家、储物箱又经 LIB_OBJECT/LIB_ITEM 带一份,于是每个这样的类里有两份 RadiantLight,编译时打印 2635 条重复声明警告。与 nightmare4 不同,本库房间(SetRadiantLight() 设的房间自身光亮)和 worn_storage(会发光的可穿戴容器)真的读的是容器那一份,不能直接删掉。改法:容器不再继承 LIB_RADIANCE,改用 protected int GetContainerLight() 取“自身光亮”,默认 0;lib/rooms/room.lpc 自己继承 LIB_RADIANCE 并在这个钩子里返回它,lib/items/worn_storage.lpc 返回 armour 那一份,两者的总光亮仍由容器(holder)一侧计算(自身加内容物)。lpcc 探针前后一致:房间 SetRadiantLight(4) 得 4,可穿戴容器 2 得 2,NPC 3 得 3,可穿戴容器放进房间后房间得 6。重复声明警告总数从 7781 降到 2764,RadiantLight 不再出现;余下的是 Short、Smell、Touch 等同一结构的其他 mixin,留待逐个处理。用只含被跟踪文件的树新建角色 zzlite:天空房间、inventory、score、读新手书后 d 进入 Elven Charity 楼上房间、quit 正常。

2026-10-06 — 剩余的重复声明

lib/special/action.lpc 自己的字符串 Help(SetHelp/GetHelp 用)与父类 verb.lpc 的私有映射 Help 同名,改名 ActionHelp(37 个子类都不直接读写它)。另记两处未改:lib/users/player.lpc 同时经 LIB_INTERACTIVE 与 LIB_LIVING(body.lpc)继承 move.lpc,isRiding 有两份并都存档(KB 06 §7.233:登录后函数读的那份被清零,骑乘状态不跨登录保留);creator 只继承 interactive 且调用 move::eventMove,不能简单去掉一条继承。lib/items/meal.lpc 自己的 Long/Short 与物品基类的私有 Short 同名,而 AddSave(({ "Long", "Short" })) 按名字取变量时取到的是先继承的那份,餐食自己的描述可能没有存档,待查。

深度功能测试(§10.7,2026-10-06)— 两处同名变量的结论

深度功能测试(§10.7,2026-10-10)— 重复继承与重复变量(第三轮)

分块扫描器整库重扫:仍有 5714 条警告,其中 2838 条 Redeclaration of global variable、几百条 inherited from both,绝大多数是同一个程序经两条继承路径进入(KB 06 §7.213)。

改动的 66 个文件加上全部 LIB_MEAL 子孙(522 个物件),HEAD 与工作树各载入一次:518 -> 519 通过,无回退;这批文件的警告 1923 -> 642。聊天相关 7 个物件 7 -> 7 通过,警告清零;带改动的只含被跟踪文件的树启动无运行错误,出现登录提示。剩下的重复声明大多在巫师目录 realms/ 下,以及既是武器又是护甲的几件物品(部分重叠,需逐个判断);机械类警告留给随后的整库整理。

- lib/items/light.lpc 自带一个空的 void AddSave(mixed n) {}(只为单独编译),在尸体(corpse.lpc 继承 storage、cook、light)上它压过了 persist 的真 AddSave,尸体的 AddSave() 全部无效。改为照 ds386 调 this_object()->AddSave(({ "Lit" })),删去空函数;lib/livings/combat/magic.lpc 的同类空函数改为原型。 - verbs/actions/jab.lpc:if (limb == "head") limb == "throat"; 和反向那行都把 = 写成 ==,消息永远显示 head;改为赋值(先显示 throat,扣血前换回 head,原作者的本意)。 - 11 个基础类的 inherited from both(item、armour、skin 的 CanSell、meal/pole/seal/storage 的 GetSave、combat 的 AddSave/GetBaseStatLevel、basenpc/player 的 look 系列、creator 的存档函数)用 scripts/lpc_name_winner.py 写明驱动本来就选的那份(逐个核对过:都是真实现,不是空壳),行为不变。 - 原型与定义不符:sefun.h 的 time_event 返回 function、ftp_data_connection 的 eventClose 返回 int、body.h 的 eventDisplayStatus 返回 int、command.h/nmsh.h/command.lpc 的 GetClient 与 interface.lpc 的 int Client 一致、npc_interactive 的 SetId/SetCapName 返回基类的值、action.lpc 的 SetHelp 返回映射。 - include/setup.h 的 mapping Religions 与 LIB_SPELL 的同名变量冲突(两个传送法术在继承后又包含它),加 SETUP_NO_RELIGIONS 开关;两个法术里 /* 写在块注释中间、consider.lpc 与 attack.lpc 的 ?: 两边类型不一。 改动文件加 700 个物品/NPC 抽样共 725 个物件,HEAD 与工作树各载入一次:719 -> 719 通过,无回退;带改动的只含被跟踪文件的树启动 0 警告,登录正常。

深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查

深度功能测试(§10.7,2026-10-12):编译警告收尾

Warnings in files that compile: 4090 (corpus scan, 191 files) -> 360 (10 files). Almost all of the 4090 were Redeclaration of global variable / inherited from both rows repeated from a few roots (KB 06 §7.213); the roots and what each one was hiding:

Left (360 warnings, 10 files): the armour-weapons (babyclaw, bearclaw, knuckles, wantlers, two ironfist, realms/zalbar/.../sphairai: LIB_ARMOUR + LIB_WEAPON, a real two-class hybrid whose split is a design call), realms/zalbar/.../natural_herb_ob (LIB_BURN + LIB_MEAL), the realms/mahkefel/wagon experiment (enter + exits + storage), and player.lpc's move.lpc reached through both interactive and living (2 rows).

Checked with probes HEAD vs tree (a shirt's and a key's GetSaveString(), an item's GetPreventDrop(), the smithy's touch, the wagon's name, the sap mask and alpenstock saves: each differs only as described above) and with lpc_listcheck.sh over the 129 changed files, every includer of a changed header, every key, help command, fear/sage/fishing/leader/mayor leaf, the clothing set and 400 random domain objects: 1150 objects, 1089 -> 1089 load, 0 regress (1150 个对象载入无回退).

Deep functional test (§10.7, 2026-10-14): compile warnings under FluffOS 970d9f09

The rebuilt driver (970d9f09) reported 419 warnings in files that compile (521 error files, unchanged): 107 Redeclaration of global variable, ~250 f() inherited from both rows, 58 Float value truncated to int, one bitwise operation on boolean values. After this pass: 101, all in one superseded draft (below); 521 error files.

Method: lpc_warnings.py rescans, lpc_fix_float_trunc.py --apply plus hand edits for the sites it lists, structural fixes for the diamonds by reading which copy each function reads and writes.

Real bugs found and fixed:

Left: realms/zalbar/oldfiles/projects/obj/natural_herb_ob.lpc inherits LIB_BURN and LIB_MEAL, two complete LIB_ITEMs (101 warnings). It is a superseded draft: the herb the spell clones, std/spells/obj/natural_herb_ob.lpc, is the same object with LIB_MEAL only. Making the draft warning-free means rewriting it, so it stays as it is.

Load check (lpc_listcheck.sh, HEAD vs tree) over every changed file, all of /lib, every npc/ object and every teller/shipseller/mayor/trainer leaf: 2432 objects, 2312 -> 2313 load (the new base), 0 regress. lpc_audit_removed_locals.py HEAD: nothing for havenmud.

Macros the global header lost (2026-10-19, KB 04 6.16)

/secure/include/global.h did not define BLADE, KNIFE; the objects that use them (2 on the list) stopped at Undefined variable/Undefined function, and since the compiler stops at the first undefined name the errors and warnings behind it stayed hidden. The sibling libs of the same lineage carry these lines in their headers: the copy of the sibling with the most files in common was added after the last #define of /secure/include/global.h; every path in a macro body exists in this lib. the listed objects have other errors as well and still do not load. (scripts/lpc_restore_defines.py)

Old-driver type slack: missing arguments, untyped parameters, array initializers, class variables (2026-10-19, KB 04 6.19, 6.20)

MudOS filled the missing arguments of a call with 0, took an untyped parameter as mixed, never compared a declared type with its initializer and let a variable be named class; this driver refuses all of it at compile time and the whole object fails. 21 file(s) fixed: /realms/vaashan/projects/crypt/room/fronthall.lpc, /realms/vaashan/projects/crypt/room/garden.lpc, /realms/vaashan/projects/crypt/room/gate.lpc, /realms/vaashan/projects/crypt/room/room1.lpc, /realms/vaashan/projects/crypt/room/room10.lpc, /realms/vaashan/projects/crypt/room/room11.lpc, /realms/vaashan/projects/crypt/room/room12.lpc, /realms/vaashan/projects/crypt/room/room13.lpc ... (21 in all) (the repair rounds of scripts/lpc_round_files.py: zeros written out at the call, mixed for untyped parameters, string X = ({...}) given its star, void functions returning values, and class variables renamed pick_class). Loaded at HEAD and in the tree, per file: 0 of them failed before and load now, none regressed.

x == "a" || "b":后面的字符串常量永远为真(2026-10-19,KB 06 §7.281)

if (arg == "yuyou" || "yu you") 里的 "yu you" 是非空字符串,永远为真,整个条件对任何 arg 都成立;作者要的是每个字符串都和变量比较。在每个字符串前补上变量和运算符(scripts/lpc_fix_literal_or.py;forrest2.lpc 是 dir == "north" && "south",手工改成 (dir == "north" || dir == "south") && ...):/domains/crystal_reaches/towns/karak/obj/fountain.lpc(1 处)、/domains/valley/areas/castle/npc/guest.lpc(1 处)。

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). Changed: /lib/users/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, 7 terms). Changed: /lib/livings/lead.lpc (1), /verbs/actions/buckle.lpc (1), /verbs/actions/chomp.lpc (1), /verbs/actions/gouge.lpc (1), /verbs/actions/intercept.lpc (1), /verbs/actions/shiv.lpc (1), /verbs/actions/skewer.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).

immortals 和 quote(2026-10-21,KB 06 §7.336)

secure/cmds/common/immortals.lpc:foreach (string letter in unguarded((: get_dir, DIR_CRES "/" :))),archive 里没有创造者目录,get_dir() 对不存在的目录答 0,foreach 报 Bad argument 2 to foreach;两层 get_dir() 都改成 ... || ({})。daemon/quotes.lpc 的 eventDisplayQuote():没有存档,名言列表是空的,Quotes[random(sizeof(Quotes))] 越界;列表为空时对玩家说 There are no quotes yet.。屏幕上这两条都只显示 A runtime error occurred, use "bug -r" to report it.,驱动控制台是安静的,真正的错误在 log/runtime(play 树里)。实测:immortals 列出空的头衔表,quote 有回答,quote 句子~作者 添加后能显示。

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 (2 files, the efun that took the 0 in brackets): /cmds/admins/furry.lpc (explode), /cmds/creators/move.lpc (sscanf). Load check HEAD against the tree: no regression.

new(MISSING)->move() / carry_object(MACRO(..))->wear() chains guarded (2026-10-22, KB 06 §7.73 addendum)

A statement new(PATH)->move(...); / carry_object(ARMOR_D("x"))->wear(); whose path names no file of this archive: new() answers 0 and ->move() on it raises Bad argument 1 to EFUN call_other(). In create() the object never loads, in reset() every spawn below the first missing one is skipped on every reset. 1 statement(s) in 1 file(s) (new) are now { object new_ob = new(PATH); if (new_ob) new_ob->move(...); } (scripts/lpc_guard_carry_object.py --new: the path is resolved with the macros the file itself sees, an ambiguous macro or a virtual-object path is left). Load check of the 1 changed files, HEAD vs tree: 0 load at HEAD, 0 load now (0 newly loading, 0 regress).

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 4 subdirectories of log/: log/adm (cmds/creators/tc.lpc), log/edits (secure/cmds/creators/ed.lpc), log/p_stats (secure/daemon/player_stats.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/adm, log/edits, log/p_stats, log/secure/tests 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.

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

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

无参数时命令对 0 的调用(玩家/--all-dirs 残留,2026-10-27,KB 06 §7.336 Also)

无参数命令把 0 交给 present / sscanf / file_exists 等会运行时报错。lpc_fix_bare_arg.py --all-dirs 在首次使用参数前加守卫。多行 if (arg && !sscanf)(吐纳默认消耗)与已有 can_learn/get_object 守卫的误报已回退;本库本批改动见该提交。