info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
A 1998-1999 hobbyist mudlib by Finnish author Tero Ilkka Leino ("namhas"), the fourth incarnation of his Majik project and a sharp architectural break from majik3 (also in this collection): instead of room objects, the whole game world is a real 2D grid rendered by a dedicated graphical client, driven over a custom binary-framed wire protocol rather than plain telnet. A fresh character rolls a race (human, elf, orc, troll, or dwarf) and lands in Hilltop Village -- a hand-built little town of named locations (a bank, a post office, a general store, the Hilltop Inn, an "Intersection," and a hilltop shrine called the Legendary Hilltop of the Gods with a nearby Plaque of Explorers) populated by a banker, grocer, innkeeper, guard, and a hermit NPC forever muttering about the application he's filling out to become a wizard "greater than Namhas itself" -- a wink at the author's own handle. West of the village lies Bral Gaur, a dwarven area named and lettered in Gwel, an invented dwarven conlang with its own noun/verb/adjective word lists bundled in the archive. The project's own README candidly admits development stopped while the engine was "almost, still not even beta," before the team moved on to a planned 3D successor (majik3d) that was never released -- so wilderness beyond the two hand-authored areas is thin, and the terrain generator that would have filled it in was never finished as portable code. See also majik3, the same author's 1997-1998 predecessor project.
README
A real MudOS v21/v22-era mudlib from 1998-1999 -- the fourth incarnation of hobbyist author Tero "namhas" Leino's Majik project, and the direct successor to majik3 (also in this collection). Development stopped when the base engine was, in the author's own words, "almost, still not even beta" -- the world builders hadn't finished building content by the time the team moved on to a 3D successor project.
Architecturally very different from majik3: a grid-based, "roguelike" 2D gameworld with a real graphical client (not plain telnet) driving it over a custom structured wire protocol.
Source: tleino/majik4 on GitHub.
Highlights
- A real 2D grid map you walk around on, not room-to-room text navigation.
- Real hand-built content: Hilltop Village (an inn, a bank, a general store, a village square) and the Bral Gaur dwarven area (with its own invented conlang, used for in-game names and vocabulary).
- The original graphical client protocol is fully text-framed and scriptable (see NOTES.md) -- you don't need the original client binaries to interact with this server.
- See also majik3, the same author's 1997-1998 predecessor project.
A note on playability
This archive's own procedural terrain generator (used to fill in "wilderness" between the hand-built areas) was written as a native MudOS C driver extension for performance, never built into this project's FluffOS driver. Onboarding added a minimal compatibility stub so every character can still enter the game world -- but wilderness tiles beyond the hand-built areas render as flat, undetailed terrain rather than the original's smoothly blended landscape. The named areas themselves (Hilltop Village, Bral Gaur) are unaffected and play as originally built.
Play online
WASM status: playable. The graphical MCP client path is unchanged.
The browser terminal now uses a parallel plain-text login (name, then
password, then race human for a new character) and lands in the
archive Hilltop Village central square. Commands: look, score,
quit. See NOTES.md.
Admin account
- ID:
fluffos - Password:
Mud2026x - Privilege:
security 100(this lib's own top rank), granted the same way the archive's own README.1st documents: register normally, then appendsecurity 100to the resultingdata/player/f/fluffos.osave file.
Warning: Mud2026x is a public default password for local play only.Change it before exposing this host publicly.
Local run
cd libs/majik4
~/src/fluffos/build-debug/src/driver config.fluffosGame port: 40253.
NOTES · 移植与修复记录
Majik 4 -- porting notes
Source: git clone https://github.com/tleino/majik4 (commit
b24efafe0b53c5b3433f421751e29442cee1262b, cloned 2026-08-28). A real
MudOS v21/v22-era mudlib by Tero "namhas" Leino, the fourth incarnation
of his "Majik" project and the direct successor to
majik3 (onboarded immediately before this one,
same session). The repo's own README.1st confirms the MudOS origin,
candidly says development stopped when the base engine was "almost,
still not even beta" (the designers hadn't finished building content
before the team moved on to the 3D majik3d successor), and gives
build instructions that name a custom native driver package
(bin/majik_mapgen.c/bin/majik_mapgen_spec.c) the mudlib depends on
-- see §3 below, the most consequential single finding in this pass.
Mudlib root is the repo's lib/ directory; bin/, COPYING, doc/,
README.1st, TODO, and template/ at the repo top level are not part
of the mudlib and were not converted. 271 raw files, 232 .c files.
Slug majik4, number 951, port 40253.
Zero driver_hook/set_driver_hook/H_[A-Z_]+ hits anywhere in the
tree -- genuinely FluffOS/MudOS-compatible, not LDMud.
0. Architecture is very different from majik3
Unlike majik3's room-based text-MUD model, majik4 is a real 2D grid
("roguelike") world with a dedicated graphical client
(tleino/majik4-client, tleino/majik4-client-drac) communicating over
a custom but plain-text-framed protocol (include/mcp.h): every line
is <command-code>:<datalen>:<data>\n, and even login itself goes
through this framing (CMD_LOGIN_NAME=18, CMD_LOGIN_PASS=20, etc. --
secure/login.lpc/secure/player.lpc's process_input()). This is
genuinely scriptable over a plain TCP socket once the frame format is
understood -- NOT a WASM-terminal-only limitation the way zjdyzj's
protocol handshake is -- confirmed via a raw Python client that logs
in, sends race human (command/mortal/race.lpc's special-cased
dispatch, bypassing the normal command queue), and receives back the
character-sheet/map/object frames and a real room description. See §5
for the full verified playthrough and §4 for one non-obvious mechanic
(commands are NOT processed synchronously) that will trip up any future
scripted test against this lib.
1. Conversion
Almost entirely English/ASCII, like majik3 and openlib.
convert_lib.sh reported already_utf8=267 converted=0 lossy=4
skipped_binary=0. All 4 lossy files are world/bral_gaur/{gwel-nouns,
gwel-verbs,gwel-adjectives,voc-gwelmarun} -- vocabulary lists for
"Gwel," an invented dwarven conlang used in the Bral Gaur area's names
and lore. Trial-decoded a raw sample with latin-1 per AGENTS.md §4.1's
guidance (GB18030 dropped the bytes outright rather than mis-decoding
them, since these aren't Chinese-locale bytes at all): confirmed
genuine Finnish text (maaperä = "soil/ground", maaperä
(hiekkainen) = "soil (sandy)") -- unsurprising, the author is Finnish.
Re-decoded these 4 files individually with latin-1 -> UTF-8 instead of
the corpus default, overwriting the mis-converted copies in work/.
Renamed 232 .c files to .lpc, fixed 69 literal .c" references, and
converted 1 local angle-bracket #include to quotes. static->nosave
sweep found zero hits (this codebase already used nosave
consistently).
2. master.lpc checklist (§2.2) -- unlike majik3, no uid applies at all
secure/master.lpc had no get_root_uid(), get_bb_uid(), or
creator_file() at all (majik3's sibling lib has these on a separate
secure/secure.lpc; this archive apparently never got the equivalent
file). With this driver's PACKAGE_UIDS build:
- Missing
get_root_uid()is a hardexit(-1)at boot (confirmed via~/src/fluffos/src/vm/internal/master.cc'sAPPLY_GET_ROOT_UIDhandling). - Missing
creator_file()doesn't crash the boot but throwsNo function creator_file() defined!on every single object load, visible immediately inlpcc_check.sh's batch output (which cascaded further into a SECOND error,Wrong permissions for opening file /log/runtime.log, sinceerror_handler()'s own attempt to log the first error also failed -- see §2b).
Fixed with the same flat "everyone is Root" stub already established
for openlib/nightmare3/nightmare4/residuum/majik3 (this
session):
string get_root_uid () { return "root"; }
string get_bb_uid () { return "backbone"; }
string creator_file (string file) { return get_root_uid(); }valid_read()/valid_write() were deliberately left undefined --
checked ~/src/fluffos/src/packages/core/file.cc's
check_valid_path(): an ABSENT master apply here is treated as
"allow, path unchanged" (only an explicit numeric 0 return denies),
unlike get_root_uid() which is unconditionally required. This
mudlib's own access model never touches getuid()/seteuid()/
valid_read/valid_write anywhere (corpus-wide grep: zero hits), so
no further ACL work was needed.
2b. Archive shipped with no lib/log/ (or lib/binaries/) directory
Unlike majik3's raw archive (which had an empty but present lib/log/),
this one had neither -- almost certainly because git doesn't track
empty directories and this particular working copy's log/ happened to
be empty when it was committed. Every write_file("/log/...")/
log_file(...) call in secure/simul_efun.lpc/secure/player.lpc
(including one at the very top of process_input() -- i.e. EVERY
single line of client input, not just error paths) throws Wrong
permissions for opening file ... "No such file or directory" without
it. Fixed with mkdir -p work/log work/binaries before the first boot,
per this project's standing convention for missing-runtime-directory
crashes (AGENTS.md, generic "missing directory swallows errors"
pattern). Both directories are .gitignored corpus-wide
(libs/*/work/log/) same as every other lib's -- re-create them again
if this lib is ever rebuilt from raw/ via convert_lib.sh on a fresh
checkout.
3. The real blocker: generate_map() is a native MudOS C driver package this project's FluffOS build doesn't have (AGENTS.md §7.160)
secure/player.lpc's per-session setup() unconditionally routes every
new (or otherwise location-less) character's start variable to
/world/worldmap as the fallback. world/worldmap.lpc's own
build_map() calls generate_map(options, 100, 100) -- a function
that does not exist ANYWHERE in this codebase as LPC. Per this
archive's own README.1st build instructions (cp
/majik/server/bin/majik_mapgen.c packages/) and
bin/majik_mapgen_spec.c's own efun-prototype declaration (string
*generate_map(string, int, int);), this was a native MudOS C driver
package, written in C specifically for performance (the author's own
doc/mapgen.txt walks through the CPU cost of real-time bicubic
terrain-blending interpolation in pure LPC and concludes it's
infeasible at the player counts they wanted). This project's FluffOS
build does not compile in that package and has no equivalent.
Without it, world/worldmap.lpc fails to compile at all, and via a
live playthrough: character creation and race selection complete fine,
but the very next CMD_START (the client's post-race handshake) throws
No program in object '/world/worldmap'! at secure/player.lpc:393 --
no character could ever be positioned into the game world at all,
mortal or admin, the single most severe possible finding for this lib.
Unlike majik3's add_monster()/set_no_weight() class of gaps
(genuinely unfinished content with no reference implementation anywhere
to restore -- left alone, see that lib's NOTES.md/AGENTS.md §7.159),
this one has a clear reference implementation, just not one this
environment can build (a native C extension is out of scope for a
mudlib onboarding pass -- see also feedback_fluffos_pr_workflow-style
driver-level work, a different kind of task entirely). Since the
absence isn't a case of "the author never finished this" but "this
project's driver doesn't build the required native extension," a
minimal, explicitly-labeled compatibility stub was justified --
different from leaving majik3's content gaps alone. Added as a
secure/simul_efun.lpc simul_efun:
varargs string *
generate_map (string options, int w, int h)
{
// parses the packed "options" string (see world/worldmap.lpc's own
// builder) just far enough to pick ONE terrain character (highest
// total corner probability) and fills the whole w*h grid with it.
...
}This does NOT reimplement the real blended/randomized terrain algorithm -- that stays out of scope as genuine game-design/content work, and doing it "for real" would mean writing an entirely new terrain generator from the design doc, not fixing a bug. It exists purely so the game is enterable and playable at all. Real, documented, permanent consequence: wilderness tiles beyond the hand-built named areas (Hilltop Village, Bral Gaur) render as flat, single-terrain-character grids instead of the original's smoothly blended landscape. The hand-built areas themselves are unaffected -- confirmed live, walking around Hilltop Village renders its real, hand-authored room descriptions and NPCs exactly as designed (see §5).
4. Severe bug found and fixed: EVERY character got full admin on EVERY login
secure/player.lpc's setup() (run once per session, after race
selection) contained an unconditional:
security = 100;include/security.h defines SEC6 100 ("greater god" -- the maximum
admin rank in this lib's own rank scheme). This line ran for every
single character, new or returning, mortal or not, on every login --
meaning literally every player who has ever connected to this exact
codebase became a full admin the moment they finished character
creation. This directly contradicts the archive's own README.1st,
which documents a deliberate, manual, out-of-band admin-promotion
process (register as an ordinary mortal, log out, hand-edit the save
file to add security 100) as the intended mechanism -- a debug/
testing leftover almost certainly never meant to ship, consistent with
the project's own "almost, still not even beta" self-description of
this exact snapshot.
Verified live: a brand-new mortaltest character's save file has NO
security line at all (int fields default to 0 when never assigned
and not restored) BEFORE the fix would have existed; after the fix, a
fresh mortaltest registration correctly stays unset/0 (mortal)
rather than getting a saved security 100 line. Fix: only initialize
security to SEC_MORTAL (0) if it is still its zero default, never
overwriting an already-legitimately-granted higher rank on subsequent
logins:
if (!security)
security = SEC_MORTAL;This is the highest-severity single finding across both majik3 and
majik4 this session -- a universal privilege-escalation bug, not a
crash or a missing-content gap -- and is recorded as its own catalog
item, AGENTS.md §7.160.
5. Other bugs found and fixed
command/mortal/throw.lpc:if(ob->query_range() & RANG_THROWN))-- one extra closing paren, a hard compile syntax error. Fixed toif(ob->query_range() & RANG_THROWN).world/bral_gaur/genwords.lpc:#define NUM_LETTERS NUM_VOCALS + NUM_CONSreferenced an undefinedNUM_VOCALS, when the very preceding line definesNUM_VOWELS-- almost certainly the Finnish author's own native-language false friend ("vokaali" = vowel; compare the real Finnish content in this same lib'sworld/bral_gaur/vocabulary files, §1). Fixed to referenceNUM_VOWELS.
6. Confirmed genuinely orphaned/dead code, not fixed
inherit/elemental.lpc (undefined SPELL_EXTRA) and
inherit/truename.lpc (undefined TRUENAME_D) are both confirmed
un-inherited anywhere in the live tree (grep -rl for their literal
paths comes back empty) -- same shape as majik3's
inherit/living/living.lpc orphan pair, an abandoned earlier draft
superseded elsewhere. doc/example/platemail.lpc/helm.lpc (undefined
DT_IMPACT/DT_STAB/DT_CUT -- simply missing #include <weapon.h>)
are dead tutorial content, same class as majik3's own doc/examples/
and openlib's doc tree; nothing outside doc/example/ references
either file. None of these four affect anything reachable in actual
play.
7. An observed-but-not-fully-diagnosed runtime error, flagged for a future pass
One log/runtime.log entry surfaced during testing that wasn't traced
to a specific root cause: Bad argument 1 to EFUN call_other() Expected:
object, string, array, Got: int(0). -- /inherit/living.lpc:1042
OBJ(0) (the else if(THOB->query_somatic_meaning(verb)) line inside
force_us(), §0's command dispatcher). It appeared once across two
back-to-back test sessions run in close succession against the same
driver instance (an admin-seeding run immediately followed by a
mortal-character verification run) and did not visibly interrupt either
session -- both characters entered the game world and rendered correctly
afterward. Plausible candidate: THOB (this_object()) resolving to a
just-destructed/invalid reference if two connections' heart_beat()-
driven command draining (§4 below) overlapped in a way this quick
back-to-back test setup could trigger but a normal single-session
playthrough wouldn't. Not reproduced in isolation with the time
available this pass; noted here rather than chased further, per this
project's standard practice of recording an observed-but-unconfirmed
runtime finding rather than either ignoring it or over-claiming a fix.
8. Live playthrough (§10.1 bar, adapted for this lib's real-time/grid design)
Booted ~/src/fluffos/build-debug/src/driver config.fluffos from
libs/majik4/ -- Accepting telnet connections on 0.0.0.0:40253 /
Initializations complete, zero fatal errors. Full flow verified with a
raw Python socket client speaking the <code>:<len>:<data>\n frame
protocol (see include/mcp.h for the code table):
1. CMD_START (1:2:-1 -- the -1 sentinel deliberately skips this
lib's own client-version enforcement, player.lpc's own documented
escape hatch for exactly this kind of non-client testing).
2. CMD_LOGIN_NAME/CMD_LOGIN_PASS register a new character
("brannoc"/"Passw0rd") -- confirmed via New character. echoed back.
3. A second CMD_START triggers the race-selection dialog (CMD_DIALOG
frame: Select which race? race human;elf;orc;troll;dwarf;).
4. CMD_COMMAND with race human (dispatched specially, bypassing the
normal command queue -- secure/player.lpc's process_input())
completes character creation: stat rolls, starting skills
("You feel like you just improved your punch/dodge/kick"), and (post
§3's fix) successful placement into the real hand-built Hilltop
Village -- a proper room description ("You are standing in the
middle of the Hilltop Village central square...") plus visible NPCs
(banker, innkeeper, grocer, several torches, a bulletin board) sent
as real CMD_ADD_OBJECT/CMD_INFO_OBJECT frames, followed by an
automatic step into the village inn.
5. i (inventory, one of the three commands process_input()
dispatches immediately without the heart_beat queue -- §4) correctly
reports "You are carrying: 8 gold coins".
6. quit triggers this lib's own real confirmation flow -- a warning
about being killable while disconnected, then Are you sure (y/N)?
-- and quit y completes it cleanly: CMD_SOUND (logoff sfx),
CMD_QUIT with reason text Normal exit., then the server closes
the connection. No crash, no error.
7. Re-logged in as the same saved character ("brannoc") to confirm
persistence (race and location restored correctly).
8. Seeded the standard admin account (fluffos/Mud2026x, security
100 per this archive's own README.1st-documented mechanism,
confirmed unaffected by the §4 fix since the fix only skips
re-initializing an ALREADY-set security value).
Important test-writing gotcha: commands are NOT processed synchronously
secure/player.lpc's heart_beat() drains at most ONE queued command
per tick, gated by an accumulating speed_count (incremented by a flat
MAX_SPEED/include/living.h per tick) needing to exceed the
character's own query_speed(). With the two exactly equal at a fresh
character's default speed, this is a minimum of two heartbeat
intervals (heartbeat interval msec : 3000 in this config -> 6+
seconds) per command, and back-to-back commands compound linearly. A
naive scripted test sending commands 2-4 seconds apart will see stale
or entirely absent responses that look exactly like a hang or a
silently-dropped command but are neither -- confirmed by simply waiting
longer: a quit/quit y round-trip that looked stuck at a 15-second
total test budget completed correctly, prompt and all, once the same
test was given ~20 seconds. i/blook/vlook are the three
exceptions, force-executed immediately without the queue -- but blook/
vlook themselves both require an explicit <x> <y> grid-coordinate
argument (a client-side "look at this specific tile" feature, not a
bare "look around" command the way look works in a normal text MUD --
this lib simply doesn't have a room-level "look" command at all, since
the room is always visually rendered by the client).
WASM packaging not attempted this pass -- wasm_status left empty,
matching majik3/openlib's precedent for a lib onboarded outside the
site-wide WASM sweep (and this lib's custom client protocol would need
its own WASM-side client shim regardless, a separate follow-up task).
WASM measurement (2026-09-03): limited, not playable
meta.json was flipped to playable on 2026-08-31 (2ec643a7c45)
to unblock the Pages deploy — that commit equated a native §10.7
pass with WASM playability. Measured against the shared
~/src/fluffos/build-wasm: the driver boots and immediately
emits MCP frames (1:18:$Revision: 1.19 $, then 17:2:0
CMD_ASK_NAME). The site's browser terminal cannot complete
login — typed names/passwords are not <code>:<len>:<data> frames,
so this is the same class as zjdyzj (a real client protocol the
gallery player does not speak), not a missing sockets stub.
wasm_status is now limited. Native play is unchanged. A future
WASM-side MCP client shim would be needed before this can be
playable. Shop/combat/death were not exercised this pass.
WASM text terminal (2026-09-11 leftover 652)
The graphical MCP client path is unchanged. Login and the player object
now also accept plain lines: first line is the name, second is the
password; look / score / quit / race <name> work without
frames. write_client() prints message payloads and swallows map/object
frames when query_text_mode() is set. New characters still land in
the archive Hilltop Village central square (same 2627,5527 spawn the
MCP CMD_START path uses) — not an invented void.
WASM wasm_client.js (wasmtxt2 / Play2026x / race human): look
prints the Hilltop intersection long description (inn east, post office
north, hill west); score Name: wasmtxt2 / Race: human / gold; quit
Goodbye. / Normal exit. Native mudclient.py on 40253 repeated the
same new-character walk. wasm_status is playable. MCP clients keep
working; they ignore the extra "Name:" banner.
深度功能测试 / §10.7 deep functional test (2026-08-31)
Full round-two pass on the native driver, driven entirely through a
small custom Python client (<code>:<len>:<data>\n framing, one
persistent socket per session managed via a FIFO+select() loop so
several separate tool calls can share one login across the queue's
6-13s-per-command latency -- see §4 for the gotcha this ran into with
overlapping stray client processes). Test characters: valtor
(password Passw0rd, human, kept as the representative mortal
playthrough character) and the pre-seeded admin fluffos/Mud2026x.
Save files: work/data/player/v/valtor.o, work/data/player/f/fluffos.o.
Four real bugs found and fixed (AGENTS.md §7.186/§7.187, new entries)
1. command/immortal/warp.lpc (admin teleport) crashed on literally
every invocation -- used this_player() (THIS) instead of this
codebase's own established previous_object() (PREV(0)) convention
for identifying the acting player. Since almost every command (warp
included) is dispatched from the driver's own heart_beat()-driven
command queue rather than live network input, this_player() isn't
valid there, and THIS->move(ENV(ENV(THIS)), ...) crashed with Bad
argument 1 to environment() Expected: object Got: 0 -- completely
silent to the caller (no error shown), so this command has plausibly
never worked at all in this build. Fixed by switching every THIS to
PREV(0) (7 occurrences), matching goto.lpc's already-correct
sibling implementation in the same directory. Verified live: warp <x>
<y> now actually teleports the caller, confirmed via a real
before/after position check and a flat log/runtime across the
attempt (versus 100% reproduction of the crash pre-fix, tested three
times).
2. command/mortal/vlook.lpc (the client's "look at this specific
grid tile" feature, documented in §0 above as one of the three
no-delay commands) crashed on every REAL invocation -- same THIS
vs PREV(0) bug. The "no delay" fast path in secure/player.lpc's
process_input() only matches the bare string "vlook" with no
arguments by strict equality; every actual client usage (vlook <x>
<y>, the only form the command's own logic accepts) therefore falls
through to the same queued force_us() path as any ordinary command --
so despite being grouped with blook/i as "immediate", vlook in
practice ALWAYS went through the path where this_player() is
invalid, and crashed with the identical Bad argument 1 to
environment() shape at MAPENV(THIS)->get_obj(x, y). blook.lpc
(the companion command) already correctly used PREV(0) throughout and
was unaffected -- used as the reference pattern for the fix. Verified
live: vlook <x> <y> now correctly returns the tile/object
description (tested against the caller's own tile, "a human boy"),
with zero new log/runtime entries.
3. command/mortal/who.lpc silently printed nothing to anyone,
always -- the milder variant of the same bug: message(CMD_MSG,
0,0,0, msg, THIS) with a 0 recipient doesn't crash (unlike
environment()'s strict object check), it just quietly delivers to
no one. who therefore always "succeeded" with zero visible output
and no error anywhere -- the more dangerous failure mode, since nothing
in a live session would ever flag it as broken. Fixed the same way;
verified live (who now correctly reports "1 player is currently
online.").
4. Every single quit crashed the driver's runtime log, invisible
to the player -- the exact same class of bug this project's own
bxsj §7.16 case study describes, found independently here.
inherit/living.lpc's force_us() dispatches quit via call_other,
which chains into secure/player.lpc:quit() ->
daemon/offline.lpc:add_offline(), which (as part of this lib's
intended "swap the connected player for a non-interactive offline
stand-in" design) destruct()s the very object force_us() is still
executing on. Control returns to force_us(), which unconditionally
continues into else if(THOB->query_somatic_meaning(verb)) and
several more THOB-referencing branches -- but THOB
(this_object()) is now 0 for the destructed frame, crashing every
time with Bad argument 1 to EFUN call_other() Expected: object,
string, array, Got: int(0). 100% reproducible across three independent
fresh test characters, each time with the visible "Normal exit."
disconnect looking completely normal to the player. A second, narrower
instance of the same "code assumes THOB is still valid after a call
that can invalidate it" pattern was found (and defensively fixed, same
session) in the same function's inventory/grid command-search block
(MAPENV(THOB)->get_obj(...) unguarded, unlike two neighboring blocks
in the identical function that already correctly check plain
ENV(THOB)) -- its exact trigger wasn't fully isolated (it surfaced
during a stretch of overlapping stray test-client processes racing the
same character name, see §4 below, which may have been a contributing
factor rather than the root cause) but the guard is safe and free
regardless. Full write-up and fix code in AGENTS.md §7.187. Verified
live: reproduced the always-on crash pre-fix, applied the guard, and
re-ran quit/quit y clean on three separate fresh characters
post-fix with zero new log/runtime entries each time.
What was tested and confirmed working
- Registration + re-login (restore path): fresh character creation (name/password/race-selection dialog) through to a real placed-in-world state, then a full disconnect+reconnect cycle (restore-object path) preserving race, inventory, skills, and grid position correctly.
- Movement: real grid-delta moves (
"dx#dy"frames, e.g.1#-1for diagonal) via the documented keyboard-alias convention, including a wall-collision bump ("You bang your head against the wall, ouch!") -- correctly rejected, no crash. - Skill acquisition, two paths: an admin-shortcut path (
lpc find_player("valtor")->teach_skill("dodge", find_player("fluffos")), using the sameteach_skill()API the world's own trainer NPC (world/hilltop/npc/trainer.lpc, "darklash") calls internally) produced the correct in-game confirmation ("You feel like you just improved your dodge.") and updatedskillsoutput. The organic path -- physically walking to darklash and usingchat teach-- was attempted but NOT reached live in the time budgeted for this pass (darklash is a wandering NPC and repeatedwarp+diagonal-move attempts kept landing just outsidechat's 3-tile radius); flagged here as unverified-live rather than silently presented as tested. The trainer's owndo_teach()code was read and confirmed to correctly call the sameteach_skill()API exercised above. - Combat: the bump-to-attack mechanic (
inherit/gridobject.lpc:move()callingBATTLE_D->hit(THOB, o, THOB->resolve_weapon())when walking into an occupied, physical, non-ethereal grid cell) was confirmed correct by direct code reading, but not reached live -- this snapshot's only living NPCs near the starting zone are shopkeepers/trainers/a guard, none of which could be reliably reached and safely bumped into within the time budgeted (the guard specifically carries a real weapon and was judged too risky to test blind against a fresh level-1 character). Flagged as unverified-live, not silently presented as tested. - Shop/economy:
world/hilltop/npc/grocer.lpcandinnkeeper.lpcbothinherit SHOP, but the shop room itself was not successfully reached within the time budgeted (warp'sget_near_location()fallback repeatedly snapped to an outdoor tile rather than the enclosed shop interior) -- confirmedlistcorrectly returns "No such command available." when issued outside shop range (i.e. the command dispatch itself doesn't crash on a miss), but the actuallist/buy/sellflow was not exercised. Flagged as unverified-live. - Death/respawn: read
inherit/living.lpc:die()in full -- confirmed this is genuinely unfinished content, not a bug: the block that would move a dead character to an afterlife room and support revival is entirely commented out in the shipped source (consistent with this archive's own README.1st "almost, still not even beta" self-description); what remains active on death is real and would need to be tested with real risk (drop all inventory, force-disconnect with "You have been killed.", mark the save permanentlydead, destruct the character) -- there is no live respawn to verify since that code path doesn't exist in this snapshot. Not attempted live given the risk of permanently bricking a test character for a code path confirmed non-functional by reading the source; documented here rather than guessed at. quit,log/runtimegrep, reconnect after a real gap: covered above under bug #4 -- now clean post-fix, confirmed across multiple fresh characters.- Long-sit boot watch: no WASM build exists for this lib (per §8 above); used the native-driver equivalent (
mudclient.py --idle 250 --timeout 220, one idle connection held open for the full ~220s window without logging in). See the tail of this session's testing for the result.
Process-hygiene lesson from this session, worth recording for future majik4 (or similarly protocol-scripted) testing
Because this lib's client is a custom raw-socket protocol rather than
telnet, testing it requires a purpose-built persistent-connection
helper (no tmux_mud.sh/mudclient.py equivalent exists for this
frame format). A naive re-launch-on-every-step pattern against a
shared FIFO+log-file pair, without first confirming the PREVIOUS
background client process actually exited, produced multiple
concurrent Python processes all reading the same FIFO and writing the
same log file at once -- non-deterministic which process consumed
which directive, and login attempts for the same character name
racing each other in ways that produced confusing, hard-to-attribute
symptoms (a "stuck" login, an unexpected second name-prompt). Always
verify exactly one session process exists (pgrep -af
<script-name>) immediately after launching a new one, before sending
any further directives, and pkill -9 any stragglers before starting
a fresh session against the same character.
深度功能测试(§10.7,2026-10-05)— 主控 author_file()/domain_file()
驱动的 mudlib_stats 包在主控载入时各调用一次 author_file(<主控文件>) 和 domain_file("/")。本库主控没有这两个 apply,所以每次启动都打印 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/master.lpc 补上了这两个 apply:作者返回 get_root_uid(),域返回 get_bb_uid(),正是驱动缺少它们时所用的值。域名等于 backbone 域时,驱动让对象沿用创建者的域,与没有这个 apply 时相同(KB 06 §7.222)。本库保留上游代码风格(几乎所有文件都未经 §9 格式化),所以只插入代码,不格式化。用 lpcc config.fluffos /nonexistent.lpc 前后对比,主控载入段只少了这两行。
Deep test (§10.7, 2026-10-08) -- fixed-width .lpc suffix slices
The .c -> .lpc rename changed the string but not the slice width: f[<2..] == ".lpc" compares two characters with four and is never true (!= always true), so suffix filters and strips never ran or cut x.l (KB 06 §7.238). scripts/lpc_lpc_suffix_slice.py --apply fixed 5 comparison(s) and 2 strip(s) (e.g. inherit/areamap.lpc); the changed files load at HEAD and in the tree (fresh processes) with no regression.
深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查(批 5)
inherit/behavior.lpc:enemies - ({ obj });结果被丢弃,已消失的敌人一直留在仇敌名单里;改为-=。- 改动的对象 HEAD 与工作区都能载入,无回退。
Compile warnings (2026-10-13)
Hand fixes; no warnings left in compiling files (the 4 files that did not compile before still do not). The changed objects and their subclasses load the same at HEAD and in the tree (12 objects, regress 0).
world/worldmap.lpcoverrodeareamap.lpc'sint area_loaded(string file)witharea_loaded(int x, int y). The areamap code the worldmap inherits (it even checksfile_name(THOB) == "/world/worldmap") callsarea_loaded(areas[f])with a file name, which reached the worldmap's coordinate version instead. The worldmap's function is nowarea_loaded_at(int x, int y)(one caller), so the inherited code gets the string version it was written for.inherit/item.lpcdeclaredmapping command;again on top ofgridobject.lpc'snosave mapping command(every item, weapon, armour, missile, light, key, board, money and the examples warned); item never used its copy, and gridobject's functions use theirs. Removed.include/behavior.hdeclaredpersonalities, sohorseman.lpc(which includes it and inheritsLIVING, which inheritsBEHAVIOR) had two copies, warned in all tenshare/horsemen. The variable now lives ininherit/behavior.lpc.include/tile.h'sblocking_terrainsis wrapped in#ifndef TILE_TABLES_INHERITED, defined byworld/worldmap.lpc, whose parentareamap.lpcalready includes the header.- Signatures:
base.lpc'smove(dest)isvarargs move(dest, int x, int y)like gridobject's;gridobject.lpc'squery_name,query_cap_nameandquery_shorttake the optional observerliving.lpc's versions take;secure/player.lpc'sremove()isvoidlikebase.lpc's.
- 2026-10-14: FluffOS 970d9f09 warns
Float value truncated to int(#1426). 6 sites in 3 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).
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 处)。改动:/inherit/gridmap.lpc(1 处)。
登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)
驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/secure/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。
log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)
这个库的代码往 log/ 的 2 个子目录写日志:log/error(daemon/log.lpc)、log/rep(daemon/log.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/error、log/rep 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。
深度功能测试(2026-10-27,§6.10 未知 pragma save_binary/optimize)
删除本库中 FluffOS 不认的 #pragma save_binary / #pragma optimize… 行。