docs: 三文档完整更新至 0.1.21
AGENTS.md:架构章节重写(0.1.14 大重构路径)+ 引擎红线(时轮/随机/事件闸/特效出口/插件/门面/双金钟罩) + 世界自演进模型 + sqlark陷阱 + 数值基调(0.1.16 修为经济化/0.1.17 市场实体化)+ 修改纪律 README.md:快速开始(1018 用例)+ 0.1.14+ 架构一览 + 守护机制(双金钟罩/事件闸/插件/回放) + 版本沿革 0.1.12-0.1.21 PROJECT_MEMORY.md:版本时间线补齐(0.1.12-0.1.21 每迭代的为什么)+ 关键架构决策 (事件闸定稿/WorldSim 四层递进/引擎表现双通道/normalize即迁移/线程定论) + 踩坑教训(era 无延续档/潮汐符号反向/事件闸竞态/每tick存档/金钟罩盲区) + 诚实技术债清单 + 修改路径
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# AGENTS.md
|
||||
|
||||
仙途家族志 · Chronicle of the Immortal Clan — Electron + React + TS 家族修仙模拟器。全部 UI 与文案为中文。
|
||||
当前版本 **0.1.21**(《大势流转》:era↔景气双向闭环 + 世界史表 + 渲染管线修复)。
|
||||
|
||||
## 命令
|
||||
|
||||
@@ -32,23 +33,42 @@ SMOKE_TEST=1 SMOKE_SHOTS_DIR=/tmp/opencode/shots npx electron ... # 附
|
||||
- 私有 registry 的 `_auth` token 只在用户级 `~/.npmrc`;项目 `.npmrc` 只保留 registry 映射,不要把 token 写进仓库。
|
||||
- Linux 无 emoji 字体:**所有图标一律用汉字印章字符**(renderer/ui/styles.css 的 `.s-icon/.res-icon/.bld-icon`),不要引入 emoji。
|
||||
|
||||
## 插件架构(0.1.8 起)
|
||||
## 架构(0.1.14 大重构后,目录与路径以此为准)
|
||||
|
||||
- **万物皆是插件**:`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 后重固化)。
|
||||
```
|
||||
src/renderer/game/ 纯 TS 引擎(无 React/DOM,vitest 直测)
|
||||
engine/GameEngine.ts 唯一门面:advance/restore/engineFromSnapshot/about/resolvePending
|
||||
engine/kernel/ Kernel.ts(三合一:Clock+Rng+BUS 转发)+ clock/rng/plugin/names/format/guide/urgency/timesense
|
||||
engine/runtime/ World.ts(大对象+normalize+bus 转发)/clocks.ts(时轮注册)/ApiFacade.ts(GameFacade)
|
||||
Systems/(production/aging/cultivation/missions/events/diplomacy/marriage/tribulation/tournament/lifecycle/combat)
|
||||
capabilities.ts(12 能力卡)/pluginManager.ts/plugin-bootstrap.ts/creation.ts/pcgen.ts
|
||||
engine/sim/ 世界自进化: WorldSim.ts / worldsim-data.ts(数值与常量唯一权威)/Market.ts / worldsim-brief.ts
|
||||
engine/narrative/ 族谱/传记/定鼎/年轴/百年报告
|
||||
data/ balance 表 + DataPackRegistry(registry.ts pack() 单源;PILL_RECIPES/FORGE_RECIPES/ERA_CONF 等)
|
||||
storage/ db(MetonaSqlark 单例)/slots(槽+快照+exportEnvelope)/migrate(CURRENT_SCHEMA=2,即 normalize 即迁移)
|
||||
types/domain.ts 全量领域类型(改状态结构先看它)
|
||||
src/renderer/ui/ React + zustand(store.ts 主状态;面板按 panel switch 挂载;订阅 revision 刷新)
|
||||
src/main/ Electron 主进程(app:// 协议 + IPC 存档导出/导入)
|
||||
```
|
||||
|
||||
## 架构要点
|
||||
## 引擎红线(0.1.14+)
|
||||
|
||||
- `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 依赖。
|
||||
- **统一时轮**:月度 phase(production/aging/cultivation/missions/events/diplomacy/epilogue)+ 年首钩子均注册于 `runtime/clocks.ts`。**新增系统 = 注册一行,禁止手改 `advanceMonth` 本体**(World.ts:329)。
|
||||
- **统一随机**:引擎只经 `World.rng`;UI 播种用 `RngHub.rollSeed()`、音效白噪用 `RngHub.audioNoise01()`;**不要**在逻辑里引入 Math.random()(引擎域 0 处)。渲染层 canvas 粒子散布的 Math.random 不进引擎状态,可留。
|
||||
- **引擎单线程定论**(0.1.21 实测):`advanceMonth` 2160 月平均 **0.05ms/tick**——无 Worker 化必要;拖帧热点在 UI 渲染与存档(都已在 0.1.20/0.1.21 修)。
|
||||
- **事件闸**:pending 事件唯一写入入口是 `events.ts` 的 `fire(w, id, priority)`(0=普通遇占放弃 / 1=高优 raid·渡劫·大比可顶替被占者,被顶替者入 `eventQueue` 下月兑现不丢失)。**禁止直写 `state.pendingEvent`**(渡劫/破境丹均已改走 fire)。`applyEventChoice` 前置校验 `pendingEvent===id`(陈旧 Modal 二次结算防线)。
|
||||
- **特效/音效语义出口**:`WorldEventBus.onFx?(em: FxEmit)` + `World.emitFx(kind, source?)`(零 rng 消耗);UI 的 `makeBus` 桥接 FxGate;`onLog kind` 音效映射保留作兜底。
|
||||
- **插件架构**(0.1.8 起,路径已迁 engine/runtime|kernel):`kernel/plugin.ts` 协议、`runtime/pluginManager.ts` 安装管线(依赖校验/异常回滚/追踪型上下文——注销时钩子全摘)、`plugin-bootstrap.ts` 三核心插件(core-systems/core-data/core-events protected)。事件多池 `world.eventPools`;**findEvent 必须带 world 参数查池**。能力卡 12 张经 `viaCap` 注册;tournament/tribulation/apprentice/season 四卡各自内部检查 `w.sysEnabled`(无独立钩子)。
|
||||
- **门面**:`runtime/ApiFacade.ts` GameFacade(act 24 项/query 6 类/subscribe 退订协议/about);**UI 面板事实走 world 直调**(act 为测试/自动化通道),`actDirect` 与 ACT_CATALOG 语义对齐(0.1.20 补齐全 24 项);store 的 `facade` 必须在 openState/startNewGame 赋值。
|
||||
- **金钟罩(双基线)**:`tests/clock.test.ts` 的 `GOLDEN`(直调冻结世界)+ `GOLDEN_RESOLVED`(自动 resolve 长跑"现实"世界),各 3 seed × 三档月数(560/1200/2160)共 18 值。当前为 **0.1.21 基线**(见注释)。指纹 `tests/fingerprint.helper.ts` 含全世界轴(era/stance/prosperity/relationsWithOthers/calamity/pendingEvent/eventQueue/worldAnnals)。**有意变更时序/数值时**:指纹一并重算并在注释注明原因。
|
||||
|
||||
## 世界自演进模型(0.1.14 sim/ 层,改数值勿动流程)
|
||||
|
||||
- **市场实体化**:`marketPool` 是真库存——世界月供给(`worldSupplyRate` × 潮汐span × era.supplyMult)vs 常驻需求(`worldDemandRate`);NPC 按 `def.sells/buys` 逐月入池出池(姿态/区域灵势/灾年调制);玩家买卖经 `WorldSim.tradeSettle` 回写(买浅价涨/卖盈价跌);池深 < `tradeFloorPct(0.35)` 断供拒单(与 `poolFloorPct` 单常量一致)。`driftMarket` rebalance=0.025 弱锚定(让供需真实撬动价格,勿调回 0.1+)。
|
||||
- **世纪弧 era**:盛世/平世/乱世/末法,`ERA_CONF`(灾年概率/供需/拍卖/tideBias)+ `ERA_DURA`(续航窗口);flow 权重和 <1(留白=延续档);**转移权重受世界温度调制**(`worldTemperature()` + `tempEraK`)——时代由天下兴衰孕育、又重塑天下。
|
||||
- **NPC 定力**:`npcDyn.prosperity/stance/relationsWithOthers`——姿态(守成/扩张/隐忍/结盟)年首重估,调制 raid/互攻/贸量/外交;互攻按 power 加权 + 打残广播;玩家劫掠/结盟写关系网(蝴蝶效应)。
|
||||
- **灾年**:持续 6 月(`calamityLeft`),减产乘子(CALAMITY_FAMILY)+ 疫病/兽潮体感 + NPC 贸易 ×0.7 + 群雄相噬 ×1.5;世界侧 `CALAMITY_EFFECT` 冲击池。
|
||||
- **天下史表**:`worldSim.worldAnnals[]`(十年一鉴 80 页留档,免 newsFeed 裁剪丢失)。
|
||||
|
||||
## MetonaSqlark(@metona-team/metona-sqlark 0.7.4)陷阱
|
||||
|
||||
@@ -56,15 +76,17 @@ SMOKE_TEST=1 SMOKE_SHOTS_DIR=/tmp/opencode/shots npx electron ... # 附
|
||||
- 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。
|
||||
- 存档防线:`loadState`/importAll 都过 `migrateIfNeeded`(TOO_NEW/未知版本 → MigrationError);normalizeGameState 是幂等兜底(**0.1.14+ 全部新字段靠它补**:worldSim 全套/era/prosperity/stance/worldAnnals 等,改字段记得同步补兜底);暂无 v3(需转换内容时才升)。
|
||||
|
||||
## 数值基调(0.1.10 重建后,勿当 bug 调回去)
|
||||
## 数值基调(勿当 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 年前旧键。
|
||||
- **0.1.16 修为经济化**:练气以上修行者月耗灵草(闭关2/普通1),无草速修 ×0.6~1.0 衰减——灵草是第一战略资源。
|
||||
- 寿元:75/150/220/340/520/850(凡人→化神)。
|
||||
- 大境界晋升走**渡劫事件三选**(硬渡/护法/压制,12 月不应自动压制);破境丹在需渡劫时药力转 `tribBoost`(+12%) 入渡劫,**不可跳过**。
|
||||
- 周期事件优先级:命运(百年/飞升)> 大比 > 传薪(年锚 flag)> 拍卖(era 概率调制)> 岁祷 > 回声。
|
||||
- `family.flag` 年份键(auction-/prayerDone-/echoDone-/recruitDone-/legacyDone-)每 tick 剪 3 年前旧键。
|
||||
|
||||
## 修改纪律(防覆盖——血泪教训,务必遵守)
|
||||
|
||||
@@ -75,9 +97,9 @@ SMOKE_TEST=1 SMOKE_SHOTS_DIR=/tmp/opencode/shots npx electron ... # 附
|
||||
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` 核对改动面与预期一致(±文件行数应与你改的块吻合)。
|
||||
5. **高危文件白名单**(world.ts / events.ts / store.ts / styles.css / cultivation.ts / **WorldSim.ts / worldsim-data.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...),调平衡不改引擎流程。
|
||||
- 提交前:`npm test`(当前 43 套件 1018 例全绿)+ `npm run typecheck` 必须绿(改存储/SQL 后再跑一次冒烟)。
|
||||
- 平衡数值集中在 `game/data/` 与 `engine/sim/worldsim-data.ts`(世界循环参数唯一权威),调平衡不改引擎流程。
|
||||
- 发布产物 `release/`、`out/` 均已 gitignore,勿入库。
|
||||
|
||||
Reference in New Issue
Block a user