Primal Darkness

✅ 可玩

pd

更新 fdf581a 2026-09-12 源码 下载 ZIP 上游 ehershey/pd

▶ 开始游玩 · Play Now

A real, historically-operated English-language fantasy MUD ("Primal Darkness", 1999-2010, mud.primaldarkness.com), Nightmare-mudlib lineage, and one of the largest libs in this collection at ~32,000 source files with two decades of genuinely-authored content behind it. Character creation offers 30 playable races running from the ordinary (human, elf, dwarf, gnome) to the exotic (illithid mind-flayers, half-dragons, driders, archangels, basilisks) -- a fresh human lands in Tirun, a hand-built hub city with a real street grid (Honin Road, Ganthus Road, Wayward/Parthos Roads) centered on Central Park, an inter-city coach line out to a second full city (Lodos), and guild halls where classes/subclasses (e.g. mage into sorceror) are earned organically by walking in and training rather than picked at creation. Beyond Tirun: Helgrath is a cold northern town built around Wilndur Square's fountain and its Yull Boulevard/Market Road crossroads; Aekari is a crystal-veined cavern dungeon; Pastarlon is an active warzone where the drow of Zolenia are besieging the town of Arlon; and Inferi Island's volcanic "Eye of Hell" is an elite-rated quest zone built around a menace that's supposedly lurked there for decades. The archive preserves real historical player and wizard save data from the mud's actual operating years, including colorful admin titles ("Archon Avatar", "Realms Avatar") carried over from the original site.

README

A real, historically-operated English-language MUD (1999-2010) built on the Nightmare mudlib lineage -- one of the largest libraries in this collection, at roughly 32,000 source files. Genuinely authored content throughout, not a stock skeleton: dozens of hand-built domains (aekari, citrin, distopia, durst, forest, frozen, guilds, helgrath, helves, inferi_island, lodos, marshes, metaisland, pastarlon, roston, saahr, tirun, and more), a 30-race character-creation system, guilds, and years of accumulated wizard-built content and real player history preserved from the original archive.

Source: ehershey/pd on GitHub. The mudlib itself lives at libraries/beta-ii/ inside that repository (a bundled Flash client and driver binaries/scripts were excluded during conversion -- this collection always uses its own driver).

Highlights

Play

Connect and answer the name/password/gender/email prompts to register, then read list and pick <race> to finish character creation. look, score, inventory, and say all work as expected once you're in the world; quit saves you and disconnects cleanly, and reconnecting with the same name/password brings you back to the exact room and equipment you left with.

Status

Boots clean natively and plays correctly end-to-end: registration, race selection, movement through real authored rooms, and the restore/reconnect path were all verified live, repeatedly, across multiple independent test accounts.

WASM status: playable. Shared WASM driver. adm/simul_efun/mail.lpc SMTP helpers are #ifndef __PACKAGE_SOCKETS__ no-ops so the eager simul_efun compiles. New-character registration (wasmpd / Test12345 / new identity / pick human) into Tirun Central Park, plus look / score ("Newbie Wasmpd the boy", 600 gold) / quit ("Reality suspended."), verified with scripts/wasm_client.js. The seeded fluffos admin password did not match Mud@2026 under WASM (old crypt). Play: https://mudlibs.fluffos.info/pd/

Admin account

fluffos / see the standard corpus password -- seeded via this lineage's (superuser)/(assist)/(allowed)/(creator)/ (approval)/(mudlib) groups in adm/db/groups.db plus a non-mortal position title in its save file (this lineage gates wizard status on both group membership AND an in-play "position"/title, not group membership alone). Verified live via update <path>, which exercises both the read and compile halves of the ACL.

Onboarding notes

See NOTES.md in this directory for the full technical bring-up log. The headline finding: two independent, corpus-wide, silently-broken codepaths, both fixed and verified live --

1. The command-dispatch daemon's directory-scan used stale .c-extension slice arithmetic left over from before this collection's standard .c.lpc rename, corrupting every single command's registered key so literally every verb except quit returned the generic "What?" fail message, for every player, from the very first boot (AGENTS.md §7.118, a previously-catalogued bug class -- this is its largest-scale confirmed instance). 2. The login object's account-restore path silently failed to grant itself read access to the player's own save file, due to a double misunderstanding of what this driver's export_uid() efun actually does -- meaning a fresh registration worked perfectly, but reconnecting to ANY already-created account silently landed the player in a session with zero working commands, including quit itself (a new catalog entry, AGENTS.md §8.22).

Also fixed: eight base-class setter functions (set_id and seven siblings) declared with parameter types narrower than the vast majority of the corpus's own call sites use (AGENTS.md §7.127 -- ~8,800 of this lib's original ~10,000 compile failures came from just these eight functions); a genuinely lost keep.h header reconstructed from surviving directory evidence; and a handful of smaller, lower-blast-radius compile-compat fixes detailed in NOTES.md.

NOTES · 移植与修复记录

pd — technical notes

Source: gh repo clone ehershey/pd (cloned 2026-08-28). Mudlib root at libraries/beta-ii/ inside that repo (a bundled FMud/ Flash client and bin/ driver binaries/scripts, both siblings of libraries/ at the repo root, were never copied into raw/ — excluded by construction, not by a later filter). Number 952, port 40254. Status: done — boots clean natively, full registration-to-gameplay playthrough AND the restore/reconnect path verified live and repeatedly.

What this lib is

A real, historically-operated English-language MUD ("Primal Darkness", 1999-2010, mud.primaldarkness.com/www.primaldarkness.com), Nightmare mudlib lineage (adm/obj/master.lpc credits "Descartes of Borg 03 august 1993, Nightmare Mudlib Version 3.0"). One of the largest libs onboarded this session: 32,436 .c/.h files (72,244 total files including data/docs/logs), all pure ASCII (already_utf8=71949, converted=51, lossy=78 per convert_lib.sh — the 78 lossy bytes were all a single garbled /bullet character in a handful of // author comment headers, purely cosmetic, no functional impact). No GB18030/BIG5 conversion needed at all.

Real, extensive, genuinely-authored content confirmed across dozens of domains: aekari, citrin, distopia, durst, forest, frozen, guilds, helgrath, helves, inferi_island, lodos, marshes, metaisland, pastarlon, roston, saahr, tirun, and more — not stock/placeholder content. Real historical player and wizard save data ships with the archive (adm/save/users/, adm/save/boats/, wizards/<name>/), which is why several early test registrations collided with pre-existing real historical account names (brennik, ashlyn both turned out to be real accounts from the original site — picked less-common fantasy names for later tests to avoid this).

4 bin/*.mud configs (primal/tna/whit/builders) in the excluded bin/ directory confirm multiple named instances of the same codebase historically — primal.mud was used as the template for this lib's config.fluffos (port/paths/limits adapted, everything else preserved).

§2.2 on-sight checklist

Conversion (convert_lib.sh)

Ran scripts/convert_lib.sh libs/pd/raw/beta-ii libs/pd/work (default GB18030 encoding — a no-op given the pure-ASCII source, confirmed before running by sampling files with a Python decode round-trip). Full run took ~26 minutes given the file count. Mechanical fixups the script applied: 21,629 literal .c" reference rewrites, 3,713 local angle-bracket #include <x.lpc> → quoted conversions, 1,017 files' staticnosave. Two script-vs-directory-structure edge cases found and hand-fixed afterward (neither is a new bug class, both are consequences of the rename step encountering something odd mid-run):

Compile-time fixes (lpcc_check.sh)

Given the scale (~32K files), ran the memory-capped sweep in chunks (fresh lpcc --batch VM per chunk, ulimit -v capped) rather than one continuous batch — a single continuous process accumulates enough resident object memory to hit std::bad_alloc well before reaching the end of a corpus this size (confirmed: ~750 files consumed 8GB in one continuous run). Final full-corpus pass: 31,923/31,935 files covered, 29,610 pass, 2,313 fail (down from ~9,988 failures before any fixes — a handful of memory-dense pockets, mostly large coordinate-grid room areas, needed a second smaller-chunk pass to get full coverage; 12 files in one chunk that hit a hard abort even at 50-file granularity remain untested, all inside the same already-known-dead wizards/amun/builders/ staging tree described below).

Dominant class: base-class setters declared narrower than the corpus uses them (AGENTS.md §7.127)

~8,800 of the original ~10,000 failures (88%) came from just 8 functions in std/object.lpc/std/room.lpc/std/monster.lpc/ std/lacky.lpc/std/living.lpc/std/vault.lpc (plus their bak/ test/wizard-personal sibling copies, since this driver compiles every .lpc in the tree standalone):

| Function | Declared as | Corpus actually calls it with | Fix | |---|---|---|---| | set_id | string | array, 7,350+ sites | mixed, coerce scalar→1-elem array | | set_swarm | int | int OR string (mixed real usage) | mixed, no coercion (only used as a boolean flag) | | set_languages | string * | array OR scalar | mixed, coerce scalar→1-elem array | | set_alignment | int | int OR quoted numeric string | mixed + to_int() before arithmetic | | set_long | string | string OR function (dynamic descriptions — query_long() already functionp()-checks) | mixed | | set_short | string | string OR function (same as above) | mixed | | set_door_func | string (3rd arg) | string OR function (query_door_func() already declared mixed) | mixed |

Each was confirmed against every other real definition/override of the same function in the corpus before widening — in every case (set_id most starkly: every non-base override already declares string *) the base declaration was the sole outlier, not the callers. See AGENTS.md §7.127 for the general "how to apply" writeup, now updated with this lib as its largest confirmed instance.

daemon/command.lpc's dispatch table (AGENTS.md §7.118 — see "Two severe bugs" below)

Missing/shadowed local headers (AGENTS.md §6.1 family)

Remaining ~2,300 failures: concentrated in confirmed-unreachable backup/draft directories

A grep sweep of the remaining failure list shows this is NOT a new systemic class — it's a long tail of syntax errors, MudOS-only-efun gaps (dump_socket_status(), old-style (: val1, val2 :) functional literals), and one duplicate-function-declaration case (wizards/amun/builders/marshes/ — 158 files with their own local sp_sap() override AND #include <amun.h>, which ALSO defines sp_sap() with different logic; the "live" copy of this same content under d/inferi_island/marshes/ has no such conflict), overwhelmingly concentrated in directories whose own names say "not live content": guildbak/, /old/, wizards/*/builders/, cmds/oldskills/, *.nul/*.bak-suffixed files. Spot-checked wizards/amun/builders/ marshes/ specifically: zero references to that path exist anywhere outside itself in the whole corpus — confirmed genuinely unreachable from the live room network, not merely low-traffic. Logged here per this project's content-vs-programming-bug scope line rather than chased file-by-file; none of it affects the live, playable game.

Two severe, corpus-wide bugs found and fixed

Both discovered because the standard §10.1 playthrough script initially only exercised fresh registration — the project's own §8.20/§8.22-style lesson about the restore path needing its own explicit test applied literally here.

1. Command dispatch totally broken (AGENTS.md §7.118, largest confirmed instance)daemon/command.lpc's rehash() builds its verb→file table with choses[j] = choses[j][1..strlen(choses[j])-3], arithmetic written for the original .c (2-char) extension; after this collection's standard .c.lpc (4-char) rename it silently registered every verb one character too long ("_look.lpc""look.l" instead of "look"), so literally every command except quit (registered directly via add_action(), not through this table) returned the driver's generic "What?" fail message for every player, from the very first boot. Fixed with the established -5 arithmetic. Verified live: before the fix, a fresh character's look/score/inventory/say all returned "What?"; after, all four (plus read list/pick <race> during creation) produced correct output.

2. Existing-account restore/reconnect totally broken (new, AGENTS.md §8.22)adm/obj/login.lpc's check_password() needs to temporarily read the connecting player's own save file (to compare the stored password hash), via a classic "borrow root through the master object" handoff: master()->load_player_from_file(name, ob)export_uid(ob); ob->restore_player(name);. Two compounding misreadings of what this driver's export_uid() actually does (confirmed against the driver's own f_export_uid() C source): it's a silent no-op unless the target's OWN euid is currently unset (every object already has one, from create()'s own seteuid(getuid())), and even when it fires it only ever sets the target's UID, never its euid — it's a one-time ownership-transfer primitive, not a privilege grant. So restore_player()'s own restore_object() call always failed master::check_access()'s permission check on /adm/save/users/... with *restore_object: read permission denied, uncaught, silently aborting check_password() (and everything queued after it) with zero trace in debug.log — a brand-new registration worked perfectly (it never calls this function at all), but reconnecting to ANY existing account — including one that had just registered and cleanly quit moments earlier — landed the player in a session with literally zero working commands, quit included, no error visible anywhere. Diagnosed via message()/debug_message() instrumentation (plain write() produces no visible output during this lineage's pre-body login sequence, hence its own code always using message("logon", ..., this_object())). Fixed with two additions mirroring this lineage's own working SAVE-side handoff (save_player()/actually_save_player(), which happens to work by accident of a different code shape): a new self-only nomask void clear_euid() { seteuid(0); } on the shared std/object.lpc base class, called by master.lpc's load_player_from_file() right before export_uid() (so the export actually fires and sets the UID to Root as intended), plus an explicit seteuid(UID_ROOT); added to std/user/save.lpc's restore_player() right before restore_object() (succeeds because the UID export_uid() just set makes master::valid_seteuid()'s own uid==UID_ROOT special case pass). Applied identically to master2.lpc and the std/test//wizards/nulvect/std/ sibling copies. Verified live end-to-end on two independent accounts: register → quit → reconnect with the same password → correct restoration of room/inventory/equipment, look/score/inventory/ quit all producing correct output (all of which returned "What?" before the fix).

Admin account seeding (§1.5)

Registered fluffos through the normal registration flow (real driver, real prompts). This lineage's wizard-status check is two-part — wizardp() (a real efun, checks an in-memory O_IS_WIZARD flag set by setup() whenever the saved position field is anything outside ({"player","high mortal","ambassador"})) AND archp()/admp() (wizardp(ob) && member_group(name,"superuser")) — so BOTH halves needed seeding: added fluffos to adm/db/groups.db's (superuser), (assist), (allowed), (creator), (approval), and (mudlib) groups (matching every real historical admin account's own membership pattern), and set fluffos.o's saved position field from the default "player" to "Root Avatar" (a non-mortal title, matching this lineage's own convention of colorful custom titles like "Archon Avatar"/"Realms Avatar" for its real admins). Verified live: logging in shows the extra Immortal/Arch news pages wizards get, and update /std/object succeeds ("/std/object: updated and loaded."), exercising both the read and compile halves of the file ACL.

WASM status

Not yet attempted — out of scope for this onboarding pass per the assigned task (native boot/playthrough only). wasm_status: "pending" in meta.json.

WASM measurement (2026-09-03)

meta.json was already playable from the 2026-08-31 deploy-unblock; the README still said "not yet packaged." Cold-boot under the shared ~/src/fluffos/build-wasm failed: eager adm/simul_efun/mail.lpc calls socket_create/socket_bind/socket_error (SMTP outbound), so the whole simul_efun object was not loadable. Wrapped those bodies in #ifndef __PACKAGE_SOCKETS__ no-ops (native unchanged). Wizard network.lpc / daemon/web.lpc socket failures remain graceful preload skips.

Verified with scripts/wasm_client.js: new character wasmpd → name-policy enter → yTest12345 twice → male → [email protected] → real name → identity newread list (30 races including human) → pick human → Tirun Central Park → score ("Newbie Wasmpd the boy", 600 gold, 70 stat / 5 skill points) → quit ("Reality suspended. See you another time!"). The seeded fluffos admin rejected Mud@2026 ("Invalid password"; driver also logged the old-crypt 8-char warning) — not chased; new registration is enough for the gallery path. Shop/combat/death were not exercised this pass. The MEMFS copy does not write wasmpd back to the host.

LPC formatter (§9)

Run 2026-08-31 (catch-up pass) — the original onboarding couldn't run node (PATH issue, since fixed via ~/.zshenv). Ran format-corpus.mjs over all 31,936 .lpc files under work/: 31,709 reformatted, 84 already-clean, 143 refused (messy legacy code). Ran all three §9 blind-spot checks: :: split (0 hits), case-label/comment merge (86 candidate spots across 8 files, manually diff-reviewed, all intact), and pre-existing-unbalanced-quote garbling (1 hit, std/obj/deed.lpc — a genuine original-source typo, fixed by hand and reformatted). Re-booted and re-verified the full live playthrough (registration, look, score, quit, restore/reconnect) — all correct.

§10.7 deep functional test (2026-08-31, round two)

Full continuous playthrough on the native driver (build-debug), going beyond the onboarding pass's registration/restore verification above — specifically to independently re-confirm the restore-path fix (§8.22) holds for a SECOND, unrelated account, and to exercise gameplay systems (race pick, class/subclass acquisition, safe-sparring, domain travel, long-sit daemon watch) the onboarding pass didn't reach. Test character: id Vulmarrow, password Test12345, male human, class Mage/subclass Sorceror — kept (not cleaned up) as a representative playthrough character. Save file: work/adm/save/users/v/vulmarrow.o.

Bug found and fixed: two objects pass a bare int to a string-typed setter, so this driver's strict compile-time type check rejects them and they never load

/d/nopk/standard/cemetary/mon/monument.lpc:12 and /d/nopk/standard/rain_forest/obj/statue.lpc:8 both called set_prevent_get(1)std/object.lpc's real declaration is void set_prevent_get(string str) (494 other call sites across the corpus correctly pass a message string, e.g. fighter/dummy.lpc's set_prevent_get("You can't take that out of the ground!")) — so both objects hit Bad type for argument 1 of set_prevent_get ( string vs int ) and never compiled at all (*No program in object '...'! every time anything tried to new() them). This is the mirror image of the already-catalogued AGENTS.md §7.127 pattern (there, a shared base setter was declared narrower than the corpus's real usage; here the base declaration is correct and these two call sites are the outliers) — narrow enough (2 files, not a systemic class) that it doesn't warrant its own new AGENTS.md entry, but worth noting as the same general "literal-argument-type mismatch on an otherwise-correct setter" shape. Found via the long-sit boot watch below: a wandering errand boy NPC's move_around() heart_beat wandered into /d/nopk/standard/cemetary/gravyar9, whose reset() tried to new() the broken monument.lpc, throwing a real (if silent-to-players) error every reset cycle — exactly the class of bug §10.0 exists to catch (invisible to registration/login smoke testing, only surfaces once a lazily-reset room with wandering NPC traffic is hit). Fixed by giving each object a real message string matching the established convention ("The monument/statue is far too heavy to carry."). Verified with a targeted lpcc --batch recompile of just these two paths — both compile clean, no more FAIL/error lines — and confirmed no recurrence of the No program in object error across a fresh driver boot plus the full long-sit idle window below.

What was tested and confirmed working