info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
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
- A real, full menu-driven registration flow: name, confirm, password, confirm password, gender, display name, email, real name.
- New characters then pick a race (
listto see the options,help RACEfor details,pick RACEto choose -- e.g.pick human) before landing in Ylsrim's central bazaar, the mudlib's own shipped example domain: a small desert town with an armoury, a weaponry, a pub, a church, and the surrounding Kaliid Road. - Classic Nightmare command system: a soul-verb social/emote engine layered underneath the normal player command set, plus a full wizard/creator toolset (
update,clone,ed, etc).
Play online
https://mudlibs.fluffos.info/nightmare4/
Admin account
- ID:
fluffos - Password:
Mud@2026 - Privilege: full creator + admin (member of the
SECUREandASSISTgroups insecure/cfg/groups.cfg), seeded through the mudlib's own real one-time installation wizard (the first connection to a fresh copy of this lib always creates the admin account this way, then replaces itself with the normal player login flow).
Warning: Mud@2026 is a public default password for local playonly. 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.fluffosGame 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:
- an unconditional
file[0..<3](assumes exactly a 2-character extension, i.e..c) -- silently produces"look.l"instead of"look"once files are 4-character.lpc; or - a guard-then-strip pair,
if (file[<2..] == ".c") file = file[0..<3];--convert_lib.sh's own literal-.c"-reference fix (correctly) rewrites the quoted".c"to".lpc", but leaves the numeric slice widths untouched, so the guard's 2-characterfile[<2..]comparison against the now-4-character".lpc"literal can never be true again -- the strip silently stops firing at all, leaving the full.lpcextension baked into every derived command/verb name.
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:
lib/editor.lpc(eventEdit()/process_input()) -- the general-purpose "edit this file, then call me back" mixin used by mail, bug reports, room-building, and the bareedcommand. Rewrote againsted(file, exit_fn, restricted):eventEdit()now callsed(file, "ed_exit", !creatorp())and stores the caller's callback; the driver invokes the nameded_exit()function on this object when the interactive session ends (replacing the old manualquery_ed_mode()==-1polling inprocess_input(), which is now unreachable dead code kept only so files chainingeditor::process_input()still compile -- the driver's C-level input loop intercepts every keystroke directly for the whole session under__OLD_ED__, bypassingprocess_input()/input_to()entirely). This file is inherited into the player body class, so before this fix no player body could compile at all -- the single most severe finding in this port after \S2.lib/nmsh.lpc(the Nightmare shell mixin, also inherited into the player body) --write_prompt()calledquery_ed_mode()to draw a different prompt while mid-edit. Since the driver draws its own prompt for the whole duration of an old-styleed()session (same reasoning as above), this branch can never fire under either ed model; dropped rather than ported.
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:
secure/cmds/creators/lsed.lpc-- see \S7; needs the "new ed" synchronous API this driver build doesn't have. A wizard-only batch scripted-edit utility, not core gameplay.secure/daemon/remote.lpc(preloaded, but preload failures are caught) -- the "Remote Creator Protocol" daemon, a legacy inbound socket letting a separate client program edit files over a raw TCP connection onPORT_RCP. EveryeventWrite(fd, ...)call site passes the connection's rawintfile descriptor where the inheritedLIB_SERVER::eventWrite(object owner, mixed val, int close)expects the owning socket *object* (owner->GetDescriptor()) -- a pre-existing arity/type mismatch against remote.lpc's own declared base class, not something this port introduced (nothing in the file defines a local, differently-typedeventWrite()override, and no git history exists to show it ever did). Whether old MudOS's looser compile-time type checking simply let this call through uncaught, or this daemon was already non-functional in the original archive, is unrecoverable from a single-commit clone; left uncompiled rather than reworking a niche wizard-only network protocol's calling convention.secure/daemon/bugs.lpc-- an in-mudlib bug-tracker daemon explicitly gated#ifdef __PACKAGE_DB__with the file's own comment "only useable if you can define PACKAGE_DB". This driver build happens to unconditionally predefine__PACKAGE_DB__(advertising SQL package support), which the original authors evidently never expected any real deployment to enable -- the gated body itself is genuinely unfinished/truncated (a bareret[row[0]] = (...with no closing braces, an undefinedretvariable, atmpused before declaration ineventAssign(), a hardcodednightmare.imaginary.comDB host). Not touched -- reconstructing a truncated, never-finished feature would be inventing content, not fixing a driver incompatibility.daemon/tmp/A_WEARON.lpc-- one of ~30 tiny runtime-generated cache files underdaemon/tmp/(each doesint TYPE() { return SOME_MACRO; }, a classic old-MudOS trick for resolving a#define's value dynamically without a real efun). All the others reference a real macro frominclude/armour_types.h; this one referencesA_WEARON, which doesn't exist anywhere in that header (the real macro isA_WEAPON) -- almost certainly a typo baked into this snapshot's cache by whatever process originally generated it, not authored content. Left in place as a harmless, non-preloaded, already- broken cache artifact rather than guessing at and "fixing" a generated file's intended macro name.
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):
- Admin install:
secure/lib/connect.lpc(the mudlib's real, built-in one-time installer, active only until the first admin is created) walked through completely -- MUD name (fluffos), password + confirm (Mud@2026), display name, gender, real name, email -- correctly addedfluffosto both theSECUREandASSISTgroups insecure/cfg/groups.cfg, createdsecure/save/creators/f/fluffos.o, and self-replacedsecure/lib/connect.lpcwith the fixedconnect.real(confirmed identical viadiffafterward) -- exactly the mechanism this codebase intends for admin bootstrap, not a hand-edited save file. - Admin login (separate connection, post-install): name + password -> three news screens (general/creator/admin, confirming the
SECUREgroup grant actually took) -> real starting room (Ylsrim's central bazaar) ->look(full room description),score("Creators have no score, get a life." -- correct, intentional creator-exempt response, not a bug),who(showsArch: Newbie Wanderer Fluffos the unaccomplished, confirming admin rank),say hello world(broadcasts correctly),quit(clean disconnect,[Fluffos quits]logged). - Full new-character registration (separate connection): name (
testchar) -> confirm y/n -> password + confirm -> gender -> display name -> email -> real name (optional) -> race selection (listshows 17 races,pick human) -> newbie news screen -> real starting room, landing correctly alongside the admin --look/score(correct level-1-human character sheet: town, alignment, age, hunger/thirst/ mood) both producing correct output,quitclean. Removed the throwawaytestcharsave afterward; kept the seededfluffosadmin account and itsrealms/fluffos/workroom.lpc, matching this collection's existing convention fornightmare3. - Driver killed by exact PID after testing (never
pkill -f).
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:
- §7.121 currency float-into-int: found and fixed (
GetNetWorth()above); every other money-handling function inlib/currency.lpc/lib/std/barkeep.lpc/lib/std/vendor.lpc/lib/teller.lpc/secure/sefun/economy.lpcalready correctly wraps its float arithmetic into_int()/to_float()at every call site checked. - §8.3a
privatecommand-dispatch demotion: not applicable to this codebase's architecture at all -- there is nocommand_hookmechanism; ordinary commands dispatch throughdaemon/command.lpc(already fixed for the §7.118 filename-slice bug at onboarding) and NPC "commands" dispatch only througheventAsk(). A script-driven check for the general shape (aprivatefunction referenced by string name fromadd_action()/call_out()within the same file, thedemonangel-class pattern) found zero matches anywhere in the corpus. - §7.122 class/marker-item duplication on reconnect: not applicable -- no
auto_load/compute_autoload_array/load_autoload_obj-style reload mechanism exists anywhere in this codebase (confirmed by grep);lib/player.lpc'sclass quest *Quests/class death *Deathsfields are tracking data, not physical reloadable items. - §7.123 bare file-scope assignment: one heuristic grep hit corpus-wide (
secure/cmds/players/tell.lpc:101's__Morse = ([... ]);), hand-verified as a false positive -- it's a local variable inside themorse()function body, not file scope, and the file compiles and works correctly.
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(),全部正常。
- 2026-10-14: FluffOS 970d9f09 warns
Float value truncated to int(#1426). 7 sites in 5 files: assignments, initializers and returns now spell the truncationto_int(...)(same value). Changed objects loaded before and after in fresh drivers, no regressions (scripts/lpc_fix_float_trunc.py, KB 04 §6.10).
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 的实测表。