info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
The real, self-identified 'Dead Souls' object library at its 3.0 point release (December 2009) -- an early snapshot of the same 3.x codebase branch that later became the 3.8.6 generation, several point releases and years earlier, with seven of that later version's nine domains: a default starting area, a town, the example town Ylsrim, an LPC-University-style campus, a cave zone, an examples area, and Praxis. Registration includes an age gate ('Are you 13 years of age or older?') followed by a race pick from eight options -- dwarf, elf, half-elf, half-orc, halfling, human, orc, or poleepkwa -- and, like dsII, the very first account ever created walks through a one-time admin-installation wizard before the mud restarts into its normal login flow. This is the most network-connected mud in the collection: it dials out to a live IMC2 hub and a separate Intermud-3 network on every boot, and even a bare, unattended boot triggers an automatic version-check that fetches the still-live dead-souls.net's release notes over a real HTTP connection.
README
The real, self-identified "Dead Souls" object library at its 3.0 point release -- released 2009-12-30, signed Cratylus @ Dead Souls (see doc/RELEASE_NOTES). A genuinely distinct release from the other four Dead Souls entries already in this collection (dsI, 1996-98 Nightmare-IV-era; dsII, 2006's "Dead Souls II"; ds386/ deadsouls_fluffos, the 2014-era 3.8.6 generation) -- but the CLOSEST of the four to ds386/deadsouls_fluffos by a wide margin: 90.8% of shared files are byte-identical, and secure/daemon/master.lpc differs from ds386's by only 8 trivial lines. Best understood as an early snapshot of the exact same "3.x" codebase branch that later became 3.8.6, captured a few point releases and several years earlier -- see NOTES.md \S1 for the full byte-level comparison against all four siblings.
Source: a ZIP archive from mudbytes.net, a now-dead site. Recovered via
the Wayback Machine
(<https://web.archive.org/web/20190802014424id_/http://mudbytes.net/files/download/2733/>)
since the original site is down. Slug dsIII, number 182, port 40229.
Highlights
- Seven shipped domains:
default,town,Ylsrim,campus(an LPC University area),cave,examples, andPraxis-- a strict subset ofds386's 9 (missing onlyamigaraandlearning, added in later point releases). - A working first-boot admin-installation wizard (
secure/lib/connect.lpc): name/password/gender/display-name/ real-name/email, then shuts the mud down (same asds386) -- a restart is required before logging in as the new admin. - Full ordinary-player registration includes an age gate ("Are you 13 years of age or older?"), then a race-selection step (
list/help RACE/pick RACE-- 8 races: dwarf, elf, half-elf, half-orc, halfling, human, orc, poleepkwa). - Strict English-only name validation (
A-Z a-z ' -only, with an explicit rejection message) -- this archive cannot register a Chinese character name; a real ASCII test account (Qinbing) was used instead for verification, same adapted bar asds386's own testing. - Makes THREE independent real outbound network connections -- confirmed live, more than any other Dead Souls-lineage lib in this collection: 1. IMC2 (
secure/daemon/imc2.lpc, preloaded) -- a real socket to a live IMC2 hub a couple seconds after boot. 2. Intermud-3 (daemon/intermud.lpc, preloaded) -- a second real connection attempt. 3. NEW, and automatic on every single boot with zero player interaction:secure/daemon/autoexec.lpc(itself preloaded) firessecure/cmds/creators/dsversion.lpc's update-check one second after boot, which fetchesdoc/RELEASE_NOTESfresh fromdead-souls.netover a real HTTP connection -- confirmed live on a bare idle boot with no login at all. The fetched content shows dead-souls.net is itself STILL LIVE (release notes through 2020), unlike mudbytes.net (this archive's actual download source, which really is dead). A related creator command,secure/cmds/creators/rss.lpc, has the same eager network behavior and fires merely from being LOADED -- which anlpcc --batchcompile sweep of this lib will do, meaning even a bare compile sweep of this lib makes real outbound network connections, not just a live boot. SeeNOTES.md\S5 for the full detail. Do not sweep this lib into high-frequency automated re-boot OR re-compile loops -- same safety-flagging convention asdsI/dsII/imud/foundation2, extended here to coverlpcc_check.shsweeps too. - The archive's own
secure/cfg/groups.cfg/secure/include/config.halready shipped with the correctADMIN/DEBUG_NAMEplaceholder tokens (unlikedsII's copy, which had the original author's real name hardcoded) -- no hand-fix needed for the admin-installer's group-membership scrubbing here. - Nearly every driver-compat bug found while porting this (
get_root_uid()/get_bb_uid()/creator_file(), thelog_error()-during-compile recursion, a staleruntime_config.hslot-numbering mismatch,OLD_EDvs. the split editor API,__DIR__/__FLUFFOS__macro collisions, a mistypedLIB_REMOTEpath,SetLong()/SetExternalDesc()/SetInternalDesc()declared too narrowly,daemon/books.lpc's array-vs-mapping mismatch, a brokenset_pre_exit_functions()call) turned out to be byte-identical to bugs already found and fixed while portingds386-- ported that fix catalog wholesale rather than rediscovering it. Full list, plus the handful of genuinely new findings (a mixed-encoding curse-word table, severalTYPE array NAME-dialect shapesds386's own sweep didn't need to handle), inNOTES.md\S3-4.
Registration flow
name -> "Do you really wish to be known as <Name>? (y/n)" -> "Are
you 13 years of age or older? (y/n)" -> password (5+ letters) ->
confirm password -> gender (male/female/neutral/none) -> email
(required, validated as user@host) -> race selection (list to see
options, pick <race> to choose) -> news screens (<return> to
continue) -> lands in /domains/default/room/ (the default domain's
start room).
The very first account ever created goes through a separate, one-time admin-installation wizard instead (see above) -- the mud shuts down after that wizard completes, and every account after the restart uses the normal flow described here.
Admin account
- id:
fluffos - password:
Mud@2026 - privilege:
SECURE/ASSIST/ELDER/TEST/INTERMUD/BUILDER/TELNET/EMOTESgroup membership (secure/cfg/groups.cfg), account file undersecure/save/creators/f/fluffos.o
Created through the mud's own first-boot installation wizard; no manual
group-membership fix needed (see Highlights above). Verified live:
score shows the correct character sheet, and update (this project's
canonical ACL/compile-permission smoke test) succeeds with Ok.
Status
Boots clean, zero fatal errors. Verified live with a real driver session
and a raw socket client: the first-boot admin installation wizard
(including the mud's self-shutdown-after-install), a restart, admin
login, a brand-new ordinary character's full registration (including
the age gate and race selection), a distinct reconnect-login code path,
and look/score/update/a clean quit all producing correct output
for both accounts.
2282 of 2291 .lpc files pass a full lpcc_check.sh batch compile
sweep (99.6%); the 9 that don't are all pre-existing non-bugs or
compile-sweep-only eval-cost artifacts (a deliberate author anti-load
trap, two builder-tool scaffolding templates, two confirmed-harmless
isolated-compile warning failures, two confirmed batch-eval-cost
artifacts that pass standalone/live, one genuine but pre-existing and
non-blocking content-side performance bug in the cave domain's orc
NPC race setup, a missing LIB_CAPTURE library component, and a
missing opcprof() efun) -- see NOTES.md \S6 for the full breakdown.
WASM status: playable. Shared WASM driver (secure/sefun/sockets.lpc
already stubbed). Admin login (fluffos / Mud@2026) into Fluffos'
workroom, plus score / quit, verified with scripts/wasm_client.js.
Play: https://mudlibs.fluffos.info/dsIII/
Local run
cd libs/dsIII
~/src/fluffos/build-debug/src/driver config.fluffosGame port: 40229. Note the THREE outbound-network-connection caveats
above (two at every boot, one also from a bare compile sweep) before
repeated/automated boots or lpcc_check.sh runs.
NOTES · 移植与修复记录
Dead Souls 3.0 -- porting notes
Source: a ZIP archive (ds3.0/, 5553 files, WINDOWS_INSTRUCTIONS.txt at
top level) from mudbytes.net, a now-dead site. Recovered via the Wayback
Machine
(<https://web.archive.org/web/20190802014424id_/http://mudbytes.net/files/download/2733/>),
re-verified live (HTTP 200, real ZIP magic bytes, 5553-file/ds3.0/
top-level-dir match) at the start of this session before extracting. The
zip bundles fluffos-2.18-ds07/ (a driver source snapshot), bin/,
win32/, extra/ build/install scaffolding -- all ignored, this project
uses its own driver. The real mudlib root is the zip's lib/ directory
(bin/mudos.cfg's own master file : /secure/daemon/master confirms
it). Slug dsIII, number 182, port 40229.
1. Duplicate check -- distinct release, but the closest sibling by far
This collection already has FOUR Dead Souls entries: dsI (~1996-98,
Nightmare-IV-era, 2 domains), dsII (2006, "Dead Souls II", 4 domains),
and ds386/deadsouls_fluffos (3.8.6, 2014-era, 9 domains, confirmed
identical codebase generation to each other). Before building out a full
pipeline, checked whether "Dead Souls 3.0" repackages any of the four:
- Self-identification:
doc/RELEASE_NOTEShas a dated version history culminating in a---- 3.0 ----entry ("Released 2009-12-30... Driver: added Kalinash's fix for Solaris on 64 bit SPARC... Added commands: anchor, unanchor..."), preceded by2.11a20-2.11a22entries -- a real, self-contained, later point release, not a copy ofdsII(Nov 2006) or a re-save ofds386(2014).README.txt's own title text is a stale "Dead Souls 2... Released December 2005" banner (last touched Jan 2008) that predates this zip's actualRELEASE_NOTEScontents -- a rolling-tarball snapshot where the README just wasn't kept in sync, not evidence this is actually the 2006 "II" release. - Domain count: 7 domains (
campus,cave,default,examples,Praxis,town,Ylsrim) -- more thandsII's 4, and a STRICT SUBSET ofds386/deadsouls_fluffos's 9 (missing onlyamigaraandlearning, both added in later 3.x point releases per their ownRELEASE_NOTESentries).dsIhas only 2. 4598 files underlib/here vs.dsII's 1937,ds386's 4727. secure/daemon/master.ccomparison -- the decisive evidence: 1157 lines here vs.ds386's 1157 (identical length) anddsII's 781/dsI's 749 (a different, much smaller shape entirely). A realdiffagainstds386's raw archive shows only 8 changed lines total -- all tiny game-balance/logging tweaks (adebug_message()string, aget_stack(1)vsget_stack()arg, a clean-up percentage26vs34, an array-size cap500vs1024, an error-counter variable) -- vs.dsII's real, substantive divergence (added globals, a rewrittenload_access()body). Againstdeadsouls_fluffos's raw copy, the same file differs by 223 lines -- meaning this archive's master.lpc is closer tods386's thands386anddeadsouls_fluffosare to EACH OTHER (189 lines apart, two nominally-same-version 3.8.6 copies from different sources).- Whole-tree file-identity check: of the 4555 relative paths shared between this archive and
ds386's raw copy, 4136 (90.8%) are byte-identical; againstdeadsouls_fluffosthe rate is only 63.3% (2874/4542). 43 files exist only here (mostlydomains/campus/adm/*admin scratch tools and cacheddomains/town/virtual/sky/*.ccoordinate-instance files); 172 exist only inds386(new content added in later point releases, including the two newer domains).
This is a genuinely distinct release -- not a byte-identical duplicate,
since 419 of the 4555 shared files differ and 43 are unique here -- but
it sits MUCH closer to ds386/deadsouls_fluffos in the lineage than
dsII ever did: this is best understood as an early-2010s snapshot of
the exact same "3.x" branch that later became 3.8.6, captured about 4-5
years and several point releases earlier. duplicate_of is null in
meta.json, but the whole fix catalog below was ported from ds386's
own NOTES.md wholesale (verified per-file via diff against ds386's
raw archive before applying each fix) rather than rediscovered from
scratch, since nearly every driver-compat bug turned out to be
byte-identical.
2. Conversion
English-language archive (public domain, from mudbytes.net) --
scripts/convert_lib.sh against raw/ds3.0/lib confirmed this:
4562/4598 files already valid UTF-8 (pure ASCII), only 5 lossy
conversions, 28 binary files skipped. 2290 .c files renamed to
.lpc, 603 literal .c" references fixed, 31 local angle-bracket
#includes converted to quotes, 1429 files touched by the
static->nosave sweep.
2.1 One source file was genuinely MIXED-encoding, not GB18030 -- and the lossy conversion broke its string-literal syntax
secure/sefun/translate.lpc's FLUBS array (the same multilingual
"drunk talk" curse-word list ds386/dsII both have) contains:
- Two Finnish words with literal Latin-1 bytes (
"kyrpä","räkä", 0xE4 =ä). - One word that was already valid UTF-8 in the raw archive:
"ХУИТА"(Cyrillic, 5 characters,\xd0\xa5\xd0\xa3\xd0\x98\xd0\xa2\xd0\x90).
A single GB18030 pass mangled BOTH differently: the Cyrillic bytes
(already-correct UTF-8) got mis-decoded as GB18030 multi-byte sequences
into garbage CJK characters, and the Latin-1 ä bytes got silently
dropped as invalid, which ate the closing quote immediately after
kyrp too ("kyrp\xe4" -> "kyrp" was NOT what happened -- the actual
result was "kyrp,"r鋕,"shawtay", a real syntax break: an unterminated
string swallowing the next comma, then an invalid bare identifier
r鋕). This is a genuine, driver-compat-relevant compile failure, not
just cosmetic mojibake -- confirmed by a first driver boot failing
simul_efun's compile entirely (*No program in object
'/secure/sefun/sefun'!). Fixed by hand: re-decoded the Finnish words as
Latin-1 and re-typed the Cyrillic word as literal UTF-8, leaving
everything else in the file (99% pure ASCII) untouched. Also
individually re-decoded with iconv -f ISO-8859-1 -t UTF-8: doc/CREDITS,
doc/help/players/credits (a Lars Pensjö name-with-diacritic), and
domains/town/txt/mystery.txt (a ¯\(°_o)/¯ ASCII-art shrug). One more
lossy file, secure/save/functions.o, turned out to be a stale
function-signature cache (secure/daemon/function.lpc's
RestoreObject()/file_exists() guard handles a missing file
gracefully and rebuilds on demand) -- deleted rather than hand-repaired,
same precedent as ds386's functions.o/books.o cache files.
3. Driver-compat fixes -- ported wholesale from ds386's catalog, verified per-file
Every fix below was checked against ds386's raw archive first (diff
on the specific file) to confirm the bug is byte-identical before
porting ds386's exact fix; all were.
- Missing
get_root_uid()/get_bb_uid()/creator_file()master applies (AGENTS.md section 7.2, and thecreator_file()extension first cataloged onds386): this driver'sPACKAGE_UIDSbuild requires all three; missing entirely. Added byte-identical stub implementations (all constant"root"/"backbone") tosecure/daemon/master.lpc, same placement asds386. log_error()'sload_object(WEB_SESSIONS_D)re-entering the compiler mid-compile (AGENTS.md section 7.60's pattern): same fix asds386-- swapped tofind_object().- A stale
runtime_config.hINT-slot-numbering mismatch (AGENTS.md section 7.89,ds386is the origin case): this lib's ownsecure/include/runtime_config.hcomputedBASE_CONFIG_INT = BASE_CONFIG_STR + 15instead of the canonical driver'sRC_BASE_CONFIG_INT = RC_LAST_CONFIG_STR + 1(=256) -- everyget_config(__MAX_EVAL_COST__)/__MAX_BYTE_TRANSFER__/__MAX_READ_FILE_SIZE__/__MAX_STRING_LENGTH__call (identical symbol set tods386's) would read the wrong config slot. Fixed by dropping in the driver's canonical header, same asds386. OLD_EDvs. this archive's splited_start()/ed_cmd()/query_ed_mode()API:lib/editor.candsecure/cmds/creators/lsed.care BYTE-IDENTICAL tods386's raw copies, andlib/interactive.c/lib/nmsh.cdiffer fromds386's by only one unrelated line (avalid_event()security check added later ininteractive.c) -- sods386's exact rewrites were copied/ported directly rather than re-derived:lib/editor.lpc(full file copied fromds386's fixed version),secure/cmds/creators/lsed.lpc(full file copied, stubs the batch-editor feature as unavailable),lib/interactive.lpc's deaded_cmd(".");ed_cmd("x");ed_cmd("Q");quit-path block removed (redundant underOLD_ED's automaticsave_ed_buffer()on destruct),lib/nmsh.lpc'squery_ed_mode()- gated custom ed-prompt branch collapsed to dead code (if(0){...}, unreachable since the driver prints its own ed prompt directly from C underOLD_ED).#define __DIR__ "..."colliding with the native compiler predefine: 13 files here (domains/{town,default,Ylsrim}/virtual/ *.lpc) still had the old manual#define __DIR__idiom thatds386's OWN copies of these same files had already dropped in a later point release (ds386'stown/default/Ylsrimvirtuals have no__DIR__define at all -- confirms this archive predates whatever point release removed them, consistent with section 1's "same branch, earlier snapshot" finding). Removed all 13; the native__DIR__produces an identical value.secure/cmds/admins/sconv.lpc's#ifndef __FLUFFOS__guarding the wrong symbol (this driver predefines bareFLUFFOS, not__FLUFFOS__): byte-identical raw file tods386's; same one-line fix (__FLUFFOS__->FLUFFOS). Left the other ~9 files using the same guard untouched, matchingds386's "no error, no fix" precedent -- all compiled and ran clean.secure/include/lib.h'sLIB_REMOTEmacro pointing at a nonexistent path (/lib/remotewhen the real file lives atsecure/lib/net/remote.lpc): same bug, same fix asds386.LIB_CAPTURE(/lib/capture, no implementation anywhere in the archive) has the same pre-existing gap asds386-- left unfixed, breaks onlydomains/Ylsrim/broken/jar.lpc(a file literally shelved in abroken/subdirectory in the original archive).- **
SetLong/SetExternalDesc/SetInternalDescdeclaredstring-only when theirGet*()counterparts already supportfunctionp()/arrayp()dynamic-description values** -- 44 real call sites use the function-value form here (more thands386's reported failure count, since this archive'scave/examples/Praxisdomains have their own additional users of the idiom). WidenedSetLong/SetExternalDescinlib/events/look.lpc,SetInternalDesc+InternalDesc's declared type inlib/events/look_in.lpc, andlib/std/room.lpc'sSetLongwrapper, all tomixed/mixed *-- exact same fix and files asds386(its ownlib/include/{room,interactive,door}.hforward declarations were NOT widened either, matchingds386's working, tested precedent that the header prototypes aren't enforced against the concrete override here). - **
daemon/books.lpc'sLoadChapters()/lib/std/book.lpc'seventLoadChapters()declaredmixed *(array) when the real payload is a mapping**: same bug, same 3-declaration fix asds386(daemon/books.lpc,lib/std/book.lpcx2). domains/Praxis/supply2.lpc'sset_pre_exit_functions( ({...}), ({...}) )(a#define-aliasedSetProperty(string, mixed)call given two arrays): byte-identical file tods386's (differs only in whitespace); every sibling room in the same directory already has the identical call commented out. Appliedds386's exact fix (comment out with the same explanatory note).secure/sefun/sockets.lpc'sforeach (array item in finalsocks)-- the samedump_socket_status-in-simul_efunarray-dialect pattern flagged as recurring across sibling libs. Fixed toforeach (mixed *item in finalsocks).
4. TYPE array NAME dialect -- 166 files via an automated fixer, plus 7 hand-caught shapes the fixer's regexes didn't cover
Wrote a quote/comment-masking Python regex fixer (handles single-name
declarators with/without default values, multi-name comma lists, bare
array NAME/array *NAME, and (TYPE array) cast forms) and ran it
across the whole work/ tree: 166 files changed on the first pass
(secure/cmds/admins/admintool.lpc's array *classes;/array
*races;, secure/sefun/sockets.lpc, secure/sefun/sefun.h, and 163
more). Then a full driver boot + a standalone re-grep surfaced 7 more
shapes the regex set didn't anticipate, each fixed individually and
verified there was exactly one instance:
lib/body.lpc:1810:class MagicProtection array GetMagicProtection(){ ... }-- aclass TYPENAME arrayFUNCTION RETURN TYPE, not a variable declaration. The generic fixer's "barearray NAME" rule actually MIS-FIRED here (matched the space beforearrayregardless of the precedingmarriage/MagicProtectiontoken, since its negative lookbehind only checks the single character immediately beforearray) and produced the WRONG result (class MagicProtection mixed *GetMagicProtection()-- inserting a spuriousmixed). Caught and corrected toclass MagicProtection *GetMagicProtection(){ ... }by hand; grepped the whole tree for the sameclass \w+ mixed \*mis-fire pattern and confirmed no other instances.secure/daemon/finger.lpc:23:private class marriage array Marriages;-- sameclass TYPENAME arrayshape, in a variable declaration this time; hit the SAME fixer mis-fire (private class marriage mixed *Marriages;). Corrected toprivate class marriage *Marriages;.lib/guard.lpc:6:private nosave array PendingGuard = ({});-- a barearray NAME = default(no element type, WITH an initializer); the fixer's bare-array rule only matched a terminator of;/,/)immediately after the name, not=. Fixed toprivate nosave mixed *PendingGuard = ({});.lib/std/story.lpc:20,65:array GetTaleKeys();/array GetTaleKeys(){ return keys( Tales ); }-- barearrayas a FUNCTION RETURN TYPE (prototype and implementation). Fixed both tomixed *GetTaleKeys().secure/sefun/sockets.lpc:14:foreach (array item in finalsocks)-- covered in section 3 above, same root pattern asds386's recurringdump_socket_status-class foreach.secure/cmds/players/history.lpc:32:string *arr, tmp = ({});-- the ORIGINAL (pre-conversion) source wasstring array arr, tmp = ({});, a multi-name declarator list WITH a default value on the second name. The fixer's multi-name rule required no=in the matched span, so it never touched this line at all, and it slipped through to a real compile failure (Type mismatch ( string vs mixed * ) when initializing tmp) since a barestring *arr, tmponly stars the FIRST name --tmpstayed scalarstringwhile assigned an array. Fixed tostring *arr, *tmp = ({});(both starred, matching the old dialect's "applies to every name in the list" semantics, same asds386's NOTES.md documents for its ownadmintool.lpcfinds).
All 7 fixes verified individually: a clean re-grep for each pattern
class found zero remaining instances, and a full driver boot + full
lpcc_check.sh batch sweep both came back clean of these specific
error signatures afterward.
5. Live verification
Booted clean (Accepting telnet connections on 0.0.0.0:40229.,
Initializations complete., zero fatal errors, zero uncaught compile
errors in log/debug.log).
Confirmed THREE independent real outbound network side effects at boot -- more than any prior Dead Souls-lineage lib in this collection, and the third one is NEW to this archive specifically:
1. IMC2 (secure/daemon/imc2.lpc, preloaded, DISABLE_IMC2/
DISABLE_INTERMUD both 0 in secure/include/config.h): makes a
real socket_connect() a couple seconds after boot. Confirmed live
(ss -tnp showed ESTABLISHED ... 204.209.44.3:8080).
2. Intermud-3 (daemon/intermud.lpc, preloaded): a second real
connection attempt, confirmed live (ss -tnp showed
SYN-SENT ... 74.207.247.83:5000).
3. NEW: an automatic release-notes HTTP fetch on every single boot,
with zero player interaction required. secure/daemon/autoexec.lpc
is ITSELF preloaded, and its create() schedules eventRun() via
call_out(..., 1) -- one second after preload completes, with a
hasrun guard so it only fires once per boot. eventRun()
unconditionally does
catch( load_object("/secure/cmds/creators/dsversion")->cmd());,
and dsversion.lpc's cmd() immediately opens a real socket to
dead-souls.net (97.107.133.86:80) and fetches /RELEASE_NOTES,
overwriting doc/RELEASE_NOTES_HTTP with the live response.
Confirmed live and reproducible on a bare idle boot with NO login
at all: doc/RELEASE_NOTES_HTTP's mtime changed within ~14 seconds
of driver start with zero connections made, and its content is a
REAL, CURRENT response from a still-live server -- the fetched
response includes release notes up through "3.9, Released
2020-04-08", meaning dead-souls.net itself is not dead (unlike
mudbytes.net, this project's actual archive source). A SEPARATE
creator command, secure/cmds/creators/rss.lpc, has the same
eager-create()-time network behavior (registers RSS feeds from
lpmuds.net/feedburner.com via call_out((: Setup :), 2)) but is
NOT itself preloaded -- it only fires if something actually loads the
file, which an lpcc --batch compile sweep does (merely compiling
secure/cmds/creators/rss.lpc as one of thousands of files in the
sweep triggers its create() and, 2 seconds later, its own real
HTTP/RSS fetches) -- confirmed by doc/RELEASE_NOTES_HTTP and
tmp/imc2.log both getting freshly rewritten after this session's
lpcc_check.sh runs, with no driver boot in between.
This means BOTH a live boot of this lib AND a bare lpcc --batch
compile sweep of this lib can make real, uncontrolled outbound
network connections to third-party servers -- extend the existing
"don't sweep into high-frequency automated re-boot loops" caveat
(already standard for dsI/dsII/imud/foundation2-class libs)
to cover automated/batch COMPILE sweeps of this lib too, not just
driver boots. doc/RELEASE_NOTES_HTTP and tmp/imc2.log were reset
to their originally-shipped/pristine state before every commit in
this session; a future re-test will regenerate fresh copies again on
its own next boot/sweep, which is expected and not a bug.
Tested with a raw Python socket client (scripts/mudclient.py), all
required flows in one continuous session:
1. First-boot admin installation wizard (secure/lib/connect.lpc):
username fluffos -> password/confirm Mud@2026 -> default display
name -> gender male -> blank real name -> [email protected] --
completed successfully; the mud self-shut-down as designed
(connect.real's shutdown() + destruct(master()), matching
dsII/ds386's installer behavior -- a driver restart is required
before the new admin can log in). secure/cfg/groups.cfg and
secure/include/config.h correctly ended up with fluffos in place
of the ADMIN/DEBUG_NAME placeholder tokens -- unlike dsII,
this archive's shipped groups.cfg/config.h already had the
correct placeholder tokens (not a hardcoded original-author name), so
no hand-fix was needed here.
2. Reboot + admin login: fluffos/Mud@2026 -> arrived in
/realms/fluffos/workroom with wizard robe+hat, general/creator/
admin news tiers all paged correctly, who-equivalent (people,
auto-sourced via .profile) showed the admin present, score
produced a correct full character sheet ("You are First Admin
Fluffos (neutral)... a level 1 Human Explorer..."), update (no
argument, current-room form) reported Ok (warnings-only compile,
no errors), quit disconnected cleanly with correct
inventory-removal messages.
3. Ordinary new-player registration (tested with Qinbing, an
ASCII name -- this archive enforces a strict A-Z a-z ' -
name-charset check with an explicit rejection message, so a real
Chinese name is out of scope here by design, same adapted bar as
ds386's own testing): name -> confirm (y) -> age-gate (y,
"Are you 13 years of age or older?") -> password -> confirm -> gender
-> [display-name step skipped, CUSTOM_FORMAT is off by default] ->
email (validated, a blank/malformed first attempt is correctly
rejected and re-prompted) -> [real-name step skipped, REAL_NAME is
off] -> race selection (list/help RACE/pick RACE -- 8 races:
dwarf, elf, half-elf, half-orc, halfling, human, orc, poleepkwa) ->
news pages -> landed in "The start room" (the default domain's
entry point) with a correct ASCII-art room map and an HP/MP/SP status
bar. Re-logged in as the same character on a second connection:
Reconnected. (confirms the reconnect path is a distinct, working
code path from fresh registration, not just registration succeeding
once), score produced correct output, quit clean.
4. update <path> on the 4 lpcc-flagged "eval-cost" failures
(section 6 below) confirmed 3 of the 4 are batch-sweep artifacts, not
real bugs -- see that section for the one (domains/cave/room/
common1) that is a genuine, but pre-existing and non-blocking,
content-side performance bug.
2282 of 2291 .lpc files pass a full lpcc_check.sh batch compile
sweep (99.6%); the 9 that don't are broken down in section 6.
6. Known pre-existing gaps and sweep artifacts (not fixed -- content/archive design or tooling artifacts, not driver-compat)
/obj/stargate:#include this is here on purpose to prevent this from loading-- a deliberate author anti-load trap, same class asds386's identical file./obj/area_roomand/open/prog: builder-tool scaffolding templates never meant to compile standalone in place (area_room.lpcis copied elsewhere by the room-building tool before its relative#includeresolves;open/prog.lpcis written bydaemon/defines.lpcat runtime with a$Splaceholder later substituted) -- same non-bug class asds386's identical files./domains/campus/chamber/elevatorand/realms/template/area/ room/sample_room: fail with ONLY a "Redeclaration of global variable" warning and no further error text -- confirmed harmless by an identically-warned sibling passing clean in the same sweep (domains/campus/chamber/tree.lpcfor the first, any of dozens ofLIB_ROOM-inheriting rooms for the second) -- the same isolated-single-file-compile artifact classds386's NOTES.md documents (AGENTS.md section 10.4)./domains/town/room/shop2and/realms/template/area/room/ sample_room(again): fail with "Eval interrupted... cost limit reached" in the BATCH sweep, but PASS when compiled standalone via a single-filelpcc --batchrun outside the shared-budget batch session -- confirmed afeedback_lpcc_sweep_eval_cost_artifact-class false positive (the wholelpcc_check.shsweep shares one continuous eval-cost counter across thousands of files; a room whosecreate()clones a largeSetInventory()list can tip over the limit purely because of what ran immediately before it in the batch, not because the file itself is slow). Also verifiedshop2live via adminupdate-- no error./domains/cave/room/common1: fails BOTH in the batch sweep AND standalone -- a genuine, pre-existing performance bug, but confirmed present byte-identically indaemon/races.cinds386's own raw archive too (not something this port introduced). Root cause:common1.lpc'screate()->SetInventory()clones anorc_female1NPC ->SetRace("orc")->NewBody()->daemon/races.lpc:534'sSetCharacterLimbs(), which hits "Too long evaluation" (5,000,000 usec) even though the actual per-race data is tiny (13HAND/LIMBlines total for "orc").AddRace()'sLIMB-parsing branch (daemon/races.lpc:365-366) has an actual duplicate-append typo --res["Limbs"] = ({ res["Limbs"]..., limb });immediately followed byres["Limbs"] += ({limb});, appending each limb twice -- present identically inds386's raw copy, i.e. a real original-archive bug, not conversion fallout. Whether that duplicate- append is the actual root cause of the 5-second hang (vs. some other expensive step in the NPC/race/body creation chain) was not fully isolated; per this project's "programming bugs only, and even then verify before applying" discipline, and becausedomains/cave/is NOT preloaded and NOT on the core registration/login/play path this pass verified, this was left as a documented, non-blocking gap rather than speculatively patched. A future deep-play pass into thecavedomain specifically should budget time to actually isolate and fix this (or confirm it's cosmetic/rare enough to leave)./domains/Ylsrim/broken/jar:LIB_CAPTUREhas no implementation anywhere in the archive -- same pre-existing gap asds386(the file is literally shelved in abroken/subdirectory in the original archive)./secure/cmds/admins/opcprof: calls theopcprof()efun, which doesn't exist in this driver build at all -- same asds386, a creator-only diagnostic tool, not on any core path.
7. WASM pass
Measured 2026-09-03 against the shared ~/src/fluffos/build-wasm.
secure/sefun/sockets.lpc was already stubbed (the ds386 sibling
treatment this section originally predicted). No further mudlib-side
compile fix was needed. Verified with scripts/wasm_client.js:
fluffos / Mud@2026 into Fluffos' workroom, score showed
"First Admin Fluffos", clean quit. IMC2/I3/HTTP version-check still
try outbound connects at boot — do not loop-reboot. Shop/combat/death
were not exercised this pass.
深度功能测试 / Deep functional test (round two, AGENTS.md §10.7)
First real *playthrough* pass on this lib (prior sessions only verified
registration + look/score/quit + the admin install wizard, per
section 5 above). Played a full, continuous session as an ordinary new
player on the native driver (build-debug), using a second admin
connection only for a controlled death test at the end. This is also
the first lib in the Dead Souls lineage (dsI/dsII/ds386/
dshakkard/deadsouls_fluffos) to get a §10.7 pass -- none of the
siblings have a "深度功能测试" heading in their own NOTES.md yet, so
the bug found below (and its fix pattern) should be checked against
all of them, not just this archive.
Newbie path: read chapter 1 in handbook (repeated through
"chapter N" for the whole book) is this lib's own onboarding doc.
Chapter 4 spells out the intended early game explicitly: level up on
weak monsters in "the newbie mansion" (reached from the village, past
a newbie-only gate guard and a locked mansion door -- the real way in
for a level-1 character is external: grab the ladder from the
gardener's shack west of the mansion, drop it under the mansion's
open second-floor window, and climb ladder/enter window), sell
loot to a shopkeeper (this archive's actual town vendors are James at
the Healers' Guild and Lars at the pub -- the handbook's own "Otik"
example is stock Dead Souls boilerplate text, not hand-tailored to
this archive's actual NPC names, a content detail, not a bug), bank
with Zoe, and eventually join a class (fighter/mage/cleric/thief) via
a guild NPC once a gating quest (Orcslayer) is done.
Test character: id Qintestds, ASCII name (per this archive's own
strict A-Z a-z ' - name-charset validation, same as ds386's and
this archive's own admin-install testing in section 5 -- a Chinese
name is out of scope here by design), password Abc12345, human
Explorer -- kept (not cleaned up) as a representative playthrough
character, currently a ghost-then-regenerated corpse sitting in "The
start room" at half HP/MP/SP after the controlled death test below.
Save file: work/secure/save/players/q/qintestds.o.
Bug found and fixed
Silver/gold/every other in-game currency silently turns into a
float the first time it passes through ANY currency-exchange
calculation, corrupting the player's Currency mapping permanently --
secure/sefun/economy.lpc's query_base_value()/query_value()/
query_base_rate()/query_player_money(), plus one sibling miss in
lib/teller.lpc's eventExchange() and one in lib/props/value.lpc's
SetBaseCost().
- Symptom: reproduced live buying a 2-silver bottle of water from Lars (the pub keeper) with
buy water from lars. The very next attempt to learn a spell from Herkimer (ask herkimer to teach light) printed:"Light costs 100 silver and you only have 88.000000."-- a raw float leaking into player-facing text instead of a clean integer silver count. Confirmed at the data level too: after asave, the raw player file literally containsCurrency (["silver":88.000000,...])-- not a display bug, the stored balance itself is now a float and stays that way forever (every future addition/subtraction against an already-float mapping value stays a float in this dialect, since a declaredinttype is compile-time only and never coerces an assignment -- see this project's ownreference_lpc_int_cast_is_compile_time_onlyprecedent and thequest_times_percent_operatorcorpus sweep for the identical class of bug in other libs). - Root cause, traced end to end:
lib/std/barkeep.lpc'seventSell()(invoked by everybuy X from <barkeep NPC>) doesx = query_value(ob->GetBaseCost(), query_base_currency(), GetLocalCurrency()); ... who->AddCurrency(GetLocalCurrency(), -x);.query_value()and its helperquery_base_value()(secure/sefun/economy.lpc:59-70before the fix) are both declared to returnint, but their bodies do real floating-point exchange-rate math (amount * rate,baseval / rate, whereratealways comes fromECONOMY_D->__Query(type,"rate"), a value the economy daemon stores and returns as a genuinefloat) and return that float without ever callingto_int()-- unlike their own two sibling functions in the exact same file,currency_mass()andcurrency_value(), which already correctly wrap their return into_int(). Once a float slips intox,AddCurrency(string type, int amount)stores it straight into theCurrencymapping (lib/currency.lpc'sCurrency[type] += amount), permanently changing that currency's runtime type for the rest of the character's life. The bank's currency-exchange command (lib/teller.lpc:227-229,ask zoe to exchange N x for y) has the identical gap on its own separate line --i = val / currency_rate(str2);-- missing ato_int()that the very next line in the same function (charge = to_int(i / (100 / GetExchangeFee()));) already has, so it independently reintroduces the same corruption even witheconomy.lpcfixed.lib/props/value.lpc'sSetBaseCost(string currency, int amount)two-argument form (used pervasively for pricing items, e.g.SetBaseCost("silver", 10)in dozens of files) has the same gap at its own point of origin:Cost = i * rate;with noto_int(), contaminating an item's *listed* price before it ever reaches a vendor transaction. - Fix: added
to_int()around the return value ofquery_base_rate(),query_player_money()(also switched its internal accumulatorxfrom a lyingintto an honestfloatthat only gets truncated at the very end, for clarity),query_base_value(), andquery_value()insecure/sefun/economy.lpc; wrappedlib/teller.lpc'si = val / currency_rate(str2);into_int(); wrappedlib/props/value.lpc'sCost = i * rate;into_int(). Every fix matches theto_int()-wrapping convention already established by sibling functions in the very same files -- this is closing an inconsistency the original code already knew the pattern for, not inventing a new one. - Verified: rebuilt nothing (LPC-only change), ran the
lpc-syntaxformatter on all three touched files (all three §9 blind-spot checks came back clean), rebooted the native driver, reconnected asQintestds. The character's pre-existingsilverbalance is still shown as63.000000in the raw save file after the fix -- expected and left as-is: the fix stops NEW corruption, it cannot retroactively un-float a value already written to a save file (that would need a one-time data migration, out of scope for a programming-bug fix). Proved the fix actually works by exercising a brand-new currency the character had never held: opened a bank account with Zoe and didask zoe to exchange 20 silver for gold(the exact previously-brokeneventExchange()path) -- after asave, the raw player file shows"gold":2with no decimal point, next to the still-float legacy"silver":63.000000in the very same mapping literal, proving the fix closes the corruption at its source.log/debug.logstayed clean (no compile/runtime errors) through the whole sequence. - Sibling libs to check:
ds386,dsI,dsII,dshakkard, anddeadsouls_fluffosshare this same Dead Souls Object Library codebase and, per this archive's own section 1 lineage analysis, are very likely to have byte-identical or near-identical copies ofsecure/sefun/economy.lpc,lib/teller.lpc, andlib/props/value.lpc-- worth a quickdiffcheck and the same three-line fix on all of them the next time any gets a §10.7 pass or economy-focused re-test.
What was tested and confirmed working
- Registration: full flow (name -> confirm -> age gate -> password + confirm -> gender -> email, validated -- a blank/malformed first attempt correctly re-prompts -- -> race pick,
list/pick human) -- landed in "The start room" with the starting Player's Handbook, t-shirt, and jeans,scoreandiboth correct immediately after. - Movement/exploration: the default domain's start room only has a
downexit into thecampusdomain (LPC University, a separate builder-tutorial zone) -- this isSetNoModify(1)-locked, deliberate content structure, not a bug. Walked from there through campus, across "University Square", north along a connecting path into the actualtowndomain (village intersection, Saquivor Road, the mansion), and confirmed door/open doormechanics, aclimb ladder/enter windowalternate-entry puzzle (the mansion's front door is locked and needs a key; the intended level-1 route is external, via a ladder retrieved from the gardener's shack), and a newbie-only gate-guard bypass (PreExit()indomains/town/room/gate.lpc, correctly let a level-1 character "sneak past" with a flavor message). - Combat: no player-reachable safe-sparring dummy exists in this archive (
domains/default/npc/dummy.lpcsits in a creator-onlyarenaroom gated byCanReceive();domains/campus/npc/dummy.lpcis never placed in any room at all -- both are inert example content, not a bug, just not wired up for ordinary players). Fell back to the handbook's own recommended weak target: rats in the mansion kitchen (domains/town/room/mansion_room7.lpc,SetLevel(1),SetMaxHealthPoints(10)). A fullkill ratfight ran cleanly for several dozen exchanges (both sides mostly missing at level 1, unarmed -- slow but correct, matches the handbook's own explanation of skill-based to-hit), correctly updatedmelee attack/melee defenseskill percentages instatfrom use, andignore all+ moving away correctly disengaged without further loss once clear of the room. - Skill/class acquisition, two paths: asked Herkimer (the Mages' Guild guildmaster) directly
to join-- correctly refused pending the Orcslayer quest ("First you must prove yourself worthy...",herkimer.lpc'sJoinGuild()). Asked himto teach light(the organic, no-guild-required spell-purchase path) -- correctly gated on affordability, which is exactly the interaction that surfaced the currency bug above. - Shop/economy: bought a claritin pill (10 silver) from James at the Healers' Guild and a bottle of water (2 silver) from Lars at the pub -- both transactions correctly moved the item and deducted silver,
i/inventory updated immediately. Opened a bank account with Zoe (ask zoe for account, 5-silver minimum deposit) and successfully exchanged currency (ask zoe to exchange 20 silver for gold) -- see the bug section above for what this surfaced and how it was verified fixed. - Death/respawn: no reachable-in-budget wild monster could kill a 420-HP level-1 character in reasonable real time (the rat fight above shows just how low the to-hit rates are at level 1), so this was triggered directly and deliberately via the seeded admin account (
fluffos/Mud@2026) on a second connection:eval object ob = find_player("qintestds"); return ob->eventDie("test smash");against the non-admin test character (never the admin's own body, which would hitlib/creator.lpc's wizard-exempteventDie()override instead and prove nothing). Confirmed the full cycle live: death message + ASCII "YOU ARE DEAD!" art,scorecorrectly showedQintestds the ghost,undeadrace, and reduced max MP while dead;regeneratecorrectly rematerialized the character at roughly half HP/MP/SP, applied the expected XP penalty (50 -> 38,score's "more experience points to advance" figure moved from 450 to 462 accordingly), and dropped the character back at the same default "start room" respawn point.log/debug.logstayed clean through the whole death/regenerate sequence. quit/ debug.log / reconnect after a real gap:quitproduced the correct "Please come back another time!" message and clean worn-item-removal text;log/debug.logshowed zero errors immediately after (this project's own §7.16/§10.7 lesson: a clean-lookingquitmessage does not by itself prove nothing broke server-side -- checked anyway, and it really was clean here). Waited a genuine 90-second wall-clock gap, reconnected with the same login/password, and landed back exactly where the character had quit (First Village Bank, same worn items re-equipped), withscorecorrectly reflecting realistic additional food/drink decay over the elapsed real time.
Minor observation, not fixed (cosmetic, not a programming bug)
domains/town/room/mansion_uhall1.lpc (and likely other rooms using
the same window-as-exit idiom) defines "window" as BOTH a raw
SetExits() key and a SetEnters() entry. The room's own
GenerateObviousExits()-generated text lists "window" as if it were
a plain bare-word direction right alongside down/west/east/south
("Obvious exits: down, west, east, window, south, enter window"), but
typing bare window actually fails ("There is no go window here.")
-- only enter window (or, per verbs/rooms/go.lpc's own help text,
presumably go window) actually works. Root cause: lib/nmsh.lpc's
bare-word-to-"go "-command pre-aliasing only covers the fixed
classic-cardinal-direction set (n/s/e/w/ne/nw/se/sw/u/d/out), never
arbitrary custom exit names, while lib/std/room.lpc's
GenerateObviousExits() blindly implode()s every raw exit key into
the display text regardless of whether it's bare-word-usable. This
looks like it would affect every room in the whole Dead Souls lineage
that names a custom exit outside the cardinal set (not something
introduced by this archive), and it's misleading-but-harmless UI text,
not a crash or wrong efun call -- documented here per the project's own
scope discipline (ERR_THERE_IS_NO is a normal, non-fatal parser
rejection, not a bug signature) rather than "fixed" by guessing at what
the room author actually intended.
Sibling sweep of ds386's round-two eventRevive()/room.lpc/combat.lpc/beggar.lpc bugs (2026-08-27)
ds386's own round-two deep-functional-test pass (this NOTES.md's own
sibling file) found and fixed four bugs and flagged all of them for a
sweep across the whole Dead Souls 3.x lineage: (1) SEVERE
eventRevive() feeding float arithmetic into AddHealthPoints()/
AddMagicPoints()'s int parameters (AGENTS.md §7.121 class), (2) the
MudOS-era replace_program() fold in lib/std/room.lpc's create()
(AGENTS.md §7.141), (3) the Wimpy fraction-vs-percentage bug in
lib/combat.lpc (AGENTS.md §7.124 class), and (4) a minor GiveMap()
reentrancy gap in domains/town/npc/beggar.lpc. Checked all four here:
- Bug #1 (
eventRevive()float corruption): NOT PRESENT, confirmed clean.lib/player.lpc'seventRevive()heals back HP/MP/SP via plain integer division (AddMagicPoints(-(GetMaxMagicPoints()/2)),AddStaminaPoints(-(GetMaxStaminaPoints()/2)),AddHealthPoints(-(GetMaxHealthPoints()/2))) -- this codebase never adopted thePERCENT_MP/PERCENT_HPfloat-#definepercentage schemeds386/dshakkard/deadsouls_fluffosuse here, so there is no float expression feeding theintparameters at all. No fix needed. - Bug #2 (
replace_program()fold inlib/std/room.lpc): PRESENT, fixed identically tods386/dsI. Removed thereplaceable(this_object()) && !GetNoReplace()→inherit_list()→replace_program(tmp[0])fold fromcreate()entirely (pure memory micro-optimization, no functional effect once applied since this driver defersreplace_program()'s effect to a periodic backend sweep, which crashes any closure built on a freshly-loaded room, e.g.eventHearTalk()'sTALK_AREAfilter, for roughly its first 5 minutes).SetNoReplace()/GetNoReplace()left in place as harmless now-unused API. - Bug #3 (
Wimpyfraction-vs-percentage): PRESENT, fixed identically tods386.lib/combat.lpc:65hadWimpy = 0.20;(declaredprivate int Wimpy, so the float literal silently truncated) instead ofWimpy = 20;, plusSetWimpy/GetWimpymistypedfloatinstead ofint. Fixed both. Verified live: booted (~/src/fluffos/build-debug/src/driver config.fluffosfromlibs/dsIII/), logged in as the existing admin account (fluffos/Mud@2026), ranwimpy->Percentage: 20%, a clean integer. - Bug #4 (
GiveMap()reentrancy indomains/town/npc/beggar.lpc): PRESENT, fixed identically tods386. Added the same!present("town map",ob)delivery-time re-check insideGiveMap()that already existed at schedule-time ininit().
Verified live end-to-end: fresh boot compiled clean (zero new
errors in log/debug.log, only the same pre-existing benign warning
classes already catalogued), say hello world worked immediately with
no crash, and wimpy showed the fixed 20%. Killed the test driver by
exact PID when done.
Source pattern for all four checks: ds386/NOTES.md's "Deep
functional test (round two, 2026-08-27)" section.
Sibling sweep: become <class> SetClass -> ChangeClass fix (2026-09-01)
Ported the fix from riftsds (AGENTS.md §7.195/§7.196; commit
11216f003b1) -- see ds386/NOTES.md's identical write-up (same
session, same batch) for the full bug mechanism. Summary: the stock
Dead Souls "Praxis" demo guild-join rooms called SetClass() directly
instead of ChangeClass(), so the multi-class privilege gate
(high_mortalp(), which excludes creators too) always silently
rejected a fresh explorer's first class despite a success message
printing.
Confirmed present here, byte-identical to ds386/riftsds's
pre-fix files: all six domains/Praxis/*_join.lpc files called
SetClass(); secure/include/compat.h was missing the same three
query_name/query_cap_name/query_gender mappings.
Fix applied: six SetClass() -> ChangeClass() edits, three
compat.h accessor lines added -- identical to ds386.
Live-verified with the seeded fluffos admin (Class reset to
"explorer" via eval between attempts, to force the exact
creator-excluded-from-high_mortalp() scenario the bug hits):
become fighter/become cleric/become mage all now print the real
success text and score confirms the genuine class change ("You are
a level 1 Human Fighter"/"Cleric"/"Mage"). become monk/kataan/
rogue do not work, but this is the identical pre-existing,
shared-lineage missing-class-data gap documented in ds386/NOTES.md
(secure/cfg/classes/ here also only ships explorer/fighter/
mage/cleric/thief -- confirmed, same as riftsds and ds386),
not a regression or a miss in this fix.
Committed only the six join-file fixes and the three compat.h
mappings; incidental save-file churn from this session's boot (seeded
fluffos admin's Class field, player list, mudinfo, snoop, preload
saves, RELEASE_NOTES_HTTP refetch) was left uncommitted. Killed the
test driver by exact PID when done.
Shop (2026-09-04 librarian slice)
Otik's general store (/domains/town/room/shop) is live on this 3.0
snapshot — the handbook's "Otik" example is not boilerplate here.
Seeded admin fluffos / Mud@2026 (88 silver on the creator save)
goto'd the store. buy wooden torch from otik completed a paid
sale: Otik said "Here is an old wooden torch for 60 silver!",
inventory showed the torch, money 88→28 silver. Purse math is
integer. Do not use bare buy torch — /domains/town/obj/rayovac.lpc
still carries "torch" in its id list. 拜师 analogue
ask herkimer to join at /domains/town/room/magic_guild is still
Orcslayer-gated ("First you must prove yourself worthy..."), same
as the earlier §10.7 pass; re-confirmed live this slice.
Bug found and fixed (heartbeat hobbled(this_player())):
same shape as deadsouls_fluffos/brassring/dsII.
lib/body.lpc heart_beat called hobbled(this_player()); during
heartbeat this_player() is 0, so disable.lpc did
0->GetMissingLimbs() every 2s. Changed to hobbled(this_object())
and added if(!objectp(ob)) return "No missing limbs."; after the
decls in secure/sefun/disable.lpc. After this boot, look/goto/buy
worked with no idle collapse. Verb-file hobbled(this_player())
checks in go/jump/enter/climb were left alone (those run in command
context). Same one-line fix applied this session to
dshakkard/riftsds/ds386 (dsI has no hobbled()).