The Dream of Seven

✅ 可玩

梦幻七域 (The Dream of Seven / dreamofseven)

dreamofseven

🔑 fluffos / Mud@2026 更新 fdf581a 2026-09-12 源码 下载 ZIP 上游 MudRen/mudos-game-ds

▶ 开始游玩 · Play Now

台湾团队自 1999 年开发的 BIG5 编码 MUD,源代码开机横幅自述"其精神源自 ES2 Mudlib 架构,特此感谢 Annihilator"——和本项目已收录的 `hell`/`zhyx` 等同属 ES II/Annihilator 血统,但只继承了最早期的 "original from Lil / rewritten by Annihilator"一支,没有后续"Doing Lu"/"yh2003"分支的痕迹,是一支更早、更独立的旁支分叉。

English

A Taiwan-origin wuxia MUD dating to February 1999, built on a distant, independently-forked branch of the ES II/Annihilator lineage. Two large regions, world1 and world2, hold content stitched together by many different wizards over the mud's history -- one contributor alone ('tmr') built an entire multi-zone questing area spanning a barbarian island, a dead forest, a mist forest, a whale river and forest, and a Taoist sect compound. New characters register with a Chinese name, choose one of six races (human, immortal-sect, demon, beast-kin, tiger-folk, or titan), and land in 朝天樓 (Chaotian Tower), an inn beside the fictional Chaotian Lake staffed by a newbie tutor and a waiter NPC, with a 500+ message community bulletin board posted right in the entryway. Sect membership is discoverable rather than menu-driven: joining the Baji Fist sect (八極門) requires finding a password phrase carved into a wall plaque inside its own hall before the sect teacher will accept a student. Skills are trained and advanced through slist/train/level/advance commands, and the game's own go command intentionally suppresses failure messages on blocked exits so that other verb handlers can take over cleanly.

README

内容亮点

本次修复的关键 bug

详见 NOTES.md

管理员账号 / Admin account

管理员名单存储在纯文本文件 adm/etc/wizlist 里;账号本身通过正常 注册流程创建,管理员权限已在游戏内跨越一次驱动重启确认显示正确。

警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。

本地运行

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

游戏端口:40240

NOTES · 移植与修复记录

dreamofseven (夢幻七域 / The Dream of Seven) -- porting notes

Source: git clone https://github.com/MudRen/mudos-game-ds (commit 07ea84ebdff5ee49cb482a520bdf1aaeda886cd0, cloned 2026-08-26). The repo's own README.md names the game "The Dream of Seven" / "夢幻七域" and lists a live BBS mirror (telnet://140.128.9.202:23 DS_MUD) and a website (http://ds.redirect.hm). Slug dreamofseven, number 938, port 40240. Quick ls libs/ sanity check against the rest of the collection found nothing else with this title or an obviously-related codebase (the closest superficial name match, ds386, is an unrelated English Dead Souls partial noted as permanently out of scope in AGENTS.md §10.6).

Lineage -- ES II / Annihilator base, but NOT any already-catalogued sub-branch

adm/obj/master.lpc's header comment is just:

// for ES II mudlib
// original from Lil
// rewritten by Annihilator (11/07/94)

-- notably missing the "modified by Xiang for XKX (12/15/95)" and "updated by Doing Lu for hell (2K)" / "Modify By Linux@lxtx for yh 2003.3" lines that mark every other ES2-descended family already in this collection (hell/zjdyaryl/xkxc98sj, yanhuangwuhun/zhyx, xkx100/xkx2017, etc. -- see AGENTS.md §11). The boot banner (adm/etc/welcome) confirms this independently and gives an exact founding date: "夢幻七域<建立架構 Feb 7, 1999>其精神源自ES2 Mudlib架構 ,特此感謝Annihilator。" This makes it a more distant, independently- forked member of the wider "ES II / 東方故事 mega-family" (AGENTS.md §11 already lists "a further, more distant sibling by the same master.lpc header" alongside kxkj/kxkj1/kxkjii2 -- this lib is consistent with that same distant-ES2 shape, though not verified byte-identical to any specific already-catalogued member). File/dir layout (adm/obj/master+adm/obj/simul_efun, not adm/single/ or adm/obj/master.c alongside a secure/) and the whole registration flow (English login id -> confirm -> Chinese name -> dual password -> email -> race selection -> confirm -> gender -> world entry) match the general ES2 shape but are this codebase's own independent implementation -- not diffed against any sibling since none in this collection shares this specific distant-ES2 shape closely enough to be worth porting fixes from wholesale; every fix below was independently diagnosed from this archive's own code and its own compile/boot output.

Raw archive: 32,946 files (~168MB) under ds/ (a README.md, player-manual/, ReferenceMaterials/, Wizard/ manual tree, and welcome.png sit alongside it at the repo root but are not mudlib source). Two full playable regions, open/world1/ and open/world2/, plus a curious open/world1/world2/ subtree that duplicates most of open/world2/'s content near-identically (not byte-for-byte -- diff -rq shows real per-file differences, mostly small) -- present identically in the raw, pre-conversion archive, so this is the upstream author's own layout (very likely a "portal copy" of world2 content reachable from within world1), not a conversion artifact.

Pipeline

1. Cloned directly into libs/dreamofseven/raw/ds/ds/ (git-hosted source, .git stripped before copying in). The actual mudlib root is the inner ds/ds/ directory (ds.config's master file : /adm/obj/master confirms it) -- one level down from where the outer clone lands the repo's own top-level README.md/manual folders. 2. Encoding: BIG5, not GB18030 -- confirmed via chardet (99% confidence) and by eyeballing a decoded write(...) string in adm/obj/master.c before running anything (a GB18030 decode of the same bytes silently "succeeds" into wrong-but-plausible-looking mojibake, per AGENTS.md §4.1's standing warning that GB18030 rarely hard-errors on real BIG5 text). scripts/convert_lib.sh libs/dreamofseven/raw/ds/ds libs/dreamofseven/work BIG5: already_utf8=793 converted=31556 lossy=384 skipped_binary=213; renamed 29,831 files to .lpc; fixed 6,427 literal .c" references (this pass turned out to be load-bearing far beyond cosmetic reference cleanup -- see the preload()/new_ob() finding below); static->nosave touched 95 files. No uppercase-.C stragglers, no "static/... string-literal collision, no #define nosave/protected static shim collision (all three checked and clear). 3. No full extensionless/whole-tree UTF-8 straggler sweep was run this session beyond convert_lib.sh's own extension-based pass (large archive, and no symptom -- garbled banner text, "COM executable for DOS" false positives, etc. -- turned up during boot/registration testing that would indicate a missed file). Worth a dedicated Python whole-tree decode-scan pass in a future session per AGENTS.md §4.1's standing recommendation, especially against doc//adm/etc/ extensionless text.

Fixes applied (driver-incompatibility bugs)

``lpc if(this_player(1) && strsrch(message, "Warning") == -1 && strsrch(message, "warning") == -1) efun::write("編譯時段錯誤﹕" + message+"\n"); ``

``lpc // BEFORE: int is_chinese(string str) { if( strlen(str)>=2 && str[0] > 160 && str[0] < 255 ) return 1; return 0; } ` The strlen(str)>=2 was meant to require "a full 2-byte BIG5 pair" under the original byte-oriented strlen(); under this driver's character-counted strlen() it instead rejects every 1-character string outright -- exactly the "length-gate variant" documented in AGENTS.md §8.1 (dfgsiiv13b's precedent), which matters here because check_legal_name() (below) probes is_chinese() on single-character suffix slices. Fixed by dropping the length requirement and checking only the first character's codepoint: `lpc int is_chinese(string str) { if( !strlen(str) ) return 0; if( str[0] >= 0x4e00 && str[0] <= 0x9fff ) return 1; return 0; } ``

``lpc // BEFORE: if( i%2==0 && !is_chinese(name[i..<0]) ) { ... } // AFTER: if( !is_chinese(name[i..<0]) ) { ... } ` This lineage has no separate named.lpc` sharding daemon (unlike the yh2003/hell families), so the fix is fully contained to these two functions.

``lpc // BEFORE: int valid_write( string file, mixed user, string func ) { object ob; if( !catch(ob = load_object(SECURITY_D)) ) return (int)ob->valid_write(file, user, func); return 0; } ` Every valid_write call re-attempts load_object(SECURITY_D) even after it's already resident, and has no guard against the compile-time re-entrancy trap this section of AGENTS.md documents extensively (a write happening while securityd.lpc itself is still mid-compile would recurse). valid_read in the same file already degrades correctly (find_object only, returns 1 if absent -- no fix needed there). Fixed with the standard reentrancy-flag pattern: `lpc private nosave int loading_security_d; int valid_write( string file, mixed user, string func ) { object ob; if (!(ob = find_object(SECURITY_D))) { if (loading_security_d) return 0; loading_security_d = 1; catch(ob = load_object(SECURITY_D)); loading_security_d = 0; } if (ob) return (int)ob->valid_write(file, user, func); return 0; } ``

``lpc if (exclude) message("tell_room", str, ob, exclude); else message("tell_room", str, ob); ... if (this_player()) message("shout", str, users(), this_player()); else message("shout", str, users()); ``

``lpc void preload(string file) { ... if (file_size(file + ".c") == -1) return; ... err = catch(call_other(file, "??")); ... } ` The driver calls this apply once per entry in master::epilog()'s returned preload list (confirmed by reading ~/src/fluffos/src/vm/internal/vm.cc's preload_objects() -- epilog() returns the file list, the driver then calls APPLY_PRELOAD once per file). After this project's standard .c->.lpc rename, file_size(file + ".c") would return -1 for every single entry -- meaning securityd, virtuald, logind, cmd_d, chinesed, emoted, aliasd, fingerd, channeld, and natured would ALL silently never preload, with zero error output (the function just returns early). This was auto-fixed by convert_lib.sh's own literal-.c"-reference sed pass, since the extension appears as its own standalone string literal (file_size(file + ".c") -> file_size(file + ".lpc")) -- confirmed by direct inspection of the converted file before ever running lpcc, and then confirmed live: the real boot's preload list shows all ten daemons loading in order. Documented here in detail because had the sed fixer NOT caught this (e.g. on a future archive using a different quoting style), the failure mode would be maximally confusing: a driver that boots with Initializations complete. and accepts connections, but where literally nothing behind logind/securityd`/etc. actually works.

``lpc object new_ob(string files) { ... if( sscanf(files, "%*s.c") != 1 ) files += ".c"; if(strsrch(files,"..")==-1) { if(file_size(files)>0) return efun::new(files); else return 0; } ... } ` new_ob() is called from adm/daemons/roomd.lpc's room-population code (the "objects" mapping every room's create() sets), plus 60+ direct call sites across std/, feature/, daemon/, and dozens of open//u/ content files -- effectively every NPC/item spawn in the game goes through it. Callers pass extensionless paths (e.g. new_ob("/open/world1/mob/sacer")); new_ob()'s own logic appends a hardcoded .c when the path doesn't already end in one. After the .c->.lpc rename, sscanf(files, "%*s.c") never matches (a path ending in .lpc does not end in the literal 2 characters .c), so .c gets appended to EVERY call, producing a filename that no longer exists on disk -- file_size() then correctly reports "no such file" and new_ob() returns 0 for every single spawn, site-wide. Also auto-fixed by convert_lib.sh's literal-.c" sed (the same string-literal-boundary reasoning as preload() above: "%*s.c" -> "%*s.lpc", files += ".c" -> files += ".lpc"), confirmed by direct inspection before the first lpcc run and then confirmed live via a fully populated starting room (an NPC "新手導師"/"店小二" both present, an in-room board with 578 messages). The same hardcoded-.c-suffix shape recurred independently in feature/skill.lpc, feature/spell.lpc, std/ skill.lpc, std/char.lpc, std/room/bank.lpc, adm/daemons/{chard,combined,virtuald,idcheckd,newquestd, enhanced}.lpc, and ~55 more files across open//u/ (mostly file_size(X+".c")-style existence probes analogous to the well-documented §7.5 "hy2002" ACL-shaped false-negative, except the root cause here is the literal extension mismatch, not an ACL) -- all mechanically covered by the same sed pass; spot-checked several post-conversion (feature/skill.lpc, feature/spell.lpc, adm/obj/master.lpc`) to confirm the fix landed correctly rather than assuming uniform coverage.

``lpc // make_inventory(): catch(ob = new_ob(file)); if( !objectp(ob) ) return 0; ... // reset_room()'s case 1: branch: if( !ob[list[i]] ) log_file("ROOM_LOAD_FAIL", base_name(room)+": can't load object : "+list[i]+"\n"); else if( ... ) { ... } ` Verified: /open/world1/tmr/quanzhen/chufang (a room whose "objects" mapping spawns an NPC that references the unshipped "xiantian-qigong" skill) now loads cleanly instead of failing outright; the same class of fix converts the bulk of the compile sweep's "Fail to load object" entries whose actual error is several frames deep in a spawned child's create()` into "the room loads, one item/NPC is silently absent" -- a narrow content gap, not a driver crash.

Confirmed content gaps (NOT fixed, per project policy)

Boot / registration verification

Not run this session

深度功能測試(§10.7 round two, 2026-08-27)

One continuous session with a fresh Chinese-named character (shenzhi / 沈知, human, male), following doc/help/newbie/newbie1's own described test path (free-drink refill points, slist/train/ advance/level for skills, the "master" sect-teacher alias route). Two real programming bugs found and fixed live; one design-shaped finding documented but left untouched; the seven standing cross-cutting patterns (§7.121/§8.3a/§7.122/§7.123/§7.124/§7.126/ §7.128) checked systematically, one confirmed hit (§7.126, new variant, also added to that AGENTS.md entry's "confirmed instances").

Bug 1 (fixed): printf("%O", ob) debug leftover leaked a raw object reference to every new player during registration

adm/daemons/logind.lpc's get_name() (the Chinese-name-acceptance handler, called for every single character creation) had a stray printf("%O\n", ob); immediately after validating the name — present byte-for-byte in the raw pre-conversion archive (raw/ds/ds/adm/ daemons/logind.c:435), so this is a pre-existing authorial debug leftover, not a conversion artifact. Reproduced live: right after typing a Chinese name, the client received a literal /obj/login#4 line before the password prompt — meaningless and slightly alarming internal-object-reference noise shown to every brand-new player, serving no game purpose. Fixed by deleting the one line; verified live that the password prompt now follows immediately with no extraneous output.

Bug 2 (fixed, SEVERE): starting room's newbie exit was unreachable for every real player due to an operator-precedence typo

open/world1/tmr/area/hotel.lpc's valid_leave():

// BEFORE:
if( dir=="newbie" || dir=="bug" && !userp(me))
        return notify_fail("那裡只有玩家才能進去。\n");

&& binds tighter than || on this driver (standard C-family precedence), so this parses as dir=="newbie" || (dir=="bug" && !userp(me)) — meaning the newbie branch is blocked unconditionally, for every player and every NPC alike, regardless of userp(me), while the clearly-intended logic (per the very next line's own dir=="newbie" branch, and per two sibling files in this same archive that implement the identical "only real players may use this exit" gate) was (dir=="newbie" || dir=="bug") && !userp(me) — block only non-player wanderers, let real players through. Confirmed via grep -rn 'dir==.*||.*dir==.*&&.*userp': u/s/suez/port/port_21.lpc and u/s/suez/port/port_06.lpc both correctly parenthesize the same shape ((dir=="east"||dir=="south") && !userp(me)), making this an isolated authorial typo on this one file, not a lineage-wide pattern.

Reproduced live: a brand-new player at the starting room 朝天樓 typing newbie got zero output and no movement (see Bug 3/observation below for why the failure was silent rather than showing the "only players may enter" message) — the entire tutorial "新手教室" room (two teaching books plus a help sign, the ONLY place doc/help/ newbie/newbie1 tells brand-new players to start) was completely unreachable for every player, from this codebase's first-ever character onward. Fixed by adding the missing parentheses; verified live post-fix: a real (non-wizard) test character typing newbie from 朝天樓 now correctly moves to open/world1/tmr/area/school/school1 ("新手教室") and can read both books.

Observation (NOT fixed, authorial pre-existing design choice): cmds/std/go.lpc discards every blocked exit's notify_fail() message

Both stringp(dest) and objectp(dest) branches of go.lpc's main() do:

if( !env->valid_leave(me, arg) ) return 1; // alick return 0->1

On this (and any standard LPC-family) driver, an add_action-registered handler returning a nonzero int tells the driver "handled, don't show the stored notify_fail() message" — so every valid_leave() rejection anywhere in this entire codebase (walls, locked doors, the newbie/bug-only gate above, sect-specific area gates, etc.) fails completely silently: no movement, no error text, nothing. Confirmed present byte-for-byte in the raw pre-conversion archive (raw/ds/ds/cmds/std/go.c:104,118, same // alick return 0->1 comment) — a deliberate, already-commented authorial change from the more usual return 0, not a conversion regression. Left untouched per this project's scope boundary: a plausible reason for the trade-off exists (preventing the driver's safe_parse_command() fallback from re-trying OTHER handlers registered under the same direction-word verb after a blocked move — a return 0 would fall through to those instead of stopping cleanly), and reversing it site-wide would be a broad, speculative behavioral change with no way to verify it doesn't break something else this large archive relies on. Documented here as an honest observation per the "when unsure, document and leave it" rule, not fixed.

Seven standing cross-cutting patterns — one confirmed hit (§7.126), six clean

New bug class confirmed (extends existing AGENTS.md §7.126, no new number): adm/daemons/bankd.lpc's club "location" field

Two of this lib's four shipped club-bank save files (data/club_bank/ god.o, data/club_bank/dragon.o) store their physical bank-room path with the stale pre-conversion .c extension baked in ("location":"/open/world1/god_club/bank.c"), while the other two ("moon", "sky") were already extensionless in the raw archive and are unaffected — proof the field is meant to be extensionless. bankd.lpc reads this value straight into load_object(dbase["location"])-> create() in two places: clear_data() (reachable live via a player's own clear <deposit|withdraw> command in std/room/club_bank.lpc, AND automatically whenever do_save()'s bank-log string grows past 1200 characters during ordinary deposit/withdraw activity) and the disabled interest_receive(). For the "god"/"dragon" clubs this would throw ("Applying a function… on a destructed or non-object") the very next time either path fires. Fixed with a small helper mirroring the project's standard §7.126 fix shape:

string resolve_location(string dir)
{
        if( stringp(dir) && strlen(dir) > 2 && dir[strlen(dir)-2..] == ".c" )
                dir = dir[0..strlen(dir)-3];
        return dir;
}

...used at both load_object(dbase["location"]) call sites. See AGENTS.md §7.126's now-extended entry for the general lesson (a plain literal path stored in save-file dbase data is just as susceptible as a macro-placeholder AREA-engine value — the mechanism differs but the root cause and fix shape are identical).

Sect join, skill training, combat — all verified clean

Quit / relogin persistence — verified clean, with a methodology note

A real quit → reconnect cycle (character shenzhi, train swordslist confirms level 1 → quit → reconnect on the SAME driver process → slist still shows level 1) confirms save/restore across a genuine quit is correct. debug.log grepped after every single quit throughout the whole session (registration, sect join, skill training, combat, multiple reconnects): zero fatal errors, zero "Fail to load object", zero uncaught-error traces — only the pre-existing benign lazy-compile warnings already noted at onboarding (stray-backslash \ escape-sequence warnings, one unused-variable warning).

Room position is intentionally reset to START_ROOM_PAST (/open/world1/tmr/area/hotel, include/login.h) on every quit regardless of where the player currently is (cmds/std/quit.lpc's unconditional me->set("startroom", START_ROOM_PAST)) — confirmed this is deliberate, pre-existing design (an "always return to the inn" convention), not a bug, since the very same file also independently calls me->save() correctly.

Methodology note for future sessions on this lib (or any lib): a mid-playtest driver restart (needed here to pick up the two live-coded fixes above) destroys any in-memory progress made since the character's last real save()/quit — this produced a confusing false alarm mid-session (a trained skill appeared to have been silently wiped by quit+relogin) that was fully explained by the driver restart having happened *between* the training and the eventual clean quit, not by any bug in the save/restore path itself. Confirmed by re-running the exact same train→quit→relogin sequence with no restart in between, which persisted correctly. Always redo a clean, no-restart-in-between save/quit/relogin cycle before trusting a persistence verdict if a driver restart happened anywhere in the preceding session.

Confirmed content gap (NOT fixed, per project policy)

Test/verification housekeeping

商店付费购买(2026-09-04 librarian shop slice)

拜师(八極門 / 李書文)已在 2026-08-27 深度测试中 live 验证,本轮不重做。

起始房间朝天樓的店小二 (open/world1/tmr/area/npc/waiter.lpc,继承 SELLMAN) list 给出真实标价清单:馒头 10、水袋 20、鸡汤 40、火把 10 等。货币是 world1 实物 past_money/obj/money/coin.lpc 古幣),不是 整数钱包;buy 另外扣 supply_point(馒头这一档扣 8 点,见 sell_list 的 mapping value)。新号 supply_point 默认为 0,心跳里 feature/damage.lpc 才会慢慢补到 level*20+40

Admin fluffos/Mud@2026 进房后(隐形问 n): eval this_player()->set("supply_point", 60)eval this_player()->receive_money(100) 各成功一次(eval 需要 /u/f/fluffos/ 工作目录存在,本机临时建了空目录,不入库)。 i 见到「一百枚古幣」。buy manto live 回「你花了10枚古幣向店小二買了 一個饅頭」,随后 i 为九十枚古幣 + 饅頭(Manto) /open/world1/tmr/area/npc/obj/manto。没有崩溃,没有「钱不够」误报。 log/debug.log 是本次开机(2026-09-04 14:20)的 live 档,只有既有的 反斜杠 warning,没有 fatal。

Upstream check: zwshen/mudos-game-ds (2026-09-04)

MudRen's copy is a fork of https://github.com/zwshen/mudos-game-ds (the original). Compared our snapshot commit 07ea84ebdff5 to zwshen master: zwshen is 1 commit ahead, and that commit only adds ds/GEMINI.md. No mudlib source change. Do not re-onboard.