- AGENTS.md 新增「修改纪律」章:改动前 git status、替换三步断言、禁止 git checkout 整文件回滚、
小步快跑先绿再进、高危文件白名单 diff 核对、迭代收尾 status/log 留痕
- PROJECT_MEMORY 补第 6 记:修改覆盖事故教训(爸爸点名:自己改代码把改过的覆盖了)
- 明确:上一轮 c52ecff 全量提交无丢失;本纪律立即生效
8.1 KiB
8.1 KiB
AGENTS.md
仙途家族志 · Chronicle of the Immortal Clan — Electron + React + TS 家族修仙模拟器。全部 UI 与文案为中文。
命令
npm test # vitest 引擎测试(tests/*.test.ts,全部纯 Node,无 DOM/DB 依赖)
npx vitest run tests/world.test.ts # 单文件测试
npm run typecheck # tsc --noEmit(npm 会打 warn 噪音,用 npx tsc --noEmit 更干净)
npm run dev # electron-vite 热更新(开发期 origin 为 http://localhost:5173)
npm run build # 产物到 out/
npm run package # build + electron-builder(Linux 打 win 包可出产物,但未签名,Windows 上可能被 SmartScreen 静默拦截;正规发布在 Windows 侧重跑此命令)
端到端冒烟(渲染 + OPFS 数据库 + 开局推进 + 可选截图):
npm run build
SMOKE_TEST=1 npx electron out/main/index.js --no-sandbox --disable-gpu # 退出码 0 = 全通
SMOKE_TEST=1 SMOKE_SHOTS_DIR=/tmp/opencode/shots npx electron ... # 附带 UI 截图
冒烟会写真实用户数据目录(Linux 下 ~/.config/Electron),跑完删掉该目录以免脏数据。
无显示环境(WSLg 掉线)会打印 [SMOKE-TIMEOUT] 退出码 2 而非卡死;GUI 冒烟必须在 X11/WSLg 在线时跑(引擎与存储逻辑的回归请靠 vitest,不要依赖 GUI)。另注意:npx electron 可能拉取新版 electron 缓存版(与本仓库 33.x 不同),二进制安装以 node_modules/electron 为准。
环境坑
- npm 12 拦截 postinstall:首次安装后须
npm install-scripts approve electron esbuild,否则 Electron 二进制缺失。 npm rebuild electron esbuild会删掉node_modules/electron/dist/electron(Linux 二进制),修复:ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ node node_modules/electron/install.js- 私有 registry 的
_authtoken 只在用户级~/.npmrc;项目.npmrc只保留 registry 映射,不要把 token 写进仓库。 - Linux 无 emoji 字体:所有图标一律用汉字印章字符(renderer/ui/styles.css 的
.s-icon/.res-icon/.bld-icon),不要引入 emoji。
插件架构(0.1.8 起)
- 万物皆是插件:
core/plugin.ts协议(Manifest/dependencies/conflicts/install/uninstall);engine/pluginManager.ts安装管线(依赖校验、异常回滚、追踪型上下文——插件注册的时轮钩子卸载时全摘);engine/plugin-bootstrap.ts三枚常驻核心插件(core-systems/core-data/core-events,protected 不可卸)。 - 事件多池:
world.eventPools(core 池受保护不可删)+eventRoll聚合抽样;第三方插件ctx.addEventPool(id, events)即注入内容。注意 findEvent 必须带 world 参数查池(events.ts),否则未知事件直接软锁。 - 能力卡:
engine/capabilities.ts12 张;时钟注册走viaCap;tournament/tribulation/apprentice/season 四卡各自内部检查w.sysEnabled(无独立钩子,勿直接调)。 - 门面:
engine/api.tsGameFacade(act 24 项/query 6 类/subscribe 退订协议/about);UI store 的facade字段必须通过 openState/startNewGame 赋值,否则 act 走残缺 fallback。 - 金钟罩:0.1.11 审计修复批次后三档指纹(47/100/180 年 × 3 seed = 9 值,见 tests/clock.test.ts;0.1.8 基线已作废、0.1.10 重建后再次漂移、0.1.11 大比 once/prune 后重固化)。
架构要点
src/renderer/game/:纯 TS 游戏引擎(无 React/DOM import),可被 vitest 直接测试;types/domain.ts是全量领域类型,改状态结构先看它。- 统一时轮
core/clock.ts:月度 phase(production/aging/cultivation/missions/events/diplomacy/epilogue)+ 年首钩子均注册于engine/clocks.ts。新增系统 = 注册一行,禁止手改advanceMonth本体。 - 统一随机
core/rng.ts:引擎只经World.rng(含nextCount审计);UI 播种用RngHub.rollSeed()、音效白噪用RngHub.audioNoise01(),不要在逻辑里引入 Math.random()。 - 金钟罩
tests/clock.test.ts:3 枚固定 seed × 三档月数(560/1200/2160 = 47/100/180 年)指纹常驻(0.1.11 基线:见注释 GOLDEN)。任何改动若破坏确定性立即红;有意变更时序/数值时指纹一并重算并在注释注明原因。 src/renderer/ui/:React + zustand(ui/store.ts)。World 的 game state 是 mutable,advance 后revision++触发重渲染;订阅revision是面板刷新惯例。- 引擎 = 种子随机数(sfc32,
core/rng.ts)+ 不可变快照存state.rng,同 seed 全程可重放(tests/world.test.ts 有确定性用例,改任何 tick 顺序都要保证仍然确定性)。 - 建档开头成员 id 硬编码
x1~x5,tests 依赖。
MetonaSqlark(@metona-team/metona-sqlark 0.7.4)陷阱
- SQL 方言为自有实现(不是 SQLite):建表列类型写
string|number|boolean|date|json,不是INTEGER/TEXT;不支持INSERT OR REPLACE、ON CONFLICT、rowid——用「SELECT 判断 → UPDATE/INSERT」或直接DELETE+INSERT。 - aria 引擎同库一个 tab 只能建一次连接(Web Locks →
ARIA_LOCKED):存取必须走game/storage/db.ts的单例getSaveSlot(slot)/getSlotManager(),禁止每次new SaveSlot/new SlotManager。 - 数据在浏览器 OPFS(renderer 内),main 进程无 DB 逻辑;开发与打包后的 origin 不同,两环境存档不互通。
- 存储层通过
SaveDbDriver抽象(storage/slots.ts),node 环境下无 OPFS,测试只覆盖引擎不覆盖 DB。
数值基调(0.1.10 重建后,勿当 bug 调回去)
- pacing.ts 的 MAJOR_RATE 是进度唯一生效因子(qi 2.2、foundation 1.4、core 0.95、nascent 0.68、spirit 0.48);
realms.ts的expBase/expGrowth/maxRealmExp曲线未参与计算(历史摆设,勿以它反推)。 - 月率 base =
1.0 + perception*0.18;65 岁起 ×0.72 衰减、8 岁以下 ×0.65(圣者护族 60+ 高境界者存在时乘 1.3)。 - 寿元:75/150/220/340/520/850(凡人→化神,0.1.10 放宽的梯度)。
- 大境界晋升走渡劫事件三选(硬渡/护法/压制);玩家 12 月不应会自动压制一年(防挂机软锁,events.ts 计数
tribPendingMonths)。 - 周期事件优先级:命运(百年/飞升)> 大比 > 传薪 > 拍卖 > 岁祷 > 回声;大比有
once,一年一届。 family.flag年份键(auction-/prayerDone-/echoDone-/recruitDone-)每 tick 剪 3 年前旧键。
修改纪律(防覆盖——血泪教训,务必遵守)
背景:曾多次出现「大改后我之前的修复被覆盖」:① 批量 python 替换命中旧文本、② git checkout 回滚临时参数时误恢复了已修复文件、③ 编辑器链式 replace 生成重复属性/孤行。均靠 tsc/vitest 事后兜住,但应防于未然。
硬性规则:
- 改动前:
git status确认工作区干净(或先提交当前成果,原子提交,一功一提交)。 - 改动中:批量替换一律用「先 assert 旧文本存在 → 替换 → 再全局 grep 断言新文本唯一」三步;禁止无检查的 sed/python 盲替。
- 禁止
git checkout -- <file>/ 整文件回滚(这是历史事故元凶)。确需回滚时:git show HEAD:<file> > /tmp/bak手动 diff 恢复,恢复后跑全量测试确认无伤。 - 优先小步快跑:每个功能块改完立即
npm test(引擎)或tsc(UI 类型),绿了再动下一处——绝不在错误状态上加新修改。 - 高危文件白名单(world.ts / events.ts / store.ts / styles.css / cultivation.ts)改动后必须
git diff --stat核对改动面与预期一致(±文件行数应与你改的块吻合)。 - 每次迭代收尾:
git status应干净 +git log --oneline确认每步都有提交记录;凡 "git stash/checkout/rm" 类命令,执行前后都git status留痕。
- 提交前:
npm test+npm run typecheck必须绿(改存储/SQL 后再跑一次冒烟)。 - 平衡数值集中在
game/data/(realms/pacing/buildings/events...),调平衡不改引擎流程。 - 发布产物
release/、out/均已 gitignore,勿入库。