Foundation I

✅ 可玩

foundation1

更新 1309d15 2026-10-22 源码 下载 ZIP

▶ 开始游玩 · Play Now

A stripped-down, classic Nightmare-3.3-derived LPC library released by George Reese ("Descartes of Borg") in April 1995 -- a bare engine (login/registration, security, command dispatch, help, mail, message boards, Intermud-3) with no combat, limbs, or stats system at all, meant as a clean base for LPC programmers to build their own game on top of, not a playable-out-of-the-box world. This is the direct, earlier ancestor of this collection's separate `foundation2` (a distinct, more developed later release by the same author) -- not a duplicate. The one shipped starting room is a softly-lit cavern with magic pools, with the real player command set (look/status/inventory/quit and more) fully implemented. An optional teaching content tree references an incompatible room/item API from a different snapshot of this engine and doesn't compile, a pre-existing content mismatch. Like foundation2, this lib makes a real outbound Intermud-3 socket connection at boot (to a near-certainly-defunct 1990s host) and should not be swept into high-frequency automated re-boot loops.

README

A stripped-down, classic Nightmare-3.3-derived LPC library, released by George Reese ("Descartes of Borg") in April 1995. Per its own shipped README: "This LPC library is designed very much for people who know what they are doing. Unlike Nightmare, it does not come with any configuration script to get you started, a combat system for a fantasy MUD, or a starter area." Foundation I is a bare 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 on top of -- not a playable-out-of-the-box game world.

This is the direct, earlier ancestor of this collection's existing foundation2 (a separate, more developed later release by the same author -- Foundation I's own README says explicitly that its documentation was due to be "customized for Foundation II, due May 15" [1995]). The two are distinct codebases with real, independently-drifted differences (see NOTES.md), not duplicates of each other.

Source: recovered from the MUDBytes File Repository (mudbytes.net, file #1043) via the Wayback Machine, since the live download endpoint is gone. Slug foundation1, number 946, port 40248.

Highlights

Registration flow

new name (lowercase letters only, 2-15 chars) -> "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) -> a news screen (<return> to continue) -> lands in the Standard domain's starting cavern.

There is no score command anywhere in this minimalist archive (matching its own README's "no ... stats system" description) -- status is the closest equivalent, printing hp/sp/mp/experience.

Admin account

Created through the mud's own normal registration flow (not a save-file hand-edit) and granted access by adding it to the existing (SECURE) group entry. See NOTES.md for the exact edit and how it was verified.

Status

Boots clean: zero fatal errors across repeated fresh-driver boots. Verified live with a real driver session and a raw socket client: a brand-new character's full registration, world entry into the Pool Room, and look/status/inventory/quit all producing correct output.

270 of 275 .lpc files pass a full lpcc_check.sh batch compile sweep; the 5 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. Shared WASM driver. dump_socket_status() in secure/SimulEfun/SimulEfun.lpc is #ifdef __PACKAGE_SOCKETS__ so the eager simul_efun compiles without the sockets package. Admin login (fluffos / Mud@2026) into the Pool Room, plus look / status / quit, verified with scripts/wasm_client.js. Play: https://mudlibs.fluffos.info/foundation1/

Local run

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

Game port: 40248.

NOTES · 移植与修复记录

Foundation I -- porting notes

Source: foundationI_fluffos_v1.zip, recovered from the MUDBytes File Repository (mudbytes.net, file #1043) via the Wayback Machine, since the live mudbytes.net file-download endpoint is gone. The CDX API (https://web.archive.org/cdx/search/cdx?url=mudbytes.net/files/download/1043*&output=json) showed one 200-status snapshot, 20190802021701 (2019-08-02); fetched as https://web.archive.org/web/20190802021701id_/http://mudbytes.net/files/download/1043/ (a 301-redirect snapshot for the bare, non-timestamped URL also exists but resolves to nothing useful directly -- the exact 200 snapshot above is the one that works). Content-verified: 2,329,667 bytes, a valid zip (store compression), 1542 entries, 897 real files under lib/ dated 1994-95. Slug foundation1, number 946, port 40248. The zip bundles a fluffos-2.9-ds2.12 driver source snapshot, 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 (its own bin/mudos.cfg confirms: mudlib directory : /mud/foundation/lib, master file : /secure/daemon/master). A full-tree grep for driver_hook/set_driver_hook across lib/ returned zero hits, confirming genuine MudOS/FluffOS lineage (not LDMud).

Relationship to foundation2 -- NOT a duplicate

This collection already has an unrelated foundation2 (174, port 40221). Foundation I and Foundation II are both by the same author, George Reese ("Descartes of Borg"), but they are two distinct, independently-shipped releases:

meta.json's duplicate_of is null -- this is treated as its own distinct onboarding target per the task, not folded into foundation2.

1. Conversion

English-language archive (not GBK-encoded) -- scripts/convert_lib.sh against raw/foundationI_fluffos_v1/lib confirmed this: 880/897 files already valid UTF-8, 1 lossy conversion, 16 skipped binaries (compiled .o save files under secure/save//daemon/save/, correctly left alone), 275 files renamed .c->.lpc, 81 literal .c" references fixed, 9 local angle-bracket #includes converted to quotes, 76 files touched by the static->nosave sweep. No stragglers (*.C, unrenamed .c) after conversion.

2. Compile-time driver-compat fixes

- std/user/refs.lpc's set_ref(string ref, ...)/get_ref(string ref) -- this file is inherited straight into the player body class (std/user.lpc via the REFS macro), so this alone broke compilation of the ENTIRE player body. Renamed ref -> refname. - daemon/refs.lpc's parallel set_ref/get_ref/resolv_ref (the daemon-side interface to the above) -- same rename. - secure/daemon/master.lpc's load_access(string cfg, mapping ref) (plus its master.h prototype) -- renamed to refmap.

lpcc_check.sh result after fixes: 270/275 files pass. The remaining 5 are pre-existing content gaps (see \S3), none boot-critical.

3. Pre-existing content gaps (not fixed -- out of driver-compat scope)

4. WASM-era standard patches

5. Live verification

Booted the real native driver (~/src/fluffos/build-debug/src/driver config.fluffos, cd'd into libs/foundation1 first) repeatedly; zero fatal errors, "Accepting telnet connections on 0.0.0.0:40248." / "Initializations complete." every time. Full registration -> world entry -> command verification, via scripts/mudclient.py, one continuous session:

name: wizbaz -> y -> password (Mud@2026) -> confirm -> gender: female ->
display name: <blank, defaults to Wizbaz> -> email: [email protected] ->
real name: <blank, defaults to "Unknown"> -> [news pager: <return>] ->
look -> status -> inventory -> quit

All correct: entered domains/Standard/center (the "Pool Room" -- "You find yourself standing in a large cavern, lit softly by glowing lichen...") with its make_pools()-cloned pool object present, look re-displayed the same room correctly, status (this lib's real score-equivalent -- there is no score command anywhere in this minimalist archive, matching its own README's "no ... stats system" description, \S2.3-style) printed hp: 1/10 sp: 1/10 mp: 1/10 experience: 0, inventory printed You are empty-handed., and quit cleanly printed Please come back another time!. Also verified the name-validation gate (BANISH_D->valid_name() correctly rejects non-lowercase-alphabetic names, e.g. a name containing a digit) and the admin account end-to-end (registration, (SECURE) grant, live _call me;move;/domains/Standard/center returning MOVE_OK (0) plus a correct look afterward). log/errors/* only ever accumulated the harmless nosave-function/prototype-mismatch warnings cataloged in \S3 -- no runtime crash signatures.

Environment note, not a Foundation I bug: in this session's sandbox, a driver launched even with setsid nohup ... & disown (\S10.8's documented workaround for "plain nohup ... & has died from stray SIGTERMs between tool calls") still reliably stopped listening/exited cleanly (no crash trace, no log signature) around the ~100-130 second mark regardless of activity -- reproduced 3 times, including one run where nothing at all was sent to it. Every actual registration/ gameplay verification above completed well inside that window in one continuous client session, so this didn't block verification, but a future session re-testing this lib (or debugging via a long-sit \S10.0 boot-watch) should budget for restarting the driver more often than usual and not read a disappearance alone as a Foundation I-side crash without also checking whether the process merely aged out of this particular sandbox's process lifetime.

WASM status: not attempted this session (wasm_status left "").

深度功能测试 / §10.7 deep functional test (2026-08-31)

Round-two pass per the queue directive to hold every recently-onboarded lib to the §10.7 bar. Since this lib has no combat/skills/stats system (confirmed in \S1-5 above, matching its own README), this pass targeted the actual engine features onboarding hadn't yet exercised: mail, help, bug reports, finger, and a real quit/reconnect cycle. Booted ~/src/fluffos/build-debug/src/driver config.fluffos from libs/foundation1/, tested via scripts/tmux_mud.sh (two concurrent sessions, fluffos the seeded admin and a fresh throwaway mortal xiaoyan), restarting the driver as needed for this sandbox's documented ~100-130s lifetime quirk (\S5) and to pick up each fix.

Bugs found and fixed

All of the following share ONE root cause, a single systemic pattern — see the new AGENTS.md \S8.23 entry for the full writeup and confirmed recurrence in the nightmare3 sibling lib:

A (: "funcname" :) single-string functional literal was valid MudOS syntax in 1993-95 (this lib's own doc/lpc/types/function states it verbatim: "makes a function pair that is equivalent to (: this_object(), "funcname" :)"), but this driver's compiler no longer honors that — calling such a "function" just returns the string, the named function is never invoked. This silently broke every edit()/more() "when you're done, call me back" hook written that way:

Left unfixed, judged low-impact: secure/std/post.lpc:34-36's set_prevent_get/drop/put( (: "remove"/"drop" :) ) are the SAME broken construct, but the caller (std/Object.lpc's allow_get()) only checks the callback's return value for TRUTHINESS — a string is truthy regardless of content — so the intended "block this action" behavior still works correctly; only the descriptive denial message is silently dropped. The affected object (the postal mailbox) is also set_invis(1) so a player can't normally target it with get/drop/put at all, making this a barely-reachable cosmetic gap. Left as-is per this lib's own existing NOTES.md precedent for similarly low-impact cosmetic nosave/warning-only issues.

A separate, unrelated bug found via finger

secure/daemon/finger.lpc's user_finger_display() had a missing-braces bug: if(this_player() && adminp(this_player())) if(...) tmp = ...; else tmp = ...; drow = tmp; — the trailing drow = tmp; was written as if it were inside the outer if, but with no braces it's an UNCONDITIONAL statement that runs for EVERY finger, admin viewer or not. Live repro: finger fluffos as the non-admin xiaoyan printed Male IdeaExchanger Coder of the HackersOn since : ... — missing the title line entirely AND missing the newline before "On since", because drow got silently reset to the stale, newline-less tmp value from several lines earlier instead of keeping the properly-built (title + bio + trailing \n) string. Fixed by adding braces around the admin-only email-line block (line ~47-52) so drow = tmp; only executes when actually appending the email line for an admin viewer. Verified fixed: finger fluffos as xiaoyan now correctly prints the title line, the bio line, "Last on: ..." (or "On since : ..." if actively connected), unread-mail count, and "No Plan." each on its own line.

Confirmed working (no fix needed)

Observed but NOT fixed — flagged for the record, not a clear bug

log/runtime accumulated several thousand *Denied write permission in save_object(). entries this session, all from daemon/intermud.lpc's eventRead() (lines ~121/145, save_object(SAVE_INTERMUD) after a startup-reply/mudlist packet) via secure/std/client.lpc's eventReadCallback. Two things both need a human's attention but neither felt safe to "fix" under this pass's driver-API-misuse-only scope: (1) the daemon apparently can NEVER persist its Intermud mudlist/nameserver state under this lib's directory-privs security model (a real functional gap, but fixing it means deciding how a system daemon should be granted write access under this lib's own custom, install-it-yourself privs scheme — a security-design judgment call, not a clear typo); (2) the sheer VOLUME of packets received in a ~20-minute session (thousands) from the hardcoded 1996 nameserver address is surprising and not something the LPC code controls either way (no crash or unbounded growth observed, just a lot of log noise) — flagged as an environment/network observation, not a lib bug.

Flagged unverified

WASM measurement (2026-09-03)

meta.json was already playable from the 2026-08-31 deploy-unblock (2ec643a7c45); the README still said "not attempted." Cold-boot under the shared ~/src/fluffos/build-wasm failed on the eager simul_efun calling socket_status() from dump_socket_status() in secure/SimulEfun/SimulEfun.lpc — same lima §1.3(c) / wilderness class. Guarded the body with #ifdef __PACKAGE_SOCKETS__ (empty-string stub otherwise). After that, scripts/wasm_client.js logged in as fluffos / Mud@2026 into the Pool Room cavern, look showed lichen, status reported hp: 1/10, and quit printed "Please come back another time!" Shop/combat/death were not exercised this pass.

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

Content-absent, not a programming bug. This is the 1995 "minimalist LPC Library" snapshot: no combat, no stats, no currency, no vendor objects, and no apprentice/guild verbs. secure/etc/shops is leftover Nightmare path list (/d/standard/supply.c, wizard cartesia/winona shops) — none of those rooms exist in this archive (zero *shop* / *supply* under work/, no do_buy/cmd_buy). Live on 40248 as seeded admin fluffos / Mud@2026 into the Pool Room cavern: buy torch, list, shop, and apprentice all printed What?. Help's player-general topics are mail/prompt/terminal — no shop. Left as-is.

深度功能测试(§10.7,2026-10-03)— site-like tree, every compile warning

Method: a tree built the way the site builds it (scripts/pristine_tree.sh: tracked files only, plus work/log and the directories scripts/wasm_keep_dirs.txt records for the slug), not the old working tree that earlier boots had filled with directories. Registration, the one-line reports, the editor-based bug, mail, passwd, chfn, suicide, tells, the soul, ~90 further commands, and disconnects at the name, password, confirm, gender, email and news-pager prompts and inside the editor and the mailer. Final run (fresh tree from HEAD): 25/25 verify checks, the chat / passwd / suicide / disconnect battery all pass, 89 commands with no error.

1. New characters could not be created on the site. After the y at "Do you really wish ... to be your name?" login.lpc calls log_file("new_players", ...), which (called from a user object) writes /log/secure/new_players. The zip never carries log/, and the keep list had no entry for foundation1, so the write raised Wrong permissions for opening file ... "No such file or directory" and new_user() aborted before input_to("choose_password"): a bare > prompt, no account. The live fluffos-boot.js showed keepDirs: []. Fix: gen_keep_dirs.py --merge foundation1 foundation2 recorded the 15 + 6 log/ directories the lib uses (log/secure, log/errors, log/etc/*, log/reports, ...). 2. tmp/ missing: bug without text (editor mode) raised the same Wrong permissions ... /tmp/<name>.bug error. Added tmp/.gitkeep. 3. secure/save/letters/ missing: mail composed fine, then *Could not open /secure/save/letters/<n>/<id>.o.tmp for a save; the letters daemon mkdirs the one-digit bucket but not its parent. Added secure/save/letters/.gitkeep. Also added realms/.gitkeep: user_path() is /realms/<name>/ and eval/~ need that directory, which a creator makes with one mkdir. 4. [0..-2] is empty in this driver (negative range ends do not count from the end; the form is [0..<2]). uptime printed a lone ., and the ~ / ~name expansion in absolute_path() produced "", so cd ~ and ls ~ went to / even with a home directory. Both fixed to [0..<2]. 5. Every compile warning fixed at its source (the first visitor used to get ~400 lines of them, because everyone is a creator and log_error prints to creators). lpcc --batch over all 275 files now reports none: - 374 nosave function declarations (the .c to .lpc conversion turned every static into nosave; FluffOS accepts nosave on variables only): protected (the historic static meaning), private ones lose the redundant nosave; reset() stays public because the reset command calls it from outside. A rejected external call is logged by the driver as apply() with insufficient permission; zero of those appeared in any run (only a deliberate probe). - 98+ unused locals removed; get_stack() listed the function name twice and never the program name (stack0 was the unused variable). - (: "remove" :) in the post box is a function returning the string "remove", not a pointer to remove() (old MudOS meant the latter): now (: remove :); (: "drop" :) called a drop() that exists nowhere and so always answered 0, now an explicit prevent_put() returning 0. Same in Examples/room/search and Examples/virtual/start ((: search_brush :) with a forward prototype). - prototypes that disagreed with the definition (query_enters, query_reply_text, date), unknown escapes (\| in the centred sprintf, \. in two sscanf patterns, \<, \P), empty if bodies and a no-op statement in master / intermud / network, the services daemon's MSG_SYSTEM spelled two ways. - secure/daemon/users.lpc inherited DAEMON and OB_USER, so /std/clean_up came in twice (four ambiguous functions and a redeclared __NoClean); it inherits OB_USER only and spells out what DAEMON added (remove() with the valid_apply check, query_prevent_shadow). - #pragma no_warnings is not used: a mudlib does not carry it. 6. Still failing to compile, unchanged (archive content gaps, section 3): Examples/etc/{chest,flu,match,torch} and secure/etc/cowtown. 7. Native only, not touched: daemon/intermud saves /save/intermud.o, which secure/cfg/write.cfg does not allow and the tree does not have, so every I3 packet from a router logs Denied write permission in save_object() (hundreds per hour in log/runtime). The site has no sockets.

2026-10-03 addendum — an unused-local deletion that broke an inactive branch

domains/Examples/room/search.lpc search_brush(): the compile-warning sweep deleted object ob; as unused, but ob is used inside the #ifdef OB_TORCH branch the driver does not compile here. Restored inside the branch. The same scope-aware check (a deleted declaration whose name still occurs under an #if in the same function) found nothing else in this lib; scripts/lpc_warnings.py now skips such locals (KB 04 §6.10).

深度功能测试(§10.7,2026-10-05)— 主控 author_file()/domain_file()

驱动的 mudlib_stats 包在主控载入时各调用一次 author_file(<主控文件>) 和 domain_file("/"),要求返回字符串。本库这两个 apply 只为 domains / realms 目录下的文件返回名字,其余返回 0,所以每次启动都打印 author_file() in the master file does not work, using root_uid as fallback 和 domain_file() in the master file does not work, using bb_ui as fallback 两行,网页版每位访客的终端里都看得到。secure/daemon/master.lpc 改成与 fluffos 组织维护的 Dead Souls 上游(deadsouls_fluffos)相同的写法:其余文件的域返回 "BACKBONE",作者返回 "NONAME"。域等于 backbone 域时驱动让对象沿用创建者的域,与返回 0 时相同;作者统计(author_stats())多出一行 NONAME,汇总 realms 目录以外的对象(KB 06 §7.222)。用 lpcc config.fluffos /nonexistent.lpc 前后对比,主控载入段只少了这两行。

A login password over 255 bytes: crypt() raises (2026-10-20, KB 06 7.294)

The driver's crypt() raises (Error in crypt(), check your salt) for a key over 255 bytes, where the old driver hashed any length. A password of 256 bytes or more typed at the login prompt threw an error in the password check and left the connection at a bare > prompt, so the login never completed (the old-password test of password the same); found by the sweep shape of 300 x. /secure/SimulEfun/SimulEfun.lpc now defines a simulated efun crypt(key, salt) of the same name: only an over-long key is cut (to 255 characters, then character by character until sizeof(to_buffer(key)) <= 255: the limit is in bytes, a CJK character takes three) before it is handed to efun::crypt(); every ordinary password hashes as before and the saved hashes are untouched (scripts/lpc_add_sefun.py crypt).

add_action() registrations of functions nobody defines (2026-10-21, KB 06 §7.349)

An add_action("fn", "verb") whose function is defined neither by the object, nor by its headers, nor by its parents, nor by any object derived from it (scripts/lpc_scan_callout_targets.py, descendants checked transitively, then function_exists() in a running driver, scripts/lpc_verify_handlers.py). The driver walks the actor's sentences from the newest one: when that sentence's function does not exist it raises *Function for verb 'drop' not found. and tries no other handler of the verb (an older dead sentence is passed over silently). Removed 2 registrations in 2 files (scripts/lpc_fix_dead_add_action.py), the verbs are left to their other handlers: /domains/Examples/virtual/start.lpc (doclimb), /std/bboard.lpc (cmd_save). Most frequent: doclimb x1, cmd_save x1.

new(MISSING)->move() / carry_object(MACRO(..))->wear() chains guarded (2026-10-22, KB 06 §7.73 addendum)

A statement new(PATH)->move(...); / carry_object(ARMOR_D("x"))->wear(); whose path names no file of this archive: new() answers 0 and ->move() on it raises Bad argument 1 to EFUN call_other(). In create() the object never loads, in reset() every spawn below the first missing one is skipped on every reset. 1 statement(s) in 1 file(s) (new) are now { object new_ob = new(PATH); if (new_ob) new_ob->move(...); } (scripts/lpc_guard_carry_object.py --new: the path is resolved with the macros the file itself sees, an ambiguous macro or a virtual-object path is left). Load check of the 1 changed files, HEAD vs tree: 1 load at HEAD, 1 load now (0 newly loading, 0 regress).

The void, the freezer and every new workroom led to a room that does not exist (2026-10-22, KB 06 §7.257 addendum)

domains/Standard/xtra/void.lpc (the room a player lands in when a move fails), xtra/freezer.lpc, std/obj/workroom.lpc (the template of every new wizard's workroom) and cmds/creator/_pupdate.lpc (parks the contents of a dissolved room) named /domains/standard/square / /domains/standard/void: lower-case standard (the directory is Standard) and a "square" this lib never shipped (Foundation's central room is /domains/Standard/center, ROOM_START of secure/include/rooms.h). The void's any_hook sends every player without a start setting there on the first command, so a player in the void got *call_other() couldn't find object '/domains/standard/square' for ever (seen by the goto probe after a failed goto), and the square exit of the freezer and of a workroom went nowhere. The four files use ROOM_START / ROOM_VOID of the lib's own header now. Proof (scripts/lpcc_eval.py, fresh process, eval object inside /domains/Standard): HEAD's three rooms answer query_exit("square") = /domains/standard/square with no file; the tree's answer /domains/Standard/center, 1,698 bytes, loads; _pupdate compiles in both. Not touched: cmds/ambassador/_law.lpc COUNCIL_CHAMBER "/domains/standard/council" (a room nobody shipped: content) and the building tutorials in doc/ that still describe the Nightmare square.

log/ subdirectories nobody created: the logging write_file() / log_file() raises (2026-10-22, KB 06 §7.358)

The code of this lib writes logs into 1 subdirectory of log/: log/chan (secure/daemon/chat.lpc). Neither the archive, a git clone nor the site pack has them (no local boot wrote into them, so gen_keep_dirs.py never recorded them), and write_file() raises *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory" for a missing directory: the function that logs stops on that line and what came after it (the reward, the flag, the next step) never runs. This lib's log_file() makes the directory itself (measured, or read from the source: the nitan family's assure_file()), so only the directories written by a direct write_file() / save_object() are listed. log/chan are now in scripts/wasm_keep_dirs.txt (scripts/lpc_keep_log_dirs.py; the site and the pristine tree create the empty directories from it). Proof (scripts/lpcc_eval.py, an eval object under /adm/daemons or similar writes /log/zzdirprobe/..., fresh processes): the measured table in KB 06 §7.358.