Dead Souls 3.8.6 (fluffos/dead-souls fork)

✅ 可玩

DeadSouls-FluffOS2019

deadsouls_fluffos

更新 492174f 2026-09-12 源码 下载 ZIP 上游 fluffos/dead-souls

▶ 开始游玩 · Play Now

`fluffos` GitHub 组织自己维护的 Dead Souls 3.8.6 fork (<https://github.com/fluffos/dead-souls>),已经预先适配现代 FluffOS (`v2019` 驱动分支)——不是从老式压缩包提取的传统档案,是这批语料库里少数几个"英文原版、本身就来自 FluffOS 生态"的收录对象之一。

English

The fluffos GitHub org's own actively-maintained fork of the classic Dead Souls 3.8.6 mudlib, pre-adapted for the modern FluffOS v2019 driver branch (tracked as a git submodule) rather than a legacy archive dump -- a separate, independently-sourced snapshot from this collection's other two Dead Souls entries (ds386, dshakkard), already carrying its own AUTO_WIZ registration, a websocket listener, and a www/ web client. New characters begin in "The start room" near the Praxis campus, then `enter town` to reach the main city; playable races are dwarf, elf, half-elf, half-orc, halfling, human, orc, and poleepkwa. The town/campus/Praxis/amigara/learning/Ylsrim/cave/examples domains are Dead Souls' own official "how to build an area" teaching material, and AUTO_WIZ lets a new registrant answer "creator" instead of "player" to get builder access immediately, with `read chapter 1 in handbook` as the suggested first step.

README

本馆只保留说明和启动脚本。 work/fluffos/dead-souls submodule(mudlib 根目录 work/lib)。LPC 在那个仓库里是 .lpc + §9 格式化——修复请提交到那个仓库,不要在本馆再改一份。OLD_ED 驱动兼容 shim(ed_compat.lpc)和管理员试玩号在 overlay/

ds386 的关系

本项目还收录了另一份 Dead Souls——libs/ds386,来自一份完全独立的、 老式压缩包归档,代表的是未经 FluffOS 生态改造的原始 Dead Souls 3.8.6 快照。两者按本项目"同一游戏不同来源快照各自保留独立 slug"的一贯做法 分别收录(参见 bxsj/bxsj1xiyouji 系列等先例),并未合并或去 重。这一份的显著特征:仓库自带 driver/ git submodule 跟踪 fluffos/fluffosv2019 分支、已经内置 AUTO_WIZ(注册时可直接 选择成为创造者)、websocket 监听支持、以及一套 www/ 网页客户端—— 是"已经被 FluffOS 生态本身认领和现代化"的一支。

内容说明

WASM / 驱动兼容说明

上游仓库使用的是"现代"编辑器 efun 组合 (ed_start()/ed_cmd()/query_ed_mode()),而本项目共享的驱动 构建是以 OLD_ED(服务语料库里占多数的老 MudOS 时代 mudlib)编译 的,这三个 efun 在这个驱动构建里并不存在——直接导致玩家类 (/lib/player.lpc)完全无法编译。已经用纯 LPC 从零实现了一套自包含 的兼容 shim(secure/sefun/ed_compat.lpc,覆盖经典 ed(1) 命令子 集),详见 NOTES.md 的完整说明。另外还发现并修复了一处影响面很广 的真实 bug:.c.lpc 改名后残留的定宽切片错误,最严重的一处让 全局命令派发表daemon/command.lpc)把每一条命令都注册错了 文件名(多出一个 .l 尾巴),导致 score 等所有走标准命令派发路径 的指令全部报错——已经系统性排查并修复了同一根因的全部 ~30 处实例, 详见 NOTES.md

在线试玩

本地测试端口如下。

管理员 / creator 账号

overlay/ 带了一个试玩创造者号:shopdive / Mud@2026。Dead Souls 本身没有「第一管理员」引导,建造权限仍可通过两条路径获得:

1. AUTO_WIZ(推荐):注册新角色时,选定种族后会被问到 "作为玩家还是创造者加入",直接输入 creator 即可在登录后不久自动 被提升为创造者。 2. groups.cfgsecure/cfg/groups.cfg 里已经把账号名 sunyc 写入了 SECURE/ASSIST/ELDER/TEST/INTERMUD/ TELNET/EMOTES 等全部权限组——把这个名字改成你要用的账号名 (小写),或者新增一行,即可让该账号获得对应权限,无需走 AUTO_WIZ 流程。

若要正式对外开放主机,请根据实际需要调整 groups.cfg 的账号名单。

本地运行

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

游戏端口:40207

NOTES · 移植与修复记录

deadsouls_fluffos — Dead Souls 3.8.6 (fluffos/dead-souls fork)

来源:fluffos/dead-souls submodule at 4e07e92(leftover 686: .lpc + §9 formatted on that repo;mudlib root work/lib; command-cache / last()/truncate() slice widened for .lpc)。 OLD_ED shim and the shopdive playtest seed are in overlay/。 端口 40207。状态:done(干净开机,注册流程全通, look/score/quit 均验证通过)。

leftover 686: work/ is the fluffos/dead-souls submodule

hosting is fluffos-upstreamwork/ 现在是 fluffos/dead-souls gitlink(pin 4e07e921a7a9d841faeb81e3ccbc49612215d537),不再是 本馆第二份格式化树。config.fluffos mudlib directory 指向 work/libpatches/ 保持为空——改名、§9 format、以及 NOTES item 1 的 .lpc 定宽切片(file[0..<3][0..<5]last(X,2) == ".lpc"last(X,4))都在那个 fluffos-org 仓库上。 Catalog-only overlay:

Item 1 里「切片宽度是本馆改名副作用、不必上游」的旧判断已经过时: leftover 686 把 fluffos/dead-souls 本身改成了 .lpc,所以那边的 切片也必须是 4 / [0..<5]

这是哪一份 Dead Souls

这是 fluffos GitHub 组织自己维护的 Dead Souls 3.8.6 fork (fluffos/dead-souls),已经预先适配现代 FluffOS(driver/ 作为 git submodule 跟踪 fluffos/fluffos 的 v2019 分支),不是从传统 压缩包提取的老档案。本项目同时收录了另一份独立来源的 Dead Souls—— libs/ds386,来自完全不同的、独立的老式压缩包归档,由另一位并发 agent 转换。两者按本项目"同一游戏的不同来源快照保留为独立 slug"的 惯例分别保留(参见 bxsj/bxsj1xiyouji/xiyouji2003 等先例), 本次转换未尝试与 ds386 合并或去重。

已知的差异线索(仅基于本 lib 自身内容,未逐行比对 ds386):这份 deadsouls_fluffos 的仓库 README 本身就记录了"3.8.6 with FluffOS 2019"的适配历史,driver/ 子模块显式跟踪 fluffos/fluffos 的 v2019 分支,且仓库内已经内置 AUTO_WIZ、websocket 支持、以及一套 www/ 网页客户端——是一份"已经被 FluffOS 生态本身认领和现代化"的 分支,而不是从 MudOS 时代的原始压缩包直接抽取。是否与 ds386 内容 完全同源、只是适配层不同,留给未来统一协调的分支比较去判断。

转换流程说明(§2 的改编版)

源已经是 git 仓库、已经是 FluffOS 适配版,因此本次转换只做了 "验证 + 必要修复",而不是完整的§2/§3/§4 归档转换流水线:

发现并修复的真实 bug

1. 全局性:.c.lpc 改名遗留的定宽切片 bug(§4.2 第 4 类),~30+ 处

convert_lib.sh 的引用修复只处理带引号的字符串字面量 (".c"".lpc"),不会、也不可能自动调整后续依赖"扩展名长度" 的定宽字符串切片/last()/truncate() 调用——原版对 2 字符的 .c 写的 str[<2..] == ".c" / last(str,2) == ".c" / truncate(str,2), 在把字符串字面量机械替换成 4 字符的 .lpc 之后,切片宽度仍然停留在 2,导致比较永远为假(2 字符切片不可能等于 4 字符字符串)、或者截断 只削掉了 2 个字符(留下 ".pc"残尾)。这不是这份 mudlib 自己的历史 bug,是这个项目自己的机械转换步骤的副作用,但只有在实际运行到对应 代码路径时才会暴露,编译期通常不报错(mixed/字符串比较不触发类型 检查)。

最严重的一处/daemon/command.lpceventRehash()—— cmd = file[0..<3];——是全局命令派发表的构建函数,扫描 cmds/players/cmds/creators/ 等目录下所有 *.lpc 文件建立 "命令名 → 文件路径"的映射。修好前,每一条注册进来的命令名都带着一 个残留的 .l 后缀(例如 /cmds/players/score.lpc 被注册成了命令名 "score.l"),导致所有走 command.lpcCMD_D->GetCommand() 这 条标准派发路径的玩家/创造者/管理员命令全部无法被找到,运行时报 *call_other() couldn't find object '/cmds/players/score.l'.——玩家 看到的现象是"A runtime error occurred."。look/quit 之所以在修复 前仍然正常,是因为它们分别走 add_action()(动词系统)和 lib/interactive.lpc 自带的 action,不经过这条 CMD_D 派发路径, 因此在深度测试的 §8.4 检查项("score 才能真正跑到玩家类的完整代码 路径,look 未必够")里被专门捕获到——这正是这条经验教训本身描述的 情形。已修复:file[0..<5]

同一根因,逐一确认并修复的其余 ~30 处(每一处都单独核实了变量来源 确实是 .lpc 文件名/路径,而不是无关的 2 字符字符串比较——例如 daemon/defines.lpclast(str,2) != ".h" 是正确的,未改动):

修复方法:逐处人工核实调用上下文(确认变量确实来自 .lpc 文件名/ 路径,排除掉表面相似但语义无关的 2 字符比较,如 .h"--""%^""_D" 等),而不是无差别全局替换——grep -rn 'last([^,]*,\s*2)\s*[=!]=\s*"\.lpc"' 用于收尾核对,改完后确认为 0 命中。

2. ed_start()/ed_cmd()/query_ed_mode() 驱动兼容 shim

/lib/editor.lpc(被 /lib/interactive.lpc/lib/player.lpc 间接 inherit,也就是每一个玩家角色对象都会 inherit 到)和 /secure/cmds/creators/lsed.lpc 调用的是 FluffOS core.spec#ifdef OLD_ED#else 分支——ed_start()/ed_cmd()/ query_ed_mode() 这一组"现代"编辑器 efun。本项目共享的驱动构建 (~/src/fluffos/build-debug)是以 OLD_ED 编译的(服务这批语料 库里占绝大多数的老 MudOS 时代 mudlib,那些用的是旧式回调型 ed()),因此这三个 efun 在这个项目自己的驱动构建里根本不存在 ——不是 mudlib 的 bug,是这个特定驱动构建选项和这份"更现代"的 Dead Souls fork 之间的兼容缺口。实测确认:ed_start(...) 在 lpcc 里报 Undefined function,导致 /lib/player.lpc 完全无法编译——这直接 挡住了每一个玩家角色的创建,是本次转换里编译层面最严重的一处阻断。

驱动侧的旧式 ed() efun 把原始连接输入直接交给驱动内部的编辑器状态 机接管(回调驱动,不是逐行可控的模型),从架构上无法用 LPC 包一层 兼容适配去模拟"modern ed_start/ed_cmd 那种逐行同步返回"的调用约定。 因此没有去 wrap 驱动的 ed(),而是在 secure/sefun/ed_compat.lpc 里用纯 LPC 从零实现了一个自包含的、不依赖任何驱动内部状态的行编 辑器,作为同名 simul_efun 提供:支持经典 ed(1) 的地址形式 (N/$/./+N/-NN,N/N;N 范围)和 a/i/c/d/p/ l/w/q/Q/x/s///[g] 命令子集——覆盖了 Dead Souls 自己内部 两个调用者(editor.lpc"$a"自动追加 + "."结束、 lsed.lpc 的脚本化编辑 + "x"/"Q")以及日常巫师交互式用法的 主要场景,但不是 src/packages/core/ed.cc 的逐字节复刻(没有正则 替换、没有 g 全局命令、没有撤销)。已在 secure/sefun/sefun.lpc#include 接入,lpcc 单独验证 /lib/player/lib/editor/lib/interactive/lib/creator/lib/nmsh/secure/cmds/creators/lsed 全部编译通过。

3. 缺失的 /open/ 目录导致两个巫师工具 daemon 的 create() 崩溃

/daemon/defines.lpcdefines 巫师指令,运行期动态生成一小段探测 代码来判断某个宏/符号是否已定义)和 /cmds/creators/types.lpc (同类思路的类型检查工具)在 create() 里都会无条件 write_file("/open/prog.lpc", ...),但整棵树里根本不存在 /open/ 目录(git 不追踪空目录,上游仓库这一个目录没有像其它好几十个空 目录那样配 .gitkeep)。目标目录不存在时 write_file()fopen() 失败会触发未捕获的 error(),使这两个 daemon 的 create() 直接崩溃、对象整体无法加载(Wrong permissions for opening file /open/prog.lpc for overwrite. "No such file or directory")。修复:创建 work/open/.gitkeepsecure/daemon/ rooms.lpcreaper.lpcchat.lpc 等多处代码本来就把 /open/ 当作一个真实存在、受信任较低的巫师临时脚本目录在用,是 Dead Souls 自身的既有惯例,不是新引入的目录)。修复后两个 daemon 都能正常 create()、lpcc 单独验证通过。

Upstream note (2026-08-31): 上面 §1(.c.lpc 定宽切片修复)和 §2(ed_start/ed_cmd driver-compat shim)均上游——前者只是 本项目自己 .c.lpc 改名步骤的副作用(上游仓库仍然全树用 .c, 不存在这个问题),后者是本项目共享驱动构建选用 OLD_ED 这个配置 选项造成的兼容缺口(本项目自己的驱动配置,不是上游 mudlib 的 bug)。§3(缺失 /open/ 目录)经审计确认是上游仓库自身的真实 bug (16 个其它必需空目录都配了 .gitkeep,唯独 /open/ 没有),已 filed upstream as PR #13: https://github.com/fluffos/dead-souls/pull/13。

4. domains/Praxis/supply2.lpcset_pre_exit_functions 误用(比照姊妹文件的既有处理方式)

secure/include/compat.h#define set_pre_exit_functions SetProperty 把这个旧式 API 名字映射到了签名完全不匹配的 SetProperty(string prop, mixed val)(单属性 setter),而 domains/Praxis/{hall,hall2,hall3,hall4,hall5}.lpc 全部 5 个姊妹 房间调用这个宏时传的是两个数组 (exits, funcs)——这 5 处调用在 上游仓库里全部已经被注释掉,只有 supply2.lpc 这一处还是活的、 未注释,编译报 Bad type for argument 1 of SetProperty ( string vs mixed * )。这是这份"更现代"的 upstream fork 自己遗留的一处内容层 不一致(不是本项目转换引入的),但既然 5/6 的姊妹房间已经用注释掉 达成了事实上的处理方案,为了让这个房间能编译,比照同样的处理方式把 这一行也注释掉,而不是去猜测重新实现这个从未真正工作过的旧 API。

Upstream note (2026-08-31): 确认这是上游仓库自身的真实 bug (supply2.c 在上游仍然是唯一一个未注释掉这行调用、因而编译失败的 文件),已比照姊妹文件的处理方式 filed upstream as PR #15: https://github.com/fluffos/dead-souls/pull/15。

已知但未修复的边角情况(不阻塞注册/核心玩法,记录备查)

以下 13 个文件在最终一轮 lpcc_check.sh --batch 里仍然报告 FAIL,均已逐一排查根因,均不在预注册流程(preload 列表)、新手房间 可达路径、或 look/score/quit 核心验证范围内,因此按 AGENTS.md "更深的内容层 bug 记录但不必修"的标准,记录而非强修:

跳过的步骤(因时间压力,记录以便后续补做)

验证记录

Sibling sweep of the dsIII §7.121 currency-float bug — confirmed present, fixed

AGENTS.md §7.121 documents a currency/economy bug found in dsIII (the Dead Souls 3.x lineage's shared secure/sefun/economy.lpc): query_base_rate(), query_player_money(), query_base_value(), and query_value() do real floating-point exchange-rate math internally but are declared to return int with no to_int() on the actual return, silently corrupting player currency into a float on every buy/sell/exchange (a declared int return type is compile-time only on this driver and never coerces a runtime float). deadsouls_fluffos shares this exact file byte-for-byte with ds386/pre-fix dsIII and had the identical gap, plus the same two sibling misses: lib/teller.lpc's eventExchange() (i = val / currency_rate(str2);) and lib/props/value.lpc's SetBaseCost() (Cost = i * rate;).

Fixed by wrapping all four secure/sefun/economy.lpc returns in to_int() (matching the convention already used by the file's own currency_mass()/currency_value()), plus the two call sites, exactly mirroring dsIII's fix. Verified live with a fresh AUTO_WIZ creator character (Qintestdsf), which let the check be done with eval as well as a real transaction rather than navigating the whole map: eval return query_value(23, "silver", "gold"); and a typeof() check both confirmed a clean int result (no residual float), and SetBaseCost() tested the same way. Then teleported (goto /domains/town/room/bank) to Zoe, opened a bank account, gave the character 100 silver via AddCurrency, and did ask zoe to exchange 23 silver for gold — printed a clean 2 gold with no decimal, and the raw save file after save showed Currency (["silver":186,"gold":2,]) — both integers, no float. log/debug.log stayed clean through the whole sequence.

Upstream note (2026-08-31): confirmed present byte-for-byte in fluffos/dead-souls upstream (secure/sefun/economy.c, lib/teller.c, lib/props/value.c), filed upstream as part of PR #12 (bundled with the two related float/int bugs documented below): https://github.com/fluffos/dead-souls/pull/12.

Sibling sweep of ds386's round-two eventRevive()/room.lpc/combat.lpc/beggar.lpc bugs (2026-08-27)

Per ds386/NOTES.md's "Deep functional test (round two, 2026-08-27)" section, checked the four flagged bug shapes here. All four present and fixed identically:

Committed only the four source-file fixes plus the new Qintestdfx test-creator account's realm/save files (following this lib's own precedent of keeping test-creator accounts, e.g. Qintestdsf above); RELEASE_NOTES_HTTP/daemon-state save churn from booting the driver was reverted first, not committed. Killed the test driver by exact PID when done.

Sibling sweep: become <class> SetClass -> ChangeClass fix (2026-09-01)

Ported the fix from riftsds (AGENTS.md §7.195/§7.196; commit 11216f003b1) -- see ds386/NOTES.md's identical write-up (same session, same batch) for the full bug mechanism. Summary: the stock Dead Souls "Praxis" demo guild-join rooms called SetClass() directly instead of ChangeClass(), so the multi-class privilege gate (high_mortalp(), which excludes creators too) always silently rejected a fresh explorer's first class despite a success message printing.

Confirmed present here, byte-identical to ds386/riftsds's pre-fix files: all six domains/Praxis/*_join.lpc files called SetClass(); secure/include/compat.h was missing the same three query_name/query_cap_name/query_gender mappings.

Fix applied: six SetClass() -> ChangeClass() edits, three compat.h accessor lines added -- identical to ds386.

Live-verified with a fresh AUTO_WIZ creator account (Qtestfixdfx, registered normally through the "creator" choice at the AUTO_WIZ prompt, following this lib's own precedent), Class starting clean at "explorer": become fighter/become cleric/become mage each printed the real success flavor text and score immediately afterward confirmed the genuine class change ("You are a level 1 Human Fighter"/"Cleric"/"Mage"). become monk/kataan/rogue were not re-tested here (the same pre-existing missing-class-data gap already confirmed on ds386/dsIII/riftsds's identical secure/cfg/classes/ listing -- explorer/fighter/mage/ cleric/thief only).

Committed the six join-file fixes, the three compat.h mappings, and the new Qtestfixdfx test-creator account's realm/save files (following this lib's own precedent above); daemon-state save churn from booting the driver (player list, mudinfo, snoop, preload class/economy/events/race/soul/stargate/unique/voting saves, files.o/functions.o) was left uncommitted. A stray CMD_EVAL_TMP_FILE.lpc scratch file left behind by the eval command in Qtestfixdfx's own realm was deleted before commit (matching the already-committed convention for Qintestdfx/Qintestdsf, whose own same-named scratch files were untouched). Killed the test driver by exact PID when done.

Confirmed present byte-identical in a fresh clone of github.com/fluffos/dead-souls (current master, commit 4ddbd3b, i.e. still un-fixed after PRs #12-16): all six lib/domains/Praxis/*_join.c files call SetClass(), and secure/include/compat.h is missing the same three query_name/query_cap_name/query_gender mappings (confirmed these resolve via global.h's COMPAT_MODE-gated auto-#include <compat.h> in every object, and that GetName/GetCapName/GetGender already exist as real accessors on player.c/race.c, so the mapping is a pure omission). Filed upstream as PR https://github.com/fluffos/dead-souls/pull/17.

Shop (2026-09-04 librarian slice)

Otik's general store (/domains/town/room/shop) is live. AUTO_WIZ creator shopdive / Mud@2026 (human, 68 starting silver from eventCompleteChar) goto'd the store, list showed the stock (wooden torch 60 silver, plastic flashlight 10, …). buy torch from otik matched the flashlight's extra "torch" id (rayovac) and completed a paid sale: Otik said "Here is a small plastic flashlight for 10 silver!", inventory two flashlights, money 48 silver (68−10−10). buy wooden torch from otik then correctly refused ("You don't have enough silver") — purse math is integer, no §7.121 float leftover on this path.

Bug found and fixed (heartbeat hobbled(this_player())): lib/body.lpc's heart_beat called hobbled(this_player()). During heartbeat this_player() is 0, so disable.lpc:7 did 0->GetMissingLimbs() every 2s (Bad argument 1 to EFUN call_other … Got: int(0)). hobbled() then returned 0, !stringp(0) was true, and eventCollapse() fired on every idle beat. First login that died mid-news left shopdive with every verb printing What? until this was changed to hobbled(this_object()) and hobbled() gained an !objectp(ob) guard. After reboot, look/goto/list/buy worked. Same this_player() line exists on ds386/dsIII/dsII/ dshakkard/riftsds/brassring — not swept this pass except brassring (next in queue). 拜师 analogue is become <class> in the Praxis guild rooms, already covered 2026-09-01.

Leftover 678 (2026-09-12) — upstream pin

fluffos/dead-souls PRs #12–#16 are the in-tree float/currency, /open/, beggar map, supply2, and replace_program() fixes. Pin advanced to 4ddbd3b964. No new rooms. Do not rebase the local FluffOS tree over those commits.