info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
A classic English-language LPC teaching/reference mudlib, positioned by its own lpmuds.net description as not ready for play -- a base for learning to build a lib without dealing much with the driver. What shipped is more complete than that framing suggests: a full login/registration/security/command-dispatch/help/mail/message-board/news/channel/Intermud-3 engine including a real first-boot admin-setup wizard, plus one small real starting domain: an entrance room with a greeter NPC leading north to a single upstairs floor holding a cafe (complete with a serving station and stained tables), a bar, a lounge, a bathroom, and a backroom, all connected to each other rather than the "three upstairs floors" a first pass at this description assumed. There is no combat/stats system at all. Also makes a real outbound Intermud-3 network connection at boot, same as imud/tmi2/skylib.
README
An English-language classic LPC teaching/reference mudlib. Per its own lpmuds.net description, LPUniversity was never meant to be a finished game -- "not ready for play... provides a chance to build a lib from the ground up without having to deal much with the driver." What actually shipped is more complete than that framing suggests: a full login/registration/ security/command-dispatch/help/mail/message-board/news/channel/Intermud-3 engine (including a real first-boot admin-setup wizard), plus one small real starting area -- "LPUniversity Cafe" -- with a greeter NPC and real room descriptions. There is no combat/stats/limbs system at all.
Source: a maintained, already-FluffOS-adapted archive
(lpuni_fluffos_v1.zip) from lpmuds.net, a now-defunct LPMud archive
site. Recovered via the Wayback Machine
(<https://web.archive.org/web/20160306051537if_/http://lpmuds.net/files/lpuni_fluffos_v1.zip>)
since the original site is down. Slug lpuni, number 177, port 40224.
Highlights
- A genuine, working first-boot admin-setup wizard: the first account ever registered on a fresh install is automatically granted admin rights (
adm/etc/new_install-gated). SeeNOTES.mdfor how this project's own admin account was created through this exact flow. - One small shipped starting domain (
areas/lpuni/): an entrance/cafe with a greeter NPC, three upstairs floors, a bar, lounge, and bathroom. - The highest-impact bugs found while porting this to FluffOS: this driver's
PACKAGE_UIDSsupport requiresget_root_uid()/get_bb_uid()/creator_file()master applies that this archive never defined (without the first two the driver can't even boot); and a.c-to-.lpcrename side effect left several wizard/object commands (update,clone,dest,clean,goto) comparing a 2-character filename slice against the 4-character string".lpc"-- always false or always true depending on the comparison direction, soupdate <file>always failed with a double.lpc.lpcsuffix, andls/morenever colorized or syntax-highlighted.lpcsource. Full details, including two runtime hygiene fixes found via live play-testing, inNOTES.md. - LPUniversity makes a real outbound network connection at boot, confirmed live:
adm/daemons/chan_d's Intermud-3 module connects to a real public I3 chat router (*yatmim) within a couple of seconds of every boot. Genuine upstream functionality, not a bug -- but this lib should not be swept into high-frequency automated re-boot loops the way this project's other (fully sandboxed) libs safely can be. SeeNOTES.md\S6.
Registration flow
select a name -> (new account?) y -> password -> confirm password ->
public e-mail address -> [Hit enter to continue] twice -> lands in a
personal workroom (wizard/admin accounts) or the LPUniversity Cafe
entrance (ordinary accounts).
Admin account
- id:
fluffos - password:
testpass123 - privilege: admin (granted automatically by the archive's own first-boot install wizard)
Verified live: update <path> (this project's canonical ACL/compile-
permission check) against a real adm/obj/master.lpc file succeeds.
Status
Boots clean: zero compile errors across repeated fresh-driver boots.
Verified live with a real driver session and a raw socket client: the
first-boot admin install, look/inventory/who/ls/more/the wizard
update command, movement into the real shipped starting domain (with a
scripted greeter NPC), and a clean quit. This engine has no combat/
stats system, so there is no score command to verify (confirmed: no
file or string literal named score exists anywhere in the archive) --
same precedent as libs/foundation2/.
138 of 151 .lpc files pass a full lpcc_check.sh batch compile sweep;
the 13 that don't are all either include-only fragments never meant to
compile standalone (the Intermud-3 i3services/*.lpc handlers, textually
#included into chdmod_i3.lpc; adm/simul_efun/overrides.lpc, textually
#included into the real simul_efun object -- both compile and load
fine as part of their real parent file) or an object whose create()
legitimately needs a live player context (std_newsclient.lpc) -- see
NOTES.md for the full triage.
WASM status: playable. Login, arrival in the starting workroom, and
quit are verified under the shared WASM driver (NOTES.md WASM
status update). Play: https://mudlibs.fluffos.info/lpuni/
Local run
cd libs/lpuni
~/src/fluffos/build-debug/src/driver config.fluffosGame port: 40224.
Note: booting this lib makes a real outbound network connection (see Highlights above) -- avoid scripting repeated/automated boots.
NOTES · 移植与修复记录
LPUniversity Mudlib -- porting notes
Source: lpuni_fluffos_v1.zip, a maintained, already-FluffOS-adapted
archive from lpmuds.net (a now-defunct LPMud archive site). The live site
is down; this exact archive was recovered from the Wayback Machine
(<https://web.archive.org/web/20160306051537if_/http://lpmuds.net/files/lpuni_fluffos_v1.zip>)
and content-verified (valid zip, non-corrupted, real content such as
bin/config.lpuni and license) before this session started. Slug
lpuni, number 177, port 40224. The zip bundles a FluffOS 2.9-ds2.07
driver source snapshot (fluffos-2.9-ds2.07/), plus bin/, win32/,
etc/ build/install scaffolding -- all ignored, this project uses its own
driver. The real mudlib root is the zip's lib/ directory.
LPUniversity is positioned by its own lpmuds.net description as a teaching/
reference base -- "not ready for play... provides a chance to build a lib
from the ground up without having to deal much with the driver" -- so a
minimal/skeletal feel was expected going in (similar in spirit to
libs/genesis/). In practice it's less skeletal than that framing
suggests: it ships a complete login/registration/security/command-dispatch/
help/mail/message-board/news/channel/Intermud-3 engine (a real
adm/etc/new_install-driven first-boot admin wizard included) *and* one
small real starting domain (areas/lpuni/, "LPUniversity Cafe" -- an
entrance, three floors, a bar/lounge/cafe/bathroom, and a greeter NPC with
real dialogue). No genesis-style from-scratch bootstrap was needed. There is
no combat/stats system at all, so (like foundation2) there is no score
command to test -- confirmed via grep, not a gap in this port.
1. Conversion
English-language archive (not GBK-encoded) -- scripts/convert_lib.sh
against raw/lpuni_fluffos_v1/lib confirmed this: 897/902 files already
valid UTF-8, only 1 lossy conversion (a stray non-ASCII byte in a plain
text/doc file, not code), 4 skipped as genuine binaries. 151 .lpc files
after the .c->.lpc rename, 110 literal .c" references fixed, 0 local
angle-bracket #includes needed converting, 7 files touched by the
static->nosave sweep (no collisions: no "static... path-literal hits,
no #define nosave static/#define protected static shims to neutralize).
2. Compile-time driver-compat fixes
foreach (array item in ...)is not valid syntax on this driver --adm/simul_efun/socket.lpc'sdump_socket_status()declared the loop variable with the bare wordarray(old-MudOS shorthand); this driver requires a real type (mixed *item, since each element ofsocket_status()'s result is itself an array). Fixed the one instance; this broke loading the whole simul_efun object (socket.lpcis#included straight intoadm/obj/simul_efun.lpc), so nothing else in the mudlib could load until this was fixed.TYPE array NAME, the second old-MudOS array-declaration dialect (AGENTS.md \S6.3) -- a type keyword directly followed by the literal wordarray(e.g.string array arr = parse(...)), distinct from barearray x;. 13 occurrences across 4 files:adm/obj/master/valid.lpc(2, including the group/access-list parser inherited straight intomaster.lpc-- this alone blocked master from loading at all),adm/obj/security_editor.lpc(4),cmds/std/channel.lpc(1),cmds/adm/access.lpc(5, the wizard-facing twin ofvalid.lpc's parser). Fixed by dropping thearraykeyword and using real array syntax (string *arr); multi-declarator lines needed a*prefix on every subsequent name too (e.g.string array accessList = keys(access), keys, arr = ({});->string *accessList = keys(access), *keys, *arr = ({});).PACKAGE_UIDSis enabled in this project's driver build, and this master object never defined any of the three applies it requires. Confirmed via the driver's own boot message ("No function get_root_uid() in master object; possibly the mudlib doesn't want PACKAGE_UIDS to be defined") and by the__PACKAGE_UIDS__predefine in the boot's LPC Predefines dump. Withoutget_root_uid()/get_bb_uid()the driver refuses to boot at all; withoutcreator_file()(called on every object load) essentially every object load would hard-error the moment boot got that far. This codebase's own security model is entirely the privs/access-list scheme already inmaster.lpc'sprivs_file()(classifies a path into[daemon]/[adm_obj]/[cmd_wiz]/etc.) -- there's no separate driver-native-uid concept anywhere in the archive. Added:
``
string get_root_uid() { return "[admin]"; }
string get_bb_uid() { return "[backbone]"; }
string creator_file(string str) {
string ret;
ret = privs_file(str);
if (!stringp(ret) || ret == "") ret = get_bb_uid();
return ret;
}
`
(Same fix class as foundation2/merentha`, this session's other
lpmuds.net onboardings -- see AGENTS.md \S2.2's checklist.)
file[<2..<1] != ".lpc"-- a.c->.lpcrename side effect, AGENTS.md \S6.3's fixed-width filename-slice bug class.<2..<1>is a 2-character slice, sized for the old 2-character.csuffix; after the literal was updated to the 4-character.lpcstring the slice was never widened, so the comparison is always true (a 2-char slice can never equal a 4-char string) and the code always appends a redundant.lpc, producing e.g.master.lpc.lpc does not exist. Found in 5 wizard/object commands --cmds/object/clone.lpc,cmds/object/dest.lpc,cmds/object/update.lpc,cmds/object/clean.lpc,cmds/wiz/goto.lpc-- all fixed to<4..<1>. Confirmed live before the fix:update /adm/obj/master.lpcfailed with/adm/obj/master.lpc.lpc does not exist; after the fix it succeeds.
- Opposite-direction instance, same root cause: cmds/file/more.lpc
used == ".lpc" (not !=) with the same undersized 2-char slice, so
the pager's syntax-highlighted-paging branch for .lpc files could
*never* match -- it silently fell through to plain unhighlighted
paging for every .lpc/.h source file. Fixed the same way.
- Structurally different instance, same root cause:
cmds/file/ls.lpc's filename_prefix() used a switch() on a single
fixed 2-char slice with a case ".lpc": arm that could never match
(the file's own comment even lampshades this: //Will hit default if
size>2) -- every .lpc file in a directory listing colorized as
plain white/unmarked instead of green. A switch() can't have
per-case slice widths, so rewrote as an if/else chain: a <4..<1>
check for .lpc first, then the original <2..<1> checks for .h
and __SAVE_EXTENSION__ (both genuinely 2 characters, not bugs).
- Grepped the whole tree afterward for any other [<N..<1] <op> ".lpc"
or [<N..<1] <op> ".h" shape at any width -- these 7 sites (the 5
!= command fixes + more.lpc + ls.lpc) were the complete set.
3. Runtime/hygiene fixes (found via live boot + play testing)
- Missing
/home/l/login/directory crashed the first-boot admin-grant flow with a raw, player-visible driver traceback (AGENTS.md \S7.11).adm/obj/login.lpc'squery_name()hardcodes"login"for the transient pre-authentication connection object;master.lpc'slog_error()writes any caught compile/runtime diagnostic touser_path(this_player()->query_name()) + "log"whenthis_player()is set. During the very first registration on a fresh install,login.lpccallssecurity_editor->enable_membership(...)twice (developer, then admin) to grant the new account admin rights; something in that call chain triggers a caught error whilethis_interactive()is still the bareloginobject, andlog_error()'s ownwrite_file()into the never-shipped/home/l/login/then throws *itself* ("Wrong permissions for opening file /home/l/login/log for append. No such file or directory") -- visible to the connecting player as a raw traceback in the middle of an otherwise-successful registration. Fixed by seedingwork/home/l/login/.gitkeep(this project's established empty-directory placeholder convention -- a barelogplaceholder would itself be swept up by this repo's own.gitignorerule for runtime log dirs,libs/*/work/**/log, and silently vanish from every future clone). Confirmed fixed: re-ran the exact same registration flow against a fresh install (adm/etc/new_installrestored, account data cleared) and the traceback no longer appears;"Success [login]: You are now an admin."now prints cleanly with no error noise around it. log_error()funnels compile WARNINGS to the player too, unguarded (AGENTS.md \S7.10, the exact documented class). The very first lazy compile of/obj/mudlib/mail_clients/mail_client.lpc(triggered byenter_world()checking for new mail) throws 27 "Illegal to declare nosave function" warnings (the harmless, expected fallout of this driver'sstatic->nosaveconversion applied to old-MudOSstaticfunction declarations, AGENTS.md \S4.3) straight into the connecting player's screen, looking exactly like a crash in the middle of normal play. Gatedlog_error()'stell_object()call on the message not containing the (case-agnostic) substring"arning:", matching this project's established fix for this driver-wide class; warnings still get written to the player's private log file, only the live broadcast is suppressed. Confirmed fixed: re-tested the same first-mail-check trigger on a fresh boot and the workroom-entry output is now clean.
4. Checked, present but inert (no fix needed)
runtime_config.h'sget_config()index numbering is stale (adm/include/driver/runtime_config.huses the old compact scheme -- string constants 0-14, thenBASE_CONFIG_INTstarting at 15 -- while this driver's canonical header reserves string-constant slots up to 255 before the int base starts, per AGENTS.md \S7.89's class of bug). Confirmed the mismatch would misdirect anyget_config()call using this bundled header's constants -- but grepped the whole mudlib andget_config(is never called anywhere, so this is dead, unexercised code, left as-is (matches the archive's own shipped header, not something this port should silently "fix" without a live symptom to verify against).
5. Checked, not present (from this session's recurring-bug checklist)
NO_ADD_ACTION:#undef'd inetc/local_options-- this build targets nativeadd_action()support, which matches this driver's actual compile-time config; noskylib-style mismatch.ed_start()/ed_cmd()/query_ed_mode(): never used: this codebase calls the old-styleed()exclusively throughout (cmds/file/ed.lpc, the mail/news clients' editor integration), which is exactly what this driver build supports -- nofoundation2/finalrealms-style mismatch.refused as an ordinary identifier: none found (\bref\bgrep across the whole tree, ignoringreference/refer-family words, is clean).explode()on aSANE_EXPLODE_STRINGedge case (e.g.explode('/','/')): none found.add_action()with a bare function value instead of a string: none found -- all 4 call sites in the archive already pass a string function name, the correct idiom for this driver.
6. Live outbound network side effect at boot (by design, not a bug -- same class as imud/tmi2/skylib, see AGENTS.md \S2.3)
Confirmed live, not just read from source: adm/etc/preload loads
/adm/daemons/chan_d unconditionally, which pulls in
adm/daemons/chmodules/chdmod_i3.lpc (Intermud-3 chat-network support).
Within a couple of seconds of every boot, log/i3.log records a real
outbound socket_create()/socket_connect() to a live public I3 router:
Notice [I3/socket]: Connecting to *yatmim, 149.152.218.102 23
Success [I3]: Connected.
Notice [I3]: Sending connection details.
Notice [I3]: Sending 'startup-req-3'This repeated (a fresh connect-and-handshake) on every single boot this
session. This is genuine, intentional upstream functionality (not
something to patch away), but it means every boot of this lib makes a
real outbound connection attempt to a real third-party service --
this lib should NOT be swept into high-frequency automated re-boot/
re-test loops the way this project's other (fully sandboxed) libs
safely can be. The lpcc_check.sh compile sweep does NOT trigger this
(it never runs create()/preload, so it's safe to re-run freely); only a
real driver boot does.
7. Boot and play test
Booted ~/src/fluffos/build-debug/src/driver config.fluffos repeatedly
(a small, deliberately-minimized number of boots given \S6 above); zero
compile errors, Initializations complete., accepting telnet connections
on 40224 every time. Verified live with a raw Python socket client:
- First-boot admin install: registered a brand-new account
fluffosthrough the real registration flow (name -> password -> confirm password -> email -> the archive's ownadm/etc/new_install-gated wizard). Landed in a real per-account workroom (cp("/areas/std/workroom.lpc", ...)), automatically granted admin ("Success [login]: You are now an admin."), confirmed viawhoshowing[ Admin ] Fluffos. - Commands:
look,inventory(You are not currently carrying anything.),who,ls(directory listing with correct.lpc/.h/dir colorization after the \S2 fix),more <file>(correct paged, syntax-aware output after the \S2 fix), and the wizardupdate <path>command (Successful [update]: /adm/obj/master.lpc was updated.-- broken before the \S2 slice-width fix).quitdisconnects cleanly (Thank you for visiting LPUniversity). - Real shipped content: moved from the workroom into
areas/lpuni/'s "LPUniversity Cafe" starting domain (entrance.lpc's exit verbcafe) -- a real room description and a greeter NPC (Johnathon) with real scripted dialogue ("Welcome to the LPUniversity Cafe. Please feel free to make your way inside."), confirming this archive ships genuine playable content, not just an empty engine. - No
scorecommand exists anywhere in this archive (greppedcmds/for a file or string literal namedscore) -- there is no stats/combat system at all, consistent with this being a deliberately bare teaching base;inventory/who/ls/more/updatesubstitute for this project's usuallook/score/quitbar, same precedent asfoundation2.
Zero uncaught runtime errors in log/log (this codebase's
error_handler() log target) across this session's boots and
play-testing, beyond the two fixed issues in \S3 above (confirmed absent
on the final re-test boots).
Admin account
- id:
fluffos - password:
testpass123 - privilege: admin (granted automatically by the archive's own first-boot install wizard,
adm/etc/new_install-- now consumed/deleted as designed, so this is the mudlib's one standing seeded admin, not a save-file hand-edit)
Verified live: update <path> (this project's canonical ACL/compile-
permission check) against a real adm/obj/master.lpc file succeeds.
WASM status update (2026-08-26, another session)
Promoted wasm_status from "" to playable. Same by-now-routine
eager-simul_efun sockets gap as several sibling libs from this same
source: adm/simul_efun/socket.lpc's dump_socket_status() called
socket_status() unconditionally. Gutted to a safe stub (diagnostic
admin tool only, not on the boot/login path). Verified with a full
scripted WASM session: login (fluffos/testpass123), arrival in the
correct starting workroom, channel auto-tuning, and a clean quit
("Fluffos has left LPUniversity... Thank you for visiting
LPUniversity") -- the full session, including quit, was captured
distinctly this time.
Deep functional test (round two, 2026-08-27)
Full §10.7 methodology pass -- this lib had never had one before (only
the onboarding-tier verification above). Read doc/FAQ and
doc/general/commands first (this lib has no dedicated newbie-help
command output beyond help/help <topic>); confirmed via source
read and live play that this is a genuinely thin teaching base like
genesis/foundation2, not a full game: no combat, no stats, no
skills/sects, no shops, no death/respawn -- so those parts of the
standard checklist are honestly N/A here, not skipped. One continuous
session: booted ~/src/fluffos/build-debug/src/driver config.fluffos
from libs/lpuni/, registered several fresh English-named non-admin
characters one after another on fresh boots (a raw Python socket
client, not tmux_mud.sh) to isolate two bugs below, tested
look/inventory/get/drop/say/movement/help/who/news/
mail(admin-only)/finger/uptime/version/mudlist, quit,
grepped the mudlib's own error log (log/log, this codebase's
error_handler() target -- confirmed empty/absent after every clean
session), then reconnected after a genuine ~90s wall-clock gap and
confirmed a fresh, non-"reconnect-to-body" login with state (last
login time, correct starting room) intact. Found and fixed two severe,
previously-undiscovered bugs, both filed as new AGENTS.md entries
(neither is a pre-existing numbered class):
- AGENTS.md §7.139 -- the entire pinkfish/ANSI colour system was dead for every player.
write()on this driver only routes throughcatch_tell()/receive_message()(whereansi_parser.lpc'sparse_pinkfish()lives) when the runtime config int"interactive catch tell"is non-zero; left at this driver's default (unset/0), every%^TAG%^marker this mudlib ever writes -- the login banner, every room description, every command's output -- rendered as literal text instead of colour, for every player, forever. The archive's OWN bundled FluffOS 2.9-ds2.07 build config (raw/lpuni_fluffos_v1/fluffos-2.9-ds2.07/ local_options.lpuni) explicitly turns this flag ON, proving the original authors built and shipped this exact mudlib requiring it. Fixed by addinginteractive catch tell : 1toconfig.fluffos(no LPC change). Verified live over a raw telnet client: before the fix, the connect banner read literally%^BOLD%^Hello And Welcome to...%^RESET%^; after, it's real ANSI escape sequences (\x1b[1m...\x1b[0;37;40m). - AGENTS.md §7.140 -- a fresh non-admin player's first use of any not-yet-compiled command could permanently break that command for the whole boot. Root cause split two ways, both fixed: 1.
adm/obj/master/valid.lpc'svalid_read()resolves its access identity fromthis_interactive()(the CONNECTED player), which is correct for real content reads but wrong for this driver'sfunc=="include"calls -- the mandatory auto-injection of the configured global include file (adm/include/global.h) into the START of every single compiled file. Since/adm/is admin-only in/adm/etc/access, a regular player's own identity got denied reading it, aborting the ENTIRE compile (not just a diagnostic) of whatever command they were the first to try. Fixed by addingif(func == "include") return 1;alongside the function's existingfile_size/restore_objectexemptions (same established pattern, same function). 2.adm/obj/login.lpc'sidle_email()only created a new account's/home/<letter>/<name>/directory (dev workroom copy + journal link) inside the one-time/adm/etc/new_installfirst-admin-grant block -- every subsequent registrant got NO home directory at all.adm/obj/master.lpc'slog_error()(the driver's compile-diagnostic callback, called for warnings too, e.g. the routine 27 "Illegal to declare nosave function" warnings frommail_client.lpc's first lazy compile on any login's new-mail check) unconditionallywrite_file()s into that directory, throwing mid-compile for every homeless player. Fixed by hoisting the home-dir/workroom/journal setup out of thenew_installgate so every account gets one (kept the actual admin-grant lines --add_path,security_editor,rm("/adm/etc/new_install")-- inside the original gate, unchanged); also added adirectory_exists()guard aroundlog_error()'swrite_file()as a defensive belt-and-suspenders measure. Reproduced live, deterministically, on repeated fresh boots before the fix: a brand-new non-admin registration's very firstinventory/helpcommand failed withCannot #include global.h+ cascadingUndefined variableerrors, then*No program in object '/cmds/std/inventory'!on every later attempt (by ANY player) for the rest of that boot. After both fixes: multiple fresh non-admin registrations (grace,harper, etc.) raninventory/help/get/say/movement cleanly as their very first commands, with zero entries inlog/log. - Checked all other standing cross-cutting patterns explicitly (§7.121 float-in-int, §8.3a private-demoted dispatch function, §7.112 unguarded NPC
init()call_out chain, §7.122 reconnect/class duplication, §7.123 bare file-scope mapping/array statement, §7.124 fraction-vs-percentage, §7.126 stale.cextension in.osaves, §7.129tell_room()/message()wrapper omitted-arg-as-0, §7.130 post-non-interactive liveness check, §7.131find_living()/find_player()withoutset_living_name(), §7.132map()-over-mapping wrong-arg binding, §7.133 undefined disconnect apply, §7.134 field defaulting to0not({}), §7.135 accessor missing a sibling's lazy-init guard, §7.137"$verb"quicktyper bypass) -- all confirmed genuinely not applicable to this lib (no floats anywhere in the codebase; no percentage/stat fields at all, matching the "no combat/stats system" finding from onboarding;set_living_name()correctly called in bothmobile.lpcandnpc.lpc;net_dead()is defined and exercised; no inventory/equipment marker-item system to duplicate on reconnect; nomap()-over-mapping call sites anywhere, only over-array; nocommand("$...")call sites anywhere) rather than silently skipped -- except the two real, confirmed, fixed bugs above (§7.140's first half is itself a new instance of the general "basic commands silently broken for a fresh character" class thegenesisprecedent warned about, though the actual mechanism here -- a master ACL misattributing the compiler's own read -- is novel enough to file as its own entry rather than folding into an existing one). - Ran the §9 LPC formatter on all three touched files (
adm/obj/login.lpc,adm/obj/master.lpc,adm/obj/master/valid.lpc); zero errors, all three self-check blind spots (::-split,case+comment merge, CJK/escape re-spacing) grepped clean; re-booted and re-ran the full registration + command playthrough after formatting to confirm no regression. - Cleaned up all throwaway test-character saves/home-dirs/journal links (several fresh registrations used to isolate the two bugs above) before committing, keeping only the seeded
fluffosadmin account.