Stray Book and Sword II

✅ 可玩

书剑飘零II

sjplii

🔑 fluffos / Mud@2026 更新 f3dcfd9 2026-09-04 源码 下载 ZIP

▶ 开始游玩 · Play Now

和 `sjplgfjxb`(书剑飘零官方教学版)同属飞白工作室的《书剑飘零》ES II 引擎家族,这份档案用的是 `adm/daemons/securityd.lpc`(没有 `sjplgfjxb` 那种 `securd`/`securityd` 双档案混淆的问题),而且与同一批档案里的 `sjpl2`(书剑飘零II)其实是同一次发布的两份快照——两者约 13,073/13,080 个档案路径相同,其中 98.2% 逐字节一致,应理解为一份发布分别打包成 zip、rar 两种形式的结果,而非各自独立开发的游戏。出生时可选四种"家境"(书香门第/商贾之家/贫寒农家/武力世家),各自带来不同的初始技能与出身技能(商贾之家带交易术、贫寒农家带乞讨术),实际出生地点也随之落在山东泰安或沿海福州的对应民居,而非固定的单一新手村(巫师权限账号仍会被固定送到长安城"大慈恩寺",是刻意的巫师起始点设计,与出生状况选择无关)。拜师用的 `apprentice` 指令身兼二职:第一次拜师加入门派,此后再敲同一指令则变成向师父磕头请安,能提升自己在门派中的地位。门派之外还有一套"江湖营生"玩法:山东粮店/马场可以打工赚钱,福州街头看卖艺人表演能长拆招技能,沿海船坞能造船出海捕鱼、练徒手搏击,这些都能换取潜能或江湖阅历。地图规模明显大于 `sjplgfjxb`:除了共享的长安"大慈恩寺"新手区,还有一整套皇宫场景(大殿、丹凤门、白虎/青龙门等)以及杭州、苏州、宁波、扬州、襄阳、成都、武汉等多座城市。

English

A Feibai Studio "Stray Book and Sword II" title on the ES II engine, sibling to sjplgfjxb (the Official Tutorial Edition, a trimmed subset of this same base). Like sjplgfjxb, a character's "birth circumstance" (scholarly/merchant/poor-farmer/martial family) sets both starting skills and actual starting location (a home in Tai'an, Shandong, or Fuzhou) rather than a single fixed newbie village; Chang'an's Great Compassion Temple is only a fallback for a failed saved-room load, though wizard-level accounts are always routed there regardless of birth circumstance, by design. The `apprentice` command does double duty — first use joins a sect, later uses become a respect-paying gesture that raises standing within it. Beyond sect life there's a whole "jianghu livelihoods" layer: day-labor at a grain store or horse ranch in Shandong, watching street performers in Fuzhou to pick up dodge/counter techniques, and building boats at a coastal shipyard to fish or practice unarmed combat, all convertible into potential or worldly experience. Its map is noticeably larger than sjplgfjxb's, adding a full imperial palace complex (main hall, Vermilion Bird Gate, White Tiger/Azure Dragon gates) plus the cities of Hangzhou, Suzhou, Ningbo, Yangzhou, Xiangyang, Chengdu, and Wuhan. Sibling sjpl2 elsewhere in this archive carries the exact same Chinese title (书剑飘零II) and turns out to be far more than a same-family engine relative: 13,073 of ~13,080 files share a path between the two archives, and 98.2% of those are byte-identical, indicating they are two snapshots of the same Feibai-Studio release (one .zip, one .rar) rather than independent games.

README

本次修复的 bug

WASM 启动/注册流程本身确实一次性顺利通过(sjplgfjxbreport_error()/CHANNEL_D 编译期递归崩溃、未定义的 REMOTE_DIR 这两个 bug在这个快照里确实不存在),但后续 §10.7 深度功能测试发现: sjplgfjxb 的另外三个 bug——d/fuzhou/npc/chess_player.lpc 编译期 error(自由函数误用 feature/name.lpcname(int raw))、 adm/daemons/logind.lpc 的两处 printf 路径泄漏、obj/board/ wizard_j.lpc/std/jboard 版 §7.86 留言板崩溃——在 sjplii 里其实同样存在,均已修复并在原生驱动上逐条验证(细节见 NOTES.md "深度功能测试"一节)。此外还独立发现并修复了 sjplgfjxb 没有的第四个问题:adm/daemons/emoted.lpc 的存档 data/emoted.o 里有一段从未转换成 UTF-8 的遗留 GBK 字节,导致 preload 时 restore() 抛出未捕获异常(§7.41 类);已给 restore() 包一层 catch() 并补上空 mapping 兜底。

管理员账号通过标准方式写入 /adm/etc/wizlist——需要注意的是这份档 案下还散落着好几个同名的 securityd.lpc/wizlistadm/adm/tmp/adm/daemons/bak/),已经用 include/globals.h/login.h 里的宏定义确认真正生效的是 adm/daemons/securityd.lpc + adm/etc/wizlist 这一对。

管理员账号 / Admin account

警告:对外公开架设前请务必修改此密码。

本地运行

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

游戏端口:40153

NOTES · 移植与修复记录

WASM 修复摘要(迁移自 meta.json 的 group_note)

sjplgfjxb 的手足档案(同一个飞白工作室《书剑飘零》ES II 代码库家族,这次用的是 adm/daemons/securityd.lpc——没有 securd/securityd 分裂,没有双档案混淆)。和 sjplgfjxb 不同,这份快照启动和注册都干净,完全不需要任何修复——sjplgfjxb 的那些 bug(report_error/CHANNEL_D、未定义的 REMOTE_DIR、损坏的 emote 存档)在这里都不存在。管理员账号播种:fluffos (admin) 加入 adm/etc/wizlist(已通过 include/globals.h 和 login.h 确认 SECURITY_D/WIZLIST 具体解析到的是 adm/daemons/securityd.lpc 和 adm/etc/wizlist——这份档案还带着好几个其它名叫 securityd.lpc/wizlist 的档案,分别在 adm/、adm/tmp/、adm/daemons/bak/ 下,都不是真正被使用的那些,符合既定的 §7.56 提醒)。已验证:完整注册(id→确认→名字→密码→确认→电子邮件→性别→出生地选择)→look/score/quit 全部干净,权限正确显示 (admin),update 成功。LPC 格式化工具对全部 12421 个档案运行;还原了 2 个其实是纯文本 Windows dir 指令输出、被粘贴成 .lpc 扩展名的档案(不是真正的代码,格式化工具试图把它们当 LPC 重新排版导致损坏),另外还有 3 个确认有真正 CJK 重新加空格损坏的档案,都是通过"去空格后比对旧档案"扫描(覆盖 68 个格式化工具触碰过的档案)找到的;另外直接逐一比对了全部 3 个 map.lpc 档案——全部干净,只是排版调整。格式化后重新验证过,干净。

§7.86 跨库扫描修复(留言板 post 崩溃)

深度功能测试(§10.7,2026-08-08)

README.md/上面的 WASM 摘要曾写"sjplgfjxb 的那些 bug 在这里都不存 在"——本次逐条独立核实后需要部分更正:chess_player.lpc 编译错误、 logind.lpc 的 printf 泄漏、/std/jboard 的 §7.86 变体这三个 sjplgfjxb 发现的 bug,在 sjplii同样存在(同一个飞白工作室代码库家族,同源 程度比先前认识到的更高);只有 report_error()/CHANNEL_D 崩溃、未定义 的 REMOTE_DIRemoted.lpc 存档损坏这三个 sjplgfjxb 独有的 bug 真的不 在 sjplii 里——不过 sjplii 自己另外还有一个不同位置的 §7.41 类损坏存 档(见下)。都已在原生驱动上逐条改前/改后验证。

本次修复的 bug

1. 一处真正的编译期 error(AGENTS.md §7.35 类,object-vs-string 参数类型不匹配,裸调用触发硬编译错误):d/fuzhou/npc/chess_player.lpc (棋摊老板"韦守儒")第 39-40 行 play_chess()printf("%s", name(this_player()));command("give chess to " + name(this_player())); 把继承自 feature/name.lpc 的本地方法 varargs string name(int raw)(取*自 己*的名字,参数是"要不要去头衔"的整数开关)当成"取对方名字"的自由 函数误用,传了一个 object 给一个只接受 int 的参数。驱动的静态类 型检查在裸调用上直接拒绝:Bad type for argument 1 of name ( int vs object ),导致这个 NPC 档案全程无法编译,福州"茶馆"填充它时会级联 *No program in object 报错。修复:删掉那行调试用 printf(§7.34 同款,无意义调试输出),command(...) 改为 this_player()->name()。改前:update /d/fuzhou/npc/chess_player 报上述两行 error;改后:成功!(只剩 /std/char/npc.lpc 两行无 关的 Unused local variable warning)。全档案 grep name(this_player() 及类似形状未发现第二处。 2. 两处 §7.34 类调试遗留adm/daemons/logind.lpcget_resp() (第 293 行)和 get_name()(第 328 行)各有一行 printf("%O\n", ob);,紧跟在中文名字确认之后、密码提示之前,把登 录物件的内部路径(/obj/login#N)原样打印给玩家。已各删一行。改 前后用全新注册的 fluffos(中文名"云中鹤")验证:改前会在"您的中 文名字:云中鹤"后多印一行 /obj/login#0;改后中文名字确认后直接 跳到"请设定您的密码:",无路径泄漏。 3. §7.86 类第三个变体obj/board/wizard_j.lpc(巫师"工作进度报 告"板,/d/wiz/jobroominherit "/std/jboard"; 之后 create() 尾巴又多余调用了 replace_program("/std/jboard");——与已扫描修复的 44 处 BULLETIN_BOARD 实例是同一致命形状,只是基类叫 /std/jboard.lpc,命令动词也不是 post 而是 project/reportdo_project()/do_report() 同样用 this_player()->edit((: lfun, ... :)) 建闭包)。已删除多余的 replace_program()。改前:全档案 §7.86 扫描时只做过编译检查,未做 过实机验证;改后:以 fluffos 管理员账号在 /d/wiz/jobroom 执行 project 深度测试计划 → 输入正文 → . 结束,新工作计画提 出。,无崩溃;update /obj/board/wizard_j 单独重新编译也确认无报 错(只有一条无关的 Unused local variable 'myid' warning)。全档 案已确认再无第二处 board 相关的 inherit X + replace_program(X) 同名组合(用 BULLETIN_BOARD/BBS_BOARD//std/jboard/ /std/bboard 分别 grep 过 45 个继承过任一板类的档案,逐一反查其 replace_program)。 4. 一处新的 §7.41 类损坏存档(sjplgfjxb 没有这一个,是 sjplii 自己独有的):adm/daemons/emoted.lpccreate() 对自己的存档 data/emoted.o 做未加保护的 restore(),preload 时抛出未捕获异 常(*restore_object(): Invalid utf8 string while restoring emote.,冒泡到 master.lpc 的 preload() catch,说明 create() 没跑完,emote 变量停在未初始化状态)。用 Python 检查 data/emoted.o 二进制内容,第 58 字节起有一段确认是遗留的 GBK 字 节(\xbf\xb4\xbf\xb4$n\xcd\xb7... 等),从未在原始编码转换阶段 被转成 UTF-8——这是货真价实的损坏存档,不是驱动或 harness 的问题。 修复(与 sjplgfjxb 完全一致的写法):create() 里把 if (!restore() && !mapp(emote)) emote = ([]); 改成 catch(restore()); if (!mapp(emote)) emote = ([]);。改前:驱动启 动日志里这行异常会冒泡到 master.lpc 自己的 CATCH()错误讯息 被拦截);改后:异常被 emoted.lpc 自己的 CATCH() 拦下(调用链 多了一层 emoted.lpc 的 CATCH() 第 43 行),create() 正常跑完, emote 兜底成空 mapping,驱动其余启动流程无变化。

排查过程中确认"不是 bug"的现象

已验证正常工作(原生驱动,fluffos/Mud@2026 管理员账号 + 一个

一次性非管理员测试角色"赵日天"/sjplcheck

管理员账号播种

adm/etc/wizlist 里此前已有 fluffos (admin) 一行,但账号本身 (data/{login,user}/f/fluffos/)此前从未被真正注册/提交过——已确认 不存在。本次走标准注册流程创建(id fluffos,中文名"云中鹤",密码 Mud@2026),登录后 目前权限:(admin) 立即正确显示(无需额外的 WIZ 密码之类的二级门槛,securityd.lpc 是这份档案里唯一、真正生效的 安全精灵),update /d/fuzhou/npc/chess_playerupdate /obj/board/wizard_j 两次写权限验证都成功。测试用的一次性非管理员账 号 sjplcheck(中文名"赵日天")及测试留言板存档(大慈恩寺板、 wizard_j 板各一条测试留言/计画)已在提交前删除,只保留 fluffos 的账号存档。

§7.100 房间基类 replace_program() 扫尾修复(2026-08-19)

ROOM 宏(/std/room)在本档案 2,290 处房间文件的 create() 里 紧跟 inherit ROOM; 之后又多余调用了一次 replace_program(ROOM); ——和姊妹档案 sjpl2(同一份 书剑飘零 血统,房间生成工具家族逐字 节相同)完全一样的 AGENTS.md §7.100 休眠 bug。用 fix_710_room.py 扫过 work/,删除 2,280 处标准形状;另有 9 处不规则形状手工修复, 和 sjpl2 的处理逐一对应:obj/roommaker.lpc 字符串拼接变体、 4 份房间生成工具副本 + 2 份内嵌同一段代码的 NPC 档案 (u/lark/xiaoyao/npc/{rich,tumu}.lpcu/jakey/npc/rich.lpc)里 "仅当房间没有 item_desc 时才拼接" 的条件变体(u/losey/roommaker.lpc u/set/obj/roommaker.lpcu/gdjk/roomm.lpcu/gdjk/hongnong/maker.lpc),以及 u/lark/yangmingzhai/ss.lpc 一处真实房间文件(replace_program 紧跟在已注释掉的 /* setup() */ 后面同一行)。u/lark/luoyang/dongmen.lpc 剩下 1 处留着未修:整份文件从 inherit ROOM(无分号)起就缺失几乎全部语 句分号,确认是转档之前就已经损坏、与本次改动无关,和 sjpl2 同一 份损坏文件的表现一致。git diff --stat 显示 2284 个文件净删 2289 行,与 sjpl2 完全对称吻合;另有 150 处转档之前已注释掉的 // 行 原样保留。work/data/ 下没有真实 .lpc 源码命中。

驱动干净启动(零新增编译错误、端口正常监听、debug.log 无任何 "cannot replace"/"cannot bind"行),巫师账号 fluffos/Mud@2026 确认"目前权限:(admin)"后 look/goto 走读 了 y/city/baozipu.lpc(曾经命中过这个 bug 的房间)正常,quit 干净退出。走读时另外撞见 y/city/baihu-n1.lpc 一处转档之前就存在 的 NPC_DIR "people/man" 宏拼接语法错误(git diff 确认本次改动 只碰了 replace_program 那一行),与本次 sweep 无关,不在修复范 围内。登录存档的增量已用 git checkout HEAD -- 撤销,未落入提交。

§7.30 uninitialized-mapping accessor sweep (2026-08-20)

Corpus-wide mechanical sweep of the feature/skill.lpc shared-lineage bug (confirmed independently on xiakexing2017/jqxz2015/haiyang2 via round-four testing): 3 accessor(s) in this file returned a raw never-initialized mapping instance variable (defaults to int 0, not ([]), until first assigned), crashing any unguarded keys()/sizeof()/indexing caller for a fresh/untrained character. Fixed at the accessor level (mapp(x) ? x : ([])) per the documented remedy. Verified via lpcc --batch static compile check only (not a live boot) as part of a large mechanical sweep; not individually functionally re-tested live on this lib.

AGENTS.md §7.19 sweep (2026-09-01): enable_player() reentrancy from init()

Same corpus-wide bug class as mhxy/wuhanzhan (AGENTS.md §7.19): this lib's feature/command.lpc enable_player() wrapper (around the raw enable_commands() efun) is reachable from an NPC's init() via a redundant create()-then-init()-calls-setup() (or reset_me() calling setup()) chain -- confirmed live via a static scan of every init() body in this lib: 45 NPC/item files call setup() directly or via reset_me() from init(), after create() already called setup() once (which already made the object living()). Calling enable_commands() a second time on an already-living() object makes the driver re-invoke that object's own init() as a side effect, which re-enters this same chain while the original call is still on the stack -- genuine reentrancy, crashing with "Too deep recursion" (most likely to surface on an NPC's first-ever preload/compile).

same as sjpl2 (shared lineage): feature/damage.lpc's revive() calls this_object()->enable_player() while the object is still living(). This confirms a bare if (living(this_object())) return; guard would be the WRONG fix (it would silently break that legitimate re-enable) -- used the same true reentrancy-flag fix as mhxy instead: a nosave private int in_enable_player_now; set for the duration of the wrapper's body, guarding only genuine same-call-stack reentrancy while leaving every legitimate re-enable (revive/wakeup/disguise) unaffected. feature/command.lpc's enable_player() had a single fall-through exit (no early returns), so one guard-at-top + one clear-at-bottom pair was sufficient. Verified via a single-file lpcc --batch compile check (PASS) -- not individually live-boot-tested.

深度功能测试(2026-09-03,第三轮)

新角度:2026-08-08 第一轮已经走完注册、战斗、完整死亡/复活和留言板。 本轮补商店,并尝试红旗镖局拜师。手足 sjplgfjxb 同一轮刚修过缺失 dishes/ 键把整张货表拖垮的 bug;这份完整档带着 d/city/obj/food/dishes/dish*.lpc,预期不会踩那个洞。