fix(06): restage all plans per D-43 (component -> combination -> integration), drop tracer-first

This commit is contained in:
unanmed 2026-09-14 12:34:49 +08:00
parent feb2f8181d
commit 85f7b1053b
11 changed files with 421 additions and 261 deletions

View File

@ -30,6 +30,7 @@ must_haves:
- "测试形态(D-01):采用混合测试形态中纯逻辑/数据模块一侧,直接单测(fake / 显式依赖注入,延续 Phase 3 D-03);战斗经 `CoreState` 的集成路径由 06-07 承担。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-system/src/combat/context.test.ts
provides: EnemyContext 全接口、光环流水线、效果阶段顺序与生命周期覆盖
@ -56,12 +57,14 @@ must_haves:
---
<objective>
以一条端到端 tracer 建立战斗系统(`packages-user/data-system/src/combat`)测试骨架,随后按 D-21..D-27 完成 `EnemyContext` 光环流水线、效果组合、四阶段顺序与全接口/全 code 覆盖(D-15 深化)。
按 D-43 的阶段化测试(构件 → 组合/流水线 → 完整/集成;取消 tracer-first)完成战斗系统(`packages-user/data-system/src/combat`)覆盖,按 D-21..D-27 覆盖 `EnemyContext` 光环流水线、效果组合、四阶段顺序与全接口/全 code 覆盖(D-15 深化)。
Purpose: `data-system/src/combat` 是纯 Layer-2 战斗契约,无 DOM、经注入 fake 协作者驱动,是证明「Node 全局 stub + 动态 import 包 + fake 注入 + `logger.catch` + 真实计时器异步」测试骨架的最干净位置。本计划同时建立阶段级 `06-COVERAGE-MAP.md`。
Purpose: `data-system/src/combat` 是纯 Layer-2 战斗契约,无 DOM、经注入 fake 协作者驱动,是证明「Node 全局 stub + 动态 import 包 + fake 注入 + `logger.catch` + 真实计时器异步」测试骨架的最干净位置。这些系统在本次之前从未被测过,直接端到端很可能整链失败且无法定位,故先构件级定位(D-43)。本计划同时建立阶段级 `06-COVERAGE-MAP.md`。
Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行)。
阶段结构(D-43):阶段 1 构件级(`EnemyContext` 单方法/单光环/单 effect 单元 + 覆盖表骨架)→ 阶段 2 组合/流水线(`buildup`/`requestRefresh`、嵌套光环、四阶段顺序、警告码)→ 阶段 3 完整/集成(`DamageContext`/`DamageSystem`/`MapDamage`/`CombatFlow` 与 D-26 属性→伤害联动)。每阶段跑绿后再进入下一阶段。
计划结构(D-28):本计划为按 D-28 重排后的 06-01 战斗系统层(系统层,`data-system/src/combat`);原 06-01/06-02 执行结果已由 D-29 标记 superseded(SUMMARY 改名保留),本计划按同号重跑,确保「接口 + warn/error code 全覆盖」落到实处。
</objective>
@ -98,12 +101,13 @@ Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行
- 本计划不测 `saveState`/`loadState`(D-32)。
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写入前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本计划小节,绝不重写他节。其它计划使用同一协议,故执行顺序不影响结果。
- **执行协议(D-33):执行本计划前必须先向用户确认;执行完成后暂停,用中文分条简要汇报本计划验证结果;详细结果写入共用的 `06-TEST-FINDINGS.md`。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
<task type="tracer">
<name>Task 1: Tracer — EnemyContext 光环流水线端到端最小路径(+ 阶段骨架)</name>
<task type="auto">
<name>阶段 1(构件级):EnemyContext 单个公开方法 + 单个光环/单 effect 单元 + 覆盖表骨架</name>
<files>packages-user/data-system/src/combat/context.test.ts, .planning/phases/06-unit-tests/06-COVERAGE-MAP.md</files>
<read_first>
packages-user/data-system/src/combat/context.ts
@ -114,13 +118,14 @@ Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行
dev.md(注释规范)
</read_first>
<action>
创建 `context.test.ts` 作为战斗系统 tracer,走通唯一一条真实端到端路径,证明整套 harness 可用:
用共享样板 1 的 `vi.hoisted` 全局 stub + `beforeAll` 动态 import 包(global `main`/`location`/`Map.prototype.getOrInsertComputed` 与 `Map.prototype.getOrInsert` 都要 polyfill,`EnemyContext` 使用后者)。
手动装配:`new EnemyContext(state)`(`state` 用最小 inline fake `IStateBase`)→ `resize(3,3)` → `registerAuraConverter(fakeConverter)` → `setEnemyAt({x,y}, fakeEnemy)` → `bindHero(fakeHero)` → `buildup()`,断言 fake 光环对范围内怪物的基础属性加成生效(例:hp/atk/def 按百分比加成后的具体数值)。
fake `IAuraConverter` 实现 `shouldConvert(special, handler)` 与 `convert(special, handler, context)`;fake `IAuraView` 实现 `priority`/`range`(用真实 `IRange` 或最小 inline range)/`getRangeParam`/`couldApplyBase`/`couldApplySpecial`/`apply`/`applySpecial`。fake `IEnemy`/`IReadonlyEnemy` 暴露 `iterateSpecials`/`addSpecial`/`deleteSpecial`/`getAttribute`/`setAttribute`/`addAttribute` 等被 `EnemyView` 使用的最小表面。
仅此一条路径,不做其它 call site、不做批量。
然后建立阶段骨架(create-or-append,幂等、顺序无关):以 `.planning/phases/06-unit-tests/06-COVERAGE-MAP.md` 为目标,先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在——不存在则创建表头,已存在则保留;随后只新增/更新 06-01 小节占位,绝不重写他节。并写入本计划已覆盖的 code→模块→用例行(97/98/99/100/101/110/106/107/102/103/104/138/139/140/141 的映射将在 Task 2/3 完成后补全用例名;此时先建表头与模块归属)。
以聚焦运行验证:tracer 必须单独跑绿再进入扩展任务。若 `EnemyContext` 的 `buildup` 因缺少 hero 绑定或 range `bindHost` 契约而无法驱动,按 D-07 暂停并向用户提问,不修改核心代码。
D-43 阶段 1(构件级):只测最小单元,不驱动整条流水线、不做端到端。
创建 `context.test.ts`,先建立 harness(共享样板 1 的 `vi.hoisted` 全局 stub + `beforeAll` 动态 import 包;global `main`/`location` 以及 `Map.prototype.getOrInsertComputed` 与 `Map.prototype.getOrInsert` 都要 polyfill,`EnemyContext` 使用后者)。
手动装配:`new EnemyContext(state)`(`state` 用最小 inline fake `IStateBase`)→ `resize(3,3)`。fake `IAuraConverter` 实现 `shouldConvert(special, handler)` 与 `convert(special, handler, context)`;fake `IAuraView` 实现 `priority`/`range`(用真实 `IRange` 或最小 inline range)/`getRangeParam`/`couldApplyBase`/`couldApplySpecial`/`apply`/`applySpecial`;fake `IEnemy`/`IReadonlyEnemy` 暴露 `iterateSpecials`/`addSpecial`/`deleteSpecial`/`getAttribute`/`setAttribute`/`addAttribute` 等被 `EnemyView` 使用的最小表面。
逐个覆盖 `EnemyContext` 的**单个公开方法**(每个 `it` 前一行中文注释;每个方法至少一条正常用例,未知/空输入返回 null 或 no-op):`resize`、`registerAuraConverter`/`unregisterAuraConverter`/`setAuraConverterEnabled`、`registerCommonQueryEffect`/`unregisterCommonQueryEffect`、`registerSpecialQueryEffect`/`unregisterSpecialQueryEffect`、`registerFinalEffect`/`unregisterFinalEffect`、`bindHero`/`getBindedHero`、`getEnemyLocator`/`getEnemyLocatorByView`/`getEnemyByLocator`/`getEnemyByLoc`/`getViewByComputed`、`setEnemyAt`/`deleteEnemy`、`scanRange`/`iterateEnemy`、`addAura`/`deleteAura`、`attachMapDamage`/`getMapDamage`、`attachDamageSystem`/`getDamageSystem`、`markDirty`、`clear`、`destroy`。
单元级效果/光环:单个 fake aura 的 `apply`/`applySpecial` 单次施加(范围内一只怪 → 断言具体基础/特殊属性数值);`Full`(`haloRange<=0`)/ `Rect`(`haloSquare`)/ `Manhattan` 三范围**各自单独**一条,并断言范围外怪物属性不变(D-22);单个 `effect` 注册后被调用一次(单次调用,不组合)。
生命周期单元:`resize` 清空并设置 width/height/indexer width;`clear` 清空全部映射与集合;`destroy` 额外解绑 `mapDamage`/`damageSystem`、清空 converter/query/final 注册与 hero 绑定(断言 getter 返回 null/空)。
随后建立阶段覆盖表骨架(create-or-append,幂等、顺序无关):以 `.planning/phases/06-unit-tests/06-COVERAGE-MAP.md` 为目标,先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在——不存在则创建表头,已存在则保留;随后只新增/更新 06-01 小节,绝不重写他节,并写入模块归属(97/98/99/100/101/110/106/107/102/103/104/138/139/140/141 的具体用例名将在阶段 2/3 完成后补全)。
本阶段不驱动 `buildup` 整链、不测嵌套光环/四阶段顺序(留待阶段 2)。若某单方法因缺少契约(如 range `bindHost`)无法驱动,按 D-07 暂停并向用户提问,不修改核心代码。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-system/src/combat/context.test.ts</automated>
@ -129,15 +134,15 @@ Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行
<acceptance_criteria>
- `context.test.ts` 在 `pnpm exec vitest run` 下零非跳过失败。
- 每个 `it` 前有单行中文注释。
- 至少断言一条完整光环路径:fake converter 命中 → 光环入表 → `buildup` → 范围内怪物基础属性被加成。
- 覆盖 `EnemyContext` 每个公开方法至少一条单元用例;三范围各自单独断言且范围外不生效;单个 aura/effect 单次施加有数值断言;`resize`/`clear`/`destroy` 生命周期有断言。
- 未修改任何生产文件;`git status` 只显示 `*.test.ts`(新增或 MODIFIED,既有测试文件允许被修改)与规划产物(`06-COVERAGE-MAP.md` / `06-TEST-FINDINGS.md`)。
- `06-COVERAGE-MAP.md` 存在且含表头与 06-01 小节(create-or-append:若表头已由其它计划建立,则直接追加/更新本小节而不再新建)。
</acceptance_criteria>
<done>一条真实的 `EnemyContext` 光环端到端路径在提交前已跑绿,阶段 code 覆盖映射表骨架存在。</done>
<done>阶段 1(构件级)的单个方法/单个光环/单 effect 单元覆盖全部跑绿后才进入阶段 2;阶段 code 覆盖映射表骨架存在。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 2: EnemyContext 全接口、三范围、嵌套光环、警告码、四阶段顺序与两条刷新路径</name>
<name>阶段 2(组合/流水线):buildup/requestRefresh 两条刷新路径、嵌套光环、四阶段顺序、警告码</name>
<files>packages-user/data-system/src/combat/context.test.ts</files>
<read_first>
packages-user/data-system/src/combat/context.ts
@ -146,32 +151,28 @@ Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行
.planning/phases/06-unit-tests/06-PATTERNS.md(共享样板 3-4,A 节 context)
</read_first>
<action>
在 `context.test.ts` 中扩展覆盖(每个 `it` 前一行中文注释):
- 接口全覆盖(D-27/D-30):逐个覆盖 `resize`、`registerAuraConverter`/`unregisterAuraConverter`/`setAuraConverterEnabled`、`registerCommonQueryEffect`/`unregisterCommonQueryEffect`、`registerSpecialQueryEffect`/`unregisterSpecialQueryEffect`、`registerFinalEffect`/`unregisterFinalEffect`、`bindHero`/`getBindedHero`、`getEnemyLocator`/`getEnemyLocatorByView`/`getEnemyByLocator`/`getEnemyByLoc`/`getViewByComputed`、`setEnemyAt`/`deleteEnemy`、`scanRange`/`iterateEnemy`、`addAura`/`deleteAura`、`attachMapDamage`/`getMapDamage`、`attachDamageSystem`/`getDamageSystem`、`buildup`、`markDirty`、`requestRefresh`、`clear`、`destroy`。未知/空输入返回 null 或 no-op(未知 locator、未注册 view 的 `markDirty`、未绑定 converter 的 `setAuraConverterEnabled`)。
- 三范围(D-22):Full(`haloRange<=0`)、Rect(`haloSquare`)、Manhattan 各一条;每条同时断言范围外怪物属性不变。
D-43 阶段 2(组合/流水线):在阶段 1 跑绿的单元之上,测构件如何组合成流水线(每个 `it` 前一行中文注释)。
- 刷新路径(D-27):`buildup`(全量,`needUpdate` 为真)与 `requestRefresh`(局部,仅脏 view + `requestedCommonContext`);`needTotallyRefresh` 命中时 `requestRefresh` 退回全量 `buildup`。驱动完整 `buildup()` 组合链(fake converter 命中 → 光环入表 → 范围扫描 → 属性加成)。
- 嵌套光环(D-23):一层(光环→特殊属性);两层(光环施加特殊属性→该特殊属性经第二 converter 转出新光环→再施加效果);优先级边界:新转换光环优先级高于当前阶段时被跳过并告警 99;同/跨优先级传播。
- 四阶段顺序与可见性(D-25):用记录调用顺序的 fake effect/aura 断言 `buildupSpecials` → `buildupBase` → `buildupQuery` → `buildupFinal`;断言前阶段对属性的修改在后阶段可见,后阶段对属性只读不可反向影响前阶段。覆盖组合:光环基础效果 ↔ 常规查询效果(`registerCommonQueryEffect` 读到光环改过的属性)、光环特殊效果 ↔ 特殊查询效果(`registerSpecialQueryEffect` 与 `processSpecialModifier` 交互)、final-effect 阶段顺序、同/跨优先级顺序。
- 警告码(D-24):97(多个 converter 同时命中同一 special)、98(删除同级/更高优先级已生效光环,走 modify 分支与 delete 分支各一次)、99(新增高优先级光环)、100(add 与 delete 同时非空,`processSpecialModifier` 与 `refreshSpecialModifier` 各一次)、101(局部刷新期间新增/删除/修改了已转换光环)。均经 `logger.catch` 或 `vi.spyOn(logger,'warn')` 观测,绝不 `expect(...).toThrow`。
- 110:未绑定 hero 时 `buildup` 告警且不刷新。
- 四阶段顺序与可见性(D-25):用记录调用顺序的 fake effect/aura 断言 `buildupSpecials` → `buildupBase` → `buildupQuery` → `buildupFinal`;断言前阶段对属性的修改在后阶段可见,后阶段对属性只读不可反向影响前阶段。覆盖组合:光环基础效果 ↔ 常规查询效果(`registerCommonQueryEffect` 读到光环改过的属性)、光环特殊效果 ↔ 特殊查询效果(`registerSpecialQueryEffect` 与 `processSpecialModifier` 交互)、final-effect 阶段顺序、同/跨优先级顺序。
- 两条刷新路径(D-27):`buildup`(全量,`needUpdate` 为真)与 `requestRefresh`(局部,仅脏 view + `requestedCommonContext`);`needTotallyRefresh` 命中时 `requestRefresh` 退回全量 `buildup`。
- 生命周期(D-27):`resize` 清空并设置 width/height/indexer width;`clear` 清空全部映射与集合;`destroy` 额外解绑 `mapDamage`/`damageSystem`、清空 converter/query/final 注册与 hero 绑定(断言之后 getter 返回 null/空)。
全部 fixture 内联;不新建跨文件 helper。
全部 fixture 内联;不新建跨文件 helper。本阶段不依赖阶段 3 的 damage/mapDamage/combat 内容。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-system/src/combat/context.test.ts</automated>
<fails_when>运行失败,或任一警告码 97/98/99/100/101/110、三范围、嵌套、四阶段顺序断言未被观测</fails_when>
<fails_when>运行失败,或 97/98/99/100/101/110、嵌套光环、四阶段顺序、两条刷新路径断言未被观测</fails_when>
</verify>
<acceptance_criteria>
- `context.test.ts` 零非跳过失败。
- 覆盖 `EnemyContext` 全部公开方法(至少一条正常用例)与全部列出警告码。
- 三范围 + 范围外不生效、一层/两层嵌套 + 优先级边界、四阶段顺序 + 阶段间可见性、全量/局部两条刷新路径、resize/clear/destroy 生命周期均有断言。
- 一层/两层嵌套 + 优先级边界、四阶段顺序 + 阶段间可见性、全量/局部两条刷新路径、全部列出警告码均有断言。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>`EnemyContext` 全接口、光环范围/嵌套/警告码、四阶段顺序与两条刷新路径全部覆盖并跑绿。</done>
<done>阶段 2(组合/流水线)在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3;七项检查(嵌套/优先级/四阶段/刷新/警告码)齐备。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 3: DamageContext/DamageSystem(含 D-26 联动)、MapDamage、CombatFlow</name>
<name>阶段 3(完整/集成):DamageContext/DamageSystem(含 D-26 联动)、MapDamage、CombatFlow</name>
<files>packages-user/data-system/src/combat/damage.test.ts, packages-user/data-system/src/combat/mapDamage.test.ts, packages-user/data-system/src/combat/combat.test.ts</files>
<read_first>
packages-user/data-system/src/combat/damage.ts
@ -184,6 +185,7 @@ Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 damage/mapDamage/combat)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测本计划的完整/跨模块行为(伤害上下文/系统 + 地图伤害 + 战斗流程,以及光环改属性后的伤害联动)。
`damage.test.ts`:注入 fake `IEnemyContext` 与 fake `IDamageCalculator`(`calculate(handler)` 返回固定 `{damage,turn}`):
- `DamageContext.getDamageInfo`/`getDamageInfoByComputed`/`getDamageInfoByHandler` 返回结果与 handler 身份(computed 怪 + locator + 绑定 hero)。
- 缺 calculator 告警 106、缺 hero 告警 107(`logger.catch`)。
@ -213,7 +215,7 @@ Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行
- 无 fake timers;`it.skip` 用例与 `06-TEST-FINDINGS.md` 的 `#06-01-N` 一一对应。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>伤害上下文/系统、地图伤害、战斗流程覆盖并跑绿,且四阶段/联动边界与既有 `#06-01-N` 跳过用例保留。</done>
<done>阶段 3(完整/集成)在阶段 1/2 跑绿的基础上全部跑绿;伤害上下文/系统、地图伤害、战斗流程覆盖完成,且四阶段/联动边界与既有 `#06-01-N` 跳过用例保留。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
</tasks>

View File

@ -30,6 +30,7 @@ must_haves:
- "mapDamage 五视图单分支输出、converter 按 special 产生视图、reducer 求和/取最大类型/合并 extra。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-state/src/enemy/calculator.test.ts
provides: MainDamageCalculator 与 getCriticalLimit 覆盖
@ -58,11 +59,13 @@ must_haves:
---
<objective>
覆盖 `packages-user/data-state/src/enemy` 顶层战斗实现的**基本功能**(D-35):伤害计算、最终效果、比较器、光环、特殊属性注册、地图伤害。
覆盖 `packages-user/data-state/src/enemy` 顶层战斗实现的**基本功能**(D-35):伤害计算、最终效果、比较器、光环、特殊属性注册、地图伤害。按 D-43 的阶段化测试组织:阶段 1 构件级(各计算器/比较器的单分支单元)→ 阶段 2 组合/流水线(光环转换器→施加流水线与 special 注册)→ 阶段 3 完整/集成(mapDamage 视图/reducer 的单分支完整行为)。
Purpose: 这些类是接入 `CoreState` 的真实内置战斗实现;`dataClosure.test.ts` 仅覆盖单个确定的 `MainDamageCalculator` 场景。本计划只验证每个组件的单分支/单路径正确性,把跨特殊属性、跨光环/效果的**组合**留到 06-07。
Purpose: 这些类是接入 `CoreState` 的真实内置战斗实现;`dataClosure.test.ts` 仅覆盖单个确定的 `MainDamageCalculator` 场景。本计划只验证每个组件的单分支/单路径正确性,把跨特殊属性、跨光环/效果的**组合**留到 06-07。组件从未被测过,故按阶段逐层定位(D-43)。
Output: 6 个同目录 `*.test.ts`。
阶段结构(D-43):每阶段跑绿后再进入下一阶段;组合语义后移至 06-07(D-35)。
</objective>
<execution_context>
@ -96,12 +99,13 @@ Output: 6 个同目录 `*.test.ts`。
- 本计划只做单分支基本功能;`saveState`/`loadState` 与组合语义不测(D-32 / D-35)。
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-02 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
<task type="auto">
<name>Task 1: MainDamageCalculator 单分支 + getCriticalLimit + MainEnemyFinalEffect + MainEnemyComparer</name>
<name>阶段 1(构件级):MainDamageCalculator 单分支 + getCriticalLimit + MainEnemyFinalEffect + MainEnemyComparer</name>
<files>packages-user/data-state/src/enemy/calculator.test.ts, packages-user/data-state/src/enemy/final.test.ts, packages-user/data-state/src/enemy/comparer.test.ts</files>
<read_first>
packages-user/data-state/src/enemy/calculator.ts
@ -112,6 +116,7 @@ Output: 6 个同目录 `*.test.ts`。
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 calculator/final/comparer)
</read_first>
<action>
D-43 阶段 1(构件级):只测单个类/方法的最小单元(各分支独立),不做组合。
`calculator.test.ts`:建立一个 inline handler 工厂(enemy/context/locator/hero/state),对 `MainDamageCalculator.calculate` 的每个分支单独断言精确 `{damage,turn}`(基本功能,各分支独立):
- 基线:heroPerDamage = atk - monDef;turn = ceil(monHp / heroPerDamage)。
- 无敌(20):默认 `{damage: Infinity, turn: 0}`;当 `state.hero.items.getItemState('cross')?.count >= 1` 时走普通计算(fake `state.hero.items.getItemState`)。
@ -135,11 +140,11 @@ Output: 6 个同目录 `*.test.ts`。
- calculator.test.ts 断言每个列出分支与 warn 137;final.test.ts 与 comparer.test.ts 断言其文档分支。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>伤害计算、最终效果、比较器的单分支基本功能覆盖并跑绿。</done>
<done>阶段 1(构件级)的伤害计算、最终效果、比较器单分支单元覆盖全部跑绿后才进入阶段 2;每分支期望值有断言。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 2: CommonAura/GuardAura 与 registerSpecials</name>
<name>阶段 2(组合/流水线):CommonAura/GuardAura 转换器→施加流水线与 registerSpecials 注册</name>
<files>packages-user/data-state/src/enemy/aura.test.ts, packages-user/data-state/src/enemy/special.test.ts</files>
<read_first>
packages-user/data-state/src/enemy/aura.ts
@ -149,6 +154,7 @@ Output: 6 个同目录 `*.test.ts`。
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 aura/special)
</read_first>
<action>
D-43 阶段 2(组合/流水线):在阶段 1 跑绿的单元之上,测转换器→光环施加与 special 注册如何组合(顶层多属性组合语义仍留 06-07,D-35)。
`aura.test.ts`:用 inline 最小 `ISpecial` 与 fake enemy/context/locator:
- `CommonAuraConverter.shouldConvert` 仅对 code 25 为真;`CommonAura.createRange`/`getRangeParam`:`haloRange <= 0` 用 Full(param 为 undefined)、`haloSquare` 用以 locator 为中心的 rect、否则 Manhattan。
- `CommonAura.apply` 对 computed enemy 加 `floor(base * hpBuff/100)`、`floor(base * atkBuff/100)`、`floor(base * defBuff/100)`,buff 为 0 时不改;`couldApplyBase` 为 true、`couldApplySpecial` 为 false、`priority` 25。
@ -166,11 +172,11 @@ Output: 6 个同目录 `*.test.ts`。
- special.test.ts 断言 code 0-27 各注册一次 + guard 默认值 + 名称/描述抽查。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>光环转换与特殊属性注册的单分支基本功能覆盖并跑绿。</done>
<done>阶段 2(组合/流水线)的光环转换器→施加与 special 注册在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 3: mapDamage 五视图 + converter + reducer(单分支)</name>
<name>阶段 3(完整/集成):mapDamage 五视图 + converter + reducer(单分支完整行为)</name>
<files>packages-user/data-state/src/enemy/mapDamage.test.ts</files>
<read_first>
packages-user/data-state/src/enemy/mapDamage.ts
@ -180,6 +186,7 @@ Output: 6 个同目录 `*.test.ts`。
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 mapDamage)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测本计划最大跨构件行为——mapDamage 视图族经 converter 组装并由 reducer 汇总的完整单分支路径(跨特殊属性组合语义仍留 06-07,D-35)。
构建 inline fake `IEnemyContext`(含 `indexer`/`getEnemyByLoc`/`getDamageSystem`)与 inline `ISpecial`,逐个单分支断言:
- `ZoneDamageView`:range 按 `zoneSquare` 为 Manhattan 或 rect;`getRangeParam` 与 range/居中一致;`getDamageWithoutCheck` 返回 `{damage: zone, type: MapDamageType.Zone, extra:{catch, repulse}}`。
- `RepulseDamageView`:Manhattan 半径 1;来源 locator 返回 null;否则 type `Repulse` 且 `repulse` 含来源 locator。
@ -199,7 +206,7 @@ Output: 6 个同目录 `*.test.ts`。
- 五视图单分支、converter 的每 special 视图集合、reducer 的求和/类型/extra 合并均有断言。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>地图伤害视图、converter、reducer 的单分支基本功能覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(code 137 → calculator.ts)写入 `06-COVERAGE-MAP.md` 的 06-02 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的地图伤害视图、converter、reducer 单分支完整行为在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(code 137 → calculator.ts)写入 `06-COVERAGE-MAP.md` 的 06-02 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -13,7 +13,7 @@ requirements: [TEST-01]
estimate:
tokens: 48000
raw_tokens: 48000
tasks: 2
tasks: 3
confidence: low
must_haves:
truths:
@ -26,6 +26,7 @@ must_haves:
- "`EnemyManager` 注册表(special/attribute 默认值)、prefab(按 code/id 的增删改查与 clone 独立性)、reuse 映射、`modifyPrefabAttribute`、comparer/脏跟踪生命周期行为与文档一致。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-base/src/enemy/enemy.test.ts
provides: Enemy 模型覆盖
@ -48,11 +49,13 @@ must_haves:
---
<objective>
完整覆盖 Layer-1 怪物数据模型 `packages-user/data-base/src/enemy`(D-14/D-36):`Enemy`、special 实现、`EnemyManager` 公开接口(除 legacy)。
完整覆盖 Layer-1 怪物数据模型 `packages-user/data-base/src/enemy`(D-14/D-36):`Enemy`、special 实现、`EnemyManager` 公开接口(除 legacy)。按 D-43 的阶段化测试组织:阶段 1 构件级(`Enemy` 属性单元 + special 类单方法单元)→ 阶段 2 组合/流水线(`Enemy` special CRUD + clone/deepEquals 独立性)→ 阶段 3 完整/集成(`EnemyManager` 注册表/prefab/reuse/comparer 跨对象行为)。
Purpose: D-14 要求验证数据模型本身(属性/special 增删改查、注册表、模板、复用映射、比较器/脏跟踪),而不只是消费它的战斗计算器。存读档集中到 06-09。
Purpose: D-14 要求验证数据模型本身(属性/special 增删改查、注册表、模板、复用映射、比较器/脏跟踪),而不只是消费它的战斗计算器。存读档集中到 06-09。数据模型从未被测过,故按阶段逐层定位(D-43)。
Output: 3 个同目录 `*.test.ts`。
阶段结构(D-43):每阶段跑绿后再进入下一阶段。
</objective>
<execution_context>
@ -82,12 +85,13 @@ Output: 3 个同目录 `*.test.ts`。
- 不测存读档(D-32);排除一切 legacy 接口/方法(D-30)。
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-03 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
<task type="auto">
<name>Task 1: Enemy 模型 + special 实现</name>
<name>阶段 1(构件级):Enemy 属性单元 + special 类单方法单元</name>
<files>packages-user/data-base/src/enemy/enemy.test.ts, packages-user/data-base/src/enemy/special.test.ts</files>
<read_first>
packages-user/data-base/src/enemy/enemy.ts
@ -97,32 +101,65 @@ Output: 3 个同目录 `*.test.ts`。
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 enemy/special)
</read_first>
<action>
`enemy.test.ts`:用 inline `IEnemyAttr` 形状对象构造 `Enemy`,覆盖:
- `getAttribute`/`setAttribute`/`addAttribute` 对数值键;`cloneAttributes` 返回结构化克隆、非别名副本。
- special CRUD:`addSpecial` 后 `getSpecial`/`hasSpecial`/`iterateSpecials`;重复 code 告警 96 且不替换既有 special;`deleteSpecial` 按对象与按 code 删除;未知 code 返回 null。
- `clone`/`copyFrom`:clone 复制属性与克隆后的 special,且与来源独立;`copyFrom` 替换属性与 special。
- 不测 `saveState`/`loadState`(D-32)。
D-43 阶段 1(构件级):只测单个类/方法的**最小单元**,不做 special CRUD 组合与克隆语义。
`enemy.test.ts`:用 inline `IEnemyAttr` 形状对象构造 `Enemy`,覆盖属性单元:
- `getAttribute`/`setAttribute`/`addAttribute` 对数值键的单个行为;各键相互独立。
- `cloneAttributes` 返回结构化克隆、非别名副本。
不测 `saveState`/`loadState`(D-32)。
`special.test.ts`:用 `defineCommonSerializableSpecial`/`defineNonePropertySpecial` + inline 配置覆盖:
- `CommonSerializableSpecial`:`setValue`/`getValue`、`getSpecialName`、`getDescription`、clone 独立性、`deepEqualsTo` 对同 code+同值为 true、对 code 或 value 不同为 false。
- `NonePropertySpecial`:value 为 undefined、clone、`deepEqualsTo` 按 code 判定。
`special.test.ts`:用 `defineCommonSerializableSpecial`/`defineNonePropertySpecial` + inline 配置,逐个覆盖两个 special 类的**单方法单元**:
- `CommonSerializableSpecial`:`setValue`/`getValue`、`getSpecialName`、`getDescription` 各自单独行为。
- `NonePropertySpecial`:value 为 undefined、名称/描述。
- 不调用任何 legacy 命名接口(如 `fromLegacyEnemy`),不测其 `saveState`/`loadState`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/enemy/enemy.test.ts packages-user/data-base/src/enemy/special.test.ts</automated>
<fails_when>聚焦运行失败,或属性/单方法单元断言缺失或与文档不符</fails_when>
</verify>
<acceptance_criteria>
- 两个文件零非跳过失败。
- enemy.test.ts 断言属性单键读写与 `cloneAttributes` 非别名。
- special.test.ts 断言两个 special 类的单方法单元(除 legacy 与存读档)。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>阶段 1(构件级)的 Enemy 属性单元与 special 单方法单元覆盖全部跑绿后才进入阶段 2。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>阶段 2(组合/流水线):Enemy special CRUD 组合 + clone/copyFrom 与 deepEquals 独立性</name>
<files>packages-user/data-base/src/enemy/enemy.test.ts, packages-user/data-base/src/enemy/special.test.ts</files>
<read_first>
packages-user/data-base/src/enemy/enemy.ts
packages-user/data-base/src/enemy/special.ts
packages-user/data-base/src/enemy/types.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 enemy/special)
</read_first>
<action>
D-43 阶段 2(组合/流水线):在阶段 1 跑绿的单元之上,测 `Enemy` 与 special 的组合/克隆独立语义。
`enemy.test.ts`:special CRUD 组合——
- `addSpecial` 后 `getSpecial`/`hasSpecial`/`iterateSpecials`;重复 code 告警 96 且不替换既有 special。
- `deleteSpecial` 按对象与按 code 删除;未知 code 返回 null。
- `clone`/`copyFrom`:clone 复制属性与克隆后的 special,且与来源独立;`copyFrom` 替换属性与 special。
`special.test.ts`:组合/独立性——
- clone 独立性(改副本不影响来源);`deepEqualsTo` 对同 code+同值为 true,对 code 或 value 不同为 false。
- `NonePropertySpecial` 的 clone、`deepEqualsTo` 按 code 判定。
不测 `saveState`/`loadState`(D-32)。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/enemy/enemy.test.ts packages-user/data-base/src/enemy/special.test.ts</automated>
<fails_when>聚焦运行失败,或重复 special 的 warn 96 未被观测,或 clone 独立性断言失败</fails_when>
</verify>
<acceptance_criteria>
- 两个文件零非跳过失败。
- enemy.test.ts 断言属性/special CRUD、clone/copyFrom 独立性、warn 96。
- special.test.ts 断言两个 special 类的全部公开方法(除 legacy 与存读档)。
- enemy.test.ts 断言 special CRUD 组合与 clone/copyFrom 独立性、warn 96。
- special.test.ts 断言 clone 独立性与 `deepEqualsTo` 判定。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>Enemy 与 special 数据模型覆盖并跑绿。</done>
<done>阶段 2(组合/流水线)的 special CRUD 与克隆/`deepEqualsTo` 独立性在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 2: EnemyManager 注册表、prefab、reuse、脏跟踪</name>
<name>阶段 3(完整/集成):EnemyManager 注册表、prefab、reuse、脏跟踪</name>
<files>packages-user/data-base/src/enemy/manager.test.ts</files>
<read_first>
packages-user/data-base/src/enemy/manager.ts
@ -131,6 +168,7 @@ Output: 3 个同目录 `*.test.ts`。
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 manager)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测 `EnemyManager` 的跨对象完整行为(注册表/prefab/reuse/comparer 脏跟踪)。
用 inline fake `IEnemyLegacyBridge`(其 `fromLegacyEnemy` 返回合成属性)与 inline `Enemy` prefab 驱动 `EnemyManager`,按 tileStore 契约形状覆盖:
- `registerSpecial` 允许覆盖(同 code 后注册者胜);`setAttributeDefaults` 设置默认值,对 function/symbol/bigint/undefined 经 `logger.catch` 观测 `logger.error(53)`。
- `addPrefab` clone 且忽略重复 code 或 id;`createEnemy(code)`/`createEnemyById(id)` 返回独立 clone、未知返回 null;`getPrefab`/`getPrefabById`;`deletePrefab` 按 code 与按 id 删除两个索引;`changePrefab` 替换并标脏。
@ -149,7 +187,7 @@ Output: 3 个同目录 `*.test.ts`。
- 断言 prefab create/get/delete/change、reuse 映射、modify、comparer 脏跟踪生命周期,以及 warn 53/117/118。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>EnemyManager 注册表、prefab、reuse、脏跟踪覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(53/96/117/118)写入 `06-COVERAGE-MAP.md` 的 06-03 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的 EnemyManager 注册表、prefab、reuse、脏跟踪在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(53/96/117/118)写入 `06-COVERAGE-MAP.md` 的 06-03 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -27,6 +27,7 @@ must_haves:
- "录像系统**完整播放**与**二次录制比对**、`error 2001–2008` 归 06-07,本计划不做(D-40)。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-common/src/replay/array.test.ts
provides: ReplayArray 全操作 + 编解码覆盖
@ -51,11 +52,13 @@ must_haves:
---
<objective>
重点覆盖录像数组 `ReplayArray` 全部公开操作与编解码,并覆盖 `ReplaySystem` 注册/record、`ReplaySandbox` 播放器与播放安全装饰器(D-16 / D-37)。
重点覆盖录像数组 `ReplayArray` 全部公开操作与编解码,并覆盖 `ReplaySystem` 注册/record、`ReplaySandbox` 播放器与播放安全装饰器(D-16 / D-37)。按 D-43 的阶段化测试组织:阶段 1 构件级(单类型参数编解码 + 单数组操作)→ 阶段 2 组合/流水线(数组操作组合/读流/扩容 + 安全装饰器组合)→ 阶段 3 完整/集成(注册/record → 沙箱播放的完整流水线)。
Purpose: `array.ts`(825 行)此前无专门测试,其流式/扩容 API 仅被间接使用;本计划经公开 `IReplayArray` 表面系统验证读、编解码与播放控制。录像存读档集中到 06-09,完整播放/二次录制比对归 06-07。
Purpose: `array.ts`(825 行)此前无专门测试,其流式/扩容 API 仅被间接使用;本计划经公开 `IReplayArray` 表面系统验证读、编解码与播放控制。录像存读档集中到 06-09,完整播放/二次录制比对归 06-07。录像系统从未被测过,故按阶段逐层定位(D-43)。
Output: 4 个同目录 `*.test.ts`。
阶段结构(D-43):每阶段跑绿后再进入下一阶段。
</objective>
<execution_context>
@ -89,12 +92,13 @@ Output: 4 个同目录 `*.test.ts`。
- 不测 `ReplayArray` 存读档(D-32);不做完整播放/二次录制(D-40)。
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-04 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
<task type="auto">
<name>Task 1: ReplayArray 全公开操作 + 编解码(boolean/整数全位宽/bigint/string/数组)</name>
<name>阶段 1(构件级):单个参数编解码 + 单个数组操作单次读回</name>
<files>packages-user/data-common/src/replay/array.test.ts</files>
<read_first>
packages-user/data-common/src/replay/array.ts
@ -103,28 +107,65 @@ Output: 4 个同目录 `*.test.ts`。
.planning/phases/06-unit-tests/06-PATTERNS.md(B 节 array)
</read_first>
<action>
D-43 阶段 1(构件级):只测最小单元——每种参数类型单个编解码 + 单个数组操作的单次读回,不做多操作组合、扩容或读流流水线。
用与 `system.ts` 初始化一致的极小 capacity/width 构造 `ReplayArray`,经公开 `IReplayArray` 表面覆盖:
- `add`/`insert`/`delete`/`set` 的命令与参数读写;`get(index)` 返回步骤 handler;`createReadStream(0)` 顺序读回每步;`rebuildIndexArray`、`setReplayArray`、`getCommandArray`、`getParamArray` 的行为与文档一致。
- `setCommandWidth` 切换位宽后读回正确;超过 uint8 的命令触发码 154(`logger.catch`)。
- 编解码全覆盖(D-37):boolean(码 151:非法值告警、合法值读回 type/value)、整数(每个可用位宽各一条,含边界值)、bigint(码 152:越界告警、边界内读回)、string、数组参数(含码 153:参数超过 255 时溢出忽略);未知 param 类型触发码 148。
- 扩容:成倍扩容(合法倍率)后仍读回;非法倍率触发码 149;达到上限触发码 150;读流过期触发码 155。
- 单个数组操作:`add`/`insert`/`delete`/`set`/`get(index)` 各一条单次读写断言(`get(index)` 返回步骤 handler)。
- 编解码单类型(D-37):boolean(码 151:非法值告警、合法值读回 type/value)、整数(每个可用位宽各一条,含边界值)、bigint(码 152:越界告警、边界内读回)、string、数组参数(含码 153:参数超过 255 时溢出忽略);未知 param 类型触发码 148。
数组保持极小;不测 `saveState`/`loadState`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts</automated>
<fails_when>运行失败,或 148/149/150/151/152/153/154/155 中任一码未被观测,或某类型编解码读回不一致</fails_when>
<fails_when>运行失败,或 148/151/152/153 中任一码未被观测,或某类型编解码读回不一致</fails_when>
</verify>
<acceptance_criteria>
- `array.test.ts` 零非跳过失败。
- 覆盖全部列出公开操作;boolean/整数(全位宽)/bigint/string/数组参数均至少一条读回断言。
- 断言码 148、149、150、151、152、153、154、155。
- `add`/`insert`/`delete`/`set`/`get` 各有单次断言;boolean/整数(全位宽)/bigint/string/数组参数均至少一条读回断言。
- 断言码 148、151、152、153。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>ReplayArray 全操作与全类型编解码覆盖并跑绿。</done>
<done>阶段 1(构件级)的单类型编解码与单数组操作覆盖全部跑绿后才进入阶段 2。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 2: ReplaySystem 注册/record + ReplaySandbox 播放器</name>
<name>阶段 2(组合/流水线):数组操作组合与读流/扩容 + 播放安全装饰器组合</name>
<files>packages-user/data-common/src/replay/array.test.ts, packages-user/data-common/src/replay/func.test.ts</files>
<read_first>
packages-user/data-common/src/replay/array.ts
packages-user/data-common/src/replay/func.ts
packages-user/data-common/src/replay/types.ts
packages-user/data-state/test/nodeReplay.test.ts
packages-user/data-state/src/replay/commands.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(B 节 array/func)
</read_first>
<action>
D-43 阶段 2(组合/流水线):在阶段 1 跑绿的单元之上,测数组操作之间的组合与读流流水线,以及安全装饰器的组合生命周期。
`array.test.ts`:
- 组合读流:`createReadStream(0)` 顺序读回多步;`rebuildIndexArray`、`setReplayArray`、`getCommandArray`、`getParamArray` 的相互作用与文档一致。
- `setCommandWidth` 切换位宽后再读回;超过 uint8 的命令触发码 154(`logger.catch`)。
- 扩容组合:成倍扩容(合法倍率)后仍读回;非法倍率触发码 149;达到上限触发码 150;读流过期触发码 155。
`func.test.ts`:覆盖 `func.ts` 播放安全装饰器的组合生命周期:`beginReplaySafetyCollection`、`endReplaySafetyCollection`、`logReplaySafetyDetail`(及内部 detail 查询)、`shouldReplay`、`ignoreReplay`:
- begin → 嵌套 decorated 调用 → end 的收集与简化详情;
- 在已有收集运行时再次 begin 触发码 159;
- 收集外调用 end 触发码 160;
- 检测命中时 end/简化详情触发码 161;
- 查询不存在的 code 详情触发码 162。
用最小 inline `IReplaySystem` 与一个被装饰的 inline 类/方法;仅在源码实际写输出处 `vi.spyOn(console/logger)`。无 fake timers。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts packages-user/data-common/src/replay/func.test.ts</automated>
<fails_when>运行失败,或 149/150/154/155/159/160/161/162 中任一码未被观测,或组合读回/装饰器生命周期断言失败</fails_when>
</verify>
<acceptance_criteria>
- 两个文件零非跳过失败。
- array 断言 `createReadStream`/`rebuildIndexArray`/`setReplayArray`/`getCommandArray`/`getParamArray`/`setCommandWidth` 组合与扩容,码 149/150/154/155。
- func 断言装饰器组合生命周期(begin/nested/end/detail)与码 159/160/161/162。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>阶段 2(组合/流水线)的数组操作组合/读流/扩容与安全装饰器在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>阶段 3(完整/集成):ReplaySystem 注册/record + ReplaySandbox 播放器</name>
<files>packages-user/data-common/src/replay/system.test.ts, packages-user/data-common/src/replay/sandbox.test.ts</files>
<read_first>
packages-user/data-common/src/replay/system.ts
@ -134,6 +175,7 @@ Output: 4 个同目录 `*.test.ts`。
.planning/phases/06-unit-tests/06-PATTERNS.md(B 节 system/sandbox)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测注册→录制→沙箱播放的完整流水线。
`system.test.ts`:构造 `ReplaySystem` 断言:构造出带配置长度/宽度的 `route`;`registerCommand` 后 `getCommand` 返回命令、未知返回 null;重复注册告警 163 且保留原命令;`record(code, ...params)` 追加到 `route` 并触发 `onRecordCommand`(`(code, route.length, params)`);`createReplaySandbox({route,reseter,startIndex?})` 调 `reseter.reset(config.save)`、保存 sandbox、触发 `onCreateSandbox`;`releaseSandbox()` 停止并清空。断言每个 `ReplaySystem` 实例的注册与 route 相互独立。
`sandbox.test.ts`:用 inline 命令驱动 sandbox(按 `commands.test.ts` cast 到 manual sandbox 接口):
@ -153,37 +195,7 @@ Output: 4 个同目录 `*.test.ts`。
- sandbox.test.ts 断言顺序步进、失败即停、156/157/158/175、play/pause/resume/stop、setSpeed。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>ReplaySystem 与 ReplaySandbox 播放器覆盖并跑绿。</done>
</task>
<task type="auto">
<name>Task 3: 播放安全装饰器/工具(func.ts)</name>
<files>packages-user/data-common/src/replay/func.test.ts</files>
<read_first>
packages-user/data-common/src/replay/func.ts
packages-user/data-common/src/replay/types.ts
packages-user/data-state/src/replay/commands.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(B 节 func)
</read_first>
<action>
覆盖 `func.ts` 的播放安全装饰器生命周期:`beginReplaySafetyCollection`、`endReplaySafetyCollection`、`logReplaySafetyDetail`(及内部 detail 查询)、`shouldReplay`、`ignoreReplay`:
- begin → 嵌套 decorated 调用 → end 的收集与简化详情;
- 在已有收集运行时再次 begin 触发码 159;
- 收集外调用 end 触发码 160;
- 检测命中时 end/简化详情触发码 161;
- 查询不存在的 code 详情触发码 162。
用最小 inline `IReplaySystem` 与一个被装饰的 inline 类/方法;仅在源码实际写输出处 `vi.spyOn(console/logger)`。无 fake timers。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-common/src/replay/func.test.ts</automated>
<fails_when>运行失败,或 159/160/161/162 中任一码未被观测</fails_when>
</verify>
<acceptance_criteria>
- `func.test.ts` 零非跳过失败。
- 断言装饰器生命周期(begin/nested/end/detail)与码 159/160/161/162。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>播放安全装饰器/工具覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(148–163、175)写入 `06-COVERAGE-MAP.md` 的 06-04 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的 ReplaySystem 与 ReplaySandbox 播放器在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(148–163、175)写入 `06-COVERAGE-MAP.md` 的 06-04 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -32,6 +32,7 @@ must_haves:
- "测试形态(D-01):勇士各子系统属纯逻辑/数据模块,直接单测(fake / 显式依赖注入,延续 Phase 3 D-03);涉及多系统协作的经 `CoreState` 集成路径由 06-07/06-09 承担。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-base/src/hero/attribute.test.ts
provides: HeroAttribute 覆盖
@ -69,11 +70,13 @@ must_haves:
---
<objective>
验证 `packages-user/data-base/src/hero` 下**每个勇士子系统**可正确运行(D-17 / D-38):attribute、modifier、location、state、equipment、equipStore、items、follower(异步)、mover(异步)、rendering。
验证 `packages-user/data-base/src/hero` 下**每个勇士子系统**可正确运行(D-17 / D-38):attribute、modifier、location、state、equipment、equipStore、items、follower(异步)、mover(异步)、rendering。按 D-43 的阶段化测试组织:阶段 1 构件级(attribute + modifier 公式单元)→ 阶段 2 组合/流水线(location/state 装配 + equipment/equipStore/items 联动 + follower 链接)→ 阶段 3 完整/集成(异步 mover + rendering)。
Purpose: 原 06-05 与 06-06 合并为本计划;新增 `rendering` 与异步 `mover`。存读档集中到 06-09。
Purpose: 原 06-05 与 06-06 合并为本计划;新增 `rendering` 与异步 `mover`。存读档集中到 06-09。勇士子系统从未被测过,故按阶段逐层定位(D-43)。
Output: 10 个同目录 `*.test.ts`(按子系统分三组:Task 1 属性/位置/状态 4 文件、Task 2 装备/物品 3 文件、Task 3 异步 follower/mover/rendering 3 文件)。
Output: 10 个同目录 `*.test.ts`(阶段 1:attribute/modifier 2 文件;阶段 2:location/state/equipment/equipStore/items/follower 6 文件;阶段 3:mover/rendering 2 文件)。
阶段结构(D-43):每阶段跑绿后再进入下一阶段;文件合并依据 D-28,不拆分为更多计划。
</objective>
<execution_context>
@ -109,26 +112,26 @@ Output: 10 个同目录 `*.test.ts`(按子系统分三组:Task 1 属性/位
- 警告路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-05-N`;`pnpm test:ci` 保持全绿。
- 不测存读档(D-32)。
- 10 个文件按子系统分三组、一组一任务(Task 1 属性/位置/状态 4 文件;Task 2 装备/物品 3 文件;Task 3 异步 follower/mover/rendering 3 文件),文件合并依据 D-28,不拆分为更多计划。
- 10 个文件按 D-43 阶段分三组(阶段 1:attribute/modifier 2 文件;阶段 2:location/state/equipment/equipStore/items/follower 6 文件;阶段 3:mover/rendering 2 文件),文件合并依据 D-28,不拆分为更多计划。
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-05 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
<task type="auto">
<name>Task 1: HeroAttribute + modifier + HeroLocation + HeroState</name>
<files>packages-user/data-base/src/hero/attribute.test.ts, packages-user/data-base/src/hero/modifier.test.ts, packages-user/data-base/src/hero/location.test.ts, packages-user/data-base/src/hero/state.test.ts</files>
<name>阶段 1(构件级):HeroAttribute 单元 + ValueModifier/PercentageModifier 公式单元</name>
<files>packages-user/data-base/src/hero/attribute.test.ts, packages-user/data-base/src/hero/modifier.test.ts</files>
<read_first>
packages-user/data-base/src/hero/attribute.ts
packages-user/data-base/src/hero/modifier.ts
packages-user/data-base/src/hero/location.ts
packages-user/data-base/src/hero/state.ts
packages-user/data-base/src/hero/types.ts
packages-user/data-state/src/event/event.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(C 节 attribute/modifier/location/state)
.planning/phases/06-unit-tests/06-PATTERNS.md(C 节 attribute/modifier)
</read_first>
<action>
D-43 阶段 1(构件级):只测最小构件——单个类/公式单元,不装配子系统。
`attribute.test.ts`:inline 数值属性对象:
- `getBaseAttribute` vs `getFinalAttribute`:base 经 `set`/`add`/`mul`/`div` 变化,final 反映已注册 modifier。
- `addModifier` 按 priority 降序排序并重算 final;已被别的属性拥有的 modifier 告警 108 且忽略;`getModifiers`/`getModifierIndex`/`iterateModifiers`;`deleteModifier`/`deleteModifierByIndex`。
@ -138,86 +141,91 @@ Output: 10 个同目录 `*.test.ts`(按子系统分三组:Task 1 属性/位
- DEV 下 modifier 返回同对象引用告警 109(`logger.catch`)。
`modifier.test.ts`:`ValueModifier.modify(value)` = `value + this.value`;`PercentageModifier.modify(value, base)` = `value + base * this.value`;默认 priority(0 与 10);`type` 字符串;`setValue` 经绑定属性重算。不测 `saveState`/`loadState`。
`location.test.ts`:inline fake `IDataCommon` + `Dir8FaceHandler` 构造 `HeroLocation`:初始 `x`/`y`/`floorId`、`getCurrentFaceDirection` 与 locator 方向一致;`setFloor`/`setPos`;`onSetPos`/`onSetFloor` 钩子携带新值。不测 `saveState`/`loadState`。
`state.test.ts`:inline fake `IDataCommon` + `Dir8FaceHandler` 构造 `HeroState`:构造器装配 `location`/`rendering`/`followers`/`items`/`equip` 并停在默认 locator;`getModifiableAttribute`/`getAttribute`/`getIsolatedAttribute` 分别返回共享属性、只读视图、独立克隆;`attachAttribute` 替换;`registerModifier` + `createModifier`/`createAndInsertModifier` 对已注册类型创建插入、未知类型告警 116 并返回 null;`changeFloor(info)` 设置朝向/移动方向/位置/楼层并按顺序触发 before/after 钩子。不测存读档。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/hero/attribute.test.ts packages-user/data-base/src/hero/modifier.test.ts packages-user/data-base/src/hero/location.test.ts packages-user/data-base/src/hero/state.test.ts</automated>
<fails_when>任一聚焦运行失败,或 108/109/116 未被观测,或钩子顺序断言失败</fails_when>
<automated>pnpm exec vitest run packages-user/data-base/src/hero/attribute.test.ts packages-user/data-base/src/hero/modifier.test.ts</automated>
<fails_when>任一聚焦运行失败,或 108/109 未被观测,或两条 modifier 公式断言失败</fails_when>
</verify>
<acceptance_criteria>
- 四个文件零非跳过失败。
- attribute 断言 base/final、优先级、存盘开关、clone/structured 与码 108/109;modifier 断言两条公式与 priority;location 断言 getter/setter/钩子;state 断言装配/属性视图/码 116/changeFloor 钩子顺序。
- 两个文件零非跳过失败。
- attribute 断言 base/final、优先级、存盘开关、clone/structured 与码 108/109;modifier 断言两条公式与 priority。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>属性、modifier、位置、勇士状态覆盖并跑绿。</done>
<done>阶段 1(构件级)的属性与 modifier 公式单元覆盖全部跑绿后才进入阶段 2。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 2: HeroEquipment + EquipmentState/HeroEquipsStore + HeroItems</name>
<files>packages-user/data-base/src/hero/equipment.test.ts, packages-user/data-base/src/hero/equipStore.test.ts, packages-user/data-base/src/hero/items.test.ts</files>
<name>阶段 2(组合/流水线):location/state 装配 + equipment/equipStore/items 联动 + follower 链接</name>
<files>packages-user/data-base/src/hero/location.test.ts, packages-user/data-base/src/hero/state.test.ts, packages-user/data-base/src/hero/equipment.test.ts, packages-user/data-base/src/hero/equipStore.test.ts, packages-user/data-base/src/hero/items.test.ts, packages-user/data-base/src/hero/follower.test.ts</files>
<read_first>
packages-user/data-base/src/hero/location.ts
packages-user/data-base/src/hero/state.ts
packages-user/data-base/src/hero/equipment.ts
packages-user/data-base/src/hero/equipStore.ts
packages-user/data-base/src/hero/items.ts
packages-user/data-base/src/hero/follower.ts
packages-user/data-base/src/hero/types.ts
packages-user/data-state/src/replay/commands.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(C 节 equipment/equipStore/items)
.planning/phases/06-unit-tests/06-PATTERNS.md(C 节 location/state/equipment/equipStore/items/follower)
</read_first>
<action>
D-43 阶段 2(组合/流水线):在阶段 1 跑绿后,测各子系统如何组合——state 装配子系统、equipment/items 经 modifier 与 equipStore 联动、follower 链接(异步 gather 用真实计时器)。
`location.test.ts`:inline fake `IDataCommon` + `Dir8FaceHandler` 构造 `HeroLocation`:初始 `x`/`y`/`floorId`、`getCurrentFaceDirection` 与 locator 方向一致;`setFloor`/`setPos`;`onSetPos`/`onSetFloor` 钩子携带新值。不测 `saveState`/`loadState`。
`state.test.ts`:inline fake `IDataCommon` + `Dir8FaceHandler` 构造 `HeroState`:构造器装配 `location`/`rendering`/`followers`/`items`/`equip` 并停在默认 locator;`getModifiableAttribute`/`getAttribute`/`getIsolatedAttribute` 分别返回共享属性、只读视图、独立克隆;`attachAttribute` 替换;`registerModifier` + `createModifier`/`createAndInsertModifier` 对已注册类型创建插入、未知类型告警 116 并返回 null;`changeFloor(info)` 设置朝向/移动方向/位置/楼层并按顺序触发 before/after 钩子。不测存读档。
构造 inline fake `IDataCommon`(`tileStore.num`/`idToNumber`、`itemStore.getData` 返回合成装备/物品,装备含 `equip.value`/`equip.percentage` 映射与 `equip.slots`)+ inline `HeroAttribute`。
`equipment.test.ts`:`setSlots`;`canEquipTo` 对数值/字符串槽返回 `CanEquip`/`NeedReplace`/`CannotEquip`;`equip` 到空槽返回 undefined 并应用 modifier 使 final 属性变化;覆盖占用槽返回旧 uid 并替换 modifier;同 uid 槽位变化且 `autoUnload` 时移动;`unequip` 返回被移除 uid 并移除 modifier、空槽返回 undefined;`equipped`/`getEquipped`/`getEquips`;`compareEquip` 逐属性 diff;未知 uid 告警 146、无可用槽告警 147(`logger.catch`)。不测 `saveState`/`loadState`。
`equipStore.test.ts`:`EquipmentState` 的公开方法与 `HeroEquipsStore.add`(返回 uid、未知 tile 返回 -1)/`get`/`count`/`delete`/`useSorter`/`instancesOf`/`instances`(按 sorter 排序、回退 uid 升序)。不测 `saveState`/`loadState`(58/59 归 06-09)。
`items.test.ts`:`itemCount`/`getItemState`/`addItem`/`getItem`/`useItem`:常量物品计数增减并在 <=0 删除;装备类路由到 equip store;Pick 类按 count 调 `effect.useEffect`;未知 tile/item no-op;`useItem` 缺物品或 `canUse` 为 false 返回 false、消耗类减并删、常量类保留、装备类返回 false。不测 `saveState`/`loadState`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/hero/equipStore.test.ts packages-user/data-base/src/hero/items.test.ts</automated>
<fails_when>运行失败,或 modifier 应用/移除断言、或 146/147 未被观测</fails_when>
</verify>
<acceptance_criteria>
- 三个文件零非跳过失败。
- equipment 断言 equip/unequip/replace、modifier 应用、compareEquip、码 146/147;equipStore 断言实例增删计数排序;items 断言 add/use/count/路由。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>装备、装备实例库、物品覆盖并跑绿。</done>
</task>
<task type="auto">
<name>Task 3: followers(异步)+ HeroMover(异步)+ HeroRendering</name>
<files>packages-user/data-base/src/hero/follower.test.ts, packages-user/data-base/src/hero/mover.test.ts, packages-user/data-base/src/hero/rendering.test.ts</files>
<read_first>
packages-user/data-base/src/hero/follower.ts
packages-user/data-base/src/hero/mover.ts
packages-user/data-base/src/hero/rendering.ts
packages-user/data-base/src/hero/types.ts
packages-user/data-common/src/common/mover.ts
packages-user/data-common/src/common/mover.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(C 节 follower/mover)
</read_first>
<action>
`follower.test.ts`:inline fake `IDataCommon` + `Dir8FaceHandler` + `HeroLocation`:
- `addFollower` 在 hero locator 追加并触发 `onAddFollower`;`getFollower`/`getAllFollowers`/`getFollowersById`(数值与字符串 id);`next`/`last` 邻居链接。
- `removeFollower`/`removeAllFollowers` 触发 `onRemoveFollower` 并清空。
- `gatherFollowers`(异步,真实计时器)逐步移动并触发 `onGatherFollowers(false)`;`gatherFollowersSync` 直接吸附并触发 `onGatherFollowers(true)`。
- 未知字符串 follower id 经 `logger.catch` 观测码 142。不测 follower 的 `saveState`/`loadState`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/hero/location.test.ts packages-user/data-base/src/hero/state.test.ts packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/hero/equipStore.test.ts packages-user/data-base/src/hero/items.test.ts packages-user/data-base/src/hero/follower.test.ts</automated>
<fails_when>运行失败,或 modifier 应用/移除断言、钩子顺序、异步 gather,或 116/142/146/147 中任一码未被观测</fails_when>
</verify>
<acceptance_criteria>
- 六个文件零非跳过失败。
- location 断言 getter/setter/钩子;state 断言装配/属性视图/码 116/changeFloor 钩子顺序;equipment 断言 equip/unequip/replace、modifier 应用、compareEquip、码 146/147;equipStore 断言实例增删计数排序;items 断言 add/use/count/路由;follower 断言 add/remove/gather/链接与码 142。
- 异步 gather 用例用真实计时器并 await;无 fake timers。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>阶段 2(组合/流水线)的 location/state 装配与 equipment/items/follower 联动在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>阶段 3(完整/集成):HeroMover(异步 tile 驱动)+ HeroRendering 钩子</name>
<files>packages-user/data-base/src/hero/mover.test.ts, packages-user/data-base/src/hero/rendering.test.ts</files>
<read_first>
packages-user/data-base/src/hero/mover.ts
packages-user/data-base/src/hero/rendering.ts
packages-user/data-base/src/hero/types.ts
packages-user/data-common/src/common/mover.ts
packages-user/data-common/src/common/mover.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(C 节 mover/rendering)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测 HeroMover 经 tile/face 驱动的完整异步移动路径与 HeroRendering 的完整钩子行为。
`mover.test.ts`:镜像 `data-common/src/common/mover.test.ts` 但针对 `HeroMover`:inline `IHeroLocation` 形状 tile + fake `IDataCommon` + `Dir8FaceHandler`;`config`/`getConfig` 往返 `noRoute`/`ignoreTerrain`/`autoSave`/`allowOutBound`;inline fake `IHeroMoveTopImpl`:Step 经 `setPos` 写入下一位置、`CannotMove` 调 `cannotEnter` 并不移动、`Hit` 调 `hit` 并停、`allowOutBound` 允许越界而默认阻挡、`ignoreTerrain` 跳过 pass/hit/enter/leave;缺 top 实现时告警 144 且 `onStepStart` 返回 `HeroMoveCode.Stop`;`onStepSettled` 在开启地形触发时按 before/current locator 调 `leave` 后 `enter`。真实计时器 + `await controller.onEnd`。
`rendering.test.ts`:`HeroRendering` 构造后 `alpha` 默认 1;`setAlpha` 更新 `alpha` 并触发挂钩的 `onSetAlpha`;`createController` 注册的钩子可解除。不测 `saveState`/`loadState`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/hero/follower.test.ts packages-user/data-base/src/hero/mover.test.ts packages-user/data-base/src/hero/rendering.test.ts</automated>
<fails_when>运行失败,或异步 gather/mover 断言未通过 awaited 控制器完成,或 144/142 未被观测</fails_when>
<automated>pnpm exec vitest run packages-user/data-base/src/hero/mover.test.ts packages-user/data-base/src/hero/rendering.test.ts</automated>
<fails_when>运行失败,或异步 mover 断言未通过 awaited 控制器完成,或 144 未被观测</fails_when>
</verify>
<acceptance_criteria>
- 三个文件零非跳过失败。
- follower 断言 add/remove/gather/链接与码 142;mover 断言 config 往返、Step/CannotMove/Hit、越界、enter/leave 顺序与码 144;rendering 断言 alpha + 钩子。
- 两个文件零非跳过失败。
- mover 断言 config 往返、Step/CannotMove/Hit、越界、enter/leave 顺序与码 144;rendering 断言 alpha + 钩子。
- 异步用例用真实计时器并 await 控制器;无 fake timers。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>followers、异步 HeroMover、HeroRendering 覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(108/109/116/142/144/146/147)写入 `06-COVERAGE-MAP.md` 的 06-05 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的 HeroMover 与 HeroRendering 在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(108/109/116/142/144/146/147)写入 `06-COVERAGE-MAP.md` 的 06-05 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -30,6 +30,7 @@ must_haves:
- "地图矩阵、点事件、图层别名/背景/脏标记、`resize`/`resize2`/crop、`compareWith`、`canPass`/`shouldHit` 委托、异步门开关(openDoor/closeDoor)均有覆盖。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-base/src/map/mapState.test.ts
provides: MapState 覆盖
@ -63,11 +64,13 @@ must_haves:
---
<objective>
在既有 `eventPath`/`eventView`/`mapLifecycle` 测试之上,全面覆盖地图系统 `packages-user/data-base/src/map`(D-18 / D-39):`MapState`、`GameMap`、`MapLayer`、tile 与 eventView、map mover,**重点**静态图块、动态图块转换、静态数组设置。
在既有 `eventPath`/`eventView`/`mapLifecycle` 测试之上,全面覆盖地图系统 `packages-user/data-base/src/map`(D-18 / D-39):`MapState`、`GameMap`、`MapLayer`、tile 与 eventView、map mover,**重点**静态图块、动态图块转换、静态数组设置。按 D-43 的阶段化测试组织:阶段 1 构件级(tile/staticTile/dynamicTile/eventView 单元)→ 阶段 2 组合/流水线(MapState 注册/激活 + GameMap 图层 + MapLayer 矩阵/静态数组/动态转换)→ 阶段 3 完整/集成(DynamicTileMover 异步移动)。
Purpose: D-18 要求更全面的地图覆盖(矩阵、点事件、图层、脏标记、resize/crop)。存读档集中到 06-09。
Purpose: D-18 要求更全面的地图覆盖(矩阵、点事件、图层、脏标记、resize/crop)。存读档集中到 06-09。地图系统从未被测过,故按阶段逐层定位(D-43)。
Output: 8 个 `*.test.ts`(其中 `eventView.test.ts` 为既有文件扩展)。
阶段结构(D-43):每阶段跑绿后再进入下一阶段。
</objective>
<execution_context>
@ -103,22 +106,59 @@ Output: 8 个 `*.test.ts`(其中 `eventView.test.ts` 为既有文件扩展)
- 不测存读档(D-32)。
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-06 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
<task type="auto">
<name>Task 1: MapState 注册/校验/激活 + GameMap 图层/别名/脏/事件层</name>
<files>packages-user/data-base/src/map/mapState.test.ts, packages-user/data-base/src/map/gameMap.test.ts</files>
<name>阶段 1(构件级):tile/staticTile/dynamicTile/eventView 单个类单元</name>
<files>packages-user/data-base/src/map/tile.test.ts, packages-user/data-base/src/map/staticTile.test.ts, packages-user/data-base/src/map/dynamicTile.test.ts, packages-user/data-base/src/map/eventView.test.ts</files>
<read_first>
packages-user/data-base/src/map/tile.ts
packages-user/data-base/src/map/staticTile.ts
packages-user/data-base/src/map/dynamicTile.ts
packages-user/data-base/src/map/eventView.ts
packages-user/data-base/src/map/types.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(D 节 tile)
</read_first>
<action>
D-43 阶段 1(构件级):只测单个 tile/view 类的最小单元,不做图层组合与移动集成。
`tile.test.ts`:对 static 与 dynamic tile:`num`/`raw`/`set` 反映 tileStore 数据;`set` 变更后恢复该图块默认 `events` 并把事件视图标纯;`tileEvent()` 为自身视图(默认恢复后 dirty 为 false)、`pointEvent()` 返回该 locator 的层点位视图(越图为 null);`setFaceDirection` 经 face binder 映射并返回结果 num。单独一条 `it` 聚焦默认事件恢复。不测存读档。
`staticTile.test.ts`:`StaticTile` 的 `num`/`raw`/`set`/`toDynamic`/`shouldSave` 行为。不测 `saveState`/`loadState`。
`dynamicTile.test.ts`:`DynamicTile` 构造缺失 raw 告警 143;`num`/`raw`/`set`(缺失告警 143);`setPos`;`getCurrentFaceDirection`;`toStatic`/`toStaticIfSafe`;`step` 返回控制器;`delete`。不测 `saveState`/`loadState`。
`eventView.test.ts`(扩展既有文件,保留既有覆盖):`LayerEventView` 的 `get`/`ref`/`set`/`delete`/`clear`/`markPure`/`dirty` 与逐优先级脏跟踪;重复 priority 触发码 136(`logger.catch`)。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/tile.test.ts packages-user/data-base/src/map/staticTile.test.ts packages-user/data-base/src/map/dynamicTile.test.ts packages-user/data-base/src/map/eventView.test.ts</automated>
<fails_when>运行失败,或 136/143 未被观测,或默认事件恢复/tile 生命周期断言失败</fails_when>
</verify>
<acceptance_criteria>
- 四个文件零非跳过失败。
- tile 断言默认事件恢复与点位联动;staticTile/dynamicTile 断言各自公开方法;eventView 断言视图操作与码 136。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>阶段 1(构件级)的 tile/staticTile/dynamicTile/eventView 单元覆盖全部跑绿后才进入阶段 2。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>阶段 2(组合/流水线):MapState 注册/校验/激活 + GameMap 图层 + MapLayer 矩阵/静态数组/动态转换</name>
<files>packages-user/data-base/src/map/mapState.test.ts, packages-user/data-base/src/map/gameMap.test.ts, packages-user/data-base/src/map/mapLayer.test.ts</files>
<read_first>
packages-user/data-base/src/map/mapState.ts
packages-user/data-base/src/map/gameMap.ts
packages-user/data-base/src/map/mapLayer.ts
packages-user/data-base/src/map/types.ts
packages-user/data-base/src/map/eventPath.test.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(D 节 mapState/gameMap)
.planning/phases/06-unit-tests/06-PATTERNS.md(D 节 mapState/gameMap/mapLayer)
</read_first>
<action>
D-43 阶段 2(组合/流水线):在阶段 1 跑绿后,测地图注册/激活、图层组合与静态数组/动态转换流水线。
`mapState.test.ts`:用 inline `createMapState` fixture(TileStore + FaceManager + RoleFaceBinder + DirectionMapper fake `IDataCommon`)覆盖:
- `createMap(id,w,h)` 注册、`getMap` 命中/未知返回 null;`fromRaw` 构建 map,并对非法 raw 返回 null 且告警码 60/61/62/63/64(缺 floor、维度非法、容器/类型非法、非数值键等分支各一条,`logger.catch`)。其中码 60 用两层长度不等的 raw(如 `map` 为 `{'0': 长度6, '1': 长度5}`)经 `MapState.fromRaw` 触发;码 61 用各层等长但总长度不能被 `raw.width` 整除的 raw(如单层长度 5、`raw.width` 为 2)经 `MapState.fromRaw` 触发;两者均由 `fromRaw` 内部的 `validateRaw` 校验发出,断言最终返回 null 且相应码被观测。
- 激活迭代:`setMapActiveStatus`/`isMapActive`/`getActiveMap`/`iterateAllMaps`;`fromRaw` 默认 inactive;`setMapList` 顺序与 `getMap` 解析;`setArea`/`activeArea`/`deactiveArea`/`useAutoActivitor`/`notifyEnterFloor` 改变激活集。
@ -132,31 +172,8 @@ Output: 8 个 `*.test.ts`(其中 `eventView.test.ts` 为既有文件扩展)
- `setEventLayer`:拒绝不在 `layerList` 的层并告警 131;接受已添加层;接受 null。
- `dirty()`(任一层或自身脏为真)、`markDirty(true/false)`;`resizeLayer(w,h,keepBlock)` 按 keepBlock 调 `resize`/`resize2`;`compareWith(map)` 缺参考标脏、否则比较。
- 不测 `saveState`/`loadState`(含 `isEmptyLayerSave`)。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/mapState.test.ts packages-user/data-base/src/map/gameMap.test.ts</automated>
<fails_when>运行失败,或 60/61/62/63/64/84/121/131 中任一码未被观测</fails_when>
</verify>
<acceptance_criteria>
- 两个文件零非跳过失败。
- mapState 断言 create/get/fromRaw 校验码(60/61/62/63/64)/激活迭代/area 激活/121/compareWith/canPass-shouldHit。
- gameMap 断言层与别名生命周期、背景/事件层、脏、resize、compareWith 与码 84/131。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>MapState 与 GameMap 覆盖并跑绿。</done>
</task>
<task type="auto">
<name>Task 2: MapLayer 矩阵、静态数组设置与动态图块转换(重点)</name>
<files>packages-user/data-base/src/map/mapLayer.test.ts</files>
<read_first>
packages-user/data-base/src/map/mapLayer.ts
packages-user/data-base/src/map/types.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(D 节 mapLayer)
</read_first>
<action>
用 inline `createMapState` fixture 构造 layer,覆盖(重点:静态图块 / 动态转换 / 静态数组):
`mapLayer.test.ts`:用 inline `createMapState` fixture 构造 layer,覆盖(重点:静态图块 / 动态转换 / 静态数组):
- 矩阵:`setBlock`/`getBlock`/`removeBlock`/`inMap`;`getMapData()`(0 或 4 参,参数个数非法告警 80、越界填充告警 81);`putMapData(array,x,y,w)`;`setMapRef(array)` 长度不匹配告警 123、否则设置引用;`getMapRef`。
- 静态数组诊断:`getMapData(4 参越界)` 告警 8(不完整数据)与 9(越界忽略)。
- 动态转换:`createDynamic(num,x,y)` 建立索引并触发 `onCreateDynamic`;`getDynamicTilesAt`/`iterateDynamicTiles`/`updateDynamicTile`;`transferToDynamic(x,y,keepEvent)`(越图告警 131、block 为 0 告警 127、清事件语义);`transferToStatic`(越界告警 128、覆盖静态告警 129、`keepEvent` 语义);`transferToStaticIfSafe`(占用返回 null);`deleteDynamic`(非本层 tile 告警 130,`onDeleteDynamic` 钩子)。
@ -167,52 +184,44 @@ Output: 8 个 `*.test.ts`(其中 `eventView.test.ts` 为既有文件扩展)
注:若 `transferToDynamic` 的越图路径实际发出的码与语义不符(当前实现发 131 而 `transferToStatic` 发 128),在断言中触发并记录为 `#06-06-N` 待用户确认;若用户判定为缺陷则按 D-05 改为正确预期 + skip。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/mapLayer.test.ts</automated>
<fails_when>运行失败,或 8/9/46/80/81/123/127/128/129/130/131 中任一码未被观测,或静态数组/动态转换断言失败</fails_when>
<automated>pnpm exec vitest run packages-user/data-base/src/map/mapState.test.ts packages-user/data-base/src/map/gameMap.test.ts packages-user/data-base/src/map/mapLayer.test.ts</automated>
<fails_when>运行失败,或 8/9/46/60/61/62/63/64/80/81/84/121/123/127/128/129/130/131 中任一码未被观测,或静态数组/动态转换断言失败</fails_when>
</verify>
<acceptance_criteria>
- `mapLayer.test.ts` 零非跳过失败。
- 断言矩阵读写、静态数组(getMapData/putMapData/setMapRef)、动态转换语义、点位事件、脏/compareWith、异步门与全部列出码。
- 三个文件零非跳过失败。
- mapState 断言 create/get/fromRaw 校验码(60/61/62/63/64)/激活迭代/area 激活/121/compareWith/canPass-shouldHit。
- gameMap 断言层与别名生命周期、背景/事件层、脏、resize、compareWith 与码 84/131。
- mapLayer 断言矩阵读写、静态数组、动态转换语义、点位事件、脏/compareWith、异步门与全部列出码。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>MapLayer 矩阵、静态数组与动态转换(重点)覆盖并跑绿。</done>
<done>阶段 2(组合/流水线)的地图注册/图层组合/动态转换在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 3: tile/staticTile/dynamicTile/eventView/mover</name>
<files>packages-user/data-base/src/map/tile.test.ts, packages-user/data-base/src/map/staticTile.test.ts, packages-user/data-base/src/map/dynamicTile.test.ts, packages-user/data-base/src/map/eventView.test.ts, packages-user/data-base/src/map/mover.test.ts</files>
<name>阶段 3(完整/集成):DynamicTileMover 异步移动生命周期</name>
<files>packages-user/data-base/src/map/mover.test.ts</files>
<read_first>
packages-user/data-base/src/map/tile.ts
packages-user/data-base/src/map/staticTile.ts
packages-user/data-base/src/map/dynamicTile.ts
packages-user/data-base/src/map/eventView.ts
packages-user/data-base/src/map/mover.ts
packages-user/data-base/src/map/dynamicTile.ts
packages-user/data-base/src/map/types.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(D 节 tile)
</read_first>
<action>
`tile.test.ts`:对 static 与 dynamic tile:`num`/`raw`/`set` 反映 tileStore 数据;`set` 变更后恢复该图块默认 `events` 并把事件视图标纯;`tileEvent()` 为自身视图(默认恢复后 dirty 为 false)、`pointEvent()` 返回该 locator 的层点位视图(越图为 null);`setFaceDirection` 经 face binder 映射并返回结果 num。单独一条 `it` 聚焦默认事件恢复。不测存读档。
`staticTile.test.ts`:`StaticTile` 的 `num`/`raw`/`set`/`toDynamic`/`shouldSave` 行为。不测 `saveState`/`loadState`。
`dynamicTile.test.ts`:`DynamicTile` 构造缺失 raw 告警 143;`num`/`raw`/`set`(缺失告警 143);`setPos`;`getCurrentFaceDirection`;`toStatic`/`toStaticIfSafe`;`step` 返回控制器;`delete`。不测 `saveState`/`loadState`。
`eventView.test.ts`(扩展既有文件,保留既有覆盖):`LayerEventView` 的 `get`/`ref`/`set`/`delete`/`clear`/`markPure`/`dirty` 与逐优先级脏跟踪;重复 priority 触发码 136(`logger.catch`)。
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测 `DynamicTileMover` 经 tile/face/map 驱动的完整异步移动生命周期(跨模块)。
`mover.test.ts`:经一个 `DynamicTile` 构造其 `DynamicTileMover` 并驱动控制器:成功步进、非 `Success` 移动码触发码 126(`logger.catch`)、`onMoveStart`/`onStepSettled` 生命周期顺序;真实计时器 + `await controller.onEnd`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/tile.test.ts packages-user/data-base/src/map/staticTile.test.ts packages-user/data-base/src/map/dynamicTile.test.ts packages-user/data-base/src/map/eventView.test.ts packages-user/data-base/src/map/mover.test.ts</automated>
<fails_when>运行失败,或 136/143/126 未被观测,或默认事件恢复/动态 tile 生命周期断言失败</fails_when>
<automated>pnpm exec vitest run packages-user/data-base/src/map/mover.test.ts</automated>
<fails_when>运行失败,或 126 未被观测,或动态 tile 移动生命周期断言失败</fails_when>
</verify>
<acceptance_criteria>
- 五个文件零非跳过失败。
- tile 断言默认事件恢复与点位联动;staticTile/dynamicTile 断言各自公开方法;eventView 断言视图操作与码 136;mover 断言生命周期与码 126。
- `mover.test.ts` 零非跳过失败。
- mover 断言生命周期与码 126。
- 异步 mover 用例用真实计时器并 await 控制器;无 fake timers。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>tile/staticTile/dynamicTile/eventView/mover 覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(8/9/46/60/61/62/63/64/80/81/84/121/123/126/127/128/129/130/131/136/143)写入 `06-COVERAGE-MAP.md` 的 06-06 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的 DynamicTileMover 在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(8/9/46/60/61/62/63/64/80/81/84/121/123/126/127/128/129/130/131/136/143)写入 `06-COVERAGE-MAP.md` 的 06-06 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -12,7 +12,7 @@ requirements: [TEST-01]
estimate:
tokens: 52000
raw_tokens: 52000
tasks: 3
tasks: 4
confidence: low
must_haves:
truths:
@ -27,6 +27,7 @@ must_haves:
- "测试形态(D-01):本计划为涉及多系统协作(战斗、录像)的经 `CoreState` 集成测试路径,与纯逻辑直测(06-01/06-05 等)共同构成混合测试形态。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-state/test/enemyCombination.test.ts
provides: 顶层伤害组合 + 系统层光环/效果组合覆盖
@ -47,13 +48,15 @@ must_haves:
---
<objective>
顶层集成(D-40):① 伤害系统的属性/加成组合(顶层多特殊属性 + 系统层光环/效果组合,D-25/D-26);② 用真实 API 构造小地图场景 + 一段录像步并正常播放;③ 二次录制验证:首次播放后系统记录一遍录像步,判定二次录像与原录像逐条完全相等,并再次播放检验。`error 2001–2008` 归本计划。
顶层集成(D-40):① 伤害系统的属性/加成组合(顶层多特殊属性 + 系统层光环/效果组合,D-25/D-26);② 用真实 API 构造小地图场景 + 一段录像步并正常播放;③ 二次录制验证:首次播放后系统记录一遍录像步,判定二次录像与原录像逐条完全相等,并再次播放检验。`error 2001–2008` 归本计划。按 D-43 的阶段化测试组织:阶段 1 构件级(真实 API 下单顶层类/单分支基线)→ 阶段 2 组合/流水线(多 special + 光环/效果组合)→ 阶段 3 完整/集成(小地图录像播放 + 二次录制比对 + 2001–2008)。
Purpose: 这是唯一验证多系统接线(战斗上下文 + 流程 + 计算器;地图 + 勇士 + 录像)的阶段工作。
Purpose: 这是唯一验证多系统接线(战斗上下文 + 流程 + 计算器;地图 + 勇士 + 录像)的阶段工作。先构件级基线再组合/集成,避免整链失败无法定位(D-43)。
**执行前必须先向用户确认**(D-40:依赖用户先接通录像记录)。
Output: 2 个 `data-state/test/` 集成测试文件。
阶段结构(D-43):每阶段跑绿后再进入下一阶段;阶段 3 为本计划的完整集成场景。
</objective>
<execution_context>
@ -90,6 +93,7 @@ Output: 2 个 `data-state/test/` 集成测试文件。
- **本计划执行前必须先向用户确认录像记录已接通(D-40);确认前不得开始写/跑用例。**
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-07 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
@ -126,7 +130,36 @@ Output: 2 个 `data-state/test/` 集成测试文件。
</task>
<task type="auto">
<name>Task 2: 顶层伤害组合 + 系统层光环/效果组合</name>
<name>阶段 1(构件级):真实 API 下单顶层类/单分支基线</name>
<files>packages-user/data-state/test/enemyCombination.test.ts</files>
<read_first>
packages-user/data-state/test/dataClosure.test.ts
packages-user/data-state/src/enemy/calculator.ts
packages-user/data-state/src/enemy/aura.ts
packages-user/data-system/src/combat/context.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 combatClosure)
</read_first>
<action>
D-43 阶段 1(构件级):先用真实 `createCoreState()` 建立构件级基线——单个顶层类/单分支在真实接线下的行为(不做多 special / 多光环组合)。
在 `enemyCombination.test.ts` 中:
- 单个 special 经真实 `MainDamageCalculator` 得到确定 `{damage,turn}`(`addPrefab` + handler 形状)。
- 单个光环经真实 `CommonAura`/`GuardAura` + 真实 `EnemyContext` 单次施加,断言基础/guard 单分支数值(不叠加第二光环/特殊属性)。
若某构件级入口未暴露,按 D-07 暂停并向用户提问,不修改核心代码。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-state/test/enemyCombination.test.ts</automated>
<fails_when>运行失败,或单 special/单光环基线断言与真实实现不符</fails_when>
</verify>
<acceptance_criteria>
- `enemyCombination.test.ts` 零非跳过失败。
- 断言至少一条单 special 伤害基线与一条单光环施加基线。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>阶段 1(构件级)的真实 API 单分支基线全部跑绿后才进入阶段 2。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>阶段 2(组合/流水线):顶层多 special 伤害组合 + 系统层光环/效果组合与四阶段顺序</name>
<files>packages-user/data-state/test/enemyCombination.test.ts</files>
<read_first>
packages-user/data-state/test/dataClosure.test.ts
@ -138,7 +171,7 @@ Output: 2 个 `data-state/test/` 集成测试文件。
.planning/phases/06-unit-tests/06-PATTERNS.md(A 节 combatClosure)
</read_first>
<action>
用真实 `createCoreState()` 与真实顶层实现驱动:
D-43 阶段 2(组合/流水线):在阶段 1 跑绿后,用真实 `createCoreState()` 与真实顶层实现驱动组合路径:
- 顶层多特殊属性组合:一个怪物同时带多个 special(如 坚固 + 连击 + 吸血 等)时,按真实 `MainDamageCalculator` 断言组合后的 `{damage,turn}`(组合语义,非单分支)。
- 系统层组合:经真实 `EnemyContext` + 真实 `CommonAura`/`GuardAura` + 真实 final effect,覆盖 光环基础效果 ↔ 常规查询效果、光环特殊效果 ↔ 特殊查询效果、final-effect 阶段顺序、同/跨优先级顺序(D-25);属性→伤害联动(D-26)。
- 支援/guard 递归与未知 guard 的 warn 137 经 `logger.catch` 观测。
@ -153,11 +186,11 @@ Output: 2 个 `data-state/test/` 集成测试文件。
- 断言至少一组多 special 伤害组合与一组系统层光环/效果组合(含阶段顺序)。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>顶层与系统层伤害/效果组合覆盖并跑绿。</done>
<done>阶段 2(组合/流水线)的顶层与系统层伤害/效果组合在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 3: 小地图录像播放 + 二次录制逐条比对 + error 2001–2008</name>
<name>阶段 3(完整/集成):小地图录像播放 + 二次录制逐条比对 + error 2001–2008</name>
<files>packages-user/data-state/test/replayPlayback.test.ts</files>
<read_first>
packages-user/data-state/test/nodeReplay.test.ts
@ -170,6 +203,7 @@ Output: 2 个 `data-state/test/` 集成测试文件。
.planning/phases/06-unit-tests/06-PATTERNS.md(B 节 replayIntegration)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测本计划的完整集成场景(真实 API 构造小地图 + 录像播放 + 二次录制比对)。
用真实 API 构造小地图场景(合成数据)+ 一段录像步并播放:
- 小地图:用真实 `createCoreState()` 的 map 公开接口构造极小地图(1..3 格),放置怪物/勇士,绑定必要状态。
- 播放:记录一小段有效录像,创建 sandbox 并用有界等待(`waitForEnded`)播放完毕;断言关键状态到达预期。
@ -188,7 +222,7 @@ Output: 2 个 `data-state/test/` 集成测试文件。
- 断言 2001–2008 全部触发。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>小地图录像播放、二次录制比对与 2001–2008 覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(2001–2008)写入 `06-COVERAGE-MAP.md` 的 06-07 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的小地图录像播放、二次录制比对与 2001–2008 在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(2001–2008)写入 `06-COVERAGE-MAP.md` 的 06-07 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -27,6 +27,7 @@ must_haves:
- "`data-common/src/common` 工具(utils/indexer/faceManager+face/mover)行为与文档一致;D-20 要求:主要测试未触及的辅助函数才补最小直接单测。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-base/src/flag/system.test.ts
provides: FlagSystem 覆盖
@ -55,11 +56,13 @@ must_haves:
---
<objective>
覆盖两个辅助模块:全局 flag 系统 `packages-user/data-base/src/flag`(D-19)与通用接口 `packages-user/data-common/src/common`(utils / indexer / faceManager + face / mover)(D-20 / D-41)。
覆盖两个辅助模块:全局 flag 系统 `packages-user/data-base/src/flag`(D-19)与通用接口 `packages-user/data-common/src/common`(utils / indexer / faceManager + face / mover)(D-20 / D-41)。按 D-43 的阶段化测试组织:阶段 1 构件级(utils 纯函数 + indexer 索引单元)→ 阶段 2 组合/流水线(FlagSystem 容器 + FaceManager/handlers + RoleFaceBinder)→ 阶段 3 完整/集成(ObjectMover 异步移动)。
Purpose: D-19 要求 flag 公开接口行为;D-20/D-41 要求 common 由主要测试自然覆盖,并对 tools/indexer/faceManager+face/mover 补直接单测。存读档集中到 06-09。
Output: 6 个 `*.test.ts`(其中 `mover.test.ts` 为既有文件扩展)。
阶段结构(D-43):每阶段跑绿后再进入下一阶段。
</objective>
<execution_context>
@ -94,75 +97,79 @@ Output: 6 个 `*.test.ts`(其中 `mover.test.ts` 为既有文件扩展)。
- 不测存读档(D-32)。
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-08 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
<task type="auto">
<name>Task 1: FlagSystem 完整公开表面</name>
<files>packages-user/data-base/src/flag/system.test.ts</files>
<name>阶段 1(构件级):朝向工具纯函数 + MapLocIndexer 索引单元</name>
<files>packages-user/data-common/src/common/utils.test.ts, packages-user/data-common/src/common/indexer.test.ts</files>
<read_first>
packages-user/data-common/src/common/utils.ts
packages-user/data-common/src/common/indexer.ts
packages-user/data-common/src/common/types.ts
packages-user/data-common/src/common/mover.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(G 节)
</read_first>
<action>
D-43 阶段 1(构件级):只测纯函数/纯数学的最小单元。
`utils.test.ts`:`getFaceMovement` 对每个 `FaceDirection`(含 `Unknown -> {0,0}`);`degradeFace` 对全部对角线与自定义 `unknown` 参数;`nextFaceDirection` 对 4 向顺/逆时针与 8 向顺/逆时针及 `Unknown` 透传;`fromDirectionString` 对每个字符串与未知字符串。纯函数同步断言。
`indexer.test.ts`:`MapLocIndexer` + `setWidth(w)`:`locToIndex(x,y)` 与 `locaterToIndex({x,y})` 一致,`indexToLocator` 对范围内索引为逆运算,换更宽 width 改变行步长。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-common/src/common/utils.test.ts packages-user/data-common/src/common/indexer.test.ts</automated>
<fails_when>运行失败,或某方向/索引映射断言缺失或错误</fails_when>
</verify>
<acceptance_criteria>
- 两个文件零非跳过失败。
- utils 覆盖全部方向/字符串;indexer 覆盖往返与宽度变化。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>阶段 1(构件级)的朝向工具与索引单元覆盖全部跑绿后才进入阶段 2。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>阶段 2(组合/流水线):FlagSystem 容器 + FaceManager/handlers + RoleFaceBinder</name>
<files>packages-user/data-base/src/flag/system.test.ts, packages-user/data-common/src/common/faceManager.test.ts, packages-user/data-common/src/common/face.test.ts</files>
<read_first>
packages-user/data-base/src/flag/system.ts
packages-user/data-base/src/flag/field.ts
packages-user/data-base/src/flag/types.ts
packages-user/data-common/src/common/faceManager.ts
packages-user/data-common/src/common/face.ts
packages-user/data-common/src/common/types.ts
packages-user/data-state/test/dataClosure.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(F 节 flag)
.planning/phases/06-unit-tests/06-PATTERNS.md(F/G 节)
</read_first>
<action>
D-43 阶段 2(组合/流水线):在阶段 1 跑绿后,测容器/注册表、朝向绑定与朝向工具组合的流水线。
创建 `flag/system.test.ts`(import 链需要时使用 `vi.hoisted` 的 `Map.getOrInsertComputed`/`getOrInsert` polyfill):
- `occupied` 初始 false,`insertField`/`setFieldValue` 后 true。
- `insertField` 返回 `FlagCommonField`,其 `get`/`set`/`toStructured` 行为符合文档;`getField` 未知返回 null;`getOrInsert` 插入默认并二次调用返回同实例。
- `getOrInsertComputed` 收到 key 并插入计算默认值;`deleteField` 移除。
- `setFieldValue` 覆盖;`addFieldValue` 对数值字段累加、对**非数值**字段经 `logger.catch` 观测码 111;`getFieldValue` 未知返回 undefined;`getFieldValueDefaults` 返回默认并插入。
- 不测 `saveState`/`loadState`(归 06-09;注意 flag `saveState()` 无压缩参数)。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/flag/system.test.ts</automated>
<fails_when>聚焦运行失败,或码 111 未被观测,或某公开方法缺少正常用例</fails_when>
</verify>
<acceptance_criteria>
- `system.test.ts` 零非跳过失败。
- 断言 D-19 列出的完整公开表面与码 111。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>FlagSystem 覆盖并跑绿。</done>
</task>
<task type="auto">
<name>Task 2: 朝向工具 + 位置索引 + FaceManager/handlers + RoleFaceBinder</name>
<files>packages-user/data-common/src/common/utils.test.ts, packages-user/data-common/src/common/indexer.test.ts, packages-user/data-common/src/common/faceManager.test.ts, packages-user/data-common/src/common/face.test.ts</files>
<read_first>
packages-user/data-common/src/common/utils.ts
packages-user/data-common/src/common/indexer.ts
packages-user/data-common/src/common/faceManager.ts
packages-user/data-common/src/common/face.ts
packages-user/data-common/src/common/types.ts
packages-user/data-common/src/common/mover.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(G 节)
</read_first>
<action>
`utils.test.ts`:`getFaceMovement` 对每个 `FaceDirection`(含 `Unknown -> {0,0}`);`degradeFace` 对全部对角线与自定义 `unknown` 参数;`nextFaceDirection` 对 4 向顺/逆时针与 8 向顺/逆时针及 `Unknown` 透传;`fromDirectionString` 对每个字符串与未知字符串。纯函数同步断言。
`indexer.test.ts`:`MapLocIndexer` + `setWidth(w)`:`locToIndex(x,y)` 与 `locaterToIndex({x,y})` 一致,`indexToLocator` 对范围内索引为逆运算,换更宽 width 改变行步长。
`faceManager.test.ts`:`FaceManager.register`/`registerById` 后 `get`/`getById` 命中、未知返回 null;`Dir8FaceHandler` 的 `degrade` 透传、`movement` 对每个方向及越界输入(`ZERO_DESCRIPTOR`)、`move` 缩放与负值、`opposite`(含 `Unknown -> Unknown`)、`next` 顺/逆时针与 `Unknown` 透传、`mapDirection`/`mapMovement` 含 `Unknown` 的 `{0,0}`;`Dir4FaceHandler` 的 `degrade` 把对角映射到 4 向、未知映射到 `Unknown`,其余方法对 4 向集正确。
`face.test.ts`:`RoleFaceBinder.malloc` 建立主朝向映射;`bind` 正常绑定;`bind` 对未知 main 经 `logger.catch` 观测码 43;`bind` 对与主朝向相同的 face 观测码 44;`getFaceOf`(命中/未知标识/未知 face 返回 null);`getFaceDirection`;`getMainFace`(命中/未知返回 null)。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-common/src/common/utils.test.ts packages-user/data-common/src/common/indexer.test.ts packages-user/data-common/src/common/faceManager.test.ts packages-user/data-common/src/common/face.test.ts</automated>
<fails_when>运行失败,或 43/44 未被观测,或某方向/索引映射断言缺失或错误</fails_when>
<automated>pnpm exec vitest run packages-user/data-base/src/flag/system.test.ts packages-user/data-common/src/common/faceManager.test.ts packages-user/data-common/src/common/face.test.ts</automated>
<fails_when>运行失败,或 43/44/111 未被观测,或某公开方法缺少正常用例</fails_when>
</verify>
<acceptance_criteria>
- 四个文件零非跳过失败。
- utils 覆盖全部方向/字符串;indexer 覆盖往返与宽度变化;faceManager 覆盖注册表与两个 handler 全表面;face.test 覆盖 RoleFaceBinder 与码 43/44。
- 三个文件零非跳过失败。
- flag 断言 D-19 列出的完整公开表面与码 111;faceManager 覆盖注册表与两个 handler 全表面;face 覆盖 RoleFaceBinder 与码 43/44。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>朝向工具、位置索引、FaceManager/handlers、RoleFaceBinder 覆盖并跑绿。</done>
<done>阶段 2(组合/流水线)的 FlagSystem、FaceManager/handlers、RoleFaceBinder 在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 3: ObjectMover 扩展覆盖</name>
<name>阶段 3(完整/集成):ObjectMover 异步移动完整路径</name>
<files>packages-user/data-common/src/common/mover.test.ts</files>
<read_first>
packages-user/data-common/src/common/mover.ts
@ -171,6 +178,7 @@ Output: 6 个 `*.test.ts`(其中 `mover.test.ts` 为既有文件扩展)。
.planning/phases/06-unit-tests/06-PATTERNS.md(共享样板 5)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测 `ObjectMover` 经 face/位置驱动的完整异步移动路径。
在既有 `mover.test.ts` 上扩展(保留既有覆盖),补齐 `ObjectMover` 全部公开方法的正常/边界用例:`setPos`/`setFaceDir`/`setMoveDir`/`tp`/`jump`/`step`/`stepFace`/`forward`/`backward`/`speed`/`face`/`animDir`/`push`/`clear`/`start`:
- 构建步骤序列(`tp`/`jump`/`step`/`stepFace`/`forward`/`backward`)后 `start()` 返回控制器,真实计时器 + `await controller.onEnd` 断言最终位置/朝向。
- `speed`/`face`/`animDir` 影响执行;`clear` 清除待执行步骤;已有移动进行中时 `start()` 返回 null(契约检测点)。
@ -186,7 +194,7 @@ Output: 6 个 `*.test.ts`(其中 `mover.test.ts` 为既有文件扩展)。
- `ObjectMover` 全部列出公开方法至少一条正常用例;异步路径用真实计时器 + await。
- 每个新增 `it` 前有单行中文注释。
</acceptance_criteria>
<done>ObjectMover 公开表面扩展覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(43/44/111)写入 `06-COVERAGE-MAP.md` 的 06-08 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的 ObjectMover 公开表面在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(43/44/111)写入 `06-COVERAGE-MAP.md` 的 06-08 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -17,7 +17,7 @@ requirements: [TEST-01]
estimate:
tokens: 68000
raw_tokens: 68000
tasks: 3
tasks: 4
confidence: low
must_haves:
truths:
@ -34,6 +34,7 @@ must_haves:
- "测试形态(D-01):本计划经 `CoreState` 集成路径(`getSaveableContent`)与各类同实例往返验证存读档,属混合测试形态的跨系统集成一侧。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 的格式登记到共用的 `06-TEST-FINDINGS.md`(模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度)。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "阶段化(D-43):计划按 构件→组合→完整 三阶段组织,每阶段跑绿后进入下一阶段;阻断性 bug 暂停等确认。"
artifacts:
- path: packages-user/data-base/src/enemy/saveLoad.test.ts
provides: Enemy/special/EnemyManager 存读档覆盖(119/120)
@ -64,11 +65,13 @@ must_haves:
---
<objective>
存读档独立系统(D-42):覆盖所有含 `saveState`/`loadState` 的类逐类同实例往返 + `CoreState` 顶层验证;仅断言关键状态字段,排除存档无关/元数据字段;压缩档全覆盖。
存读档独立系统(D-42):覆盖所有含 `saveState`/`loadState` 的类逐类同实例往返 + `CoreState` 顶层验证;仅断言关键状态字段,排除存档无关/元数据字段;压缩档全覆盖。按 D-43 的阶段化测试组织:阶段 1 构件级(enemy + hero 各类同实例往返)→ 阶段 2 组合/流水线(map + replay + flag 各类同实例往返)→ 阶段 3 完整/集成(CoreState 顶层 saveables 往返)。
**执行前必须先向用户确认**(D-42:依赖用户调整 CoreState 可存档内容,例如录像存档目前未计入但应计入,并新增公开 save/load 入口)。
Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
阶段结构(D-43):每阶段跑绿后再进入下一阶段;阶段 3 为 CoreState 顶层验证。
</objective>
<execution_context>
@ -104,6 +107,7 @@ Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
- **执行前必须先向用户确认(D-42)CoreState 可存档内容与公开 save/load 入口已就绪;未确认前不得开始。**
- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-09 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- **阶段门禁(D-43):本计划按 构件 → 组合/流水线 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
</execution_rules>
<tasks>
@ -140,7 +144,7 @@ Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
</task>
<task type="auto">
<name>Task 2: enemy + hero 各类存读档往返(含压缩档与码 119/120/58/59)</name>
<name>阶段 1(构件级):enemy + hero 各类同实例存读档往返(含压缩档与码 119/120/58/59)</name>
<files>packages-user/data-base/src/enemy/saveLoad.test.ts, packages-user/data-base/src/hero/saveLoad.test.ts, .planning/phases/06-unit-tests/06-SAVE-EXCLUSIONS.md</files>
<read_first>
packages-user/data-base/src/enemy/enemy.ts
@ -158,6 +162,7 @@ Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
.planning/phases/06-unit-tests/06-PATTERNS.md(C/E 节)
</read_first>
<action>
D-43 阶段 1(构件级):只测单个可存档类的同实例往返,不做跨系统顶层集成。
`enemy/saveLoad.test.ts`:对 `Enemy`、`CommonSerializableSpecial`/`NonePropertySpecial`、`EnemyManager` 分别同实例往返(三档压缩):
- Enemy:属性关键字段 + special 关键值;`loadState` 时实例上存在但存档缺失的 special 经 `logger.catch` 观测码 120;`saveState` 返回分离克隆(改返回对象不影响实例)。
- EnemyManager:`saveState` 只序列化脏 code;`loadState` 缺 prefab 经 `logger.catch` 观测码 119。
@ -183,12 +188,12 @@ Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
- `06-SAVE-EXCLUSIONS.md` 存在并逐类列出排除字段与依据。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>enemy/hero 存读档往返与排除清单覆盖并跑绿。</done>
<done>阶段 1(构件级)的 enemy/hero 存读档往返与排除清单覆盖全部跑绿后才进入阶段 2。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>Task 3: map + replay + flag 存读档往返(码 55/122/124)与 CoreState 顶层验证(码 112/113)</name>
<files>packages-user/data-base/src/map/saveLoad.test.ts, packages-user/data-common/src/replay/saveLoad.test.ts, packages-user/data-base/src/flag/saveLoad.test.ts, packages-user/data-state/test/saveablesRoundTrip.test.ts</files>
<name>阶段 2(组合/流水线):map + replay + flag 各类同实例往返(码 55/122/124)</name>
<files>packages-user/data-base/src/map/saveLoad.test.ts, packages-user/data-common/src/replay/saveLoad.test.ts, packages-user/data-base/src/flag/saveLoad.test.ts</files>
<read_first>
packages-user/data-base/src/map/mapState.ts
packages-user/data-base/src/map/gameMap.ts
@ -197,12 +202,11 @@ Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
packages-user/data-base/src/map/dynamicTile.ts
packages-user/data-common/src/replay/array.ts
packages-user/data-base/src/flag/system.ts
packages-user/data-state/src/core.ts
packages-user/data-state/test/fixtures/closed-loop.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(D/E/F 节)
</read_first>
<action>
D-43 阶段 2(组合/流水线):在阶段 1 跑绿后,测地图/录像/flag 各系统的存读档往返(系统内多构件组合)。
`map/saveLoad.test.ts`:对 `MapState`、`GameMap`、`MapLayer`、`StaticTile`、`DynamicTile` 同实例往返(三档压缩):
- MapState:`loadState` 在非 `NoCompression` 且未 `compareWith` 时观测码 55;缺楼层观测码 122。
- MapLayer:关键块/点位事件恢复;引用缺失观测码 124;三档压缩循环参照 `mapLifecycle.test.ts` 的显式关键字段。
@ -212,7 +216,32 @@ Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
`replay/saveLoad.test.ts`:`ReplayArray.saveState`/`loadState` 同实例往返:命令与参数(含 boolean/整数/bigint/string/数组)读回一致;不比较内部容量/元数据。
`flag/saveLoad.test.ts`:`FlagSystem.saveState()`(无压缩参数)/`loadState` 同实例往返:已知字段值 + `occupied` 恢复;`saveState` 返回分离克隆。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/saveLoad.test.ts packages-user/data-common/src/replay/saveLoad.test.ts packages-user/data-base/src/flag/saveLoad.test.ts</automated>
<fails_when>运行失败,或 55/122/124 中任一码未被观测,或某类缺少同实例往返断言</fails_when>
</verify>
<acceptance_criteria>
- 三个文件零非跳过失败。
- map/replay/flag 各类同实例往返;断言码 55/122/124。
- 只用关键字段显式断言,无整体深度相等。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>阶段 2(组合/流水线)的地图/录像/flag 存读档往返在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若本阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。</done>
</task>
<task type="auto">
<name>阶段 3(完整/集成):CoreState 顶层 saveables 往返(4 id × 3 压缩档,码 112/113)</name>
<files>packages-user/data-state/test/saveablesRoundTrip.test.ts</files>
<read_first>
packages-user/data-state/src/core.ts
packages-user/data-state/test/fixtures/closed-loop.ts
packages-user/data-state/test/dataClosure.test.ts
packages-user/data-common/src/save/types.ts
.planning/phases/06-unit-tests/06-PATTERNS.md(D/E/F 节)
</read_first>
<action>
D-43 阶段 3(完整/集成):在阶段 1/2 跑绿后,测 `CoreState` 顶层完整验证。
`saveablesRoundTrip.test.ts`:经 `CoreState.getSaveableContent(id)`:
- 对 4 个 id(`@system/hero`/`flags`/`maps`/`enemy`)× 3 档 `SaveCompression`:设已知关键字段 → `saveState` → 改字段 → `loadState` → 只断言关键字段(hero base 属性、flag 字段、map 层块/点位事件、enemy prefab 属性)。不做整体深度相等。
- 断言快照分离:save 后改活对象不影响快照关键字段。
@ -222,16 +251,15 @@ Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
- Node-safe `createCoreState()`;不 `saveSystem.init`、不加 `fake-indexeddb`(D-10)。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/saveLoad.test.ts packages-user/data-common/src/replay/saveLoad.test.ts packages-user/data-base/src/flag/saveLoad.test.ts packages-user/data-state/test/saveablesRoundTrip.test.ts</automated>
<fails_when>运行失败,或 55/112/113/122/124 中任一码未被观测,或 4×3 往返矩阵缺失某组合</fails_when>
<automated>pnpm exec vitest run packages-user/data-state/test/saveablesRoundTrip.test.ts</automated>
<fails_when>运行失败,或 112/113 中任一码未被观测,或 4×3 往返矩阵缺失某组合</fails_when>
</verify>
<acceptance_criteria>
- 四个文件零非跳过失败。
- map/replay/flag 各类同实例往返;断言码 55/122/124。
- saveablesRoundTrip 覆盖 4 id × 3 压缩档,显式关键字段 + 快照分离 + 未知 id null + 码 112/113。
- `saveablesRoundTrip.test.ts` 零非跳过失败。
- 覆盖 4 id × 3 压缩档,显式关键字段 + 快照分离 + 未知 id null + 码 112/113。
- 每个 `it` 前有单行中文注释。
</acceptance_criteria>
<done>地图/录像/flag 存读档往返与 CoreState 顶层验证覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(55/58/59/112/113/119/120/122/124)写入 `06-COVERAGE-MAP.md` 的 06-09 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
<done>阶段 3(完整/集成)的 CoreState 顶层 saveables 往返在阶段 1/2 跑绿的基础上全部跑绿。若本阶段发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认;不得跳过或弱化。完成后以 create-or-append 方式将本计划的 code→模块→用例行(55/58/59/112/113/119/120/122/124)写入 `06-COVERAGE-MAP.md` 的 06-09 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。</done>
</task>
</tasks>

View File

@ -88,6 +88,7 @@
- **D-31:** warn/error 全覆盖 —— 以 `packages/common/src/logger.json` 为权威码表;范围 = 本计划模块**可达**的 code(排除其他层如渲染/音频,及 legacy 路径);每个 code 至少一条触发断言(经 `logger.catch`);产出**阶段级 `06-COVERAGE-MAP.md`**(code → 模块 → 用例)映射表。
- **D-32:** 存读档集中 —— 所有 `saveState`/`loadState` 测试从各计划移出,集中到 06-09。唯一例外:06-07 的录像播放所需状态重置可**最小使用**存档数据,但不测存读档本身。
- **D-33:** 执行节奏 —— **每个计划执行前先向用户确认**;执行完暂停,用中文**分条简要汇报**本计划验证结果;详细结果写入共用的 `06-TEST-FINDINGS.md`。
- **D-43:** 阶段化测试(**应用于全部 06-01..06-09**)—— 先做**构件级**测试(单个类/函数/单个光环/单个 effect 等最小单元),再做**组合/流水线**测试,最后做**完整/集成**测试。**取消 tracer-first**;每阶段跑绿后再进入下一阶段。某阶段发现**阻断性 bug**(导致后续阶段无法运行)时,**暂停并向用户汇报、等确认**(D-05/D-07),不得跳过或弱化为通过。原因:这些系统在本次之前从未被测过,直接端到端很可能整链失败且无法定位,必须先分阶段定位。
### 各计划边界
- **D-35:** 06-02 只测顶层实现**基本功能**(单分支、按预期输出);属性/加成**组合**后移至 06-07。范围:`MainDamageCalculator` / `MainEnemyFinalEffect` / `MainEnemyComparer` / `CommonAura(+Converter)` / `GuardAura(+Converter)` / `registerSpecials` / mapDamage 五视图 + converter + reducer。

View File

@ -308,6 +308,19 @@
---
## 补充讨论(2026-09-14):阶段化测试(D-43)
**背景(用户指出)**:战斗系统写完后从未测过,直接端到端大概率整链跑不通,应先分阶段测再完整测。
| 决策 | 选择 |
|------|------|
| 06-01 阶段划分 | 三阶段:构件 → 流水线 → 完整 |
| 适用范围 | 全部 9 个计划 |
| 阶段间阻断性 bug | 暂停并向用户汇报、等确认(D-05/D-07) |
| tracer-first | 取消 |
---
## the agent's Discretion
- 各系统内部具体测哪些函数/边界、用例命名与文件切分。