# AGENTS.md

仙途家族志 · Chronicle of the Immortal Clan — Electron + React + TS 家族修仙模拟器。全部 UI 与文案为中文。

## 命令

```bash
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 数据库 + 开局推进 + 可选截图）：

```bash
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 的 `_auth` token 只在用户级 `~/.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.ts` 12 张；时钟注册走 `viaCap`；tournament/tribulation/apprentice/season 四卡各自内部检查 `w.sysEnabled`（无独立钩子，勿直接调）。
- **门面**：`engine/api.ts` GameFacade（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 后重固化）。

## 引擎系统（0.1.14 GameEngine）

- **入口单点** `engine/GameEngine.ts`：new GameEngine({seed,datapack}) → world/facade/kernel/datapack/sim；
  advance/act/query/subscribe/about/installPlugin/snapshot/restore。/ UI store 只认识 engine（world 为兼容仍暴露）。
- **内核三合一** `engine/kernel/Kernel.ts`：Clock + Rng + 事件总线——单实例；World 的 clock/rng 注入自内核（双时钟已合一，勿再造）。
- **世界自进化** `engine/sim/WorldSim.ts`（worldsim 相位 +「天下演序」能力卡可停用）：
  资源循环市场（marketPool 供需→Market 行情乘子）/ NPC 演化（换代/势力=境界+财+兵）/ 秘境灵气（探索消耗+恢复）/
  灵气潮汐（天雨期×修炼）/ 灾年签（联动市场池）/ 天下快讯（newsFeed 滚动 N）。**所有随机走 w.rng**（禁 Math.random）。
- **目录**：engine/kernel（底层原子）/ engine/runtime（World/插件/门面/Systems×13）/ engine/sim（经济+世界演化）/ engine/narrative（史书族谱维度）。
- 旧目录（game/core、engine/systems、engine/world.ts、engine/api.ts）**全部废弃**，勿再引用。

## 架构要点

- `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 事后兜住，但应防于未然。

**硬性规则**：
1. **改动前**：`git status` 确认工作区干净（或先提交当前成果，原子提交，一功一提交）。
2. **改动中**：批量替换一律用「先 assert 旧文本存在 → 替换 → 再全局 grep 断言新文本唯一」三步；禁止无检查的 sed/python 盲替。
3. **禁止 `git checkout -- <file>` / 整文件回滚**（这是历史事故元凶）。确需回滚时：`git show HEAD:<file> > /tmp/bak` 手动 diff 恢复，恢复后跑全量测试确认无伤。
4. **优先小步快跑**：每个功能块改完立即 `npm test`（引擎）或 `tsc`（UI 类型），绿了再动下一处——绝不在错误状态上加新修改。
5. **高危文件白名单**（world.ts / events.ts / store.ts / styles.css / cultivation.ts）改动后必须 `git diff --stat` 核对改动面与预期一致（±文件行数应与你改的块吻合）。
6. 每次迭代收尾：`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，勿入库。
