Foundation II LPC Library

✅ 可玩

Foundation II

foundation2

更新 a3c585c 2026-09-05 源码 下载 ZIP

▶ 开始游玩 · Play Now

A stripped-down classic descendant of the Nightmare mudlib lineage from the mid-1990s, positioned by its own boot banner as an engine for hardcore LPC programmers, not a beginner-friendly game world -- it ships no combat, limbs, or stats system at all. What shipped and works: a real first-boot admin-setup installation wizard, full login/registration, security/privs access control, command dispatch, help, mail, message boards, and Intermud-3 networking, plus one small starting domain. Two optional example/teaching content trees included in the archive reference an incompatible, differently-named room/item API from a different snapshot of this engine and don't compile, so they're not playable content.

README

A stripped-down, classic descendant of the Nightmare mudlib lineage, originally released by George Reese ("Descartes of Borg") in the mid- 1990s. Per the mud's own boot banner: "This mud is NOT for newcomers to LPC. You must be hardcore." Foundation II ships no combat, limbs, or stats system at all -- it's a minimal, complete engine (login/ registration, security, command dispatch, help, mail, message boards, Intermud-3) meant as a clean base for LPC programmers to build their own game world on top of, not a playable-out-of-the-box game.

Source: a maintained, already-FluffOS-adapted archive (foundation2_fluffos_v1.zip) from lpmuds.net, a now-defunct LPMud archive site. Recovered via the Wayback Machine (<https://web.archive.org/web/20160306101019if_/http://lpmuds.net/files/foundation/foundation2_fluffos_v1.zip>) since the original site is down. Slug foundation2, number 174, port 40221.

Highlights

Registration flow

new name -> "Do you really wish <name> to be your name? (y/n)" -> password (5+ letters) -> confirm password -> gender (male/female) -> display name (blank = default) -> email (required, must look like user@host) -> real name (optional, blank OK) -> two news screens (<return> to continue) -> lands in the Standard domain's starting cavern.

Every account (mortal or wizard) saves under the same secure/save/ creators/ tree -- this engine has no separate mortal/wizard save-file split by default.

Admin account

Created through the mud's own first-boot installation wizard (see NOTES.md), not a save-file hand-edit. Verified live: update <path> (this project's canonical ACL/compile-permission check) against a real cmds/player 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 installation wizard, a brand-new ordinary character's full registration, and look/inventory/who/save/a soul command (smile)/the wizard update command all producing correct output, and a clean quit. This engine has no combat/stats system, so there is no score command to verify (see NOTES.md for what substitutes for it in this port's verification pass).

223 of 246 .lpc files pass a full lpcc_check.sh batch compile sweep; the 23 that don't are all non-preloaded, non-boot-critical example/ teaching content with a pre-existing content/API mismatch predating this port (see NOTES.md \S3) -- expected for a deliberately minimal engine like this one, not a sign of missing conversion work.

WASM status: playable. Login as fluffos into the starting cavern and inventory are verified under the shared WASM driver (NOTES.md WASM status update). Play: https://mudlibs.fluffos.info/foundation2/

Local run

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

Game port: 40221.

NOTES · 移植与修复记录

Foundation II -- porting notes

Source: foundation2_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/20160306101019if_/http://lpmuds.net/files/foundation/foundation2_fluffos_v1.zip>) and content-verified (valid zip, non-corrupted) before this session started. Slug foundation2, number 174, port 40221. The zip bundles a FluffOS 2.9-ds2.13 driver source snapshot (fluffos-2.9-ds2.13/), plus bin/, win32/, old/ build/install scaffolding -- all ignored, this project uses its own driver. The real mudlib root is the zip's lib/ directory.

Foundation II is a stripped-down classic descendant of the Nightmare mudlib lineage: no combat/limbs system at all, positioned (per its own boot banner) as a minimal base "NOT for newcomers to LPC. You must be hardcore" -- aimed at LPC programmers building their own game on top of a bare engine, not a playable-out-of-the-box game world. This matches what shipped: a working login/registration/security/command-dispatch/help/mail/board/ Intermud-3 engine, one small "Standard" starting-cavern domain, and two optional teaching-only content trees (domains/Examples/, domains/School/) that reference a different, incompatible content API (see \S3) -- there is no score/combat/stats system anywhere to test.

1. Conversion

English-language archive (not GBK-encoded) -- scripts/convert_lib.sh against raw/foundation2_fluffos_v1/lib confirmed this: 1126/1160 files already valid UTF-8, only 1 lossy conversion (doc/faq/lpmud, a handful of stray invalid bytes in a plain-text FAQ file, not code), 244 files renamed .c->.lpc, 67 literal .c" references fixed, 13 local angle-bracket #includes converted to quotes, 118 files touched by the static->nosave sweep.

One file needed the same static->nosave treatment by hand: secure/lib/login.real is not .c/.lpc (so the mechanical sweep skipped it) but is real, live LPC source -- the mudlib's own first-boot admin-setup wizard (secure/lib/login.lpc, see \S5) cp()s it over login.lpc at the end of setup and it becomes the mud's actual ongoing login/registration object from then on. Its 45 static declarations were converted the same way by hand, then verified by compiling it standalone under its real target path before ever letting the install flow copy it into place live.

2. Compile-time driver-compat fixes

- Follow-on bug in the fix's own sscanf() use: creator_file(str) strips a clone-instance suffix via sscanf(str, "%s#%d", base, ext), same idiom as nightmare3's version -- but nightmare3's version never hits the case where str has no # at all in a context that matters, while every one of this driver's *own* boot-time preload calls does. An unmatched %s target keeps its int-0 default (not ""), so base stayed the integer 0 for any non-cloned path, and passing that into file_privs()'s file[0..1] string-indexing crashed with "Cannot index type 'int' using [ .. ] operator" -- caught by preload()'s own catch(), so non-fatal, but every single preloaded daemon hit it (confirmed via log/catch). Fixed by falling back to base = str whenever sscanf() doesn't report a full 2-field match.

- secure/cmds/creator/grant.lpc: file[strlen(file)-1] != "/" compares a single character (an int) against a string literal -- this driver's strict-types pass makes that a hard "!= always true" compile error (not just a warning), unlike whatever drove the original archive. Fixed to the character literal != '/'. - secure/cmds/creator/banish.lpc: used UID_BANISH (a real constant, defined in secure/include/security.h) without including that header -- had a literal duplicate #include <daemons.h> instead (one of the two copies replaced with the missing include). - secure/cmds/creator/memcheck.lpc: inherit DAEMON; -- DAEMON is not a macro anywhere in this codebase (the real one, used everywhere else, is LIB_DAEMON); a lone pre-existing typo, not something this port introduced. Fixed to LIB_DAEMON.

3. Bundled example/teaching content targets a different, incompatible API (not fixed -- pre-existing content mismatch)

23 of 246 .lpc files still fail lpcc_check.sh's batch sweep (down from 227 before the fixes in \S2); every one of them is non-preloaded, non-boot- critical teaching/example content, and every failure traces to the same root cause: this content was written against a different room/item API than the one this engine actually ships.

None of these 23 files are in secure/cfg/preload.cfg, referenced from the Standard starting domain, or reachable from any command a normal player or the seeded admin would run -- confirmed via a full live boot and play session with zero related errors.

4. First-boot admin-setup wizard (not a bug -- this engine's own onboarding flow)

Unlike most archives in this project (which need a wizard account hand-seeded via a save-file edit or a normal-registration + config-edit combo), Foundation II ships its own genuine one-time superuser setup flow: secure/lib/login.lpc starts as a small self-contained "installation process" wizard (name -> password -> confirm -> capitalized-name -> gender -> real name -> email), and on completion it clones the real, full-featured login/registration object over itself (cp("/secure/lib/login.real", "/secure/lib/login.lpc"), see \S1), destructs the temporary admin object and the master object (forcing a clean reload of both), and disconnects with "Login as your admin character." From then on the mud behaves like any other -- new characters register through the normal flow (name -> confirm (y/n) -> password -> confirm -> gender -> display name (blank OK, defaults to the account name) -> email, which must match user@host and is NOT optional/skippable -> real name (optional, blank OK) -> two "Press <return> to continue" news screens -> world entry).

The project's admin account was created through this exact first-boot flow (not a save-file hand-edit): id fluffos, password Mud@2026, granted ASSIST+SECURE group membership in secure/cfg/groups.cfg automatically by the setup flow itself (it renames the archive's placeholder admin group entries to the chosen admin's name), and given a real per-account "realm" directory (realms/fluffos/workroom.lpc, copied from domains/Standard/xtra/workroom.lpc by the same flow). Verified live with update <path> (this project's canonical ACL/compile check) against a real cmds/player file.

Note for anyone re-running this install flow: every account in this engine saves under secure/save/creators/, not a separate mortal-vs- wizard split -- #ifdef PLAYERS branches throughout the codebase (e.g. creator_file()'s own base-name check) are dead in this build (PLAYERS is never defined), consistent with this being a bare "for LPC experts" engine with no mortal/wizard distinction baked in by default.

5. Live outbound network side effect at boot (by design, not a bug -- same class as imud, see AGENTS.md \S2.3)

daemon/intermud.c (Intermud-3 protocol support, preloaded unconditionally via secure/cfg/preload.cfg) is guarded by #ifndef __PACKAGE_SOCKETS__ #error ...#endif -- and this project's driver build has the sockets package enabled (confirmed via nm: f_socket_create is present), so the guard doesn't fire. Its create() unconditionally schedules call_out((: Setup :), 2), and Setup() calls eventCreateSocket() against a hardcoded real address (204.209.44.3:8080, the historic *i4 Intermud-3 router) roughly 2 seconds after every boot. This is genuine, intentional upstream functionality (not something to patch away), but -- exactly like imud (AGENTS.md \S2.3) -- it means every boot of this lib makes a real outbound connection attempt to a real third party. That router is a 1990s-era service and near-certainly long defunct by now (no connection-success evidence was seen in any boot log this session), but this 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.

6. Boot and play test

Booted ~/src/fluffos/build-debug/src/driver config.fluffos repeatedly; zero compile errors, Initializations complete., accepting telnet connections on 40221 every time. Verified via a raw Python socket client (scripts/mudclient.py), end to end:

Zero uncaught runtime errors in log/runtime/log/catch across this session's boots and play-testing (the log/catch entries seen mid-session were creator_file() traces from the lpcc_check.sh batch runs that share this lib's log/ directory, not from the live driver -- confirmed by their file-modification time not advancing while the real driver was up).

WASM status update (2026-08-26, another session)

Promoted wasm_status from "" to playable. Same root-cause class as ds386/discworld/deadsouls_fluffos/tmi2: secure/sefun/ sefun.lpc's own dump_socket_status() (part of the eagerly-loaded simul_efun object) called socket_status() unconditionally, undefined on this driver build (no sockets package). Gutted to a safe stub (diagnostic admin tool only, not on the boot/login path). No other socket-related eager-load failures found. Verified with a scripted WASM session: login (fluffos/Mud@2026), arrival in the correct starting cavern room, and inventory producing correct output. quit wasn't recaptured distinctly in this transcript but is already verified clean under native testing above and untouched by this fix.

深度功能测试(round two, 2026-08-27)

Full §10.7 continuous playthrough against the native debug driver (~/src/fluffos/build-debug/src/driver config.fluffos), never done for this lib before (only the original onboarding-tier boot/play pass in \S6/\S "WASM status update" above). This is a genuinely minimal engine (no combat/limbs/stats/score, one small starting room with no working exits -- see \S3), so the test surface is: login/registration, look/inventory, save, mail (IIPS), who, quit+reconnect, and a systematic grep sweep for every standing cross-cutting bug class tracked in AGENTS.md (\S7.121-\S7.135, \S8.3a).

Playthrough (admin fluffos/Mud@2026, plus a throwaway English test character Aldric, cleaned up before commit): full first-boot admin login and a brand-new registration (name -> confirm -> password -> confirm -> gender -> display name -> email -> real name -> news screen -> world) both landed cleanly in /domains/Standard/center with correct look/inventory output. save, the mail command (IIPS letter reader, empty folder, clean q-driven exit), and who all worked correctly. quit printed the normal "Please come back another time!" with zero log/runtime growth; reconnected after a real multi-minute wall-clock gap and got a genuinely fresh Setup() run (not a "Reconnected." link-restore), landing back in the same room with the same empty inventory -- no state loss, no duplication. Confirmed (per \S3) that the starting room's own pool-spawning code is commented out in domains/Standard/center.lpc's create(), and even if it were re-enabled both pool destinations are dead ends (one points at a non-existent /domains/Discussion/MudOS, the other at domains/School/room/entrance -- one of the already-documented incompatible-API School files that doesn't compile) -- so the starting cavern legitimately has zero working exits; this is pre-existing content/archive state, not something introduced by this port, and not a programming bug to invent a fix for.

Cross-cutting pattern sweep -- grepped systematically for every class in AGENTS.md \S7.121-\S7.135 and \S8.3a. Confirmed genuinely not applicable/structurally clean on this lib for: \S7.121 (only float usage in the whole tree is secure/sefun/percent.lpc's own correctly- float-declared percent()), \S8.3a (zero private command-dispatch/ call_out-target functions anywhere -- every add_action/call_out target checked is a plain public/nomask function), \S7.112 (no NPCs at all outside the non-compiling domains/Examples/npc), \S7.122 (lib/autosave.lpc's AutoLoad item-reload mechanism is correctly gated by lib/interactive.lpc's own Setup re-entry guard -- net_dead()/reconnect via boot_copy() never re-invokes Setup() at all, so the autoload-on-Setup path only ever runs once per genuinely fresh login), \S7.123 (the one raw grep hit, secure/cmds/player/tell.lpc:109's morse() local variable assignment, is inside a function body, not file scope -- false positive), \S7.124 (no percentage/threshold fields of this shape exist at all -- no combat/wimpy system), \S7.126 (no coordinate-area door save-data of this shape), \S7.129 (secure/sefun/communications.lpc's tell_room()/say()/shout() wrappers all explicitly convert a falsy/omitted exclude to ({}) before forwarding to message() -- already safe, unlike the buggy es1 shape this class describes), \S7.130 (disconnect handling goes through the net_dead() apply directly, no heart_beat()-based query_idle() polling anywhere), \S7.131 (lib/interactive.lpc:63 calls set_living_name(GetKeyName()) inside Setup(), and find_player()/find_living() lookups were directly exercised live via secure/lib/login.lpc's own reconnect- detection and secure/cmds/creator/call.lpc/whisper/tell -- already confirmed working), \S7.132 (no map() over a mapping anywhere in the tree -- every map() call site targets an array), \S7.133 (net_dead() -- the correct apply name for this driver -- is genuinely defined in lib/interactive.lpc/lib/creator.lpc/ lib/chat.lpc), \S7.134 (lib/room.lpc's own "extra descriptions contributed by present items" accumulator, ExtraLongs, IS initialized at its declaration site in create() -- structurally the same feature class as the arkadia/genesis bug but already safe here), \S7.135 (checked every lazily-guarded global family found via grep -- lib/nmsh.lpc's Nicknames/Aliases/Xverbs, lib/access.lpc's __ReadAccess/__WriteAccess, secure/daemon/master.lpc's Groups/ReadAccess/WriteAccess, daemon/banish.lpc's __Names/__TmpBanish/etc. -- every one is unconditionally initialized in its own file's create(), not merely guarded lazily per-accessor, so there's no "one accessor forgot the guard" gap to find).

New bug found and fixed: secure/daemon/events.lpc (the reboot- warning/scheduled-event daemon, EVENTS_D) defines GetEvents() and GetRebootInterval() as its real query accessors, but two OTHER files call a same-purpose function under a different name that is never defined anywhere in the whole tree: cmds/creator/events.lpc's cmd() (the player-facing events <wizard> command, reachable by any arch/creator) called EVENTS_D->query_events() at its second branch (the events command's first branch, no argument, already correctly calls GetEvents() -- so this is an internal naming inconsistency within the SAME file, not a guess), and secure/sefun/events.lpc's (dead, uncalled) event_pending() made the identical mistake. This predates this project's port entirely -- confirmed via raw/ foundation2_fluffos_v1/lib/{cmds/creator,secure/sefun}/events.c, the pristine 1990s archive already has this exact mismatch, byte for byte. On this driver, a call to an undefined function via -> returns a bare int(0), and (mapping)0's cast is a no-op (this driver's (type) casts are compile-time hints only, see the reference_lpc_ int_cast_is_compile_time_only note) -- so mp stayed int(0), and the very next line's keys(mp) crashed uncaught: reproduced live, events somebody as the seeded fluffos admin threw *Bad argument 1 to keys() Expected: mapping Got: 0 (log/runtime, /cmds/creator/ events line 36), aborting the command with no player-facing indication beyond the raw driver error text. Fix: both call sites changed from EVENTS_D->query_events() to EVENTS_D->GetEvents(), matching the daemon's own real, only accessor and the working sibling branch already right above it in the same file (cmds/creator/events.lpc:35, secure/sefun/events.lpc:16). Verified live post-fix: events somebody now correctly prints "somebody doesn't have any events pending." with zero log/runtime growth; a full re-boot-and-retest after running the \S9 LPC formatter on both touched files (all three formatter blind-spots checked clean: no :: mis-split, no case/comment merge -- neither file has a switch, no CJK/escape re-spacing) reconfirmed the fix.

Related gap found, NOT fixed (documented per this section's own "when unsure, don't guess" rule): cmds/player/nextreboot.lpc (a real, reachable player command) calls EVENTS_D->query_next_reboot(), which also does not exist anywhere in the tree -- and unlike query_events(), there is no matching same-purpose sibling function to redirect it to (GetRebootInterval() only returns the configured *interval* in hours, not an absolute next-reboot timestamp). This doesn't crash (the undefined-function call_other returns 0 and the command just prints a bogus "Wed Dec 31 16:00:00 1969 PST" instead -- confirmed live), but fixing it correctly would mean inventing new bookkeeping (e.g. tracking mud-start time and deriving boot_time + RebootInterval*3600) rather than just correcting a mistaken function name, and the entire auto-reboot subsystem it would report on is already dormant by original-archive design: secure/daemon/ events.lpc's own eventPollEvents() self-rescheduling poll loop -- the only code path that ever checks uptime() > RebootInterval*3600 and triggers eventReboot() -- is never started at all, because its sole call_out((: eventPollEvents :), 60) in create() is commented out. Confirmed via raw/foundation2_fluffos_v1/lib/secure/daemon/ events.c that this call_out was ALREADY commented out in the pristine 1990s archive, not something broken by this port. Left untouched as a pre-existing, structurally dormant feature gap rather than guessed at. (A third, related latent bug in the same dead subsystem, also pre-existing and also left untouched: secure/sefun/ events.lpc's event() wrapper calls EVENTS_D->AddEvent(f, when, reg) with 3 arguments of the wrong types against AddEvent()'s real 6-parameter (string c, string s, string f, mixed *a, int w, int r) signature -- but event() itself is never called by anything anywhere in the tree, so this is unreachable dead code, not a live crash.)

Evidence left in place: the seeded admin account secure/save/creators/f/fluffos.o (age/login-time counters naturally advanced by this session's repeated logins). The throwaway test character Aldric (secure/save/creators/a/aldric.o, secure/save/postal/a/aldric/) was deleted before committing, per this project's standing cleanup convention.

Shop + 拜师 (2026-09-04 librarian slice)

Content-absent, same class as foundation1. Bare 1995 engine: no combat, no stats, no currency, no vendor objects, no apprentice verbs. Live on 40221 as seeded admin fluffos / Mud@2026 in /domains/Standard/center: buy, list, shop, apprentice, join, become, and 拜师 all printed What?. look/inventory still work. Left as-is.