The Brass Ring

✅ 可玩

brassring

更新 3e5694f 2026-10-27 源码 下载 ZIP 上游 LashMUD/TheBrassRing

▶ 开始游玩 · Play Now

A Dead Souls 3.8.x-based game-in-development (self-titled 'The Brass Ring') that ships as a content overlay for Dead Souls rather than a full mudlib on its own. On top of the underlying Dead Souls engine, TBR bundles a full DikuMud Alfa port (domains/diku-alfa/, 1185 files -- Midgaard and its zones) and is actively building its own bespoke fantasy continent, Etnar (the village of Wyr, with NPCs Albert Derby/Waltin Kelley/Caltan Dowini/the Pierce family already in place and a virtual wilderness room system). Character creation includes an age gate, a screen-reader accessibility question, an 8-race pick, and a complete 6/4/4 primary/secondary/minor skill selection across all 28 skills; new characters enter play in Saquivor, the real starting town (distinct from the tutorial start room), from which Etnar's Wyr (The Lounge) and diku-alfa's Temple of Midgaard are both reachable. No outbound network connections at boot -- unlike several sibling Dead-Souls-family libs, this one is safe for high-frequency automated re-boot loops.

README

A Dead Souls 3.8.x-based game-in-development. The village of Saquivor is the starting town; from there, roads lead west past the old church, across a humpbacked bridge, and into a dark forest. Bundled alongside the game's own custom content is a full port of the classic DikuMud Alfa world (the city of Midgaard and its surrounding zones) and the beginnings of a bespoke fantasy continent called Etnar -- the village of Wyr, with the Bramblewood and Vexwood forests and the town of Orgon planned around it.

Source: gh repo clone LashMUD/TheBrassRing. Slug brassring, number 963, port 40265.

Etnar

Etnar is the game's own original setting-in-progress, not carried over from the base Dead Souls or DikuMud content. The village of Wyr is the region the author was actively building at the time of this snapshot, with rooms and NPCs (Albert Derby, Waltin Kelley, Caltan Dowini, the Pierce family) already in place, plus a virtual (procedurally-served) room system covering the wider wilderness. The char and q'in subdirectories are reserved for future regions the game's own readme.txt describes as "the lands of Q'in and Char."

Status

Boots clean under this project's native FluffOS driver, zero compile or fatal errors. Verified live with two independent new characters: full registration (name, age gate, screen-reader question, password, gender, email, race pick, and a 6/4/4 primary/secondary/minor skill selection covering all 28 skills) all the way into the game world; enter town correctly reaching Saquivor's "Village Road Intersection"; Etnar's Wyr village and the bundled DikuMud content both reached and rendering correctly; look/score/update/quit all producing correct output; and reconnecting restoring the same character, in the same room, with the same inventory.

Admin account: fluffos / Mud@2026 (this project's standard seeded admin, granted by moving its save file into secure/save/creators/ after a normal registration -- see NOTES.md section 5).

WASM status: playable. Shared WASM driver. Admin login (fluffos / Mud@2026) through the news pager into the Temple of Midgaard, plus hp: 430/430 / quit ("Please come back another time!"), verified with scripts/wasm_client.js. Play: https://mudlibs.fluffos.info/brassring/

See NOTES.md for the full porting log, including how this session reconstructed the game's entire missing security-daemon layer (this repo never once committed a secure/master.lpc/simul_efun engine of its own -- it ships as a pure content overlay meant to be dropped onto an existing Dead Souls 3.9 install) and the real programming bugs found along the way.

No outbound network connections at boot (IMC2 is disabled by this game's own shipped config, and its Intermud daemon never actually opens a socket) -- unlike several other Dead-Souls-family libs in this collection, this one is safe for high-frequency automated re-boot loops.

Local run

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

Game port: 40265.

NOTES · 移植与修复记录

The Brass Ring -- porting notes

Source: gh repo clone LashMUD/TheBrassRing (cloned 2026-08-31). Slug brassring, number 963, port 40265.

1. What this repo actually is: a content-only overlay, not a full mudlib

The repo's own README.md/Setup.txt say it outright: this holds "files based on the Dead Souls mudlib ... used to develop The Brass Ring mud," and the setup instructions are literally "download the dead souls mud library from http://dead-souls.net ... then replace the files in the ds3.9/lib directories with the files in this repository." Confirmed by file counts: the repo ships only 1,717 files total, and per-directory counts are a small fraction of a real Dead Souls tree (daemon/: 2 vs ds386's 60; secure/: 26 vs 568; cmds/: 8 vs 150; lib/: 21 vs 318). Only the files the author actually touched or added were ever committed.

The hard consequence: secure/daemon/master.lpc, secure/sefun/sefun.lpc, and the entire rest of the security-daemon engine layer were never committed to this repo at all -- not gitignored, genuinely absent from git history (git log --all -- lib/secure/daemon/master.c returns zero commits, and there is no .gitignore in this repo at all). Without a master.lpc the game cannot boot under any driver.

Bundled inside the same repo (not a separate clone) is a complete port of DikuMud Alfa (domains/diku-alfa/, 1,185 files -- the city of Midgaard and its zones) with a top-level note crediting "the DikuMud to Dead Souls port." The sibling repo LashMUD/DikuMud-to-Dead-Souls-Port was checked and found to be the same content already folded into this repo (confirmed file-for-file on room/90.zon/rm_9000.c, byte-identical to the truncated file present here -- see section 6.4) -- it did not need to be cloned separately.

2. Reconstructing the missing security layer, following the ds386/dshakkard precedent

This is the exact situation dshakkard (185, ds-hakkard) and, before it, the general Dead-Souls-lineage guidance in AGENTS.md section 1.5 already cover: ds386's own raw archive, and its own already driver-compat-fixed work/ tree, are the correct substitute for a missing Dead Souls engine layer -- not invented content, since ds386 (Dead Souls 3.8.6) is confirmed the same lineage TBR is built on (its Setup.txt says "ds3.9," close enough that dshakkard's own precedent -- also a 3.8.6-vs-3.9-labelled game -- used the same ds386 substitution successfully).

Procedure actually followed, at whole-tree scale rather than just secure/:

1. rsync -a --ignore-existing ds386's pristine raw archive (libs/ds386/raw/ds3.8.6/lib/) onto TheBrassRing's own raw clone (libs/brassring/raw/lib/). --ignore-existing means TBR's own 1,717 files were never touched; every directory or file TBR didn't ship (all of secure/daemon, secure/sefun, most of cmds/, entire top-level directories like estates/, realms/, save/, www/, etc.) was filled in from ds386's pristine source. Verified after merge: 573 files under secure/ (568 from ds386 + TBR's own 26, with only overlapping paths -- secure/cfg/preload.cfg, the secure/include/*.h set, secure/daemon/{autoexec,finger,players}, secure/lib/connect, secure/obj/medtric, secure/sefun/light, and one each under secure/cmds/admins and secure/cmds/creators -- correctly kept as TBR's own versions). 2. Ran scripts/convert_lib.sh on the merged raw tree (GB18030->UTF-8 -- a no-op here, this repo was already plain ASCII/UTF-8 -- .c->.lpc rename, static->nosave, literal .c" reference fixes) to produce libs/brassring/work. 3. Then, critically, went one step further than a plain "copy ds386's raw" substitution: every file the mechanical rename step touched that TBR itself never shipped (identified precisely via git status --porcelain --untracked-files=all inside the raw clone, since the rsync --ignore-existing step left ds386's added files untracked in that git working copy -- 4,676 files) was replaced again, this time with ds386's own already driver-compat-fixed work/ copy of the same path (after the .c->.lpc rename). This reuses every hand-fix ds386's own onboarding already discovered (the get_root_uid()/get_bb_uid() stubs, the valid_read/valid_write lazy-load reentrancy guard, etc. -- see AGENTS.md section 7.1/7.2) instead of rediscovering them from a fresh mechanical-only conversion of ds386's *raw* (unfixed) source. All 4,676 substitutions matched a real path in ds386's work/ -- zero misses. 4. TBR's own 1,717 files went through nothing but the ordinary mechanical convert_lib.sh pass; they needed their own, TBR-specific fixes on top (section 3 below).

This means secure/daemon/master.lpc, secure/sefun/sefun.lpc, secure/daemon/players.lpc's non-TBR sibling pieces, and every other inherited engine file in this lib are not reconstructed from scratch in the sense of writing new code -- they are ds386's own real, already-tested Dead Souls 3.8.6 engine, verified to be the correct substitute the same way dshakkard verified it (every /secure/* path TBR's own code actually references was checked to exist in ds386, and TBR's shipped secure/include/config.h and secure/cfg/* are TBR's own real customized versions, not substitutions).

3. Genuine programming bugs found and fixed (TBR's own code, or the bundled diku-alfa content)

None of the bugs below live in the reused ds386 engine layer -- that part came pre-fixed. All of them are in content this session actually had to touch: TBR's own custom files, or the bundled DikuMud port.

3.1 array is not a reserved word in this driver build -- pervasive TYPE array NAME old-MudOS dialect (AGENTS.md section 6.3, much larger instance than previously catalogued)

~/src/fluffos/src/compiler/internal/lexer_utils.cc only lexes the word array as the L_ARRAY token when the driver is built with ARRAY_RESERVED_WORD defined -- this build isn't, so array is a plain identifier. Every TYPE array NAME declaration (global, local, function-return-type, and function-parameter forms alike) is a hard syntax error here: the parser reads TYPE as the type, array as the *declared name*, and chokes on whatever follows. AGENTS.md section 6.3 already catalogues this class from isolated single-lib instances; here it was the single largest driver-compat fix of this session -- found in every layer of TBR's own hand-written code (lib/body.lpc, lib/combat.lpc, lib/combat_r_o.lpc, lib/living.lpc, lib/genetics.lpc, lib/base_trainer.lpc, lib/meal.lpc, lib/props/deterioration.lpc, lib/std/base_armor.lpc, lib/lvs/abilities.lpc, daemon/help.lpc, daemon/seasons.lpc, secure/lib/connect.lpc, secure/daemon/finger.lpc, secure/daemon/players.lpc, secure/cmds/admins/mudconfig.lpc, secure/obj/medtric.lpc, powers/npc/magic_user.lpc) and in the bundled diku-alfa content (domains/diku-alfa/etc/{magic_user, base_trainer}.lpc and 4 cityguard NPCs) and etnar's own bespoke NPCs (albert_derby.lpc, waltin_kelley.lpc) -- 37 files, ~120 sites.

Because lib/body.lpc and lib/player.lpc (see 3.2) are inherited by essentially every player/NPC object, this single bug class cascaded into 1,873 of 3,800 files (49%) failing a full lpcc_check.sh batch sweep before it was found and fixed -- a textbook instance of AGENTS.md section 6.4's "one shared root cause, not N bugs."

Fix: mechanical script (fix_type_array.py, not checked in) that drops the word array and inserts the real TYPE *NAME pointer-array syntax, adding one * per comma-separated declarator (the classic "TYPE * a, b; only stars the first name" sub-bug -- section 6.3's other variant -- would otherwise reappear if only the first name in a list got starred). Two shapes needed hand-fixing after the script ran:

3.2 lib/player.lpc: un-starred oldparties -- the exact bug already catalogued on ds386's own raw archive

Setup()'s string oldparties = PARTY_D->GetOldParties(); (missing the *) is passed to RemoveExtraChannels() and foreached as if it were an array, causing Bad type for argument 1 of RemoveExtraChannels ( string * vs string ). This is not a new discovery -- dshakkard's own meta.json already documents "a real un-starred string oldparties bug in lib/player.lpc present byte-identically in ds386's own raw archive" from that session's TYPE * a, b;-class sweep. TBR's lib/player.c is its own tracked file (not reused from ds386's already-fixed copy), so it carried the same pre-existing upstream typo forward unfixed. Confirmed the fix (ds386/work/lib/player.lpc already has string *oldparties) and applied the identical one-character change. This bug alone masked a huge share of the section 3.1 cascade, since player.lpc is inherited by every player body -- fixing it (after fixing every direct TYPE array site) is what let the corpus-wide compile-sweep failure rate drop from 49% to under 1%.

3.3 domains/etnar/wyr/wyr/npc/pierce/pierce_pascal.lpc: missing closing brace drops a whole function

checkPrank() opens 2 extra brace levels (an if block, then a switch) but only closes 1 before the file moves on to checkPlacement() -- a hard "unexpected (" parse error at the next function's own declaration, since the parser is still inside checkPrank()'s body. Confirmed genuinely missing (not a convert_lib.sh fallout) by a raw brace count (21 open, 20 close) and by diffing against the sibling draft file pierce_pascal_1.lpc (a much later, unfinished rewrite of the same NPC's logic, not currently inherited/loaded by anything -- left as-is, it's the author's own in-progress scratch file, not dead weight to clean up). Fixed by adding the one missing }.

3.4 domains/diku-alfa/room/74.zon/npc/7044_blob_lemure.lpc: SertClass() typo

SertClass("fighter") -- undefined function; should be SetClass(). A plain one-character transposition typo, one instance, in the bundled diku-alfa content (not TBR's own file). Fixed directly.

3.5 domains/diku-alfa/room/79.zon/meals/6013_water_barrel.lpc: wrong argument type to SetMealType()

SetMealType("water") -- SetMealType() (lib/meal.lpc) takes the int bitmask constants from include/meal_types.h (MEAL_FOOD/MEAL_DRINK/etc.), not a string; every sibling meal file in the same directory (e.g. 7090_waybread.lpc) correctly passes MEAL_FOOD. Fixed to SetMealType(MEAL_DRINK), matching the object's actual purpose (a water barrel).

3.6 #define __DIR__ collisions in etnar's and diku-alfa's virtual-room servers -- same class dshakkard already catalogued

16 virtual/server.lpc/virtual/void.lpc files (all 6 etnar sub-regions -- virtual, char, q'in, wyr/baramblewood, wyr/orgon, wyr/vexwood, wyr/wyr -- plus diku-alfa/virtual/) carry a manual #define __DIR__ "/domains/<name>/virtual/" that collides with this driver's own native __DIR__ predefine ("Illegal to redefine a predefined value"). AGENTS.md/dshakkard's own notes already catalogue this exact pattern ("6 bespoke-domain virtual-room files with the redundant manual #define __DIR__", fixed there by deleting the line since the driver's own value is already correct). Same fix applied here, at larger scale (16 files, since both etnar and the bundled diku-alfa port use the standard Dead Souls virtual-room template).

4. Config changes for playability (not bugs -- shipped defaults that would have blocked testing)

Confirmed no live outbound network connections at boot: TBR's own secure/include/config.h ships DISABLE_IMC2 1 (verified this actually gates secure/daemon/imc2.lpc's socket_create()/ socket_connect() calls behind #if DISABLE_IMC2, not just a cosmetic flag); DISABLE_INTERMUD is 0 but daemon/intermud.lpc makes no socket calls of its own at all in this codebase (it's a local chat-channel abstraction only). No RELEASE_NOTES/version-check HTTP fetch runs automatically either (TBR's own secure/daemon/autoexec.lpc is much smaller than ds386's and only calls IMC2_D->UnSetAutoDisabled(), itself a no-op while DISABLE_IMC2 is set). Unlike ds386/dshakkard/havenmud, this lib is safe for high-frequency automated re-boot loops.

5. Admin seeding (fluffos / Mud@2026) -- the Dead Souls file-move convention, made easier by a lucky default

Per AGENTS.md section 1.5's Dead-Souls-lineage note: admin-ness here is a save-file-directory fact (secure/save/creators/<letter>/<name>.o vs secure/save/players/<letter>/<name>.o), not a rank flag. Procedure followed exactly as documented on riftsds: registered fluffos through the completely normal new-player flow (name/age/password/ gender/email/race/skill-selection, AUTO_WIZ left at its shipped 0 so no special creator prompt was needed), then with the driver stopped, moved secure/save/players/f/fluffos.o to a newly-created secure/save/creators/f/fluffos.o, then rebooted.

One bonus finding: TBR's own shipped secure/cfg/groups.cfg already lists fluffos in every ACL group (SECURE, ASSIST, ELDER, TEST, INTERMUD, BUILDER, TELNET, EMOTES) -- confirmed byte-identical to ds386's own default groups.cfg, i.e. this is Dead Souls' own stock placeholder example name, not something this project seeded. So the file-move above was the *only* step actually needed; groups.cfg required no edit.

Verified: update /domains/town/room/start succeeds with no ACL denial as fluffos (creator-only compile permission confirmed working), and fluffos can goto anywhere in the world (used to verify the etnar and diku-alfa content -- see section 6).

6. Live boot + playthrough verification

Boots with zero compile errors and zero fatal errors in log/debug.log across every test session in this write-up (grep count: 0 every time). lpcc_check.sh full-corpus batch sweeps hit FluffOS's normal "batch mode never unloads" memory ceiling on this unusually large corpus (3,800 files -- the full Dead Souls 3.8.6 engine plus the bundled 1,185-file DikuMud port) before finishing a complete pass every time it was tried (killed by this session's own 8-9.5GB RSS safety net, per AGENTS.md section 10.4's documented memory-balloon caution for mega-libs); the largest completed sample covered 2,590 of 3,800 files (68%) with a 99.2% pass rate and zero unexplained failures after the section 3 fixes landed, so per section 10.4's own guidance ("on mega-libs the boot + interactive test is the sufficient verification gate") the live boot below is the authoritative check, not the partial sweep.

Verified live, twice with two independent new characters (fluffos/qintest) plus one full reconnect:

7. A known, pre-existing content gap (not fixed -- matches upstream, not a driver-compat bug)

domains/diku-alfa/room/90.zon/rm_9000.lpc is genuinely truncated mid-create() (opens the function, sets climate/light, then the file just ends -- no closing braces at all). Confirmed this is not conversion fallout: cloned the sibling repo LashMUD/DikuMud-to-Dead-Souls-Port directly and its room/90.zon/rm_9000.c is byte-for-byte the same 17-line truncated file. This is an authentic upstream content gap in the original DikuMud port, left as-is per this project's content-vs-driver-compat boundary (AGENTS.md's standing rule: fix programming bugs, don't invent missing content).

Also left as-is, matching classes already documented on ds386/dshakkard's own lpcc sweeps (pre-existing, non-bugs): obj/stargate.lpc's deliberate #include "this is here on purpose to prevent this from loading" anti-load trap; secure/cmds/admins/ opcprof.lpc's call to the opcprof() efun, which this driver doesn't implement; and two builder scratch/template files (open/prog.lpc's $S template placeholder and #define with no name, obj/area_room.lpc's reference to a ../customdefs.h template file that was never meant to exist standalone).

8. §9 LPC formatter

Ran format-corpus.mjs across all 3,817 .lpc files: 3,812 reformatted, 5 unchanged, 0 errors. All three documented blind-spot checks came back clean: zero ::-parent-call-split hits (grep -rnE ':\s:\s*[a-zA-Z_]+\(' ), zero damaged-escape-sequence hits (grep -rl '\\ n'), and a manual review of every case LABEL: //comment line in the corpus (diku-alfa's magic_user.lpc switch statement and secure/daemon/imc2.lpc's mode-name comments) confirmed no comment-swallowing damage -- all case bodies and fall-throughs intact. Re-booted and re-ran the full fluffos-reconnect smoke test after formatting: zero new compile/fatal errors, same room/inventory/output as before formatting.

9. §10.7 deep functional test (round two, 2026-08-31)

Full continuous native-driver session beyond the onboarding smoke test, with a fresh mortal (Questora, human, PRIMARY blade/melee attack+defense/stealth/detection, SECONDARY bargaining/healing/ tracking/concealment, MINOR faith/fishing/stealing/multi-hand) driven via scripts/tmux_mud.sh, plus the seeded fluffos admin for eval/goto-assisted navigation between this lib's disconnected content clusters (etnar's bespoke domain and the bundled diku-alfa port share no real exits with domains/town, confirmed by grep -- eventMoveLiving(), the same primitive secure/cmds/creators/goto.lpc itself uses, is the correct way to relocate a living object here; object->move(dest) is NOT a real function in this codebase and silently no-ops with no error via ->-dispatch -- a footgun worth recording for any future session scripting movement here).

Tooling note: this lib's registration flow ("Do you use a screen reader...") drives an ANSI-cursor-addressed overhead-map/stat-panel HUD that assumes a normal ~24-row terminal. A tmux_mud.sh-style pane taller than that (the tool's default -y 500) makes tmux capture-pane show badly interleaved stale-and-fresh text (the HUD only repaints rows within its assumed viewport, leaving everything below frozen at whatever was last drawn there) -- looks exactly like a hung/unresponsive connection but isn't. Fix: tmux resize-window -t <session> -y 50 right after start. Confirmed via independent eval file_name(environment(player)) ground-truth checks that the game state was always correct even when the pane looked frozen/garbled.

Tested and confirmed working correctly, live: guild-independent skill training via ask <trainer> to train <skill> (the class's own in-character hint text, not the more natural-sounding but WRONG ask <trainer> train <skill> or bare train <skill>, both of which this verb's own rule table -- verbs/common/ask.lpc's SetRules("LIV STR", ...) routes to a plain spoken-sentence handler, not eventAsk()/CommandResponses at all -- confirmed correct rejection "You need more training points!" at level 1/0 training points, per design); real melee combat vs. a placed diku-alfa mob (Beastly Fido, bidirectional limb-targeted damage, a live "You are a bit more adept with your melee attack" skill-improvement message, clean flee); the Pet Shop's economy loop end-to-end (inquire correct price list, adopt <pet> correctly refused for insufficient gold, then correctly succeeded once funded, with the right currency deduction); etnar's Wyr village (The Lounge -> The Common Room -> The Entrance to the Cyclops Inn, matching onboarding's own documented room text) and diku-alfa's Midgaard city square area, both with correct room text/exits and zero new debug.log errors; quit -> debug.log grep (clean) -> a real ~3.5-minute gap (the long-sit watch below) -> reconnect, correctly restoring the exact same character/location/ worn-inventory/vitals; a ~200s synchronous idle long-sit boot watch (python3 scripts/mudclient.py ... --timeout 200 --idle 300, this lib has no WASM build yet) sitting on an open connection at the login prompt -- ended in the login object's own normal inactivity timeout, zero new debug.log errors, driver RSS stable (~2.2GB, expected for this 3,800+-file mega-lib per its own onboarding notes).

Death/respawn and a full guild-class questline were not reached -- budgeted out in favor of the two real bugs below and the broad etnar+diku-alfa content-verification pass the task specifically asked for; flagged honestly as unverified-live rather than silently skipped.

Bugs found and fixed

1. AGENTS.md §7.193 (NEW) -- two DikuMud-port SetShort() calls hold a full "long room-appearance" sentence instead of a bare noun phrase, and this codebase's living-NPC room-listing code unconditionally appends a posture verb phrase to GetShort() regardless of what it already says, producing a doubled, grammatically broken line. domains/diku-alfa/room/30.zon/npc/ 3095_pet_shop_boy.lpc had SetShort("A Pet Shop Boy is here, humming gently."); domains/diku-alfa/room/41.zon/npc/ 4101_troll.lpc had SetShort("A large mean-looking troll is here"). lib/body.lpc's GetHealthShort() just wraps GetShort() in a health-color code with no stripping; lib/events/ describe.lpc's room-listing code then buckets livings by GetPosition() and appends " is standing here."/" is lying down."/etc. unconditionally to that same string for the POSITION_STANDING (etc.) bucket -- confirmed live: looking at the Pet Shop showed "A Pet Shop Boy is here, humming gently. is standing here." Every OTHER living NPC checked in the same corpus (3064_drunk.lpc: "a singing, happy Drunk", 3061_janitor.lpc: "the Janitor", 3062_fido.lpc: "Beastly Fido") correctly uses a bare noun phrase for SetShort(), matching a DikuMud port convention where the original Diku MOB file's "long description" (Diku's own room-listing line, self-contained) was supposed to stay separate from the short combat-message name -- these 2 files mistakenly got the Diku long-description text copied into SetShort() instead. (Non-living scenery objects like 3600_candlestick.lpc/3134_bench.lpc correctly DO bake "is standing here" directly into their own SetShort(), because inanimate items are rendered via a different, non-appending display path -- confirmed by checking all_inventory(env) is filtered by living(ob) before the posture-bucketing code ever runs, so this is not a contradiction, just two different, both-legitimate conventions for two different object categories.) Fixed by stripping the redundant sentence fragment from both SetShort() calls, matching the established living-NPC convention ("a humming Pet Shop Boy", "a large mean-looking troll"). Verified live (destruct + respawn a fresh instance of each): the pet shop now correctly shows "A humming Pet Shop Boy is standing here." How to apply generally: any Dead-Souls lib hosting a DikuMud (or similar Diku-lineage) port is suspect for this exact class -- grep -rE 'SetShort\("[^"]* is (here|standing|lying)' domains/ (or the equivalent short-description setter) to find any living NPC whose short description was left as a full self-contained sentence instead of a bare noun phrase.

2. domains/diku-alfa/room/30.zon/rm_3031.lpc's pet-shop adopt command has the if (!name)/if (name) branches swapped/confused for 4 of 5 pet types, a copy-paste mistake (confirmed via the file's own cmd_adopt-equivalent switch statement): the if (!name) branch (meant for "no custom name given") for wolf, rottweiler, puppy, and kitten still executed ob->SetShort("a pet <animal> named " + capitalize(name)) with name actually 0/undefined, producing a dangling "a pet kitten named " (rendered live as "A pet kitten named is standing here." once combined with bug #1's posture-append behavior) -- confirmed by adopting an unnamed kitten and reproducing this exact broken line. The SetId() calls in the same branch also uselessly included the undefined name/ capitalize(name) values in the identifier array. The beagle case had the OPPOSITE bug: its if (name) branch (custom name given) was byte-identical to its if (!name) branch, so a beagle NEVER got its custom name applied even when the player supplied one. Fixed all 5: the if (!name) branches for wolf/rottweiler/puppy/kitten now just set a bare "a pet <animal>" short description (matching the beagle's own already-correct no-name branch) with no bogus name values in SetId(); the beagle's if (name) branch now actually applies "a pet beagle named " + capitalize(name) (matching the pattern the other 4 pets already use in their own if (name) branches). Verified live, both directions: adopt puppy (no name) now correctly shows "A pet puppy is standing here."; adopt puppy Rex (with a name, after destructing/re-adopting) correctly shows "A pet puppy named Rex is standing here." No new AGENTS.md entry -- this is a narrow, single-room copy-paste bug in bespoke vendor script, not a shared-library pattern likely to recur elsewhere.

Non-bugs (documented, not fixed)

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 (secure/sefun/sockets.lpc already stubbed). secure/daemon/imc2.lpc still fails to compile on socket_status() — a graceful preload skip, not on the login path. Verified with scripts/wasm_client.js: fluffos / Mud@2026 through the news pager into the Temple of Midgaard (/domains/diku-alfa/room/30.zon/rm_3001), hp: 430/430, quit printed "Please come back another time!" / "Fluffos quits The Brass Ring." Shop/combat/death were not exercised this pass.

Shop (2026-09-04 librarian slice)

The 2026-08-31 §10.7 pass already did the diku-alfa pet-shop adopt loop. This pass did a regular paid retail buy at the bundled Dead Souls town store (/domains/town/room/shop), which that pass never touched. Seeded creator fluffos / Mud@2026: eval return this_player()->AddCurrency("silver", 80) (purse was gold-only), goto the store, list (wooden torch 60 silver), buy wooden torch from otik → Otik "Here is an old wooden torch for 60 silver!", inventory has the torch, money 20 silver + 134 gold.

Same heartbeat bug as deadsouls_fluffos: lib/body.lpc called hobbled(this_player()) (0 during heart_beat). Fixed identically (this_object(), plus !objectp guard in disable.lpc). No new collapse/call_other(0) spam after the reboot used for this buy. Pet-shop adopt was not re-run. 拜师 analogue is Dead Souls class join / skill trainers, already covered 2026-08-31.

2026-10-03 — elevator buttons

domains/town/obj/ebutton1.lpc and ebutton2.lpc called car->SetDoor(1), the name the elevator had before it renamed its own function SetDoorClosed (it shadowed LIB_EXITS::SetDoor(dir, file)); the old name resolves to the inherited exit function and the call is a silent no-op, so the call buttons never closed the car door. Changed to SetDoorClosed(1), as in dsIII and ds386 (KB 06 §7.211).

深度功能测试(§10.7,2026-10-03)— compile warnings, inherit diamonds, and the ds386 fixes ported

Method. work/ is the corpus formatter's output of the Dead Souls 3.8.6 sources plus this lib's own Diku-Alfa zone, Wyr domain and a few lib classes (factions, rent, the skill-picking login). For 1409 of the 1508 files ds386's warning pass changed, brassring's tokens equal ds386's pre-fix tokens, and format(ds386 pre) reproduces brassring's HEAD byte for byte (1360 files; 49 more are byte-identical unformatted), so those files were set to format(ds386 post) with scripts/lpc_twin_port.py adopt (KB 08 §10.13): they equal ds386's verified result token for token. The other 87 files carry brassring's own code; they went through a fuzzy hunk applier and scripts/lpc_warnings.py brassring --fix, and lpc_twin_port.py audit then listed every hunk ds386 made that brassring lacked (three empty if () {} shells, cedit's two int ret, master's inactive-branch locals, player's CanGet, the council's prototypes), which were finished by hand. Eleven admin-realm sample files ds386 touched do not exist here. 2778 files changed: 2631 .lpc, 144 .h, and the runtime-swapped secure/lib/connect.real, secure/daemon/update.patch/update.blank (KB 06 §7.216).

Result: 9795 -> 4 distinct diagnostics. The four are the same three dead archive files ds386 keeps: obj/area_room.lpc (includes a ../customdefs.h that does not exist), obj/stargate.lpc and open/prog.lpc (deliberately uncompilable). scripts/lpc_diamonds.py brassring lists no overlapping inherits. The baseline was 4243 Redeclaration rows (2058 files), 3452 nosave functions (2602 files), 468 unused locals, 120 override argument-count disagreements, 58 unknown escapes, 15 bare return;, 15 (: "name" :) pointers and the inherited from both families.

Brassring-only changes (everything else is ds386's, see its NOTES):

Real bugs found while fixing warnings:

Verification. Golden-master oracle (HEAD vs working tree, 228 programs, KB 08 §10.12): 1398 public -> protected flips (the mechanical pass), 26 definer changes (player CanGet/CheckTimer, the chamber/elevator/vehicle door and language picks, trainer init, worn_storage GetRadiantLight, virt_land's rename), 130 duplicate variables removed or renamed, 63 radiance rows (14 -> 7, the glow fix) and three added functions (SetVirtualCoordinates); nothing else moved. Paired live sessions as admin fluffos/Mud@2026 on a HEAD tree and a working-tree copy (165 family steps plus 63 brassring-specific ones): the differences are the fixes above, the river's You can't enter that! -> You can't enter that., the time-of-day text and wandering-NPC chatter. A fresh mortal registered through the skill-picking login (PickSkillsorClass .. InputPrimarySkills, rewritten by the pass), killed an orc in the start room and survived; log/runtime and log/catch stayed empty.

深度功能测试(§10.7,2026-10-04)— Praxis comma-form function pointers

The 30 comma-form pointers in the 21 Praxis files and the target fixes made in ds386 (see its NOTES section of the same date: SetSearch handlers return their text, the helm asks GetMorality(), test_invis takes the viewer, look_at_vault/look_at_manhole restored as text) carry over here: scripts/lpc_twin_port.py adopt ds386 brassring --pre HEAD --apply (all 21 are formatted twins: format(ds386 fixed file)). All 21 files compile without a diagnostic, and a live spot check (search field, search mounds, look at mound/manhole/vault/treats, the planning room, a voting hall) answers as in ds386.

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

Deep test (§10.7, 2026-10-08) -- fixed-width .lpc suffix slices

The .c -> .lpc rename changed the string but not the slice width: f[<2..] == ".lpc" compares two characters with four and is never true (!= always true), so suffix filters and strips never ran or cut x.l (KB 06 §7.238). scripts/lpc_lpc_suffix_slice.py --apply fixed 1 comparison(s) and 1 strip(s) (e.g. daemon/help.lpc); the changed files load at HEAD and in the tree (fresh processes) with no regression.

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

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

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

if (arg = "button" || arg = "pill"), if (!arg || arg = "") return 0;, else if (check = 0 && tempB != ""): = was typed for ==, so the condition is the constant: the branch is always taken for a non-zero number or any string (even "") and never for 0, and the variable is overwritten; the driver does not warn. Every such = became == (scripts/lpc_fix_cond_assign.py, 9 terms). Changed: /cmds/creators/cryptogram.lpc (1), /cmds/creators/rot.lpc (1), /domains/campus/npc/jennybot.lpc (1), /domains/default/weap/grenade.lpc (1), /domains/default/weap/plasma.lpc (1), /domains/default/weap/rpg.lpc (1), /domains/default/weap/torpedo.lpc (1), /lib/lead.lpc (1), /verbs/builders/reload.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).

The suffix strip after a directory read (2026-10-20, KB 06 7.296)

After the .c -> .lpc rename the names read with get_dir("*.lpc") were still cut by two characters ($1[0..<3]): baguadao.lpc became baguadao.l, and every path built from it failed to load (stories, battlefields, events, help indexes, player-made item data, summon lists). Now [0..<5] (scripts/lpc_fix_getdir_strip.py). Changed: /daemon/help.lpc (2).

help <topic> on a plain clone, the help index names, telnet and setreboot without an argument (2026-10-21, KB 06 7.212, 7.296, 7.336)

help <topic> loads /daemon/help, whose LoadIndices() adds get_dir() results with +: secure/cmds/common, verbs/spells and verbs/undead are directories the archive ships empty and a git clone does not have (git keeps no empty directory; the site recreates them from scripts/wasm_keep_dirs.txt). Booted without them, the first help <topic> printed A runtime error occurred (Bad type argument to +. Had array and int) and the half-built daemon answered every later topic with "no results". Every directory is now read through DirList() (get_dir(pattern) || ({})); help look, help telnet, help races and help commands answer with the four directories removed (PLAY_RMDIRS). The index also cut three characters from every command name (f = function(string str) { return str[0..<3]; }, assigned above the get_dir() calls where the 2026-10-20 pass does not look): help commands listed abandon.l and help telnet found nothing; now [0..<5]. The mortal telnet with no argument raised Bad argument 1 to sscanf() Expected: string Got: 0 (the bare-verb sweep: A runtime error occurred), and the usage line it meant to print came from this_object()->help(), a function no object defines: it tests !args || now and prints GetHelp(); the administrators' setreboot called the same missing help() and went on without a return, it prints its syntax and returns now. Changed: /daemon/help.lpc, /secure/cmds/players/telnet.lpc, /secure/cmds/admins/setreboot.lpc.

Creator and administrator commands with no argument, the A* padding (2026-10-21, KB 06 7.336, 7.338)

The creator / administrator verbs had never been run, only compiled. Swept as the seeded administrator (play_sweep_lib.py SLUG --wiz --account fluffos:Mud@2026 --shapes one), seven of them raised on the bare command: areaclone and cryptogram (sscanf(0, ...)), pause (lower_case(0)), html (read_file(0)), encre (player_save_file() runs before the argument is looked at and raises Bad argument 1 to save_file()), resolve and wget (the daemons RESOLV_D->eventResolve(0) and WGET_D->eventGet(0) run sscanf(0, ...) / strsrch(0, ...)). Each tests the argument first and prints its GetHelp(). The A* sefun's find_path() (the creator command astar) died with (s)printf(): Null pad string specified because it pads the map with sprintf("%N'\27's", ""), a pad string that has no display width in FluffOS; it pads with spaces and replaces them, and astar prints its demo route again. The sweep must skip removeraces (it empties the race table: every NPC created afterwards fails in its heart beat). Changed: /secure/cmds/builders/areaclone.lpc, /cmds/creators/cryptogram.lpc, /secure/cmds/admins/pause.lpc, /cmds/creators/html.lpc, /secure/cmds/admins/encre.lpc, /cmds/creators/resolve.lpc, /secure/cmds/creators/wget.lpc, /secure/sefun/astar.lpc.

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

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

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

scripts/play_combat_probe.py brassring --account ... --clone ...: the administrator cloned /domains/town/npc/orc_shaman, /domains/Ylsrim/npc/balrog, /domains/default/npc/dummy, /domains/Praxis/cemetery/mon/ghost3, /domains/Praxis/obj/mon/goblin_fighter, /estates/f/fluffos/area/npc/fighter into its room and attacked each for 20 s (6 fights read): no driver line and no error text on the screen. (KB 08 §10.16)

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. 4 statement(s) in 4 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 4 changed files, HEAD vs tree: 4 load at HEAD, 4 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. 2 more function(s) stay missing (other 2): 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, /secure/lib/net/h_ftpd.lpc:eventCmdPass. 1 more are commented out by the author with the pointer left behind: on purpose, untouched.

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

The code of this lib writes logs into 2 subdirectories of log/: log/harass (lib/interface.lpc), log/secure/log (secure/lib/connect.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/harass, log/secure/log 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.