Nightmare 4

✅ 可玩

nightmare4

更新 1309d15 2026-10-22 源码 下载 ZIP 上游 MudRen/nightmare4

▶ 开始游玩 · Play Now

The fourth major generation of the Nightmare Object Library (source headers alternate between crediting "Nightmare IV"/"Nightmare IVr2/r3/r6" and "Nightmare V", reflecting incremental revisions by original author George Reese and others through 1996-97), a different and later generation from `nightmare3` already in this collection (that lib is the `fluffos/nightmare3` fork's own distinct Praxis-domain snapshot; this one ships its own separate example domain, Ylsrim, a small desert bazaar town, and a materially different mudlib layout -- e.g. a dedicated secure/lib/net/ socket-object layer, a class-based Race/Class/Season data-daemon design, and PACKAGE_DB/PACKAGE_UIDS driver-package awareness baked into the source). Nightmare is one of the most historically influential LPMud mudlib families -- an ancestor of the TMI-2 lineage that several other libs in this collection (including some of the Chinese-MUD codebases) ultimately trace back to. New characters go through a real menu-driven registration (name, password, gender, display name, email, real name) and then pick a race from a fixed list (list/help/pick) before landing in Ylsrim's central bazaar. English-language content, included deliberately for its historical significance alongside `nightmare3`.

README

The fourth major generation of the Nightmare Object Library -- one of the oldest and most historically influential LPMud mudlib families, an ancestor of the TMI-2 lineage that several other libs in this collection (including some of the Chinese-MUD codebases) ultimately trace back to. Distinct from nightmare3 elsewhere in this collection: a later generation with its own separate example domain, its own socket/net object layer, and a class-based Race/Class/Season data-daemon design.

Source: MudRen/nightmare4 on GitHub. Unlike the rest of this "Chinese MUD Museum" collection, this lib's content is in English -- included here deliberately for its historical significance to the wider LPMud family tree.

Highlights

Play online

https://mudlibs.fluffos.info/nightmare4/

Admin account

Warning: Mud@2026 is a public default password for local play
only. Change it before exposing this host publicly.

Status

WASM status: playable. A dump_socket_status() sefun calling socket_status() unconditionally (a netstat creator-command diagnostic, not on the boot/login path) was fatal to simul_efun's compile without the sockets package -- guarded with #ifdef __PACKAGE_SOCKETS__. Full session verified clean after the fix: registration, race selection, look/score/quit. See NOTES.md \S13.

Local run

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

Game port: 40234.

NOTES · 移植与修复记录

Nightmare 4 -- porting notes

Source: git clone https://github.com/MudRen/nightmare4 (commit ad4235d477f9cf7bdc67893fd26d6e6722c39e31... actually first commit per its single-commit history, cloned 2026-08-26). The repo's own description: "An influential early lib family. Newly revamped: Unix and Windows driver included together." Top level bundles a full fluffos-2.9-ds2.14 driver source tree, bin/mudos.cfg, and win32/driver.exe alongside the actual mudlib -- all ignored per this project's convention (own driver build at ~/src/fluffos). Mudlib root is the repo's lib/ directory: 1018 raw files, 18MB. Slug nightmare4, number 932, port 40234.

0. Confirmed genuinely distinct from nightmare3

Before investing effort: nightmare3 (already in this collection, port 40208) is the fluffos/nightmare3 fork -- its own Praxis-domain snapshot. This archive ships a completely different example domain (domains/Ylsrim, a desert bazaar town, vs. nightmare3's domains/Praxis/Examples), a different file layout (lib/std/ for base object classes, secure/lib/net/ for a dedicated socket-object layer that nightmare3 doesn't have, class-based Race/Class/ Season data daemons), 568 .lpc files after conversion vs. nightmare3's 822, and its own header versioning that alternates between "Nightmare IV"/"Nightmare IVr2/r3/r6" and "Nightmare V" across files (incremental in-house revisions, not a rename) -- a genuinely different generation/snapshot of the same family, not a duplicate.

1. Conversion

Pure ASCII archive -- no encoding-normalization work needed. convert_lib.sh renamed 568 .c files to .lpc, fixed 125 literal .c" references, converted 26 local angle-bracket includes to quotes, and ran the static->nosave sweep across 498 files with no string-literal or #define nosave static-shim collisions found.

One file entirely escaped this pipeline: secure/lib/connect.real (no .c extension, so convert_lib.sh's glob never touched it). This is real, live LPC source -- the mudlib's own one-time admin-installer (secure/lib/connect.lpc) cp()s this file over itself once the admin account is created, so it becomes the actual live login/registration object for every subsequent connection. It still had 31 raw static keywords on functions (fixed with the same \bstatic\b->nosave sed) and three TYPE array NAME/(TYPE array) dialect sites (see \S3) that only surfaced once compiled standalone -- both bugs would otherwise have surfaced silently, well after this port's own testing, the moment a real admin install ran on a copy that skipped this manual step. Any future archive in this collection should be checked for other non-.c-extensioned live source the mechanical pipeline would miss (grep -rL for text files containing inherit/#include outside *.lpc/*.h) -- secure/sefun/to_object.orig and a couple of Emacs .h~ backups were also found this way but confirmed genuinely dead (never referenced by any cp()/#include/load_object()), left alone.

2. Missing get_root_uid()/get_bb_uid()/creator_file() --

blocked literally every single object load

The single most severe bug in this port, exactly AGENTS.md \S7.2's catalogued class but with an extra wrinkle. This driver was built with PACKAGE_UIDS, which makes set_master() call master::get_root_uid() and (on first load) master::get_bb_uid() -- if either is undefined, the driver exit(-1)s the whole process immediately, with only No function get_root_uid() in master object; possibly the mudlib doesn't want PACKAGE_UIDS to be defined. printed. master.lpc had neither. Added both as flat stubs ("Root"/"Backbone"), matching the identical fix already present in this collection's nightmare3 sibling.

That alone was NOT enough: with both defined, give_uid_to_object() (called on every single object load, not just at master-boot) then requires master::creator_file() too -- entirely missing here. Every load silently failed via a catch()-swallowed error(), so the very first individual-file compile tests (/lib/std/room, /lib/std/item) both "failed to load" with zero output on stdout -- only log/catch/log/runtime (this mudlib's own separate error log, written by master.lpc's error_handler()) showed the real message: *master object: No function creator_file() defined!. Since this codebase's actual ACL model is built entirely on query_privs()/ file_privs() (check_access() in master.lpc), not on driver-level uids -- nothing else in the archive ever calls getuid()/seteuid() for a real decision (confirmed by grep) -- a flat "everyone is Root" stub is a minimal, behavior-preserving fix, not a new ownership scheme:

string get_root_uid() { return "Root"; }
string get_bb_uid() { return "Backbone"; }
string creator_file(string str) { return get_root_uid(); }

master.lpc also used ref (a reserved word on this driver, \S7.2's sibling class) as a plain mapping parameter name in load_access(string cfg, mapping ref)/ref[fl] = ... -- the exact class documented for mortremains earlier this session. Renamed to refmap (3 sites: the .h prototype, the definition, and the one use).

3. The pervasive .c->.lpc filename-slice bug -- this driver

build's whole command/verb/help dispatch architecture assumes a 2-character extension

Exactly the bug class flagged before starting this port (previously found in sunshadow's daemon/command.lpc), except here it is not one isolated instance but the dominant recurring pattern across this entire codebase's dispatch machinery -- Nightmare's command daemon, verb daemon, spell-casting daemon, help-index daemon, room/door ResolveObjectName(), update/clone/create/showtree (wizard file commands), and inventory-key normalization all independently reimplement "strip the trailing extension" using either:

Both shapes are equally fatal, just failing in opposite directions (one corrupts the name, the other leaves it un-stripped) -- either way every command lookup, verb lookup, spell-cast, and help-topic match breaks silently, with no compile error and no crash. Root-caused via a direct compile of /daemon/command (the file /lib/lib/command.c's cmdAll() calls into for every ordinary player command) once the \S2 fix let objects load at all. Swept the whole corpus for this shape (grep for \[0\.\.<3\] and [<2..] ==/!= ".lpc") and fixed every site by widening the arithmetic from a 2-character to a 4-character extension ([0..<3]->[0..<5], [<2..]->[<4..], and the accompanying strlen(x) > 2 guards to > 4):

daemon/command.lpc (command-name dispatch, unconditional -- the most severe: every ordinary command in the entire mudlib routes through this), daemon/verbs.lpc (2 sites: verb registration + reload), daemon/help.lpc (4 sites: the index-builder closure plus 3 more inline slices, and a guarded topic normalizer), lib/detect.lpc (2 sites, spell-cast verb matching), lib/enter.lpc and lib/exits.lpc (ResolveObjectName(), identical in both), lib/props/inventory.lpc (SetInventory()'s key normalizer), secure/cmds/creators/update.lpc (2 sites: the wizard update command's own extension-append AND extension-strip, the append side previously always true post- rename -- update /std/room would have looked for /std/room.lpc.lpc), secure/cmds/creators/create.lpc, cmds/creators/clone.lpc, secure/sefun/to_object.lpc, and cmds/creators/showtree.lpc. lib/history.lpc:118's superficially similar neu[<2..] == "/g" / neu[0..<3] was checked and confirmed unrelated (stripping a 2-character sed-style /g flag suffix from a user-typed regex, not a filename) -- left untouched.

4. Old-MudOS TYPE array NAME declaration dialect -- systemic, not

an isolated slip

This codebase's actual house style writes every array-typed declaration as two words (string array foo, mixed array bar...) rather than string *foo -- 376 sites across 109 files, both for function return types/parameters and local variable declarations, plus a class X array variant (structs-of-arrays) that a plain (string|int|...) array regex misses. Swept mechanically: every (string|int|object|mixed|float|mapping|buffer|function)\s+array\b -> \1 * (one false positive reverted: a string literal in lib/events/dig.lpc's own error message, "...or mixed array, Got: ..."), plus the two class NAME array sites by hand (lib/body.lpc's GetMagicProtection(), secure/daemon/finger.lpc's Marriages). The same dialect resurfaced in secure/lib/connect.real (\S1, escaped the mechanical sweep entirely) as both a declaration and two (string array) casts on RACES_D->GetRaces(1).

5. TYPE * a, b; -- the * binds to the first declarator only

The other half of AGENTS.md's general \S6.3 catalog, found repeatedly: old code intends every comma-separated name in a multi-declarator line to share the pointer/array-ness of the first, but this driver (like standard C) only applies * to the first name -- silent until the second name is later used as an array and hits a hard Bad assignment/ Invalid types to '-' error. Fixed only the instances that actually failed to compile (per this project's policy against blind bulk-fixing every superficially similar declaration): daemon/races.lpc's AddRace() (tmp), daemon/classes.lpc's AddClass() (tmp), daemon/help.lpc's GetIndices() (val, confirmed via its member_array(tmp, val) use requiring an array), lib/chapel.lpc's Religion/Deities/DeityIds (all three, confirmed via Religion[1]/ member_array(deus, DeityIds)/etc.), secure/cmds/players/history.lpc (arr, tmp), and cmds/creators/clean.lpc (items, users). Several superficially identical lines elsewhere (daemon/banish.lpc's loop-counter i, cmds/players/date.lpc, secure/daemon/finger.lpc's Religion, secure/cmds/players/peer.lpc's ret) were checked and left alone -- they compile cleanly as-is, meaning the second name is genuinely used as a scalar there.

6. Bare array declaration -- hard syntax error here, not a silent

no-op

lib/history.lpc's private nosave array History; (no element type) is AGENTS.md's general "bare array x;" class, but manifests as a hard compile error on this driver (unexpected L_IDENTIFIER) rather than the silently-inert declaration documented elsewhere in this project's catalog -- and since lib/history.lpc is inherited straight into the player body class (lib/interface/lib/creator/lib/player/ lib/interactive all failed transitively from this one line), it blocked every player body from compiling at all. Fixed to mixed *History (confirmed via its allocate()/sizeof()/string-indexing uses).

7. ed_start()/ed_cmd()/query_ed_mode() -- this build uses

__OLD_ED__, only the old ed() efun exists

Exactly the catalogued class, hit in two independent files:

secure/cmds/creators/lsed.lpc (a wizard utility that drives the editor programmatically, feeding it a whole script of ed commands with no real user typing, to batch sed-style edits across many files) depends on the synchronous, in-process "new ed" API in a way that has no equivalent under __OLD_ED__ at all -- old ed() hands control to the driver's C-level input loop for a real interactive user, there is no way to feed it commands programmatically from LPC. Left as a documented, non-portable gap (see \S10) rather than reimplementing a text editor in LPC.

8. SetLong()/closures -- the same class already seen this session

in sunshadow, independently present here

obj/wed_ring.lpc, obj/portal.lpc, and three domains/Ylsrim/room/ files (armoury, sand_room, weaponry) call the single-argument SetLong() with an old-style closure (SetLong((: RealLong :))) for a dynamic long description, but every implementation in the inherit chain (lib/std/room.lpc, lib/events/look.lpc) only accepted string. Both already had the machinery to evaluate a functionp()/arrayp() value at *read* time (GetInternalDesc()/GetExternalDesc() already branch on functionp(val)/arrayp(val)) -- only the *setters* and their backing fields were narrowly typed. Widened SetLong()/ SetInternalDesc()/SetExternalDesc() to mixed, and the backing InternalDesc field (lib/events/look_in.lpc) from string to mixed (ExternalDesc in look.lpc was already mixed). door.lpc's own two-argument SetLong(side, long) already took mixed long and needed no change. Left lib/interactive.lpc's player-body override of SetLong() narrow (string, doing a strsrch(str, "$N") check) since nothing calls it with a closure and widening it would need an additional stringp() guard for no exercised benefit.

Related, smaller: lib/comp/seal.lpc's eventOpen() called eventPick(who, tool) -- the real signature is varargs mixed eventPick(object who, string id, object tool) (three params: an optional lock/key id string in the *middle*, then the tool). The 2-argument call is missing the id argument entirely, most likely an artifact of eventPick() being extended with a new middle parameter after this call site was written and never updated -- fixed to eventPick(who, 0, tool), preserving the exact same runtime behavior (the function already treats a missing/0 id as "no specific key") while satisfying the compiler's arity/type check. This blocked every lockable door/container in the inherit chain (lib/std/limb/corpse/storage, lib/door, lib/trap, several domains/Ylsrim rooms) from loading.

9. Missing directory breaks a get_dir() +-chain

daemon/help.lpc's LoadIndices() builds the "commands" help index by +-concatenating nine get_dir() calls across nine different command/ verb directories -- but secure/cmds/common/ doesn't exist in this archive snapshot (confirmed: verbs/spells, verbs/undead, cmds/undead are ALSO missing, and the original author already guarded those specific calls with || ({}), just not this one). This driver's get_dir() on a directory that doesn't exist returns something that isn't an array, so mid-chain array + non-array throws Bad type argument to +. Had array and int. at runtime, breaking create() for the whole help daemon -- caught by nothing, since create() isn't wrapped in a catch() anywhere in its call chain, so daemon/help itself never finished loading. Guarded the one unguarded call the same way the original author already guarded its siblings: (get_dir(DIR_SECURE_COMMON_CMDS + "/*.lpc") || ({})).

10. Known gaps -- documented, not invented or mechanically forced

Four files remain uncompilable after the fixes above (a 99.3% pass rate, 564/568, on the full lpcc_check.sh batch sweep); none of them are preloaded in a way that blocks boot (master.lpc's preload() wraps every preload call in its own catch()), and none are reachable from the standard registration/gameplay path exercised in \S11:

11. Verification

Booted the real FluffOS driver (~/src/fluffos/build-debug/src/driver) against config.fluffos -- Initializations complete. reached, clean of any new fatal errors in log/catch/log/runtime beyond the single, already-documented secure/daemon/remote preload failure (\S10). Verified end to end with a raw Python socket client (no tmux_mud.sh/mudclient.py shortcuts):

12. Genuine outbound network connection at boot

daemon/intermud.lpc (preloaded) is the Intermud-3-protocol daemon; its create() hardcodes Nameservers = ({ ({ "*i4", "204.209.44.3 8080" }) }) and, 2 seconds after boot (call_out((: Setup :), 2)), opens an outbound socket to that address to register with the Intermud nameserver network. Unlike the similar mortremains/ sunshadow-adjacent findings this session (where the hardcoded 1990s target address was confirmed unreachable), a live ss -tnp check during this port's own boot test found the connection actually reached ESTABLISHED state -- that specific address apparently still answers on port 8080 today. This is genuine, intended upstream functionality (not a bug to patch away), but per this project's established convention, nightmare4 should not be swept into high-frequency automated reboot/deep-test loops the way most of this corpus safely can be -- each boot is a real outbound network event to a real, live remote host, more so than the merely-attempted connections documented elsewhere in this collection.

13. WASM status

playable. Booted ~/src/fluffos/build-wasm/src against this lib via scripts/wasm_client.js. secure/sefun/sefun.lpc's dump_socket_status() (a cmds/creators/netstat.lpc-only diagnostic, not on the boot/login path) called socket_status() unconditionally, which is Unknown efun without the sockets package -- fatal at simul_efun compile time, identical shape to this project's ds386/dsII/lima entries. Guarded the function body with #ifdef __PACKAGE_SOCKETS__ ... #else return ""; #endif. After the fix, a full WASM session verified clean: real menu-driven registration (name, confirm, password + confirm, gender, display name, email, real name), race selection (list / pick human), landing in Ylsrim's central bazaar, look/score, and quit. (Initial runs with too-short --idle values between wasm_client.js sends appeared to hang mid-registration at the gender prompt -- reproduced identically on the *native* driver with the same fast timing, confirmed purely a test-harness pacing artifact via a slower --idle 2.0, not a WASM-specific or mudlib bug.)

14. Deep functional test, round two (2026-08-27, AGENTS.md §10.7)

One continuous session against ~/src/fluffos/build-debug/src/driver, a raw Python socket client (no tmux_mud.sh), plus a separate admin (fluffos/Mud@2026) connection for eval-based verification. Registered two full new characters through the real menu-driven flow (name -- rejects any digit, letters/'/- only, 2-15 chars -- confirm y/n, password x2, gender male/female spelled out in full, display name, email requiring a real user@host shape, optional real name, then list/pick RACE), landed in Ylsrim's bazaar both times. Walked bazaar -> n (Kaliid Road) -> w (Kaliid Road near the Fighters' Hall) -> enter hall -> n (through the oak door) to reach Roshd Burlyneck, used ask roshd to describe fighters then ask roshd to join fighters -- class join works exactly as documented (score correctly flips from "Newbie Wanderer ... Drifter" to "Newbie Fighter ... Fighter", skills populates the fighter's primary/secondary/other skill table from a clean zero baseline). Tested the pub economy path (enter pub -> ask lars to list -> correct menu response "I currently supply ale for 3747." -> ask lars to serve ale -> correctly rejected with "You do not have that much in electrum" for a broke new character -- no crash, GetCost() already correctly uses to_int()). Confirmed the bare command forms (list, serve ale typed directly, without ask lars to) are NOT valid verbs in this codebase at all -- SetCommandResponses() is only ever dispatched from eventAsk(), so every NPC "command" here is really an ask NPC to VERB sentence; this is the correct, intended dispatch shape (confirmed by reading lib/sentient.lpc), not a missing-verb bug.

Two real bugs found and fixed (both silent -- zero compile error, zero crash, invisible to lpcc_check.sh and to a clean boot):

1. lib/currency.lpc's GetNetWorth() -- declared to return int but its body sums amt / tmp (a float currency_rate() divisor) into a float net_worth local and returned that float bare, with no to_int() -- the exact §7.121 shape (declared int, real float value, no runtime coercion on this driver). Live caller lib/living.lpc:479's eventSteal() assigns the result straight into a declared-int amt used for random(7*amt)/random(amt)/ AddStatPoints("coordination", random(amt)) in the stealing-skill training path -- not just the cosmetic cmds/creators/stat.lpc "net worth" display. Fixed: return net_worth; -> return to_int(net_worth);. Verified live via admin eval: ob->AddCurrency("gold", 37); ob->GetNetWorth() now returns a real int (intp() true), previously would have been a bare float. (lib/mayor.lpc's near-identical int cost = (currency_rate(...) * Tax); shape at lines 27/49 was checked and left alone -- Tax defaults to 0 and nothing in this archive snapshot ever inherits lib/mayor.lpc at all, confirmed by a corpus-wide inherit grep, so it's genuinely dead/unreachable code, not a live bug.)

2. lib/combat.lpc's Wimpy field (the auto-flee-at-low-health safety threshold) -- a brand NEW bug class, written up as AGENTS.md §7.124. private int Wimpy; is initialized in create() with Wimpy = 0.20; (a bare fraction), but every real consumer in this codebase (cmds/players/wimpy.lpc's player-facing wimpy PERCENTAGE command, cmds/players/score.lpc) treats it strictly as an integer percentage 0-100 (wimpy on sets it to the literal 23). The runtime gate in eventReceiveDamage(), if( Wimpy < percent(hp, GetMaxHealthPoints()) ) return x; (skip fleeing when true), is therefore comparing 0.20 against an integer 0-100 -- true for virtually every nonzero health percentage, so the auto-flee safety net was silently dead for every character from character creation, until the player manually ran wimpy NUMBER themselves. SetWimpy()/GetWimpy() were also declared to return float (matching the buggy literal) instead of int (matching the field's own declaration and every real caller) -- a second, dependent bug: once a real nonzero integer sits in Wimpy, the declared-float return silently widens it, and callers' (int) casts (compile-time-only on this driver, see reference_lpc_int_cast_is_compile_time_only) don't convert it back, corrupting the player-facing display ("Percentage: 20.000000%" instead of "20%"). Fixed both: Wimpy = 0.20; -> Wimpy = 20;, and float SetWimpy(float wimpy)/float GetWimpy() -> int SetWimpy(int wimpy)/int GetWimpy().

Verified live in two independent ways: (a) a brand-new character (freshtwo) created strictly after the fix shows score's mood line as "You are feeling wimpy." and the bare wimpy command correctly prints "Percentage: 20%\nCommand: go out" (a pre-existing character, wanderling, created before the fix still shows "You have wimpy turned off" -- expected, the fix does not retroactively repair an already-corrupted save, same caveat as §7.121/§7.122); this held after a full quit/reconnect cycle, so the fixed default survives a real save/restore round-trip, not just the in-memory session. (b) an admin eval directly exercised the exact comparison used in eventReceiveDamage() against freshtwo's real GetMaxHealthPoints(): simulated 15%-health now correctly evaluates to "should flee", simulated 50%-health correctly evaluates to "should not flee" -- both were "should not flee" before the fix, confirming the safety net was unconditionally dead, not just mis-tuned.

Live combat against a real hostile was not reached in this session: this archive's Ylsrim example domain ships exactly one NPC weak enough for a level-1 test character to safely engage (domains/Ylsrim/npc/traveller.lpc, level 5, non-aggressive, s_bazaar's SetInventory spawns it via unique(file, 1)) -- every other domain NPC (max/shiela, level 15 shopkeepers; lars, level 12 barkeep; roshd/priest, level 45 class leaders) is far too strong for a fair fight, and domains/Ylsrim/npc/ balrog.lpc (an "example simple NPC" file) is never actually placed in any room at all (confirmed by grep -- dead/template content, not reachable). The traveller itself turned out to be blocked by its own intended rarity design, not a bug: daemon/unique.lpc's GetUniqueCopy() enforces an 86400 * rare second real-world cooldown between spawns (rare=1 here, i.e. 24 real hours), and save/unique.o already had a timestamp from this lib's own onboarding-time testing less than a day old, so unique() correctly returned 0 (confirmed live via eval: unique(fn,1) returns 0, catch() shows no error) and the NPC legitimately could not respawn yet -- this is the same "ghost absence is often a feature" shape documented elsewhere in this project, not a bug to patch around. Given the risk of permanently disrupting a shared shopkeeper NPC (or the same 24h-cooldown unique mechanism) for an incremental confirmation, combat plumbing was instead verified end-to-end via the two eval checks above (b) rather than a live fight; a full live fight (and the death/respawn cycle) is flagged here as not verified live, an honest gap rather than a silent skip, should a future session have more wall-clock room (or catch the traveller's cooldown window) to complete it.

All four known cross-cutting bug patterns checked explicitly:

Systematic re-check of the §7.118 filename-slice class (this lib's dominant bug at onboarding, ~15 files): every remaining [0..<N]/[<N..]/strlen(x)-N-shaped slice in the corpus was individually triaged. All were confirmed to be doing something else entirely (stripping a trailing / one character at a time regardless of length in secure/lib/file.lpc, dropping the last word of a explode(str," ") array in daemon/chat.lpc/secure/obj/post.lpc, dropping the last array element in secure/daemon/bboard.lpc, singularizing an English category name for an error message in daemon/help.lpc, trimming an appended / sentinel character in secure/cmds/creators/grant.lpc) -- no further live instances of the dominant onboarding-time bug class remained.

Test characters left as evidence: wanderling (pre-fix, demonstrates the "wimpy turned off" post-restore symptom), freshtwo (post-fix, level 1 human, joined the fighter class, correctly shows wimpy at 20%/"go out"). freshbie/freshbie2/grimtest/ grimtest2/freshtwo-style throwaway registration retries with typos were not all cleaned up; harmless partial/duplicate throwaway accounts from mid-flow registration mistakes (invalid name/email formats rejected before a character object was ever created) are not persisted at all by this mudlib's own registration flow, so no additional cleanup was needed beyond what's listed here.

Sibling note for nightmare3/residuum: check both for the same Wimpy = 0.20-style fraction-vs-percentage literal shape in their own combat/status mixins if they share this lineage's lib/combat.lpc ancestry -- residuum is already known (per §7.118's own writeup) to share the identical filename-slice dispatch bug with nightmare4, so its combat internals are a plausible direct copy too.

Driver killed by exact PID after each reboot (never pkill -f); the Intermud-3 outbound connection noted in §12 was observed reaching ESTABLISHED again on each of the three reboots this session, exactly as before -- no change in that behavior, and the session stayed well within a small number of manual reboots rather than any automated loop.

深度功能测试(§10.7,2026-10-05)— round three: 注册、选种族、进入世界

复测原因:queue 中的 history 序列(tmi2 > nightmare3 > nightmare4 > lpmud141); 本 lib 之前只有无日期的一轮测试(第 14 节 的 round two 为 2026-08-27)。

流程(原生驱动 build-debug,ulimit -v 6291456 限制内存,PID 精确结束): banner → 注册(新名 Zephyrq,y,密码 ≥5 字母,male,显示名默认,email, 真实姓名可留空)→ 在同一连接内 pick human(账号要到选种族完成后才保存, 中途断开会留下未保存的半成品,再次登录会被当作新注册)→ 新手新闻按回车 → score、 inventory、quit 正常。新手进入 Ylsrim 中央集市。

存档字母目录的一次性失败(未复现,未修):首次注册时 ConfirmPassword → SetPassword → save_player 报 Could not open /secure/save/players/z/zephyrq.o.tmp for a save(log/runtime,14:50)。随后该字母目录出现,同一驱动里 Quintara(q 目录原本 不存在)完整注册、进入世界,没有再报错。master.lpc 的 player_object() 会在目录缺失时 mkdir 字母目录,所以这次更像首次启动时的一次性竞争,而不是持续存在的缺陷。log/runtime 里另有一条 n/nativetest.o.tmp 的旧记录,早于本次测试。若将来再出现,先看 player_object() 的 if(!NewPlayer) return 0; 早退分支是否被触发。

运行期日志:config.fluffos 的 debug log file 是 debug.log,但启动后并不生成 该文件;本 lib 只通过 log_file() 写 work/log/runtime、work/log/errors。判断"无错误" 时应读这些文件,而不是 debug.log(参见 KB 08 §10.9)。

编译警告:本次未扫描,待下一轮处理。(更正:meta.json 虽写 submodule-patch,但本 lib 的 work/ 是本仓库直接跟踪的文件,不是 gitlink,lpc_warnings.py nightmare4 可以直接扫,不需要 scratch 基线。)

测试账号清理:Zephyrq、Quintara 的 users 存档、postal 目录,以及由测试 重写的 save/economy.o、save/intermud.o 均已删除或还原;secure/save/players/ 下 的字母目录也已删除(它们是本次测试创建的)。

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

深度功能测试(§10.7,2026-10-05)— 首次注册存档失败的原因找到了;编译警告清理(机械部分)

1. 首次存档失败(上文“未复现,未修”的那条)原因找到并修复(KB 06 §7.227)。 secure/daemon/master.lpc 的 compile_object() 为新玩家建目录时写成 else if (players 目录不存在) mkdir(players); else if (字母目录不存在) mkdir(字母目录);:父目录不存在时只建父目录、跳过字母目录,于是这位玩家第一次 save_player 打开 players/<字母>/<名字>.o.tmp 失败,密码没有存下;再试一次父目录已在,才建出字母目录。git 不跟踪 secure/save/players/ 下任何文件,所以全新检出、以及网页版每位访客都会遇到。第二个 else if 改为 if(dsI 早已如此)。用只含被跟踪文件的树(git archive + 本次改动)验证:注册 qanine 首次即存档到 players/q/qanine.o,重新登录、走到战士公会 ask roshd to join fighters、score 变为 Fighter、quit 都正常。同样的写法也在 dsII、foundation2、havenmud 修了。 2. 编译警告(机械部分):scripts/lpc_warnings.py nightmare4 --fix,874 个 nosave 函数改 protected(被别处按名调用的 192 个去掉 nosave 保持 public)、删 34 行 #pragma、删 121 个未用局部变量、补 17 处 varargs、2 处无效转义;lpc_audit_removed_locals.py 复核 170 个被删声明,0 个需要处理。按名动态调用只有 secure/daemon/localpost.lpc(读 secure/cfg/aliases.cfg),其中用到的 query_groups 仍是 public;套接字回调、call_out、input_to 走驱动内部调用,可以调用 protected 函数。整树诊断 1373 → 164,剩下的是结构性警告(同名全局变量重复声明、菱形继承、覆盖函数返回类型不一致、LIB_LIMB 宏重定义),下一步处理。管理员会话(look、who、走到战士大厅、say、smell、eval、update、help)无权限警告。 3. 已有的问题(不是本次引入):secure/daemon/remote.lpc 编译失败,它按旧版接口调用 eventWrite(fd, ...),而 secure/lib/net/server.lpc 的 eventWrite 第一个参数是对象;secure/cfg/aliases.cfg 的 creator: 别名调用主控不存在的 query_creators。

深度功能测试(§10.7,2026-10-05,续)— 结构性警告清零,顺带修掉的真问题

启动时警告与错误全部清零;整树扫描 164 → 0 条警告,余下 4 个文件编译失败,都不是本次引入的(见第 6 条)。

1. LIB_LIMB 指向不存在的文件。 secure/include/lib.h 先 include std.h(LIB_LIMB = /lib/std/limb,存在),又把它重定义为 /lib/limb(不存在),后者生效。战斗中肢体被砍断时 lib/body.lpc 的 new(LIB_LIMB) 失败,断肢不会出现,戴在上面的护具也不会落下。删掉 lib.h 里那行;new("/lib/std/limb") 实测可用。 2. **.c→.lpc 转换把 T array a, b 改成 T * a, b**,只有第一个名字还是数组(KB 03)。secure/daemon/finger.lpc 的 Religion 从玩家存档读回却声明成字符串,Religion[1] 取到的是字符编码,finger 的信仰一栏因此不对;lib/magic.lpc 的 send_to、daemon/help.lpc 的 fun、daemon/classes.lpc 的 ots 同样补回 *(这几个运行时不受影响)。 3. 少写的 return 与写错的运算符:lib/living.lpc 的 CanCastMagic() 在没有环境时写成 "You are nowhere!";,没有返回,继续往下用空环境;lib/events/jump.lpc 的 GetJumps() 只写了 Jumps;,永远返回 0;cmds/creators/force.lpc 的 GetErorMessage() 同理;daemon/soul.lpc 的 res == who; 应为赋值,传入种族名字符串时 res 一直是空的。spells/resurrection.lpc 先算出 chance = level + random(治疗技能),判定却仍用 level,治疗技能完全不起作用,改为用 chance。secure/sefun/sefun.lpc 的 get_stack() 把函数名栈打印了两次而没打印程序名,第一个改为 stack0。 4. 菱形继承(KB 04 §6.10,scripts/lpc_diamonds.py):lib/comp/container.lpc 不再继承 LIB_RADIANCE,它在 npc、interactive、storage 里那份光亮变量从未被设置(只有物品调用 SetRadiantLight()),容器自身光亮项按原样取 0,玩家、NPC、储物箱的光亮计算不变(实测 NPC SetRadiantLight(3) 加身上点燃的火把 = 10);lib/living.lpc 不再继承 LIB_SMELL(living 只与 LIB_OBJECT 一起使用,后者已带嗅觉),删去只是转调 smell:: 的 direct_smell_obj();lib/std/corpse.lpc 多余的 inherit LIB_SMELL 删掉。这与 fluffos 组织维护的 Dead Souls 上游的做法一致。lib/npc.lpc 自己的等级变量改名 NpcLevel(lvs/abilities.lpc 另有一个 private Level),lib/nmsh.lpc 删去从未使用的 CommandFail。同名函数两边都有的,按驱动原本的选择写明(scripts/lpc_name_winner.py,look 类 4 个、CanSell、jar 的 GetShort、pole 的 GetSave);GetSave() 在 seal 里写成 close 与 lock 两份列表相加,storage 与 jar 返回 item 的完整列表(没有代码调用组合类的 GetSave(),运行时无变化)。 5. 原型与返回类型对齐:body.h 的 SetParalyzed、command.h/nmsh.h/command.lpc 的 GetClient、post.h 的 query_reply_text、update.lpc 的 eventUpdate、sefun.h 的 time_event、move.lpc 的 eventPrint;interactive 的 SetId/SetCapName 返回值与父类一致;room 的 RemoveItem/SetItems、germ 的 eventSuffer、limb 的 GetSaveString 改成父类的返回类型;daemon/include/classes.h、races.h 类定义后多余的 ;;tell 帮助里残留的 ("help", ...) 逗号表达式。 6. /secure/daemon/remote(远程创作者协议)从预载中注释掉(与 fluffos 组织的 Dead Souls 上游、本库同源的 dsI、dsII 相同)。它按 Foundation II 的旧套接字接口写成(eventWrite(fd, ...)),本库 secure/lib/net/server.lpc 的接口是按连接对象的,所以每次启动都编译失败;协议登录后还要加载每位创作者的 ~/adm/remote 对象,本档案里一个都没有。文件保留。havenmud 同样处理。其余编译失败的文件也都是原有的:secure/daemon/bugs.lpc(连接 nightmare.imaginary.com 数据库的未完成原型)、secure/cmds/creators/lsed.lpc(需要驱动没有的 ed_start)、daemon/tmp/A_WEARON.lpc(残留)。 7. 验证:全新启动 0 警告 0 错误;管理员会话 smell、look at me、finger(信仰一栏)、help tell、eval 取 NPC 等级(12)、新建断肢对象、点燃火把后的光亮(火把 7,NPC 3+7=10)、jar 的 GetSave()、GetId(),全部正常。

x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)

if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。改动:/lib/interface.lpc(1 处)。

if (x = 常量):条件里写成赋值的比较(2026-10-20,KB 06 §7.290)

if (arg = "button" || arg = "按钮")、if (!arg || arg = "") return 0;、else if (check = 0 && tempB != ""):把 == 写成了 =,条件的值就是那个常量,分支对所有非零数和任何字符串(连 "" 都算)永远成立、对 0 永远不成立,变量还被覆盖;驱动对此没有任何警告。每一处 = 改成 ==(scripts/lpc_fix_cond_assign.py,共 1 处)。改动:/lib/lead.lpc(1 处)。

登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)

驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/secure/sefun/sefun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。

help 菜单的列宽:FluffOS 的 terminal_colour() 会给空的 RESET 换上 ANSI 复位序列(2026-10-21,KB 06 §7.337)

help commands、help races 的编号菜单每格只剩一个字母([1] a[2] a[3] b[4] b):/daemon/terminal 的 no_colours() 把整张表(RESET 也是空串)交给 terminal_colour(),FluffOS 会把空的 RESET 换成 ESC[49;49m ESC[0;10m(15 字节)并写回映射,strip_colours() 带着转义序列返回,arrange_string() 少算了这部分宽度,把每格截短。no_colours() 现在把 RESET 映射为退格符再删掉(Dead Souls 3.x strip_colours() 的做法);help races 按列列出 [1] artrell [2] dwarf ...。修改:/daemon/terminal.lpc。

巫师命令不带参数时对 0 调用 efun(2026-10-21,KB 06 §7.336)

巫师(管理员)把命令敲成不带参数时,驱动把 0 传给字符串参数;下面的命令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...,这条指令从来没有用过。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py 在第一处使用参数的语句前加守卫:用 GetHelp() 的文件打印 GetHelp() 后 return 1;;没有 help() 的是 return notify_fail("…\n");。改动(3 个文件,括号里是接住 0 的 efun):/cmds/creators/force.lpc(sscanf)、/cmds/creators/move.lpc(sscanf)、/secure/cmds/creators/banish.lpc(lower_case)。加载检查 HEAD 与工作树对照,没有退步。

注册到不存在函数的 add_action,第二轮:宏路径父类和静态解析不了的对象(2026-10-21,KB 06 §7.349)

第一轮的静态解析读不了经宏路径继承父类的对象(#define ROOM STD_DIR "room/room" 这类定义的正文以另一个宏开头)和它解析不了的继承,这些对象当作 UNSURE 没有扫;第二轮修了解析器,重扫并对这些对象在运行时问 function_exists()。add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 1 条注册(1 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/lib/bboard.lpc(cmd_save)。最多的函数名:cmd_save 1 处。

log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)

这个库的代码往 log/ 的 2 个子目录写日志:log/adm(secure/cmds/creators/call.lpc)、log/harass(lib/interface.lpc)。压缩包、git clone 和站点包里都没有这些目录(本地启动没有写过它们,gen_keep_dirs.py 没记),write_file() 对不存在的目录抛 *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory",调用它的函数在写日志那一行中断,后面的奖励、标志、步骤都没有执行。本库的 log_file() 是 write_file(LOG_DIR + file, text),目录不存在就抛同样的错误(实测)。把 log/adm、log/harass 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。