diff --git a/.planning/phases/06-unit-tests/06-01-PLAN.md b/.planning/phases/06-unit-tests/06-01-PLAN.md
index f4359b7..9a0cf62 100644
--- a/.planning/phases/06-unit-tests/06-01-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-01-PLAN.md
@@ -5,56 +5,59 @@ type: execute
wave: 1
depends_on: []
files_modified:
+ - packages-user/data-system/src/combat/context.test.ts
- packages-user/data-system/src/combat/damage.test.ts
- packages-user/data-system/src/combat/mapDamage.test.ts
- - packages-user/data-system/src/combat/context.test.ts
- packages-user/data-system/src/combat/combat.test.ts
- - .planning/phases/06-unit-tests/COVERAGE.md
+ - .planning/phases/06-unit-tests/06-COVERAGE-MAP.md
autonomous: true
requirements: [TEST-01]
estimate:
- tokens: 52000
- raw_tokens: 52000
+ tokens: 64000
+ raw_tokens: 64000
tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 '为核心系统(数据层等)补齐单元测试' is treated as satisfied by behavior unit tests over the seven in-scope data-layer systems, gated by `pnpm test:ci` green; no ROADMAP edge verifier resolved this requirement (spec-less probe returned unclassified/unresolved), so this remains an assumption the user must confirm, not a proven edge."
- - DamageContext/DamageSystem return calculator output and emit logger codes 106/107 on missing calculator/hero, and expose the `with(hero)` isolated context
- - MapDamage registers converter/reducer, reduces separated damages, warns 102/103 on missing converter/reducer, and drops enemy-sourced damage on deleteEnemy
- - EnemyContext resolves locators/views/computed enemies and returns null for unknown lookups
- - CombatFlow rejects foreign-state contexts/damage with warn 138, rejects duplicate script priority with warn 140, and runs scripts/hooks in the documented order
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:`EnemyContext`(含 `resize`/`clear`/`destroy`/`buildup`/`requestRefresh`/`markDirty`/`scanRange`/`iterateEnemy`/全部 register/unregister/bind/attach/get/set/delete 与 `addAura`/`deleteAura`)、`DamageContext`、`DamageSystem`、`MapDamage`、`CombatFlow` 的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法一律排除(D-27 / D-30)。"
+ - "code 全覆盖:本计划可达的 warn/error 码 97、98、99、100、101、110(光环/修饰器流水线)、106、107(伤害上下文/系统)、102、103、104(地图伤害)、138、139、140、141(战斗流程)各至少一条触发断言,经 `logger.catch` / `logger.error` spy 观测(D-24 / D-31);105 在当前实现不可达,排除。"
+ - "光环范围:Full(`haloRange<=0`)/ Rect(`haloSquare`)/ Manhattan 三种范围全部覆盖,并断言光环范围之外的怪物不被加成(D-22)。"
+ - "嵌套光环:一层(光环施加特殊属性)与两层(光环→特殊属性→再经转换器产生新光环→再施加效果)均覆盖,并含优先级边界(新增光环只能影响更低优先级阶段;同/跨优先级传播)(D-23)。"
+ - "效果阶段顺序:显式断言 `buildupSpecials` → `buildupBase` → `buildupQuery` → `buildupFinal` 四阶段顺序,并验证阶段间可见性(前阶段修改被后阶段看到,后阶段不能反向影响前阶段)(D-25)。"
+ - "属性→伤害联动:`final-effect`/光环改完属性后,`DamageContext` 缓存与脏标记随之更新,`getDamageInfo`/`markDirty`/`deleteEnemy`/`with(hero)` 行为正确(注入 fake `IDamageCalculator`)(D-26)。"
+ - "刷新路径:`buildup`(全量)与 `requestRefresh`(局部)两条路径都覆盖;`resize`/`clear`/`destroy` 生命周期清空并解绑附件(D-27)。"
artifacts:
- - path: packages-user/data-system/src/combat/damage.test.ts
- provides: DamageContext/DamageSystem behavior coverage
- - path: packages-user/data-system/src/combat/mapDamage.test.ts
- provides: MapDamage behavior coverage
- path: packages-user/data-system/src/combat/context.test.ts
- provides: EnemyContext behavior coverage
+ provides: EnemyContext 全接口、光环流水线、效果阶段顺序与生命周期覆盖
+ - path: packages-user/data-system/src/combat/damage.test.ts
+ provides: DamageContext/DamageSystem 覆盖(含 D-26 联动)
+ - path: packages-user/data-system/src/combat/mapDamage.test.ts
+ provides: MapDamage 覆盖
- path: packages-user/data-system/src/combat/combat.test.ts
- provides: CombatFlow behavior coverage
- - path: .planning/phases/06-unit-tests/COVERAGE.md
- provides: api-coverage declaration for this test-only phase
+ provides: CombatFlow 覆盖
+ - path: .planning/phases/06-unit-tests/06-COVERAGE-MAP.md
+ provides: 阶段级 code → 模块 → 用例 映射表(本计划创建并写入 06-01 行)
key_links:
- - "damage.test.ts / combat.test.ts drive `DamageContext`, `DamageSystem`, `CombatFlow` through injected fake `IEnemyContext`/`IDamageCalculator` without mocking the data-layer interfaces"
- - "all four files import the system under test through its own module path plus `@user/data-base` / `@user/data-common` barrels, proving the public import chain in Node"
+ - "context.test.ts 用测试用 fake `IAuraConverter`/`IAuraView` 手动装配(`new EnemyContext()` + `registerAuraConverter` + `setEnemyAt` + `bindHero`)驱动,绝不引用/重测顶层真实实现(D-21)"
+ - "全部文件经自身模块路径 + `@user/data-base` / `@user/data-common` / `@motajs/common` barrel 导入,证明 Node 下公开导入链可用,不 mock 数据层接口"
+ - "`.planning/phases/06-unit-tests/06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):本计划首次写入时创建表头(`| code | 模块 | 用例 | 计划 |`)+ 06-01 小节;若表头已存在则只追加/更新本小节,绝不重写他节。后续计划用同一幂等协议,因此与其它 wave 1 计划执行顺序无关"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只新增 `*.test.ts` 与规划产物"
+ - "不安装覆盖率工具(`@vitest/coverage-*`),不设覆盖率阈值(D-09)"
+ - "不引入 `fake-indexeddb`,不做 CoreState 端到端存读档(D-10)"
+ - "疑似 bug 不得弱化为通过;按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "不建立跨文件共享 fixture/factory helper,fixture 全部文件内联(D-02/D-03)"
+ - "异步路径只用真实计时器,禁用 `vi.useFakeTimers()`(D-04)"
+ - "不测试 `saveState`/`loadState`(集中到 06-09)(D-32)"
---
-Establish the phase's test harness with a leading end-to-end tracer, then complete behavior coverage for the Layer-2 combat system `packages-user/data-system/src/combat` (D-01, D-15).
+以一条端到端 tracer 建立战斗系统(`packages-user/data-system/src/combat`)测试骨架,随后按 D-21..D-27 完成 `EnemyContext` 光环流水线、效果组合、四阶段顺序与全接口/全 code 覆盖(D-15 深化)。
-Phase constraints. D-04: async action paths (combat scripts/hooks) use real timers and await controller completion, never fake timers; D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command; D-09: coverage is reference-only, not a judgment standard, so no coverage tooling is introduced and `@vitest/coverage-*` is not installed.
+Purpose: `data-system/src/combat` 是纯 Layer-2 战斗契约,无 DOM、经注入 fake 协作者驱动,是证明「Node 全局 stub + 动态 import 包 + fake 注入 + `logger.catch` + 真实计时器异步」测试骨架的最干净位置。本计划同时建立阶段级 `06-COVERAGE-MAP.md`。
-Purpose: Prove the Node test harness (global stubs, dynamic module bag, injected fake collaborators, `logger.catch` assertion, real-timer async) on one real system path before the rest of the phase expands. `packages-user/data-system/src/combat` is the pure Layer-2 combat contract (`DamageContext`/`DamageSystem`/`MapDamage`/`EnemyContext`/`CombatFlow`) and the cleanest place to prove the tracer because it is driven entirely through injected `IEnemyContext`/`IDamageCalculator` fakes with no DOM.
-
-Output: 4 new co-located `*.test.ts` files, a `COVERAGE.md` api-coverage declaration, and the initial `06-TEST-FINDINGS.md` schema (shared append-only sink).
+Output: 4 个同目录 `*.test.ts` 与 `06-COVERAGE-MAP.md`(表头 + 06-01 行)。
@@ -71,9 +74,9 @@ Output: 4 new co-located `*.test.ts` files, a `COVERAGE.md` api-coverage declara
@.planning/phases/06-unit-tests/06-PATTERNS.md
@.planning/codebase/TESTING.md
@dev.md
+@packages-user/data-system/src/combat/context.ts
@packages-user/data-system/src/combat/damage.ts
@packages-user/data-system/src/combat/mapDamage.ts
-@packages-user/data-system/src/combat/context.ts
@packages-user/data-system/src/combat/combat.ts
@packages-user/data-system/src/combat/types.ts
@packages-user/data-system/src/combat/enemy.ts
@@ -82,144 +85,130 @@ Output: 4 new co-located `*.test.ts` files, a `COVERAGE.md` api-coverage declara
@packages-user/data-common/src/store/tileStore.test.ts
-
-| Source | Item | Covered by |
-|--------|------|-----------|
-| GOAL | 为核心系统(数据层等)补齐单元测试 (data-layer slice) | 06-01..06-09 |
-| GOAL | Phase 6 non-data (render/legacy) coverage | Deferred per 06-CONTEXT `` (not planned) |
-| REQ | TEST-01 | 06-01..06-09 |
-| CONTEXT | D-01 mixed tiers | 06-01/02/03 integration-capable; 06-05..09 pure/DI |
-| CONTEXT | D-02/D-03 minimal inline fixtures | 06-01..06-09 |
-| CONTEXT | D-04 real timers + await onEnd | 06-01 (combat flow), 06-05/06 (mover), 06-08 |
-| CONTEXT | D-05/D-06 skip + 06-TEST-FINDINGS.md | 06-01 creates sink; all plans append |
-| CONTEXT | D-07 per-system report, no core edits | all |
-| CONTEXT | D-08 `pnpm test:ci` gate | all |
-| CONTEXT | D-09 no coverage tooling | all |
-| CONTEXT | D-10..D-13 saveables round-trip | 06-08 |
-| CONTEXT | D-14 enemy data model full | 06-03 |
-| CONTEXT | D-15 combat built-ins run | 06-01/02/08 |
-| CONTEXT | D-16 replay array + system + first divergence | 06-04/08 |
-| CONTEXT | D-17 hero each subsystem | 06-05/06 |
-| CONTEXT | D-18 map expanded coverage | 06-07 |
-| CONTEXT | D-19 flag public surface | 06-09 |
-| CONTEXT | D-20 common natural/ minimal direct | 06-09 |
-| RESEARCH | none (no RESEARCH.md; PATTERNS.md is the pattern source) | n/a |
-| ASSUMPTION | TEST-01 edge probe unclassified/unresolved | Flagged in must_haves.truths (above); user confirmation required |
-
-
-- This phase writes tests only. Do not create or edit any file under `packages-user/*/src` except the `*.test.ts` files listed in `files_modified`.
-- Every `it(...)` must be immediately preceded by a single-line Chinese comment describing the covered behavior (dev.md 注释规范). Update the comment when the test scope changes.
-- Use real timers and `await controller/`sandbox` completion` for async paths; never `vi.useFakeTimers()` (D-04).
-- If a test exposes a suspected bug: write the test to the CORRECT expectation, mark it `it.skip` / `it.todo`, and put a Chinese comment pointing at `06-TEST-FINDINGS.md` using a plan-scoped anchor `#06-01-N`. `pnpm test:ci` MUST stay green.
-- `06-TEST-FINDINGS.md` is a shared append-only sink and is intentionally NOT listed in `files_modified` so wave-1 plans stay parallel; append your own plan-scoped section, never rewrite other sections.
-- Report findings per system in the plan SUMMARY; do not fix core code in this phase (D-07).
+- 只写测试,绝不修改 `packages-user/*/src` 下的非 `*.test.ts` 文件。
+- 每个 `it(...)` 之前必须有一行中文注释说明覆盖内容(dev.md 注释规范),测试范围变化时同步更新。
+- 异步动作(战斗脚本/钩子)用真实计时器 + await 控制器完成,禁用 `vi.useFakeTimers()`(D-04)。
+- 疑似 bug:按正确预期编写、标记 `it.skip`/`it.todo`、中文注释锚定 `#06-01-N` 并登记 `06-TEST-FINDINGS.md`;`pnpm test:ci` 必须保持全绿(D-05)。
+- 本计划不测 `saveState`/`loadState`(D-32)。
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写入前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本计划小节,绝不重写他节。其它计划使用同一协议,故执行顺序不影响结果。
+- **执行协议(D-33):执行本计划前必须先向用户确认;执行完成后暂停,用中文分条简要汇报本计划验证结果;详细结果写入共用的 `06-TEST-FINDINGS.md`。**
- Task 1: Tracer — DamageContext/DamageSystem behavior end-to-end (+ phase scaffolding)
- packages-user/data-system/src/combat/damage.test.ts, .planning/phases/06-unit-tests/COVERAGE.md
+ Task 1: Tracer — EnemyContext 光环流水线端到端最小路径(+ 阶段骨架)
+ packages-user/data-system/src/combat/context.test.ts, .planning/phases/06-unit-tests/06-COVERAGE-MAP.md
+
+ packages-user/data-system/src/combat/context.ts
+ packages-user/data-system/src/combat/types.ts
+ packages-user/data-system/src/combat/enemy.ts
+ packages-user/data-system/src/path/system.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(共享样板 1-5,A 节 context)
+ dev.md(注释规范)
+
+
+ 创建 `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 暂停并向用户提问,不修改核心代码。
+
+
+ pnpm exec vitest run packages-user/data-system/src/combat/context.test.ts
+ 聚焦运行出现任何未跳过失败,或文件因未解析的模块/全局而无法收集
+
+
+ - `context.test.ts` 在 `pnpm exec vitest run` 下零非跳过失败。
+ - 每个 `it` 前有单行中文注释。
+ - 至少断言一条完整光环路径:fake converter 命中 → 光环入表 → `buildup` → 范围内怪物基础属性被加成。
+ - 未修改任何生产文件;`git status` 只显示 `*.test.ts`(新增或 MODIFIED,既有测试文件允许被修改)与规划产物(`06-COVERAGE-MAP.md` / `06-TEST-FINDINGS.md`)。
+ - `06-COVERAGE-MAP.md` 存在且含表头与 06-01 小节(create-or-append:若表头已由其它计划建立,则直接追加/更新本小节而不再新建)。
+
+ 一条真实的 `EnemyContext` 光环端到端路径在提交前已跑绿,阶段 code 覆盖映射表骨架存在。
+
+
+
+ Task 2: EnemyContext 全接口、三范围、嵌套光环、警告码、四阶段顺序与两条刷新路径
+ packages-user/data-system/src/combat/context.test.ts
+
+ packages-user/data-system/src/combat/context.ts
+ packages-user/data-system/src/combat/types.ts
+ packages-user/data-system/src/combat/enemy.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(共享样板 3-4,A 节 context)
+
+
+ 在 `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-23):一层(光环→特殊属性);两层(光环施加特殊属性→该特殊属性经第二 converter 转出新光环→再施加效果);优先级边界:新转换光环优先级高于当前阶段时被跳过并告警 99;同/跨优先级传播。
+ - 警告码(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。
+
+
+ pnpm exec vitest run packages-user/data-system/src/combat/context.test.ts
+ 运行失败,或任一警告码 97/98/99/100/101/110、三范围、嵌套、四阶段顺序断言未被观测
+
+
+ - `context.test.ts` 零非跳过失败。
+ - 覆盖 `EnemyContext` 全部公开方法(至少一条正常用例)与全部列出警告码。
+ - 三范围 + 范围外不生效、一层/两层嵌套 + 优先级边界、四阶段顺序 + 阶段间可见性、全量/局部两条刷新路径、resize/clear/destroy 生命周期均有断言。
+ - 每个 `it` 前有单行中文注释。
+
+ `EnemyContext` 全接口、光环范围/嵌套/警告码、四阶段顺序与两条刷新路径全部覆盖并跑绿。
+
+
+
+ Task 3: DamageContext/DamageSystem(含 D-26 联动)、MapDamage、CombatFlow
+ 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
packages-user/data-system/src/combat/damage.ts
- packages-user/data-system/src/combat/types.ts
- packages-user/data-system/src/combat/context.ts
- packages-user/data-system/src/path/system.test.ts
- .planning/phases/06-unit-tests/06-PATTERNS.md (shared boilerplate 1-5, section A)
- dev.md (注释规范)
-
-
-Create `damage.test.ts` as the phase tracer: the thinnest real path that drives the Layer-2 combat damage contract end-to-end through its public classes in Node, with no DOM and no mocking of data-layer interfaces.
-
-Use the `vi.hoisted` global-stub + dynamic-import bag boilerplate from 06-PATTERNS.md shared pattern 1 (copy the `mapLifecycle.test.ts` shape) because the import chain reaches `@user/data-common` and `@motajs/common`.
-
-Build a minimal inline fake `IEnemyContext` (or reuse an inline fake shape) and a minimal inline fake `IDamageCalculator` whose `calculate(handler)` returns a fixed `{ damage, turn }`, then:
-- construct `DamageContext` with the fake context and assert `getDamageInfo(view)` / `getDamageInfoByComputed(enemy)` return `{ handler, damage, turn }` for the calculator result and `handler` identity matches the computed enemy + locator + hero.
-- assert `getDamageInfoByHandler(handler)` returns the raw calculator result.
-- assert missing calculator and missing hero paths emit logger codes 106 and 107 using `logger.catch` (never `expect(...).toThrow`) per 06-PATTERNS.md shared pattern 4.
-- construct `DamageSystem` and assert caching: two consecutive `getDamageInfo` calls return the same object, `markDirty(view)` / `markAllDirty()` / `deleteEnemy(view)` force recalculation, `useCalculator` / `bindHeroStatus` call `markAllDirty`, and `with(hero)` returns a `DamageContext` sharing the calculator and returning the passed hero.
-- cover `calculateCritical(view, attribute, precision)` generator behavior on a driven critical limit using a fake calculator whose `getCriticalLimit` returns a known bound, plus the early-return guards (no hero / no calculator / no locator / already at upper limit).
-
-Then create the phase scaffolding:
-- `COVERAGE.md` in the phase directory with a one-paragraph reasoned declaration: "No external API integration: this phase adds only in-repo Vitest behavior tests over the Node-only data layer; no external SDK/HTTP/CLI client is added or exercised." (api-coverage contribution).
-- Create `.planning/phases/06-unit-tests/06-TEST-FINDINGS.md` with a header and the D-06 column table: 模块/接口、现象、最小复现、疑似原因、影响面、建议修复方向、关联 skip 用例、严重度.
-
-Verify with a focused run before moving on: the tracer MUST pass green on its own.
-
-
- pnpm exec vitest run packages-user/data-system/src/combat/damage.test.ts
- the focused vitest run reports any failed or unexpectedly skipped test, or the file cannot be collected due to an unresolved module/global
-
-
- - `damage.test.ts` runs green under `pnpm exec vitest run` with zero non-skipped failures.
- - Every `it` has an immediately preceding single-line Chinese comment.
- - The file asserts at least: normal result spread, handler identity, logger codes 106 and 107, cache hit/invalidation, `with(hero)`, and one `calculateCritical` yield path.
- - No production file is modified; `git status` shows only new test files plus `COVERAGE.md`.
- - `COVERAGE.md` and `06-TEST-FINDINGS.md` exist with the required header/columns.
-
- One real combat damage path is proven end-to-end green, and the phase's findings/coverage scaffolding exists; this sink is where D-05 skipped correct-expectation tests point.
-
-
-
- Task 2: MapDamage + EnemyContext behavior
- packages-user/data-system/src/combat/mapDamage.test.ts, packages-user/data-system/src/combat/context.test.ts
-
packages-user/data-system/src/combat/mapDamage.ts
- packages-user/data-system/src/combat/context.ts
- packages-user/data-system/src/combat/enemy.ts
- packages-user/data-system/src/combat/types.ts
- packages-user/data-common/src/store/tileStore.test.ts (registry/contract table pattern)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: mapDamage, context)
-
-
-`mapDamage.test.ts`: drive `MapDamage` through inline fake `IEnemyContext` + fake `IMapDamageConverter` + fake `IMapDamageReducer` + fake `IMapDamageView` whose `getRange`/`getRangeParam`/`getDamageWithoutCheck` are deterministic. Assert: `addMapDamage`/`deleteMapDamage` accumulate sourceless damage and mark the index dirty; `getReducedDamage` returns the reducer result and null when no separated damage; missing reducer warns 103, missing converter warns 102; `getSeparatedDamage` unions sourced+sourceless; `deleteEnemy` removes sourced damage; `markEnemyDirty` warns 104 when the view has no store and no locator. Use `logger.catch` for warning branches.
-
-`context.test.ts`: drive `EnemyContext` with inline real `Enemy`-like objects (or the real `Enemy` from `@user/data-base` through a created prefab) and assert the registry contract in the `tileStore` style: `setEnemyAt` then `getEnemyByLocator`/`getEnemyByLoc`/`getEnemyLocatorByView`/`getEnemyLocator`/`getViewByComputed` return the registered objects; unknown locators/locators for unknown objects return `null`; `deleteEnemy` removes view+locator mappings; `resize` clears and sets `width`/`height` and indexer width; `scanRange`/`iterateEnemy` yield the registered entries; register/unregister aura converters, common query effects, special query effects, and final effects (assert add then remove leaves the registry empty). Keep fixtures inline and minimal (D-02/D-03). Do not mock data-layer interfaces.
-
-
- pnpm exec vitest run packages-user/data-system/src/combat/mapDamage.test.ts packages-user/data-system/src/combat/context.test.ts
- either focused run fails, or a warning assertion does not observe the documented logger code
-
-
- - Both files run green with zero non-skipped failures.
- - `mapDamage.test.ts` asserts warn codes 102, 103, 104 and reduce/delete behavior.
- - `context.test.ts` asserts register/lookup/unknown-null/delete/resize for the context registry.
- - Fixtures are constructed inline; no shared helper file is created.
- - Every `it` has a preceding single-line Chinese comment.
-
- MapDamage and EnemyContext public behavior is covered and green.
-
-
-
- Task 3: CombatFlow binding, script ordering, and battle flow
- packages-user/data-system/src/combat/combat.test.ts
-
packages-user/data-system/src/combat/combat.ts
packages-user/data-system/src/combat/types.ts
- packages-user/data-system/src/event/eventDispatch.test.ts (fake collaborator + manual-resolver await proof)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: combat)
+ packages-user/data-system/src/combat/context.ts
+ packages-user/data-system/src/event/eventDispatch.test.ts
+ packages-user/data-common/src/store/tileStore.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(A 节 damage/mapDamage/combat)
-Drive `CombatFlow` with a minimal inline fake `IStateBase` and fake enemy view / context / damage objects:
-- `bindContext`/`bindDamage` accept same-`state` objects and reject foreign-state objects with logger warn 138 (assert via `logger.catch`); passing `null` clears them.
-- `bindHero(null)`/`bindHero(hero)` set `hero`.
-- `addCombatScript` sorts by descending priority, rejects a duplicate priority with warn 140 and leaves the list unchanged.
-- `battle(view)` and `battleComputed(enemy)` return `null` and warn 139 when context or hero is missing; warn 139 for the missing damage context and warn 141 when `getDamageInfoByHandler` returns null.
-- Async completion proof (real timers, no fake timers): a script whose `before` resolves after a manually-released promise must complete before `onAfterCombat` runs; assert call order `[script.before, hooks.onBeforeCombat, script.after, hooks.onAfterCombat]` and that a `before` returning truthy short-circuits (skips hooks/after).
-Use the manual-resolver await pattern from `eventDispatch.test.ts` referenced in 06-PATTERNS.md. Keep all fixtures inline.
+ `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`)。
+ - `DamageSystem` 缓存:连续两次 `getDamageInfo` 同对象;`markDirty(view)`/`markAllDirty()`/`deleteEnemy(view)` 强制重算;`useCalculator`/`bindHeroStatus` 触发 `markAllDirty`。
+ - D-26 联动:在 `EnemyContext` 完成 `buildup`(fake 光环改属性)后接入该 `DamageSystem`,断言 `getDamageInfo` 反映新属性、且 `buildup`/`clear`/`setEnemyAt` 使缓存失效;`with(hero)` 返回共享 calculator、返回传入 hero 的 `DamageContext`。
+ - `calculateCritical(view, attribute, precision)` 生成器:用 fake `getCriticalLimit` 与已知临界值,断言 yield 的临界点与 `info`;已知 `#06-01-1`(`info.damage` 与 `nextValue` 不对应)继续以正确预期 + `it.skip` 保留。
+
+ `mapDamage.test.ts`:fake `IEnemyContext` + fake `IMapDamageConverter`/`IMapDamageReducer`/`IMapDamageView`(`getRange`/`getRangeParam`/`getDamageWithoutCheck` 确定)。
+ - `addMapDamage`/`deleteMapDamage` 累积无来源伤害并标脏;`getReducedDamage` 返回 reducer 结果、无分离伤害返回 null;缺 reducer 告警 103、缺 converter 告警 102;`getSeparatedDamage` 合并有来源 + 无来源;`deleteEnemy` 移除有来源;`markEnemyDirty` 在 view 无 store 且无 locator 时告警 104。已知 `#06-01-2`(`deleteEnemy` 幽灵伤害)以正确预期 + `it.skip` 保留。105 在当前实现不可达,不写断言。
+
+ `combat.test.ts`:fake `IStateBase` 与 fake enemy view/context/damage 驱动 `CombatFlow`:
+ - `bindContext`/`bindDamage` 接受同 state 对象、拒绝异 state 对象告警 138(`logger.catch`),传 null 清空。
+ - `bindHero(null)`/`bindHero(hero)` 设置 hero。
+ - `addCombatScript` 按优先级降序;重复优先级告警 140 且列表不变。
+ - `battle(view)`/`battleComputed(enemy)` 在缺 context 或 hero 时返回 null 并告警 139;缺 damage context 告警 139;`getDamageInfoByHandler` 返回 null 告警 141。
+ - 异步完成证明(真实计时器,无 fake timers):手动 resolver 控制的 `before` 必须先于 `onAfterCombat` 完成;断言调用顺序 `[script.before, hooks.onBeforeCombat, script.after, hooks.onAfterCombat]`。已知 `#06-01-3`(`before` 真值短路与文档相反)以正确预期 + `it.skip` 保留并注明按接口文档语义。
- pnpm exec vitest run packages-user/data-system/src/combat/combat.test.ts
- the focused run fails or the ordering assertion does not observe the documented before/after sequence
+ pnpm exec vitest run 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
+ 任一聚焦运行失败,或 106/107/102/103/104/138/139/140/141 中任一码未被观测
- - `combat.test.ts` runs green with zero non-skipped failures.
- - Asserts warn codes 138, 139, 140, 141 and the script/hook ordering with an awaited promise.
- - No fake timers used; async proof relies on real timers plus a manual resolver.
- - Every `it` has a preceding single-line Chinese comment.
+ - 三个文件零非跳过失败。
+ - damage.test.ts 覆盖缓存/失效/`with(hero)`/`calculateCritical` 并断言 106、107;D-26 联动有断言。
+ - mapDamage.test.ts 断言 102、103、104 与 reduce/delete 行为。
+ - combat.test.ts 断言 138、139、140、141 与 awaited 脚本顺序。
+ - 无 fake timers;`it.skip` 用例与 `06-TEST-FINDINGS.md` 的 `#06-01-N` 一一对应。
+ - 每个 `it` 前有单行中文注释。
- CombatFlow binding, guard warnings, and awaited script ordering are covered and green.
+ 伤害上下文/系统、地图伤害、战斗流程覆盖并跑绿,且四阶段/联动边界与既有 `#06-01-N` 跳过用例保留。
@@ -229,50 +218,50 @@ Use the manual-resolver await pattern from `eventDispatch.test.ts` referenced in
| Boundary | Description |
|----------|-------------|
-| none | This plan only adds in-repo Node Vitest tests; it introduces no runtime input surface, network call, DOM access, or filesystem write. |
+| none | 仅新增仓库内 Node Vitest 测试;不引入运行时输入面、网络调用、DOM 访问或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-01-01 | Tampering | test files under `packages-user/data-system/src/combat` | low | mitigate | Tests import only tracked project modules and construct synthetic in-memory fixtures; no package install and no core source edit (gate: `pnpm test:ci` plus review that only `*.test.ts` changed). |
-| T-06-01-02 | Information disclosure | test fixtures | low | accept | Fixtures are synthetic numbers/ids; no secrets or real game data are read (D-02). |
-| T-06-01-03 | Elevation of privilege | test execution environment | low | accept | Vitest runs in Node without elevated privileges; no network or filesystem side effects are introduced by the tests. |
-| T-06-01-SC | Tampering | package installs | high | mitigate | No new dependency is installed in this plan; if any install becomes necessary, stop and require a package-legitimacy checkpoint before proceeding. |
+| T-06-01-01 | Tampering | `data-system/src/combat` 下测试文件 | low | mitigate | 只导入仓库内已跟踪模块、构造合成内存 fixture;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-01-02 | Information disclosure | 测试 fixture | low | accept | 合成数值/id,无密钥、无真实游戏数据(D-02)。 |
+| T-06-01-03 | Denial of service | 假光环/效果无限循环 | medium | mitigate | `buildup` 的优先级 while 循环用有限优先级集合;每条用例有界,30s 测试超时为兜底。 |
+| T-06-01-SC | Tampering | 包安装 | high | mitigate | 本计划不安装任何新依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- `pnpm exec vitest run packages-user/data-system/src/combat/damage.test.ts packages-user/data-system/src/combat/mapDamage.test.ts packages-user/data-system/src/combat/context.test.ts packages-user/data-system/src/combat/combat.test.ts` passes.
-- `pnpm test:ci` passes for the full suite (existing 18 test files stay green).
-- `git status` confirms only new `*.test.ts`, `COVERAGE.md`, and `06-TEST-FINDINGS.md` are added; no production file is modified.
-- No `it.skip`/`it.todo` is added unless a corresponding `#06-01-N` entry exists in `06-TEST-FINDINGS.md`.
+- 聚焦运行:`packages-user/data-system/src/combat` 下 4 个测试文件全部通过。
+- `pnpm test:ci` 全量绿(既有测试文件保持绿)。
+- `git status` 仅显示 `*.test.ts`(新增或 MODIFIED,既有测试文件允许被修改)与规划产物(`06-COVERAGE-MAP.md` / `06-TEST-FINDINGS.md`);无生产文件改动。
+- 任何 `it.skip`/`it.todo` 均有对应 `#06-01-N` 的 `06-TEST-FINDINGS.md` 条目。
+- **执行协议**:执行前向用户确认;执行后暂停并以中文分条汇报。
-- 4 combat Layer-2 behavior test files exist and pass.
-- Warn codes 106/107/138/139/140/141 and MapDamage 102/103/104 are asserted through `logger.catch`.
-- The tracer passed on its own before expansion tasks ran.
-- `COVERAGE.md` declaration and the `06-TEST-FINDINGS.md` schema exist.
+- 4 个战斗 Layer-2 行为测试文件存在并通过。
+- D-21..D-27 的光环范围/嵌套/优先级/警告码/四阶段顺序/属性→伤害联动全部断言。
+- `EnemyContext` 每个公开方法至少一条正常用例;15 个可达 code 全部触发断言。
+- `06-COVERAGE-MAP.md` 表头与 06-01 行存在。
Test files:
+- `packages-user/data-system/src/combat/context.test.ts`
- `packages-user/data-system/src/combat/damage.test.ts`
- `packages-user/data-system/src/combat/mapDamage.test.ts`
-- `packages-user/data-system/src/combat/context.test.ts`
- `packages-user/data-system/src/combat/combat.test.ts`
-Inline helper symbols (defined once per file, not shared):
-- `damage.test.ts`: `TestModules`, `modules`, `createEnemyContext`, `createCalculator`, `createEnemy`
-- `mapDamage.test.ts`: `createMapDamageFixture`, `FakeConverter`, `FakeReducer`, `FakeView`
-- `context.test.ts`: `createContextFixture`, `createEnemy`
-- `combat.test.ts`: `createCombatFixture`, `FakeScript`, `createDeferred`
+Inline helper symbols(每文件内联,不共享):
+- `context.test.ts`: `TestModules`、`modules`、`FakeConverter`、`FakeAura`、`createContextFixture`
+- `damage.test.ts`: `createEnemyContext`、`createCalculator`、`createHandler`
+- `mapDamage.test.ts`: `createMapDamageFixture`、`FakeConverter`、`FakeReducer`、`FakeView`
+- `combat.test.ts`: `createCombatFixture`、`FakeScript`、`createDeferred`
Planning artifacts:
-- `.planning/phases/06-unit-tests/COVERAGE.md`
-- `.planning/phases/06-unit-tests/06-TEST-FINDINGS.md` (initial header + schema)
+- `.planning/phases/06-unit-tests/06-COVERAGE-MAP.md`(create-or-append:表头 + 06-01 行;若表头已存在则仅追加/更新本小节)
diff --git a/.planning/phases/06-unit-tests/06-02-PLAN.md b/.planning/phases/06-unit-tests/06-02-PLAN.md
index 98a2916..489bf7f 100644
--- a/.planning/phases/06-unit-tests/06-02-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-02-PLAN.md
@@ -14,51 +14,53 @@ files_modified:
autonomous: true
requirements: [TEST-01]
estimate:
- tokens: 58000
- raw_tokens: 58000
+ tokens: 56000
+ raw_tokens: 56000
tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - MainDamageCalculator produces the documented damage/turn for each special branch (无敌/魔攻/连击/支援/先攻/破甲/反击/净化/固伤/仇恨/吸血/负伤) against a synthetic handler
- - MainEnemyFinalEffect applies 坚固 and 模仿; MainEnemyComparer compares base attributes and deep-equal specials
- - Aura converters select the correct range and apply hp/atk/def buffs; GuardAura registers guard locators
- - registerSpecials registers the full special code table (0-27) and guard defaults
- - Map damage views compute range params and per-locator damage; the converter builds views per special; the reducer sums damage and unions catch/repulse extras
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:`MainDamageCalculator`(`calculate`/`getCriticalLimit`)、`MainEnemyFinalEffect`(`apply`/`priority`)、`MainEnemyComparer`(`compare`)、`CommonAura`/`CommonAuraConverter`、`GuardAura`/`GuardAuraConverter`、`registerSpecials`、mapDamage 五视图 + `MainMapDamageConverter` + `MainMapDamageReducer` 的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法排除(D-30 / D-35)。"
+ - "code 全覆盖:本计划可达的 warn 码 137(支援怪缺失 guard)至少一条触发断言,经 `logger.catch` 观测(D-31);数据层 `logger.error(53)` 归 06-03,本计划用 fake manager 不触发。"
+ - "本计划只测顶层实现的**基本功能**(单分支、按预期输出);属性/加成等**组合语义后移至 06-07**(D-35)。"
+ - "`MainDamageCalculator.calculate` 对 无敌/魔攻/连击/多段/支援/先攻/破甲/反击/净化/吸血/固伤/仇恨/负伤 各单分支输出确定 `{damage,turn}`;`getCriticalLimit` 对 坚固 与一般怪正确。"
+ - "`CommonAura`/`GuardAura` 各自转换器 `shouldConvert` 仅匹配对应 code,范围参数与基础/guard 加成按单分支计算。"
+ - "`registerSpecials` 注册 code 0-27 各一次并设置 guard 属性默认值。"
+ - "mapDamage 五视图单分支输出、converter 按 special 产生视图、reducer 求和/取最大类型/合并 extra。"
artifacts:
- path: packages-user/data-state/src/enemy/calculator.test.ts
- provides: MainDamageCalculator branch coverage
+ provides: MainDamageCalculator 与 getCriticalLimit 覆盖
- path: packages-user/data-state/src/enemy/final.test.ts
- provides: MainEnemyFinalEffect coverage
+ provides: MainEnemyFinalEffect 覆盖
- path: packages-user/data-state/src/enemy/comparer.test.ts
- provides: MainEnemyComparer coverage
+ provides: MainEnemyComparer 覆盖
- path: packages-user/data-state/src/enemy/aura.test.ts
- provides: CommonAura/GuardAura coverage
+ provides: CommonAura/GuardAura 及转换器覆盖
- path: packages-user/data-state/src/enemy/special.test.ts
- provides: registerSpecials coverage
+ provides: registerSpecials 覆盖
- path: packages-user/data-state/src/enemy/mapDamage.test.ts
- provides: map damage view/converter/reducer coverage
+ provides: mapDamage 视图/converter/reducer 覆盖
key_links:
- - "calculator.test.ts builds an inline fake `IReadonlyEnemyHandler` (enemy/context/locator/hero/state) exactly as `data-state/test/dataClosure.test.ts` does, extending the existing partial coverage rather than duplicating it"
- - "mapDamage.test.ts drives the view classes and `MainMapDamageConverter`/`MainMapDamageReducer` through the `@user/data-system` interfaces"
+ - "calculator.test.ts 复用 `data-state/test/dataClosure.test.ts` 的 inline fake `IReadonlyEnemyHandler` 形状,扩展既有单点覆盖而非重复"
+ - "special.test.ts 用 inline fake `IEnemyManager`(不 mock 数据层接口)驱动 registerSpecials"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-02 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它 wave 1 计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只新增 `*.test.ts`"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`,不做 CoreState 端到端存读档(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "不使用 fake timers(D-04)"
+ - "不测 `saveState`/`loadState`(D-32),不测属性/加成组合语义(D-35,移入 06-07)"
---
-Complete behavior coverage for the top-level combat implementation `packages-user/data-state/src/enemy` (D-01, D-15): damage calculation, final effects, comparison, auras, special registration, and map damage.
+覆盖 `packages-user/data-state/src/enemy` 顶层战斗实现的**基本功能**(D-35):伤害计算、最终效果、比较器、光环、特殊属性注册、地图伤害。
-Phase constraints. D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command.
+Purpose: 这些类是接入 `CoreState` 的真实内置战斗实现;`dataClosure.test.ts` 仅覆盖单个确定的 `MainDamageCalculator` 场景。本计划只验证每个组件的单分支/单路径正确性,把跨特殊属性、跨光环/效果的**组合**留到 06-07。
-Purpose: These classes are the real built-in combat implementation wired into `CoreState`. `dataClosure.test.ts` covers only a single deterministic `MainDamageCalculator` case; every special branch, the guard/support path, aura conversion, and the map-damage views are currently untested.
-
-Output: 6 new co-located `*.test.ts` files.
+Output: 6 个同目录 `*.test.ts`。
@@ -79,124 +81,123 @@ Output: 6 new co-located `*.test.ts` files.
@packages-user/data-state/src/enemy/final.ts
@packages-user/data-state/src/enemy/types.ts
@packages-user/data-state/test/dataClosure.test.ts
-@packages-user/data-state/src/replay/commands.test.ts
@packages-user/data-system/src/path/system.test.ts
+@packages-user/data-state/src/replay/commands.test.ts
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures are inline and minimal (D-02/D-03); do not read real game data.
-- Use `logger.catch` / `vi.spyOn(logger, ...)` for warning paths (never `expect(...).toThrow`).
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-02-N` into `06-TEST-FINDINGS.md`; `pnpm test:ci` stays green.
+- 只写测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03);不读取真实游戏数据。
+- 警告路径经 `logger.catch` / `vi.spyOn(logger, ...)`,绝不 `expect(...).toThrow`。
+- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-02-N`;`pnpm test:ci` 保持全绿。
+- 本计划只做单分支基本功能;`saveState`/`loadState` 与组合语义不测(D-32 / D-35)。
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-02 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- Task 1: MainDamageCalculator + final effect + comparer
+ Task 1: MainDamageCalculator 单分支 + getCriticalLimit + MainEnemyFinalEffect + MainEnemyComparer
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
packages-user/data-state/src/enemy/calculator.ts
packages-user/data-state/src/enemy/final.ts
packages-user/data-state/src/enemy/comparer.ts
- packages-user/data-state/test/dataClosure.test.ts (existing calculator fixture and handler shape)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: calculator, final, comparer)
+ packages-user/data-state/src/enemy/types.ts
+ packages-user/data-state/test/dataClosure.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(A 节 calculator/final/comparer)
-`calculator.test.ts`: build one inline handler factory (enemy, context, locator, hero, state) in the `dataClosure.test.ts` style and, for each branch of `MainDamageCalculator.calculate`, assert exact `{ damage, turn }`:
-- baseline: heroPerDamage = atk - monDef; turn = ceil(monHp / heroPerDamage).
-- 无敌 (20) returns `{ damage: Infinity, turn: 0 }` unless `hero.items.getItemState('cross')?.count >= 1`; assert both sides by faking `state.hero.items.getItemState`.
-- 魔攻 (2) uses monAtk without subtracting hero def; 连击 4/5 multiply enemyPerDamage by 2/3; 多段 (6) multiplies by `value`.
-- 支援 guard (guard set non-empty, not already inGuard): recursively adds the guard's turn/damage; missing guard locator emits warn 137 via `logger.catch`; assert `inGuard` does not leak (a second top-level calculate is unaffected).
-- 先攻 (1) adds one enemyPerDamage; 破甲 (7) adds value%/100 * def; 反击 (8) adds value%/100 * atk to enemyPerDamage; 净化 (9) adds value * mdef.
-- 吸血 (11): value%/100 * hp added to damage, and to monHp only when `add` is true.
-- 负伤 flag: `state.flags.getFieldValueDefaults('enableNegativeDamage', false)` gates clamping negative damage to 0; 固伤 (22) and 仇恨 (17, `flags.getFieldValueDefaults('hatred', 0)`) bypass mdef.
-- return value `damage` is floored.
-- `getCriticalLimit('atk')`: 坚固 (3) returns Infinity, otherwise `def + hp`.
+ `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`)。
+ - 魔攻(2)用 monAtk 不减 hero def;连击 4/5 敌伤乘 2/3;多段(6)乘 `value`。
+ - 先攻(1)加一次敌伤;破甲(7)加 `value%/100 * def`;反击(8)加 `value%/100 * atk` 到敌伤;净化(9)加 `value * mdef`;吸血(11)`value%/100 * hp` 加到伤害、`add` 为真时加到 monHp;固伤(22)与仇恨(17,`flags.getFieldValueDefaults('hatred',0)`)绕过 mdef;负伤 flag 控制负伤害截断为 0。
+ - 支援 guard(单分支):guard 非空且未 inGuard 时递归累加 guard 的 turn/damage;guard 位置缺失时经 `logger.catch` 观测 warn 137。
+ - 返回值 `damage` 取整;`getCriticalLimit('atk')`:坚固(3)返回 Infinity,否则 `def + hp`。
-`final.test.ts`: assert `MainEnemyFinalEffect.apply` raises def to `max(def, heroAtk - 1)` for 坚固 (3), copies hero atk/def for 模仿 (10), and leaves other enemies untouched; `priority` is 0.
+ `final.test.ts`:`MainEnemyFinalEffect.apply` 对 坚固(3)把 def 抬到 `max(def, heroAtk - 1)`、对 模仿(10)复制 hero atk/def、其它怪不变;`priority` 为 0。
-`comparer.test.ts`: assert `MainEnemyComparer.compare` is true for identical attributes+specials and false when any base attribute differs or when special sets differ in length/code/value (`deepEqualsTo`).
+ `comparer.test.ts`:`MainEnemyComparer.compare` 属性 + specials 完全相同时为 true;任一基础属性不同、或 special 集合长度/code/value 不同(`deepEqualsTo`)为 false。
-Keep helper `createEnemy`/`createHandler` inline in each file (no cross-file fixture). If an expected value contradicts the implementation, follow the skip+findings rule.
+ helper `createEnemy`/`createHandler` 各文件内联;若期望与实现矛盾,按 skip+findings 规则处理。
pnpm exec vitest run 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
- the focused run fails, or a branch assertion does not match the documented arithmetic
+ 任一聚焦运行失败,或某单分支期望值与文档算术不符、或 warn 137 未被观测
- - All three files pass with zero non-skipped failures.
- - calculator.test.ts asserts every listed special branch and warn 137.
- - final.test.ts and comparer.test.ts assert their documented branches.
- - Every `it` has a preceding single-line Chinese comment.
+ - 三个文件零非跳过失败。
+ - calculator.test.ts 断言每个列出分支与 warn 137;final.test.ts 与 comparer.test.ts 断言其文档分支。
+ - 每个 `it` 前有单行中文注释。
- Calculator, final effect, and comparer behavior is covered and green.
+ 伤害计算、最终效果、比较器的单分支基本功能覆盖并跑绿。
- Task 2: Auras + special registration
+ Task 2: CommonAura/GuardAura 与 registerSpecials
packages-user/data-state/src/enemy/aura.test.ts, packages-user/data-state/src/enemy/special.test.ts
packages-user/data-state/src/enemy/aura.ts
packages-user/data-state/src/enemy/special.ts
- packages-user/data-base/src/enemy/special.ts (defineCommonSerializableSpecial / defineNonePropertySpecial)
- packages-user/data-common/src/store/tileStore.test.ts (registry contract pattern)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: aura, special)
+ packages-user/data-base/src/enemy/special.ts
+ packages-user/data-common/src/store/tileStore.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(A 节 aura/special)
-`aura.test.ts`: with inline minimal `ISpecial` objects and a fake enemy/context/locator:
-- `CommonAuraConverter.shouldConvert` is true only for code 25.
-- `CommonAura.createRange`/`getRangeParam`: `haloRange <= 0` uses full range (undefined param); `haloSquare` uses a rect centered on the locator; otherwise a Manhattan range.
-- `CommonAura.apply` adds `floor(base * hpBuff/100)`, `floor(base * atkBuff/100)`, `floor(base * defBuff/100)` to the computed enemy, and no-ops when a buff is 0; `couldApplyBase` is true, `couldApplySpecial` false, `priority` 25.
-- `GuardAuraConverter.shouldConvert` is true only for code 26; `GuardAura.getRangeParam` is the 3x3 rect; `apply` adds the guard locator to `enemy.getAttribute('guard')` for a different locator and skips the source's own locator; when `context.getViewByComputed` returns null, `apply` no-ops.
+ `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。
+ - `GuardAuraConverter.shouldConvert` 仅对 code 26 为真;`GuardAura.getRangeParam` 为 3x3 rect;`apply` 对**不同** locator 把 guard 位置加入 `enemy.getAttribute('guard')`、跳过来源自身 locator;`context.getViewByComputed` 返回 null 时 no-op。
-`special.test.ts`: call `registerSpecials` on an inline fake `IEnemyManager` and assert every code 0-27 is registered exactly once with a `getSpecialName`/`getDescription` function, and `setAttributeDefaults('guard', ...)` was called; spot-check `getSpecialName` for a serializable special (e.g. code 6 returns `n连击` for value n; code 25 returns a halo description) and a none-property special (code 1 「先攻」). Use a minimal inline fake manager (no mock of data-layer interfaces). Keep fixtures inline.
+ `special.test.ts`:用 inline fake `IEnemyManager` 调用 `registerSpecials`,断言 code 0-27 各注册一次且带 `getSpecialName`/`getDescription`,并调用了 `setAttributeDefaults('guard', ...)`;抽查一个可序列化 special(如 code 6 返回 `n连击`)与一个 none-property special(如 code 1「先攻」)的名称/描述。不 mock 数据层接口。fixture 内联。
pnpm exec vitest run packages-user/data-state/src/enemy/aura.test.ts packages-user/data-state/src/enemy/special.test.ts
- the focused run fails, or a registration/buff assertion does not match the documented behavior
+ 运行失败,或转换器匹配/范围参数/加成数值/注册数量断言与文档行为不符
- - Both files pass with zero non-skipped failures.
- - aura.test.ts asserts range selection, buff math, and guard-locator behavior.
- - special.test.ts asserts codes 0-27 registered once plus guard defaults and name/description spot checks.
- - Every `it` has a preceding single-line Chinese comment.
+ - 两个文件零非跳过失败。
+ - aura.test.ts 断言范围选择、buff 数学与 guard-locator 行为(各单分支)。
+ - special.test.ts 断言 code 0-27 各注册一次 + guard 默认值 + 名称/描述抽查。
+ - 每个 `it` 前有单行中文注释。
- Aura conversion and the special registry are covered and green.
+ 光环转换与特殊属性注册的单分支基本功能覆盖并跑绿。
- Task 3: Map damage views, converter, reducer
+ Task 3: mapDamage 五视图 + converter + reducer(单分支)
packages-user/data-state/src/enemy/mapDamage.test.ts
packages-user/data-state/src/enemy/mapDamage.ts
- packages-user/data-state/src/enemy/special.ts (IZoneValue)
- packages-user/data-state/src/enemy/types.ts (MapDamageType)
- packages-user/data-system/src/combat/mapDamage.ts (IMapDamage* contracts)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: mapDamage)
+ packages-user/data-state/src/enemy/special.ts
+ packages-user/data-state/src/enemy/types.ts
+ packages-user/data-system/src/combat/mapDamage.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(A 节 mapDamage)
-Build an inline fake `IEnemyContext` (with `indexer`, `getEnemyByLoc`, `getDamageSystem`) and inline `ISpecial` values, then assert:
-- `ZoneDamageView`: range is Manhattan or rect per `zoneSquare`; `getRangeParam` matches `range`/centering; `getDamageWithoutCheck` returns `{ damage: zone, type: MapDamageType.Zone, extra: {catch, repulse} }`.
-- `RepulseDamageView`: Manhattan radius 1; returns null at the source locator; otherwise type `Repulse` with `repulse` containing the source locator.
-- `LaserDamageView`: range param uses the face handler's `mapMovement` directions; damage type `Layer`.
-- `BetweenDamageView`: only the forward-adjacent locator (delta sum 1 and both deltas positive) qualifies; requires the neighbor to have special 16; returns half of hero final hp; when `core.flags.betweenAttackMax` is true it uses the damage system's computed damages (fall back to half when the system is missing); otherwise returns half directly.
-- `AmbushDamageView`: type `Unknown`, damage 0, catch contains the locator.
-- `MainMapDamageConverter.convert` pushes a view per present special (15, 16, 18, 24, 27) and uses `state.faceManager.get(FaceGroup.Dir4)!` for the laser view.
-- `MainMapDamageReducer.reduce`: sums damage, picks the type of the max-damage item, and unions `catch`/`repulse`.
-Guard the `core.*` global reads with the shared global stub if the import chain requires it. Keep fixtures inline; if a `core`-dependent expectation is environment-sensitive, follow the skip+findings rule rather than weakening it.
+ 构建 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。
+ - `LaserDamageView`:range param 使用 face handler `mapMovement` 方向;damage type `Layer`。
+ - `BetweenDamageView`:按实现实际语义断言(排除 `deltaX<=0 && deltaY<=0`,即向右/向下相邻命中,避免同对怪重复计算;06-02 SUMMARY 已澄清),需邻居含 special 16;返回 hero 最终 hp 的一半;`core.flags.betweenAttackMax` 为真时用伤害系统 computed 值(系统缺失回退一半)。
+ - `AmbushDamageView`:type `Unknown`,damage 0,catch 含 locator。
+ - `MainMapDamageConverter.convert` 对存在的 special(15/16/18/24/27)各推入一个 view,laser 用 `state.faceManager.get(FaceGroup.Dir4)!`。
+ - `MainMapDamageReducer.reduce` 求和、取最大伤害项的类型、合并 `catch`/`repulse`。
+ 如 import 链需要,使用共享全局 stub;fixture 内联。若某 `core` 依赖期望随环境变化,按 skip+findings 规则处理。
pnpm exec vitest run packages-user/data-state/src/enemy/mapDamage.test.ts
- the focused run fails, or a view/reducer assertion does not match the documented output
+ 运行失败,或某视图/reducer 的分支输出与实现实际语义不符
- - `mapDamage.test.ts` passes with zero non-skipped failures.
- - Asserts each of the five views, the converter's per-special view set, and the reducer's sum/type/extra union.
- - Every `it` has a preceding single-line Chinese comment.
+ - `mapDamage.test.ts` 零非跳过失败。
+ - 五视图单分支、converter 的每 special 视图集合、reducer 的求和/类型/extra 合并均有断言。
+ - 每个 `it` 前有单行中文注释。
- Map damage views, converter, and reducer behavior is covered and green.
+ 地图伤害视图、converter、reducer 的单分支基本功能覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(code 137 → calculator.ts)写入 `06-COVERAGE-MAP.md` 的 06-02 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -206,27 +207,30 @@ Guard the `core.*` global reads with the shared global stub if the import chain
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-02-01 | Tampering | tests under `packages-user/data-state/src/enemy` | low | mitigate | Only tracked in-repo modules are imported; fixtures are synthetic; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-02-02 | Information disclosure | fixtures | low | accept | Synthetic numbers/ids only; no real game data or secrets (D-02). |
-| T-06-02-03 | Denial of service | global monkey-patching in tests | low | accept | Any `vi.stubGlobal` is cleaned up in `afterAll`; tests do not run long loops or spawn processes. |
-| T-06-02-SC | Tampering | package installs | high | mitigate | No new dependency; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-02-01 | Tampering | `data-state/src/enemy` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成 fixture;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-02-02 | Information disclosure | fixture | low | accept | 合成数值/id,无真实游戏数据或密钥(D-02)。 |
+| T-06-02-03 | Repudiation | 全局 stub 泄漏 | low | accept | 任何 `vi.stubGlobal` 在 `afterAll` 拆除;用例相互独立。 |
+| T-06-02-SC | Tampering | 包安装 | high | mitigate | 不新增依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for all 6 files pass; `pnpm test:ci` full suite stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-02-N` entry in `06-TEST-FINDINGS.md`.
+- 聚焦运行全部 6 个文件通过;`pnpm test:ci` 全量绿。
+- `git status` 仅显示 `*.test.ts`(新增或 MODIFIED,既有测试文件允许被修改)与规划产物(`06-COVERAGE-MAP.md` / `06-TEST-FINDINGS.md`);无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-02-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-02 小节(code 137);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前向用户确认;执行后暂停并用中文分条汇报。
-- 6 `data-state/src/enemy` behavior test files exist and pass.
-- Every special branch of `MainDamageCalculator`, the aura converters, the special registry, and the map-damage views/converter/reducer are asserted.
+- 6 个 `data-state/src/enemy` 行为测试文件存在或已修改并通过(既有文件允许 MODIFIED)。
+- 顶层实现在单分支层面的全部公开方法均有正常用例,warn 137 有触发断言。
+- 组合语义未在本计划出现(留给 06-07)。
@@ -239,14 +243,17 @@ Test files:
- `packages-user/data-state/src/enemy/mapDamage.test.ts`
Inline helper symbols:
-- `calculator.test.ts`: `createHandler`, `createEnemy`, `special`
-- `final.test.ts`: `createHandler`, `createEnemy`
+- `calculator.test.ts`: `createHandler`、`createEnemy`、`special`
+- `final.test.ts`: `createHandler`、`createEnemy`
- `comparer.test.ts`: `createEnemy`
-- `aura.test.ts`: `createEnemy`, `createSpecial`, `createContext`
+- `aura.test.ts`: `createEnemy`、`createSpecial`、`createContext`
- `special.test.ts`: `FakeEnemyManager`
-- `mapDamage.test.ts`: `createContext`, `createSpecial`, `FakeFaceHandler`
+- `mapDamage.test.ts`: `createContext`、`createSpecial`、`FakeFaceHandler`
+
+Planning artifacts:
+- `06-COVERAGE-MAP.md` create-or-append 06-02 小节(code 137 → calculator.ts → 用例名)
diff --git a/.planning/phases/06-unit-tests/06-03-PLAN.md b/.planning/phases/06-unit-tests/06-03-PLAN.md
index 9764b4e..1c5d7a3 100644
--- a/.planning/phases/06-unit-tests/06-03-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-03-PLAN.md
@@ -11,43 +11,46 @@ files_modified:
autonomous: true
requirements: [TEST-01]
estimate:
- tokens: 42000
- raw_tokens: 42000
+ tokens: 48000
+ raw_tokens: 48000
tasks: 2
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - Enemy attribute get/set/add, special add/get/has/iterate/delete, clone/cloneAttributes/copyFrom, and saveState/loadState round-trip behave as documented
- - CommonSerializableSpecial / NonePropertySpecial clone/save/load and deepEqualsTo behave as documented
- - EnemyManager registry (special/attribute), prefab by code/id, reuse mapping, dirty tracking, and compareWith/comparer logic behave as documented
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:`Enemy`、`CommonSerializableSpecial`、`NonePropertySpecial`、`EnemyManager` 的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法(`IEnemyLegacyBridge`、`fromLegacyEnemy`、`legacyIdToCode` 等)一律排除(D-14 / D-30 / D-36)。"
+ - "code 全覆盖:本计划可达的 warn/error 码 53(setAttributeDefaults 非法默认值)、96(重复 special)、117(compareWith 多次调用)、118(无 comparer 时 updateDirty)各至少一条触发断言(D-31 / D-36)。"
+ - "loadState 专属码 119(prefab 缺失)、120(special 缺失)与 `saveState`/`loadState` 一并归 06-09,本计划不测(D-32)。"
+ - "`Enemy` 属性增删改查、special 增删查迭代、`clone`/`cloneAttributes`/`copyFrom` 行为与文档一致且相互独立。"
+ - "`CommonSerializableSpecial`/`NonePropertySpecial` 的 value 读写、`getSpecialName`/`getDescription`、clone 独立性、`deepEqualsTo` 判定与文档一致。"
+ - "`EnemyManager` 注册表(special/attribute 默认值)、prefab(按 code/id 的增删改查与 clone 独立性)、reuse 映射、`modifyPrefabAttribute`、comparer/脏跟踪生命周期行为与文档一致。"
artifacts:
- path: packages-user/data-base/src/enemy/enemy.test.ts
- provides: Enemy model coverage
+ provides: Enemy 模型覆盖
- path: packages-user/data-base/src/enemy/special.test.ts
- provides: special model coverage
+ provides: CommonSerializableSpecial/NonePropertySpecial 覆盖
- path: packages-user/data-base/src/enemy/manager.test.ts
- provides: EnemyManager registry/dirty coverage
+ provides: EnemyManager 注册表/prefab/reuse/脏跟踪覆盖
key_links:
- - "enemy.test.ts extends the exact behavior already covered in `data-state/test/dataClosure.test.ts:104-118` (create/mutate/save/load) to the full `Enemy` surface"
- - "manager.test.ts drives `EnemyManager` through an inline fake `IEnemyLegacyBridge` and inline `Enemy` instances, following the tileStore registry contract shape"
+ - "enemy.test.ts 扩展 `data-state/test/dataClosure.test.ts:104-118` 的 create/mutate 形状到完整 `Enemy` 表面(去掉其 save/load 部分)"
+ - "manager.test.ts 用 inline fake `IEnemyLegacyBridge` 与 inline `Enemy` 实例驱动,遵循 tileStore 注册表契约形状"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-03 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它 wave 1 计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只新增 `*.test.ts`"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "不使用 fake timers(D-04)"
+ - "不测 `saveState`/`loadState`(D-32),不测任何名称含 legacy 的接口/方法(D-30)"
---
-Cover the Layer-1 enemy data model `packages-user/data-base/src/enemy` in full (D-14): `Enemy`, the special implementations, and `EnemyManager`.
+完整覆盖 Layer-1 怪物数据模型 `packages-user/data-base/src/enemy`(D-14/D-36):`Enemy`、special 实现、`EnemyManager` 公开接口(除 legacy)。
-Phase constraints. D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command.
+Purpose: D-14 要求验证数据模型本身(属性/special 增删改查、注册表、模板、复用映射、比较器/脏跟踪),而不只是消费它的战斗计算器。存读档集中到 06-09。
-Purpose: D-14 requires the data model itself (attribute/special CRUD, save/load, dirty tracking, registry, prefab creation by code/id, reuse mapping, comparers) to be verified, not only the combat calculators that consume it.
-
-Output: 3 new co-located `*.test.ts` files.
+Output: 3 个同目录 `*.test.ts`。
@@ -69,76 +72,82 @@ Output: 3 new co-located `*.test.ts` files.
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures inline and minimal (D-02/D-03). Use `SaveCompression` for save/load.
-- Warning paths via `logger.catch` / `vi.spyOn(logger, ...)`.
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-03-N`; `pnpm test:ci` stays green.
+- 只写测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03)。
+- 警告路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
+- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-03-N`;`pnpm test:ci` 保持全绿。
+- 不测存读档(D-32);排除一切 legacy 接口/方法(D-30)。
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-03 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- Task 1: Enemy model + special implementations
+ Task 1: Enemy 模型 + special 实现
packages-user/data-base/src/enemy/enemy.test.ts, packages-user/data-base/src/enemy/special.test.ts
packages-user/data-base/src/enemy/enemy.ts
packages-user/data-base/src/enemy/special.ts
packages-user/data-base/src/enemy/types.ts
- packages-user/data-state/test/dataClosure.test.ts (create/mutate/save/load enemy shape)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: enemy, special)
+ packages-user/data-state/test/dataClosure.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(A 节 enemy/special)
-`enemy.test.ts`: construct `Enemy` with an inline `IEnemyAttr`-shaped object and cover:
-- `getAttribute`/`setAttribute`/`addAttribute` on numeric keys; `cloneAttributes` returns a structured-cloned, non-aliased copy.
-- special CRUD: `addSpecial` then `getSpecial`/`hasSpecial`/`iterateSpecials`; adding a duplicate code warns 96 and does not replace the existing special; `deleteSpecial` by object and by numeric code removes it; unknown code returns null.
-- `clone`/`copyFrom`: clone copies attributes and cloned specials and is independent of the source; `copyFrom` replaces both attributes and specials.
-- `saveState(compression)` / `loadState(state, compression)`: attribute round-trip; a special present on the instance but missing from the saved map warns 120 and is left unchanged; `saveState` returns a deep clone (mutating the returned attrs does not change the enemy).
+ `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)。
-`special.test.ts`: use `defineCommonSerializableSpecial` / `defineNonePropertySpecial` with an inline config to cover `CommonSerializableSpecial` (setValue/getValue, getSpecialName, getDescription, fromLegacyEnemy via an inline legacy object, clone independence, saveState/loadState round-trip including structured-clone semantics, deepEqualsTo true for same code+equal value and false for code/value mismatch) and `NonePropertySpecial` (value undefined, clone, saveState returns undefined, loadState no-op, deepEqualsTo by code).
+ `special.test.ts`:用 `defineCommonSerializableSpecial`/`defineNonePropertySpecial` + inline 配置覆盖:
+ - `CommonSerializableSpecial`:`setValue`/`getValue`、`getSpecialName`、`getDescription`、clone 独立性、`deepEqualsTo` 对同 code+同值为 true、对 code 或 value 不同为 false。
+ - `NonePropertySpecial`:value 为 undefined、clone、`deepEqualsTo` 按 code 判定。
+ - 不调用任何 legacy 命名接口(如 `fromLegacyEnemy`),不测其 `saveState`/`loadState`。
pnpm exec vitest run packages-user/data-base/src/enemy/enemy.test.ts packages-user/data-base/src/enemy/special.test.ts
- the focused run fails, or the duplicate-special warn 96 / missing-special warn 120 is not observed
+ 聚焦运行失败,或重复 special 的 warn 96 未被观测,或 clone 独立性断言失败
- - Both files pass with zero non-skipped failures.
- - enemy.test.ts asserts attribute/special CRUD, clone/copyFrom independence, save/load round-trip, warn 120, and the deep-clone contract of saveState.
- - special.test.ts asserts both special classes' full surface.
- - Every `it` has a preceding single-line Chinese comment.
+ - 两个文件零非跳过失败。
+ - enemy.test.ts 断言属性/special CRUD、clone/copyFrom 独立性、warn 96。
+ - special.test.ts 断言两个 special 类的全部公开方法(除 legacy 与存读档)。
+ - 每个 `it` 前有单行中文注释。
- Enemy and special data models are covered and green.
+ Enemy 与 special 数据模型覆盖并跑绿。
- Task 2: EnemyManager registry, prefabs, reuse, dirty tracking
+ Task 2: EnemyManager 注册表、prefab、reuse、脏跟踪
packages-user/data-base/src/enemy/manager.test.ts
packages-user/data-base/src/enemy/manager.ts
packages-user/data-base/src/enemy/types.ts
packages-user/data-common/src/store/tileStore.test.ts
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: manager)
+ .planning/phases/06-unit-tests/06-PATTERNS.md(A 节 manager)
-Drive `EnemyManager` with an inline fake `IEnemyLegacyBridge` (its `fromLegacyEnemy(enemy, attrs)` returns synthetic attrs) and inline `Enemy` prefabs. Assert the registry contract in the tileStore style:
-- `registerSpecial`/`registerSpecial` overwrite is allowed (last registration wins for the code); `setAttributeDefaults` sets a default and rejects function/symbol/bigint/undefined with `logger.error(53)`.
-- `addPrefab` clones and ignores a duplicate code or id; `createEnemy(code)` / `createEnemyById(id)` return independent clones and null for unknown; `getPrefab`/`getPrefabById` return the stored prefab; `deletePrefab` by code and by id removes both indexes; `changePrefab` replaces and marks dirty.
-- `reusePrefab(source, code, id)`: `getPrefab`/`getPrefabById` and `createEnemy` resolve through the reuse mapping to the source prefab; unknown source is a no-op.
-- `modifyPrefabAttribute` applies the modify callback, re-indexes on changed code/id, and marks dirty; unknown code is a no-op.
-- comparator + dirty: before any `compareWith`, dirty tracking is inert; `attachEnemyComparer` + first `compareWith(reference)` clears the dirty set; a second `compareWith` warns 117 and refreshes dirty; `updateDirty` without a comparer warns 118 and marks dirty; `refreshDirty` drops entries equal to the reference.
-- `saveState(compression)` serializes only dirty codes; `loadState` warns 119 for a code missing from the prefab table and refreshes dirty afterwards.
-Keep inline fixtures; assert warning codes via `logger.catch`.
+ 用 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` 替换并标脏。
+ - `reusePrefab(source, code, id)`:`getPrefab`/`getPrefabById`/`createEnemy` 经复用映射解析到来源 prefab;未知来源 no-op。
+ - `modifyPrefabAttribute` 应用回调、code/id 变化时重建索引并标脏;未知 code no-op。
+ - comparer + 脏跟踪:任何 `compareWith` 前脏跟踪惰性;`attachEnemyComparer` + 首次 `compareWith(reference)` 清空脏集;第二次 `compareWith` 告警 117 并刷新脏集;无 comparer 的 `updateDirty` 告警 118 并标脏;`refreshDirty` 丢弃与 reference 相等的条目。
+ - 不测 `saveState`/`loadState`(含 119/120),集中到 06-09。
+ fixture 内联;警告码经 `logger.catch` 观测。
pnpm exec vitest run packages-user/data-base/src/enemy/manager.test.ts
- the focused run fails, or a registry/dirty assertion or a warning code (53/117/118/119) is not observed
+ 聚焦运行失败,或注册表/脏跟踪断言、或 53/117/118 中任一码未被观测
- - `manager.test.ts` passes with zero non-skipped failures.
- - Asserts prefab create/get/delete/change, reuse mapping, modify, comparator dirty lifecycle, save/load, and warn codes 53/117/118/119.
- - Every `it` has a preceding single-line Chinese comment.
+ - `manager.test.ts` 零非跳过失败。
+ - 断言 prefab create/get/delete/change、reuse 映射、modify、comparer 脏跟踪生命周期,以及 warn 53/117/118。
+ - 每个 `it` 前有单行中文注释。
- EnemyManager registry, prefab, reuse, and dirty behavior is covered and green.
+ EnemyManager 注册表、prefab、reuse、脏跟踪覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(53/96/117/118)写入 `06-COVERAGE-MAP.md` 的 06-03 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -148,27 +157,29 @@ Keep inline fixtures; assert warning codes via `logger.catch`.
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-03-01 | Tampering | tests under `packages-user/data-base/src/enemy` | low | mitigate | Only tracked in-repo modules; synthetic fixtures; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-03-02 | Information disclosure | fixtures | low | accept | Synthetic attrs/specials only; no real game data (D-02). |
-| T-06-03-03 | Repudiation | global stub leakage | low | accept | Any `vi.stubGlobal` is torn down in `afterAll`; tests are independent. |
-| T-06-03-SC | Tampering | package installs | high | mitigate | No new dependency; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-03-01 | Tampering | `data-base/src/enemy` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成 fixture;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-03-02 | Information disclosure | fixture | low | accept | 合成属性/special,无真实游戏数据(D-02)。 |
+| T-06-03-03 | Repudiation | 全局 stub 泄漏 | low | accept | 任何 `vi.stubGlobal` 在 `afterAll` 拆除;用例独立。 |
+| T-06-03-SC | Tampering | 包安装 | high | mitigate | 不新增依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for all 3 files pass; `pnpm test:ci` stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-03-N` entry in `06-TEST-FINDINGS.md`.
+- 三个文件聚焦运行通过;`pnpm test:ci` 全量绿。
+- `git status` 仅新增 `*.test.ts`;无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-03-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-03 小节(code 53/96/117/118);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前向用户确认;执行后暂停并用中文分条汇报。
-- 3 `data-base/src/enemy` behavior test files exist and pass.
-- D-14's full data-model surface (attributes/specials/save/load/dirty/registry/prefab/reuse/comparer) is asserted.
+- 3 个 `data-base/src/enemy` 行为测试文件存在并通过。
+- D-14/D-36 的数据模型表面(属性/special/注册表/prefab/reuse/comparer 脏)全部断言;legacy 与存读档未测。
@@ -178,11 +189,14 @@ Test files:
- `packages-user/data-base/src/enemy/manager.test.ts`
Inline helper symbols:
-- `enemy.test.ts`: `createEnemy`, `createSpecial`
-- `special.test.ts`: `makeConfig`, `makeLegacyEnemy`
-- `manager.test.ts`: `FakeBridge`, `createPrefab`, `createManager`
+- `enemy.test.ts`: `createEnemy`、`createSpecial`
+- `special.test.ts`: `makeConfig`、`makeLegacyEnemy`(仅作合成属性载体,不调用 legacy 接口)
+- `manager.test.ts`: `FakeBridge`、`createPrefab`、`createManager`
+
+Planning artifacts:
+- `06-COVERAGE-MAP.md` create-or-append 06-03 小节
diff --git a/.planning/phases/06-unit-tests/06-04-PLAN.md b/.planning/phases/06-unit-tests/06-04-PLAN.md
index d2102d7..fb87b13 100644
--- a/.planning/phases/06-unit-tests/06-04-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-04-PLAN.md
@@ -5,57 +5,55 @@ type: execute
wave: 1
depends_on: []
files_modified:
- - packages-user/data-common/src/replay/system.test.ts
- packages-user/data-common/src/replay/array.test.ts
+ - packages-user/data-common/src/replay/system.test.ts
- packages-user/data-common/src/replay/sandbox.test.ts
- packages-user/data-common/src/replay/func.test.ts
- - packages-user/data-state/test/replayIntegration.test.ts
autonomous: true
requirements: [TEST-01]
estimate:
- tokens: 54000
- raw_tokens: 54000
+ tokens: 60000
+ raw_tokens: 60000
tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - ReplayArray stores commands/params, expands within bounds, and streams reads with the configured command width
- - ReplaySystem registers commands, rejects duplicates with warn 163, records to `route`, and dispatches hooks
- - ReplaySandbox steps sequentially, stops on first failure (warns 156/157/158/175), and handles play/pause/resume/stop
- - Replay safety decorators collect and end correctly (codes 159/161)
- - An integration replay against the fixed contract stops on first divergence and reports index/code/params
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:`ReplayArray`(`add`/`insert`/`delete`/`set`/`setCommandWidth`/`get`/`createReadStream`/`rebuildIndexArray`/`setReplayArray`/`getCommandArray`/`getParamArray`)、`ReplaySystem`(`registerCommand`/`getCommand`/`record`/`createReplaySandbox`/`releaseSandbox`)、`ReplaySandbox`(`setSpeed`/`getReplayed`/`play`/`pause`/`resume`/`stop`/`step`)以及 `func.ts` 播放安全装饰器/工具的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法排除(D-30 / D-37)。"
+ - "code 全覆盖:本计划可达的码 148(未知 param 类型)、149(expand 倍数非法)、150(数组达上限)、151(boolean 越界)、152(bigint 越界)、153(参数超过 255)、154(uint16→uint8 命令截断)、155(读流过期)、156(读流过期需重建沙箱)、157(未知命令)、158(步骤执行失败)、159(安全收集重复开始)、160(安全收集意外结束)、161(安全检测命中)、162(安全详情缺失)、163(命令重复注册)、175(notExecuted 失败)各至少一条触发断言,经 `logger.catch` / `logger.warn` spy 观测(D-31 / D-37)。"
+ - "编解码覆盖:boolean、整数(全部位宽)、bigint、string、数组参数各自经 `add`/`set` 写入后由 `createReadStream`/`get` 正确读回(type 与 value 一致)(D-37)。"
+ - "`ReplayArray` 全部公开操作(含 `rebuildIndexArray`/`setReplayArray`/`getCommandArray`/`getParamArray`/`commandWidth`)行为与文档一致;数组扩容后读回仍正确。"
+ - "`ReplaySandbox` 播放器 `step`/`play`/`pause`/`resume`/`stop` 与 `finalizeLast` 按顺序执行、失败即停,纯同步断言 + 真实计时器(无 fake timers)(D-04 / D-37)。"
+ - "录像系统**完整播放**与**二次录制比对**、`error 2001–2008` 归 06-07,本计划不做(D-40)。"
artifacts:
- - path: packages-user/data-common/src/replay/system.test.ts
- provides: ReplaySystem coverage
- path: packages-user/data-common/src/replay/array.test.ts
- provides: ReplayArray coverage
+ provides: ReplayArray 全操作 + 编解码覆盖
+ - path: packages-user/data-common/src/replay/system.test.ts
+ provides: ReplaySystem 覆盖
- path: packages-user/data-common/src/replay/sandbox.test.ts
- provides: ReplaySandbox coverage
+ provides: ReplaySandbox 播放器覆盖
- path: packages-user/data-common/src/replay/func.test.ts
- provides: replay-safety decorator coverage
- - path: packages-user/data-state/test/replayIntegration.test.ts
- provides: replay integration coverage
+ provides: 播放安全装饰器覆盖
key_links:
- - "sandbox.test.ts and replayIntegration.test.ts reuse the manual-stepping harness from `data-state/src/replay/commands.test.ts` and `data-state/test/nodeReplay.test.ts` (cast to a manual sandbox interface, drive `step()` directly)"
- - "replayIntegration.test.ts cross-checks behavior against `.planning/phases/03-data-completion/03-REPLAY-CONTRACT.md` and `03-REPLAY-DIAGNOSTICS.md`"
+ - "array.test.ts 用 `data-state/test/nodeReplay.test.ts` harness 作为最接近样板,仅经公开 `IReplayArray` 表面断言"
+ - "sandbox.test.ts 复用 `data-state/src/replay/commands.test.ts` 的手动 stepping harness(cast 到 manual sandbox 接口,直接驱动 `step()`)"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-04 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它 wave 1 计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只新增 `*.test.ts`"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "异步路径只用真实计时器,禁用 `vi.useFakeTimers()`(D-04)"
+ - "不测 `ReplayArray.saveState`/`loadState`(D-32),不做完整录像播放/二次录制(D-40,移入 06-07)"
---
-Cover the replay system `packages-user/data-common/src/replay` (array/system/sandbox/func) plus one integration path through `CoreState` (D-16).
+重点覆盖录像数组 `ReplayArray` 全部公开操作与编解码,并覆盖 `ReplaySystem` 注册/record、`ReplaySandbox` 播放器与播放安全装饰器(D-16 / D-37)。
-Phase constraints. D-04: async replay paths use real timers and await controller/sandbox completion (`await` controller `onEnd`), never fake timers, while pure computation asserts synchronously; D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command.
+Purpose: `array.ts`(825 行)此前无专门测试,其流式/扩容 API 仅被间接使用;本计划经公开 `IReplayArray` 表面系统验证读、编解码与播放控制。录像存读档集中到 06-09,完整播放/二次录制比对归 06-07。
-Purpose: D-16 requires the replay array read/write and the replay system itself to be verified against the existing contracts (`03-REPLAY-CONTRACT.md`, `03-REPLAY-DIAGNOSTICS.md`), including stopping and reporting the first divergence (Phase 3 D-07). `array.ts` (825 lines) currently has no dedicated test.
-
-Output: 4 new co-located `*.test.ts` files plus `data-state/test/replayIntegration.test.ts`.
+Output: 4 个同目录 `*.test.ts`。
@@ -77,111 +75,113 @@ Output: 4 new co-located `*.test.ts` files plus `data-state/test/replayIntegrati
@packages-user/data-common/src/replay/types.ts
@packages-user/data-state/src/replay/commands.test.ts
@packages-user/data-state/test/nodeReplay.test.ts
-@packages-user/data-state/test/dataClosure.test.ts
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures inline and minimal (D-02/D-03); small replay arrays.
-- Async paths use real timers; no fake timers (D-04).
-- Warning paths via `logger.catch` / `vi.spyOn(logger, ...)`.
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-04-N`; `pnpm test:ci` stays green.
+- 只写测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03);使用小录像数组。
+- 异步路径用真实计时器,禁用 fake timers(D-04)。
+- 警告路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
+- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-04-N`;`pnpm test:ci` 保持全绿。
+- 不测 `ReplayArray` 存读档(D-32);不做完整播放/二次录制(D-40)。
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-04 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- Task 1: ReplaySystem + ReplayArray
- packages-user/data-common/src/replay/system.test.ts, packages-user/data-common/src/replay/array.test.ts
+ Task 1: ReplayArray 全公开操作 + 编解码(boolean/整数全位宽/bigint/string/数组)
+ packages-user/data-common/src/replay/array.test.ts
- packages-user/data-common/src/replay/system.ts
packages-user/data-common/src/replay/array.ts
packages-user/data-common/src/replay/types.ts
- packages-user/data-state/src/replay/commands.test.ts (registration order / per-instance independence)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section B: array, system)
+ packages-user/data-state/test/nodeReplay.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(B 节 array)
-`system.test.ts`: construct a `ReplaySystem` and assert:
-- the constructor builds a `route` `ReplayArray` with the configured init lengths/width; `registerCommand` then `getCommand` returns the command and an unknown code returns null; a duplicate registration warns 163 and keeps the original; `record(code, ...params)` appends to `route` and fires `onRecordCommand` hooks with `(code, route.length, params)`; `createReplaySandbox({route, reseter, startIndex?})` calls `reseter.reset(config.save)`, stores `sandbox`, and fires `onCreateSandbox`; `releaseSandbox()` stops and clears it.
-- each `ReplaySystem` instance has independent registrations and route.
-
-`array.test.ts`: build a small `ReplayArray` (use the same init sizes/width as `system.ts:33-41`) and assert through the public `IReplayArray` surface referenced in `types.ts`: `add(command, params)` then streaming `createReadStream(0)` reads each step in order; `length`/expiry behavior at the end; `commandWidth` is honored; `saveState`/`loadState` round-trip a small route; expansion beyond init capacity still reads back correctly. Keep arrays tiny.
+ 用与 `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。
+ 数组保持极小;不测 `saveState`/`loadState`。
- pnpm exec vitest run packages-user/data-common/src/replay/system.test.ts packages-user/data-common/src/replay/array.test.ts
- the focused run fails, or the duplicate-registration warn 163 / route round-trip assertion is not observed
+ pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts
+ 运行失败,或 148/149/150/151/152/153/154/155 中任一码未被观测,或某类型编解码读回不一致
- - Both files pass with zero non-skipped failures.
- - system.test.ts asserts registration, warn 163, record+hook, sandbox creation/reset, and per-instance independence.
- - array.test.ts asserts add/stream/save/load over the public `IReplayArray` surface.
- - Every `it` has a preceding single-line Chinese comment.
+ - `array.test.ts` 零非跳过失败。
+ - 覆盖全部列出公开操作;boolean/整数(全位宽)/bigint/string/数组参数均至少一条读回断言。
+ - 断言码 148、149、150、151、152、153、154、155。
+ - 每个 `it` 前有单行中文注释。
- ReplaySystem and ReplayArray behavior is covered and green.
+ ReplayArray 全操作与全类型编解码覆盖并跑绿。
- Task 2: ReplaySandbox + replay safety helpers
- packages-user/data-common/src/replay/sandbox.test.ts, packages-user/data-common/src/replay/func.test.ts
+ Task 2: ReplaySystem 注册/record + ReplaySandbox 播放器
+ packages-user/data-common/src/replay/system.test.ts, packages-user/data-common/src/replay/sandbox.test.ts
+ packages-user/data-common/src/replay/system.ts
packages-user/data-common/src/replay/sandbox.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 节 system/sandbox)
+
+
+ `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 接口):
+ - `step()` 执行下一步、触发 `onStep`、成功返回 true;失败命令告警 158(含 code+params)并停止,后续不执行。
+ - `finalizeLast`:命令变化或读流过期时执行 `notExecuted`,失败告警 175;未知末命令告警 157;过期读流告警 156。
+ - `play`/`pause`/`resume`/`stop`:`pause()` 在暂停时 resolve;`resume()` 从暂停点继续;`stop()` 暂停并触发 `onStopReplay`;已在播放或已结束时 `play()` no-op;route 走完后 `ended` 置位。
+ - `setSpeed` 触发 `onSpeedSet`;`getReplayed()` 计数正确。
+ 用真实 promise,无 fake timers。
+
+
+ pnpm exec vitest run packages-user/data-common/src/replay/system.test.ts packages-user/data-common/src/replay/sandbox.test.ts
+ 运行失败,或 156/157/158/163/175 中任一码未被观测,或播放/暂停/继续顺序断言失败
+
+
+ - 两个文件零非跳过失败。
+ - system.test.ts 断言注册、163、record+hook、沙箱创建/reset、实例独立性。
+ - sandbox.test.ts 断言顺序步进、失败即停、156/157/158/175、play/pause/resume/stop、setSpeed。
+ - 每个 `it` 前有单行中文注释。
+
+ ReplaySystem 与 ReplaySandbox 播放器覆盖并跑绿。
+
+
+
+ Task 3: 播放安全装饰器/工具(func.ts)
+ packages-user/data-common/src/replay/func.test.ts
+
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 (manual sandbox stepping)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section B: sandbox, func)
+ packages-user/data-state/src/replay/commands.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(B 节 func)
-`sandbox.test.ts`: create a `ReplaySystem` with inline commands and drive the sandbox directly (cast to a manual sandbox interface as in `commands.test.ts`):
-- `step()` executes the next command, fires `onStep`, and returns true on success; a failing command warns 158 with code+params and stops (later commands do not execute).
-- `finalizeLast` runs `notExecuted` when the command changes or the reader expires; failure warns 175; an unknown last command warns 157; an expired reader warns 156.
-- `play`/`pause`/`resume`/`stop`: `pause()` resolves when replay pauses; `resume()` continues from the paused point; `stop()` pauses and fires `onStopReplay`; `play()` is a no-op when already playing or ended; `ended` is set after the route finishes.
-- `setSpeed` fires `onSpeedSet`.
-
-`func.test.ts`: cover the replay-safety decorator lifecycle from `func.ts` (`beginReplaySafetyCollection`, `endReplaySafetyCollection`, `logReplaySafetyDetail`, `shouldReplay`, `ignoreReplay`) using a minimal inline `IReplaySystem` and a decorated inline class/method; assert begin → nested decorated calls → end → detail behavior and the reset lifecycle (codes 159/161). Use `vi.spyOn(console/logger)` only where the source writes output. No fake timers — the `pause()`/`resume()` assertions use real promises.
+ 覆盖 `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。
- pnpm exec vitest run packages-user/data-common/src/replay/sandbox.test.ts packages-user/data-common/src/replay/func.test.ts
- the focused run fails, or a stop-on-failure warning (156/157/158/175) or a decorator lifecycle code (159/161) is not observed
+ pnpm exec vitest run packages-user/data-common/src/replay/func.test.ts
+ 运行失败,或 159/160/161/162 中任一码未被观测
- - Both files pass with zero non-skipped failures.
- - sandbox.test.ts asserts sequential stepping, first-failure stop, warn codes 156/157/158/175, and play/pause/resume/stop.
- - func.test.ts asserts the decorator lifecycle and codes 159/161.
- - Every `it` has a preceding single-line Chinese comment.
+ - `func.test.ts` 零非跳过失败。
+ - 断言装饰器生命周期(begin/nested/end/detail)与码 159/160/161/162。
+ - 每个 `it` 前有单行中文注释。
- ReplaySandbox and replay-safety decorators are covered and green.
-
-
-
- Task 3: Replay integration through CoreState
- packages-user/data-state/test/replayIntegration.test.ts
-
- packages-user/data-state/test/nodeReplay.test.ts
- packages-user/data-state/test/dataClosure.test.ts
- packages-user/data-state/test/fixtures/closed-loop.ts
- packages-user/data-state/src/replay/types.ts
- .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md
- .planning/phases/03-data-completion/03-REPLAY-DIAGNOSTICS.md
- .planning/phases/06-unit-tests/06-PATTERNS.md (section B: replayIntegration)
-
-
-Create `data-state/test/replayIntegration.test.ts` that drives `createCoreState()` plus `ReplaySystem`/`ReplaySandbox` end-to-end through a small fixed replay:
-- assert the eight stable commands are registered in order and every code resolves via `state.replaySystem.getCommand`.
-- record a short valid route, create a sandbox, play it to completion with real timers (`await` the sandbox end/`waitForEnded` pattern), and assert the recorded route round-trips.
-- first-divergence behavior (Phase 3 D-07 / D-16): a route whose second command fails must stop, keep later commands unexecuted, and emit warn 158 carrying the command code and params; cross-check against `03-REPLAY-DIAGNOSTICS.md`.
-Inline minimal fixtures; reuse `createCoreState()` (Node-safe). Do not touch `saveSystem.init`/IndexedDB. If a contract mismatch is found, write the correct-expectation test and mark it skipped with a `#06-04-N` findings entry.
-
-
- pnpm exec vitest run packages-user/data-state/test/replayIntegration.test.ts
- the focused run fails, or the first-divergence report does not include index/code/params
-
-
- - `replayIntegration.test.ts` passes with zero non-skipped failures.
- - Asserts the eight-command order, a successful short replay, and stop-on-first-divergence with warn 158.
- - Every `it` has a preceding single-line Chinese comment.
-
- Replay integration through CoreState is covered and green.
+ 播放安全装饰器/工具覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(148–163、175)写入 `06-COVERAGE-MAP.md` 的 06-04 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -191,45 +191,49 @@ Inline minimal fixtures; reuse `createCoreState()` (Node-safe). Do not touch `sa
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-04-01 | Tampering | tests under `data-common/src/replay` and `data-state/test` | low | mitigate | Only tracked in-repo modules; synthetic routes; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-04-02 | Denial of service | replay loop termination | medium | mitigate | Every loop is bounded (bounded `step()` count / `waitForEnded` iteration cap) so a non-terminating replay cannot hang the suite; the existing 30s test timeout is the backstop. |
-| T-06-04-03 | Information disclosure | fixtures | low | accept | Synthetic command arrays only; no real game data (D-02). |
-| T-06-04-SC | Tampering | package installs | high | mitigate | No new dependency; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-04-01 | Tampering | `data-common/src/replay` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成 route;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-04-02 | Denial of service | 播放循环不终止 | medium | mitigate | 所有循环有界(bounded `step()` 次数 / `waitForEnded` 迭代上限),30s 测试超时兜底。 |
+| T-06-04-03 | Information disclosure | fixture | low | accept | 合成命令数组,无真实游戏数据(D-02)。 |
+| T-06-04-SC | Tampering | 包安装 | high | mitigate | 不新增依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for all 5 files pass; `pnpm test:ci` stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-04-N` entry in `06-TEST-FINDINGS.md`.
+- 四个文件聚焦运行通过;`pnpm test:ci` 全量绿。
+- `git status` 仅新增 `*.test.ts`;无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-04-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-04 小节(17 个码);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前向用户确认;执行后暂停并用中文分条汇报。
-- 4 `data-common/src/replay` behavior test files plus `data-state/test/replayIntegration.test.ts` exist and pass.
-- First-divergence stop-and-report behavior is asserted against the recorded contract.
+- 4 个 `data-common/src/replay` 行为测试文件存在并通过。
+- `ReplayArray` 全公开操作与全类型编解码覆盖;`ReplaySystem`/`ReplaySandbox`/安全装饰器覆盖。
+- 未测存读档;未做完整播放/二次录制(归 06-07)。
Test files:
-- `packages-user/data-common/src/replay/system.test.ts`
- `packages-user/data-common/src/replay/array.test.ts`
+- `packages-user/data-common/src/replay/system.test.ts`
- `packages-user/data-common/src/replay/sandbox.test.ts`
- `packages-user/data-common/src/replay/func.test.ts`
-- `packages-user/data-state/test/replayIntegration.test.ts`
Inline helper symbols:
-- `system.test.ts`: `createSystem`, `createCommand`
-- `array.test.ts`: `createArray`, `createRoute`
-- `sandbox.test.ts`: `createSandbox`, `createCommand`
-- `func.test.ts`: `FakeReplaySystem`, `DecoratedTarget`
-- `replayIntegration.test.ts`: `waitForEnded`, `createFailingRoute`
+- `array.test.ts`: `createArray`、`createRoute`、`paramCases`
+- `system.test.ts`: `createSystem`、`createCommand`
+- `sandbox.test.ts`: `createSandbox`、`createCommand`
+- `func.test.ts`: `FakeReplaySystem`、`DecoratedTarget`
+
+Planning artifacts:
+- `06-COVERAGE-MAP.md` create-or-append 06-04 小节
diff --git a/.planning/phases/06-unit-tests/06-05-PLAN.md b/.planning/phases/06-unit-tests/06-05-PLAN.md
index 2a7c732..4dd3cce 100644
--- a/.planning/phases/06-unit-tests/06-05-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-05-PLAN.md
@@ -9,49 +9,68 @@ files_modified:
- 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
+ - 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
+ - packages-user/data-base/src/hero/mover.test.ts
+ - packages-user/data-base/src/hero/rendering.test.ts
autonomous: true
requirements: [TEST-01]
estimate:
- tokens: 46000
- raw_tokens: 46000
+ tokens: 72000
+ raw_tokens: 72000
tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - HeroAttribute computes base vs final values, sorts modifiers by priority, gates modifier saving, clones, and round-trips structured base values
- - ValueModifier/PercentageModifier modify values by the documented formulas
- - HeroLocation stores position/floor/face and round-trips its save state
- - HeroState wires its subsystems, creates/registers modifiers, and save/loads the full hero snapshot with explicit key-field assertions
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:`HeroAttribute`、`ValueModifier`/`PercentageModifier`、`HeroLocation`、`HeroState`、`HeroEquipment`、`EquipmentState`/`HeroEquipsStore`、`HeroItems`、`HeroFollower`/`HeroFollowersController`、`HeroMover`、`HeroRendering` 的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法排除(D-17 / D-30 / D-38)。"
+ - "code 全覆盖:本计划可达的码 108(重复 add 同一 modifier)、109(modifier 返回同引用)、116(未注册 modifier 类型)、142(follower id 无法转数字)、144(未绑定 hero move top 实现)、146(未知 uid 装备)、147(无可用装备槽)各至少一条触发断言(D-31)。"
+ - "loadState 专属码 58/59(equipStore load)与全部 `saveState`/`loadState` 测试归 06-09,本计划不测(D-32)。"
+ - "勇士每个子系统可正确运行:attribute(base/final/优先级/克隆)、modifier(公式)、location(位置/楼层/朝向/钩子)、state(装配/属性视图/modifier 注册/changeFloor 钩子顺序)、equipment(装备/卸下/替换/compareEquip)、equipStore(实例增删计数排序)、items(常量/消耗/装备/拾取)、follower(增删/gather 异步/链接)、mover(异步移动码)、rendering(alpha + 钩子)。"
artifacts:
- path: packages-user/data-base/src/hero/attribute.test.ts
- provides: HeroAttribute coverage
+ provides: HeroAttribute 覆盖
- path: packages-user/data-base/src/hero/modifier.test.ts
- provides: modifier coverage
+ provides: modifier 覆盖
- path: packages-user/data-base/src/hero/location.test.ts
- provides: HeroLocation coverage
+ provides: HeroLocation 覆盖
- path: packages-user/data-base/src/hero/state.test.ts
- provides: HeroState coverage
+ provides: HeroState 覆盖
+ - path: packages-user/data-base/src/hero/equipment.test.ts
+ provides: HeroEquipment 覆盖
+ - path: packages-user/data-base/src/hero/equipStore.test.ts
+ provides: EquipmentState/HeroEquipsStore 覆盖
+ - path: packages-user/data-base/src/hero/items.test.ts
+ provides: HeroItems 覆盖
+ - path: packages-user/data-base/src/hero/follower.test.ts
+ provides: follower 覆盖(含异步 gather)
+ - path: packages-user/data-base/src/hero/mover.test.ts
+ provides: HeroMover 异步移动覆盖
+ - path: packages-user/data-base/src/hero/rendering.test.ts
+ provides: HeroRendering 覆盖
key_links:
- - "state.test.ts extends the existing saveable-hero round-trip in `data-state/test/dataClosure.test.ts:169-179` with explicit key-field assertions (D-13) and modifier/follower recovery"
- - "location.test.ts mirrors the minimal CoreState fixture in `data-state/src/event/event.test.ts` for setFloor/setPos/face getters"
+ - "equipment.test.ts 沿用 `data-state/src/replay/commands.test.ts:200-253` 建立的 equip/unequip/getEquipped 边界形状"
+ - "mover.test.ts 沿用 `data-common/src/common/mover.test.ts` 的抽象 mover 模式:真实计时器 + `await controller.onEnd`(D-04)"
+ - "rendering.test.ts 用 Hookable 挂钩验证 `setAlpha` 触发 `onSetAlpha`"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-05 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它 wave 1 计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只新增 `*.test.ts`"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "异步路径只用真实计时器 + `await`,禁用 `vi.useFakeTimers()`(D-04)"
+ - "不测任何 `saveState`/`loadState`(D-32)"
---
-Cover the core hero data subsystems `packages-user/data-base/src/hero` — attribute, modifier, location, state (D-17).
+验证 `packages-user/data-base/src/hero` 下**每个勇士子系统**可正确运行(D-17 / D-38):attribute、modifier、location、state、equipment、equipStore、items、follower(异步)、mover(异步)、rendering。
-Phase constraints. D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command.
+Purpose: 原 06-05 与 06-06 合并为本计划;新增 `rendering` 与异步 `mover`。存读档集中到 06-09。
-Purpose: D-17 requires each hero subsystem to be verified as runnable. This plan covers the attribute/modifier computation core, the location model, and the hero state aggregate/save envelope; the storage subsystems (equipment/equipStore/items/follower/mover) are covered by 06-06.
-
-Output: 4 new co-located `*.test.ts` files.
+Output: 10 个同目录 `*.test.ts`(按子系统分三组:Task 1 属性/位置/状态 4 文件、Task 2 装备/物品 3 文件、Task 3 异步 follower/mover/rendering 3 文件)。
@@ -68,112 +87,134 @@ Output: 4 new co-located `*.test.ts` files.
@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/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/mover.ts
+@packages-user/data-base/src/hero/rendering.ts
@packages-user/data-base/src/hero/types.ts
-@packages-user/data-state/test/dataClosure.test.ts
-@packages-user/data-state/src/event/event.test.ts
-@packages-user/data-system/src/path/system.test.ts
+@packages-user/data-state/src/replay/commands.test.ts
+@packages-user/data-common/src/common/mover.test.ts
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures inline and minimal (D-02/D-03).
-- Warning paths via `logger.catch` / `vi.spyOn(logger, ...)`.
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-05-N`; `pnpm test:ci` stays green.
+- 只写测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03)。
+- 异步路径用真实计时器 + `await controller.onEnd`(D-04)。
+- 警告路径经 `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,不拆分为更多计划。
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-05 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- Task 1: HeroAttribute + modifiers
- packages-user/data-base/src/hero/attribute.test.ts, packages-user/data-base/src/hero/modifier.test.ts
+ Task 1: HeroAttribute + modifier + HeroLocation + HeroState
+ 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
packages-user/data-base/src/hero/attribute.ts
packages-user/data-base/src/hero/modifier.ts
- packages-user/data-base/src/hero/types.ts
- .planning/phases/06-unit-tests/06-PATTERNS.md (section C: attribute, modifier)
-
-
-`attribute.test.ts`: with an inline numeric attribute object, cover:
-- `getBaseAttribute` vs `getFinalAttribute`: base changes via `set`/`add`/`mul`/`div` and final reflects registered modifiers.
-- `addModifier` sorts by descending `priority` and recomputes final; adding a modifier already owned by another attribute warns 108 and is ignored; `getModifiers`/`getModifierIndex`/`iterateModifiers` reflect registration; `deleteModifier`/`deleteModifierByIndex` unbind and remove.
-- save-enabled gating: `addModifier(name, modifier, false)`, `setModifierSaveEnabled`, `getModifierSaveEnabled`.
-- `markModifierDirty` recomputes only the owning attribute; an unbound modifier key is ignored.
-- `clone({cloneModifier: false})` copies base only; default clone copies modifiers and recomputes; `getModifiableClone` is an independent clone; `toStructured` returns a detached structured clone.
-- In `DEV` a modifier returning the same object reference warns 109 (assert via `logger.catch` on a modifier that returns the same object).
-
-`modifier.test.ts`: assert `ValueModifier.modify(value)` = `value + this.value` and `PercentageModifier.modify(value, base)` = `value + base * this.value`; default priorities (0 and 10); `type` strings; `clone` produces an equal independent modifier; `setValue` recomputes through the bound attribute and `saveState`/`loadState` round-trip the value.
-
-
- pnpm exec vitest run packages-user/data-base/src/hero/attribute.test.ts packages-user/data-base/src/hero/modifier.test.ts
- the focused run fails, or the warn 108/109 assertions are not observed
-
-
- - Both files pass with zero non-skipped failures.
- - attribute.test.ts asserts base/final computation, priority ordering, save gating, clone/structured behavior, and warn codes 108/109.
- - modifier.test.ts asserts both modifier formulas and clone/save behavior.
- - Every `it` has a preceding single-line Chinese comment.
-
- HeroAttribute and modifier behavior is covered and green.
-
-
-
- Task 2: HeroLocation model
- packages-user/data-base/src/hero/location.test.ts
-
packages-user/data-base/src/hero/location.ts
- packages-user/data-base/src/hero/mover.ts (constructor face handling)
- packages-user/data-state/src/event/event.test.ts (minimal CoreState location fixture)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section C: location)
-
-
-Create `location.test.ts` with an inline fake `IDataCommon` and a `Dir8FaceHandler`:
-- construct `HeroLocation` from an inline `IFacedTileLocator` and assert initial `x`/`y`/`floorId` (undefined) and `getCurrentFaceDirection()` matching the locator direction.
-- `setPos(x, y)` updates coordinates; `setFloor(id)` updates the floor; `getCurrentFaceDirection` reflects `mover.setFaceDir`.
-- hook `onSetPos`/`onSetFloor` fire with the new values.
-- `saveState()` returns `{x, y, direction, floorId}` and `loadState` restores all four, including resetting `mover.faceDirection`.
-Keep the fake state minimal (tileStore/itemStore/mapStore/eventStore/faceManager/directionMapper as needed).
-
-
- pnpm exec vitest run packages-user/data-base/src/hero/location.test.ts
- the focused run fails, or a save/load round-trip assertion does not restore a field
-
-
- - `location.test.ts` passes with zero non-skipped failures.
- - Asserts getters, setters, hooks, and full save/load round-trip.
- - Every `it` has a preceding single-line Chinese comment.
-
- HeroLocation behavior is covered and green.
-
-
-
- Task 3: HeroState aggregate and save envelope
- packages-user/data-base/src/hero/state.test.ts
-
packages-user/data-base/src/hero/state.ts
packages-user/data-base/src/hero/types.ts
- packages-user/data-state/test/dataClosure.test.ts (saveable hero round-trip)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section C: state)
+ packages-user/data-state/src/event/event.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(C 节 attribute/modifier/location/state)
-Create `state.test.ts` using an inline fake `IDataCommon` and `Dir8FaceHandler` to construct `HeroState`:
-- constructor wires `location`/`rendering`/`followers`/`items`/`equip` and starts at the default locator.
-- `getModifiableAttribute`/`getAttribute`/`getIsolatedAttribute` return the shared attribute, a readonly view, and an independent clone respectively; `attachAttribute` swaps the attribute.
-- `registerModifier` + `createModifier`/`createAndInsertModifier`: a registered type creates and inserts a modifier; an unknown type warns 116 and returns null.
-- `changeFloor(info)` sets face/move direction, position, and floor, and fires before/after hooks in order.
-- `saveState(compression)` then mutate key fields (attribute base value, location x/y/floor, a modifier value) then `loadState` restores them — assert only the explicit key fields (D-13), never a whole-object deep equality; a modifier with save disabled is not persisted; followers are cleared then re-added from the save.
-Use `SaveCompression.NoCompression`. Keep fixtures inline.
+ `attribute.test.ts`:inline 数值属性对象:
+ - `getBaseAttribute` vs `getFinalAttribute`:base 经 `set`/`add`/`mul`/`div` 变化,final 反映已注册 modifier。
+ - `addModifier` 按 priority 降序排序并重算 final;已被别的属性拥有的 modifier 告警 108 且忽略;`getModifiers`/`getModifierIndex`/`iterateModifiers`;`deleteModifier`/`deleteModifierByIndex`。
+ - modifier 存盘开关:`addModifier(name, modifier, false)`、`setModifierSaveEnabled`、`getModifierSaveEnabled`。
+ - `markModifierDirty` 只重算所属属性;未绑定键忽略。
+ - `clone({cloneModifier:false})` 只复制 base;默认 clone 复制 modifier 并重算;`getModifiableClone` 独立;`toStructured` 返回分离的结构化克隆。
+ - 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 钩子。不测存读档。
- pnpm exec vitest run packages-user/data-base/src/hero/state.test.ts
- the focused run fails, or the save/load assertion uses a whole-object deep equality instead of explicit key fields
+ 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
+ 任一聚焦运行失败,或 108/109/116 未被观测,或钩子顺序断言失败
- - `state.test.ts` passes with zero non-skipped failures.
- - Asserts subsystem wiring, modifier registry (incl. warn 116), changeFloor hook order, and explicit key-field save/load with a save-disabled modifier excluded.
- - Every `it` has a preceding single-line Chinese comment.
+ - 四个文件零非跳过失败。
+ - attribute 断言 base/final、优先级、存盘开关、clone/structured 与码 108/109;modifier 断言两条公式与 priority;location 断言 getter/setter/钩子;state 断言装配/属性视图/码 116/changeFloor 钩子顺序。
+ - 每个 `it` 前有单行中文注释。
- HeroState aggregate and save envelope are covered and green.
+ 属性、modifier、位置、勇士状态覆盖并跑绿。
+
+
+
+ Task 2: HeroEquipment + EquipmentState/HeroEquipsStore + HeroItems
+ 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/equipment.ts
+ packages-user/data-base/src/hero/equipStore.ts
+ packages-user/data-base/src/hero/items.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)
+
+
+ 构造 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`。
+
+
+ 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
+ 运行失败,或 modifier 应用/移除断言、或 146/147 未被观测
+
+
+ - 三个文件零非跳过失败。
+ - equipment 断言 equip/unequip/replace、modifier 应用、compareEquip、码 146/147;equipStore 断言实例增删计数排序;items 断言 add/use/count/路由。
+ - 每个 `it` 前有单行中文注释。
+
+ 装备、装备实例库、物品覆盖并跑绿。
+
+
+
+ Task 3: followers(异步)+ HeroMover(异步)+ HeroRendering
+ 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
+
+ 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)
+
+
+ `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`。
+
+ `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`。
+
+
+ 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
+ 运行失败,或异步 gather/mover 断言未通过 awaited 控制器完成,或 144/142 未被观测
+
+
+ - 三个文件零非跳过失败。
+ - follower 断言 add/remove/gather/链接与码 142;mover 断言 config 往返、Step/CannotMove/Hit、越界、enter/leave 顺序与码 144;rendering 断言 alpha + 钩子。
+ - 异步用例用真实计时器并 await 控制器;无 fake timers。
+ - 每个 `it` 前有单行中文注释。
+
+ followers、异步 HeroMover、HeroRendering 覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(108/109/116/142/144/146/147)写入 `06-COVERAGE-MAP.md` 的 06-05 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -183,27 +224,30 @@ Use `SaveCompression.NoCompression`. Keep fixtures inline.
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-05-01 | Tampering | tests under `packages-user/data-base/src/hero` | low | mitigate | Only tracked in-repo modules; synthetic fixtures; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-05-02 | Information disclosure | fixtures | low | accept | Synthetic attribute/location numbers only; no real game data (D-02). |
-| T-06-05-03 | Repudiation | hook ordering assertions | low | accept | Tests assert documented ordering with plain arrays; no shared mutable global. |
-| T-06-05-SC | Tampering | package installs | high | mitigate | No new dependency; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-05-01 | Tampering | `data-base/src/hero` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成 fixture;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-05-02 | Denial of service | 异步 mover/gather 循环 | medium | mitigate | 异步断言 await 控制器 `onEnd`,30s 测试超时兜底;无无界轮询。 |
+| T-06-05-03 | Information disclosure | fixture | low | accept | 合成数值/id,无真实游戏数据(D-02)。 |
+| T-06-05-SC | Tampering | 包安装 | high | mitigate | 不新增依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for all 4 files pass; `pnpm test:ci` stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-05-N` entry in `06-TEST-FINDINGS.md`.
+- 十个文件聚焦运行通过;`pnpm test:ci` 全量绿。
+- `git status` 仅新增 `*.test.ts`;无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-05-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-05 小节(7 个码);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前向用户确认;执行后暂停并用中文分条汇报。
-- 4 hero core behavior test files exist and pass.
-- Attribute/modifier/location/state behavior including the save envelope is asserted with explicit key fields.
+- 10 个 `data-base/src/hero` 行为测试文件存在并通过。
+- 每个勇士子系统(含 rendering 与异步 mover/gather 与 follower)均有覆盖。
+- 未测存读档。
@@ -212,14 +256,29 @@ Test files:
- `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`
+- `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`
+- `packages-user/data-base/src/hero/mover.test.ts`
+- `packages-user/data-base/src/hero/rendering.test.ts`
Inline helper symbols:
-- `attribute.test.ts`: `TestModifier`, `createAttribute`
-- `modifier.test.ts`: `TestModifier`, `createAttribute`
-- `location.test.ts`: `createState`, `createFaceHandler`, `createLocation`
-- `state.test.ts`: `createState`, `createFaceHandler`, `createHeroState`
+- `attribute.test.ts`: `TestModifier`、`createAttribute`
+- `modifier.test.ts`: `createAttribute`
+- `location.test.ts`: `createState`、`createFaceHandler`、`createLocation`
+- `state.test.ts`: `createState`、`createFaceHandler`、`createHeroState`
+- `equipment.test.ts`: `createState`、`createEnv`、`createItem`
+- `equipStore.test.ts`: `createState`、`createEnv`、`FakeSorter`
+- `items.test.ts`: `createState`、`createEnv`、`createItem`
+- `follower.test.ts`: `createState`、`createLocation`、`createController`
+- `mover.test.ts`: `TestTile`、`FakeTopImpl`、`createMover`
+- `rendering.test.ts`: `createState`、`FakeRenderingHook`
+
+Planning artifacts:
+- `06-COVERAGE-MAP.md` create-or-append 06-05 小节
diff --git a/.planning/phases/06-unit-tests/06-06-PLAN.md b/.planning/phases/06-unit-tests/06-06-PLAN.md
index d0c7cb1..4293f87 100644
--- a/.planning/phases/06-unit-tests/06-06-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-06-PLAN.md
@@ -5,57 +5,67 @@ type: execute
wave: 1
depends_on: []
files_modified:
- - 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
- - packages-user/data-base/src/hero/mover.test.ts
+ - 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
+ - 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
autonomous: true
requirements: [TEST-01]
estimate:
- tokens: 56000
- raw_tokens: 56000
+ tokens: 72000
+ raw_tokens: 72000
tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - HeroEquipment equips/unequips/replaces, applies and removes equipment modifiers, and reports equip status; compareEquip diff is deterministic
- - HeroEquipsStore adds/deletes/counts/sorts instances and round-trips save/load with uid continuity
- - HeroItems adds/uses/counts constant and consumable items, routes equipment, and round-trips save/load
- - HeroFollowersController adds/removes/gathers followers and HeroFollower links/reports neighbors and save/loads
- - HeroMover moves asynchronously through its top implementation with the documented move codes
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:`MapState`、`GameMap`、`MapLayer`、`MapTileBase`(tile)、`StaticTile`、`DynamicTile`、`LayerEventView`(eventView)、`DynamicTileMover`(mover)的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法排除(D-18 / D-30 / D-39)。"
+ - "code 全覆盖:本计划可达的码 8、9、46、60、61、62、63、64、80、81、84、121、123、126、127、128、129、130、131、136、143 各至少一条触发断言(D-31)。其中 60(各图层长度不一致)与 61(总长度不能被 raw.width 整除)经 `MapState.fromRaw`(内部先走 `validateRaw` 校验)触发,二者均可达,不得省略。"
+ - "loadState 专属码 55(MapStore 缺 reference)、122(MapStore 楼层缺失)、124(MapLayer 引用缺失)与全部 `saveState`/`loadState` 测试归 06-09,本计划不测(D-32)。"
+ - "重点覆盖:静态图块(`StaticTile` num/raw/set/shouldSave/toDynamic)、动态图块转换(`transferToDynamic`/`transferToStatic`/`transferToStaticIfSafe`/`createDynamic`/`deleteDynamic`/`updateDynamicTile`)与静态数组设置(`getMapData`/`putMapData`/`setMapRef`/`getMapRef`)行为正确。"
+ - "地图矩阵、点事件、图层别名/背景/脏标记、`resize`/`resize2`/crop、`compareWith`、`canPass`/`shouldHit` 委托、异步门开关(openDoor/closeDoor)均有覆盖。"
artifacts:
- - path: packages-user/data-base/src/hero/equipment.test.ts
- provides: HeroEquipment coverage
- - path: packages-user/data-base/src/hero/equipStore.test.ts
- provides: HeroEquipsStore coverage
- - path: packages-user/data-base/src/hero/items.test.ts
- provides: HeroItems coverage
- - path: packages-user/data-base/src/hero/follower.test.ts
- provides: follower coverage
- - path: packages-user/data-base/src/hero/mover.test.ts
- provides: HeroMover coverage
+ - path: packages-user/data-base/src/map/mapState.test.ts
+ provides: MapState 覆盖
+ - path: packages-user/data-base/src/map/gameMap.test.ts
+ provides: GameMap 覆盖
+ - path: packages-user/data-base/src/map/mapLayer.test.ts
+ provides: MapLayer 矩阵/静态数组/动态转换覆盖
+ - path: packages-user/data-base/src/map/tile.test.ts
+ provides: MapTileBase 覆盖
+ - path: packages-user/data-base/src/map/staticTile.test.ts
+ provides: StaticTile 覆盖
+ - path: packages-user/data-base/src/map/dynamicTile.test.ts
+ provides: DynamicTile 覆盖
+ - path: packages-user/data-base/src/map/eventView.test.ts
+ provides: LayerEventView 扩展覆盖
+ - path: packages-user/data-base/src/map/mover.test.ts
+ provides: DynamicTileMover 覆盖
key_links:
- - "equipment.test.ts follows the established equipment boundary (`equip`/`unequip`/`getEquipped`) already spied in `data-state/src/replay/commands.test.ts:200-253`"
- - "mover.test.ts follows the abstract-mover pattern from `data-common/src/common/mover.test.ts` with real timers and `await controller.onEnd`"
+ - "全部新文件复用 `data-base/src/map/mapLifecycle.test.ts` 的 inline `createMapState` fixture + `vi.hoisted` 全局 stub 形状"
+ - "mapState.test.ts 镜像 `eventPath.test.ts` 的 fromRaw 校验与 logger 警告路径"
+ - "eventView.test.ts 为既有文件扩展(保留既有覆盖)"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-06 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它 wave 1 计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只修改 `*.test.ts`"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "异步路径只用真实计时器 + `await`,禁用 `vi.useFakeTimers()`(D-04)"
+ - "不测 `saveState`/`loadState`(D-32)"
---
-Cover the hero storage/movement subsystems `packages-user/data-base/src/hero` — equipment, equipStore, items, follower, mover (D-17).
+在既有 `eventPath`/`eventView`/`mapLifecycle` 测试之上,全面覆盖地图系统 `packages-user/data-base/src/map`(D-18 / D-39):`MapState`、`GameMap`、`MapLayer`、tile 与 eventView、map mover,**重点**静态图块、动态图块转换、静态数组设置。
-Phase constraints. D-04: async paths (the hero mover and async follower gather) use real timers and `await` controller `onEnd`, never fake timers, while pure computation asserts synchronously; D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command.
+Purpose: D-18 要求更全面的地图覆盖(矩阵、点事件、图层、脏标记、resize/crop)。存读档集中到 06-09。
-Purpose: D-17 requires each hero subsystem to run correctly. Equipment slotting, the equipment instance store, item add/use, follower management, and the asynchronous hero mover are the remaining untested subsystems after 06-05.
-
-Output: 5 new co-located `*.test.ts` files.
+Output: 8 个 `*.test.ts`(其中 `eventView.test.ts` 为既有文件扩展)。
@@ -68,118 +78,139 @@ Output: 5 new co-located `*.test.ts` files.
@.planning/phases/06-unit-tests/06-PATTERNS.md
@.planning/codebase/TESTING.md
@dev.md
-@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/mover.ts
-@packages-user/data-base/src/hero/types.ts
-@packages-user/data-state/src/replay/commands.test.ts
-@packages-user/data-common/src/common/mover.test.ts
+@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/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/types.ts
+@packages-user/data-base/src/map/mapLifecycle.test.ts
+@packages-user/data-base/src/map/eventPath.test.ts
+@packages-user/data-base/src/map/eventView.test.ts
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures inline and minimal (D-02/D-03).
-- Async paths use real timers + `await controller.onEnd`; no fake timers (D-04).
-- Warning paths via `logger.catch` / `vi.spyOn(logger, ...)`.
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-06-N`; `pnpm test:ci` stays green.
+- 只改测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03);import 链需要时用 `vi.hoisted` 全局 stub + 动态模块包。
+- 警告路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
+- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-06-N`;`pnpm test:ci` 保持全绿。
+- 不测存读档(D-32)。
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-06 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- Task 1: HeroEquipment + HeroEquipsStore
- packages-user/data-base/src/hero/equipment.test.ts, packages-user/data-base/src/hero/equipStore.test.ts
+ Task 1: MapState 注册/校验/激活 + GameMap 图层/别名/脏/事件层
+ packages-user/data-base/src/map/mapState.test.ts, packages-user/data-base/src/map/gameMap.test.ts
- packages-user/data-base/src/hero/equipment.ts
- packages-user/data-base/src/hero/equipStore.ts
- packages-user/data-base/src/hero/types.ts
- packages-user/data-state/src/replay/commands.test.ts (equip/unequip boundary)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section C: equipment, equipStore)
+ packages-user/data-base/src/map/mapState.ts
+ packages-user/data-base/src/map/gameMap.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)
-Build an inline fake `IDataCommon` whose `tileStore.num`/`idToNumber` and `itemStore.getData` return small synthetic equipment items (with `equip.value`/`equip.percentage` maps and `equip.slots`), plus an inline `HeroAttribute`.
-`equipment.test.ts`: `setSlots`; `canEquipTo` returns `CanEquip`/`NeedReplace`/`CannotEquip` for numeric and string slots; `equip` onto an empty numeric slot returns undefined and applies modifiers so final attributes change; equipping over an occupied numeric slot returns the previous uid and replaces modifiers; equipping the same uid whose slot changes with `autoUnload` moves it; `unequip` returns the removed uid and removes its modifiers, returns undefined for an empty slot; `equipped`/`getEquipped`/`getEquips` reflect state; `compareEquip` returns the per-attribute diff; unknown uid paths warn 146 (assert via `logger.catch`); `saveState`/`loadState` restores slots and re-equips.
-`equipStore.test.ts`: `add` returns a uid and -1 for an unknown tile; `get`/`count`/`delete`; `instancesOf`/`instances` sort by a supplied sorter and fall back to ascending uid; `useSorter(null)` restores uid ordering; `saveState`/`loadState` round-trips instances and continues `nextUid` after the max loaded uid; a missing item on load logs `logger.error(59)` (assert via `logger.catch`).
+ `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` 改变激活集。
+ - `createLayerState` 对已存在楼层告警码 121。
+ - `compareWith(ref)` 按参考标记脏;`canPass`/`shouldHit` 委托活跃 map/谓词(inline fake 谓词)。
+ - 不测 `saveState`/`loadState`(55/122 归 06-09)。
+
+ `gameMap.test.ts`:inline `IDataCommon` 构造 `GameMap`:
+ - `addLayer`/`removeLayer`/`hasLayer` 与 `onUpdateLayer` 钩子;`setLayerAlias`/`getLayerByAlias`/`getLayerAlias`,重复别名告警 84。
+ - `setBackground`/`getBackground` 与 `onChangeBackground`;`setActiveStatus` 切换 `active`。
+ - `setEventLayer`:拒绝不在 `layerList` 的层并告警 131;接受已添加层;接受 null。
+ - `dirty()`(任一层或自身脏为真)、`markDirty(true/false)`;`resizeLayer(w,h,keepBlock)` 按 keepBlock 调 `resize`/`resize2`;`compareWith(map)` 缺参考标脏、否则比较。
+ - 不测 `saveState`/`loadState`(含 `isEmptyLayerSave`)。
- pnpm exec vitest run packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/hero/equipStore.test.ts
- the focused run fails, or the modifier apply/remove assertion or warn/error code is not observed
+ pnpm exec vitest run packages-user/data-base/src/map/mapState.test.ts packages-user/data-base/src/map/gameMap.test.ts
+ 运行失败,或 60/61/62/63/64/84/121/131 中任一码未被观测
- - Both files pass with zero non-skipped failures.
- - equipment.test.ts asserts equip/unequip/replace, modifier application, compareEquip, and warn 146.
- - equipStore.test.ts asserts add/get/count/delete/sort, uid continuity after load, and error 59.
- - Every `it` has a preceding single-line Chinese comment.
+ - 两个文件零非跳过失败。
+ - mapState 断言 create/get/fromRaw 校验码(60/61/62/63/64)/激活迭代/area 激活/121/compareWith/canPass-shouldHit。
+ - gameMap 断言层与别名生命周期、背景/事件层、脏、resize、compareWith 与码 84/131。
+ - 每个 `it` 前有单行中文注释。
- Equipment and equip-store behavior is covered and green.
+ MapState 与 GameMap 覆盖并跑绿。
- Task 2: HeroItems + followers
- packages-user/data-base/src/hero/items.test.ts, packages-user/data-base/src/hero/follower.test.ts
+ Task 2: MapLayer 矩阵、静态数组设置与动态图块转换(重点)
+ packages-user/data-base/src/map/mapLayer.test.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
- .planning/phases/06-unit-tests/06-PATTERNS.md (section C: items, follower)
+ 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)
-`items.test.ts`: inline fake `IDataCommon` itemStore/hero.items with synthetic constant/consumable/equipment/pick items:
-- `addItem` increments a constant item count and deletes at <=0; `addItem` for equipment routes to the equip store; `addItem` for a Pick item invokes `effect.useEffect` per count; unknown tile/item is a no-op.
-- `itemCount`/`getItemState`; `getItem` adds one; `useItem` returns false for a missing item or when `canUse` is false, applies `useEffect`, and decrements/deletes a consumable, leaves a constant; returns false for equipment.
-- `saveState`/`loadState` round-trips constants/consumables/equipment with explicit key-field assertions.
-
-`follower.test.ts`: inline fake `IDataCommon` + `Dir8FaceHandler` + a `HeroLocation`:
-- `addFollower` appends at the hero locator and fires `onAddFollower`; `getFollower`/`getAllFollowers`/`getFollowersById` (numeric and string id); `next`/`last` neighbor links.
-- `removeFollower`/`removeAllFollowers` fire `onRemoveFollower` and clear the list.
-- `gatherFollowers` (async, real timers) steps each follower and fires `onGatherFollowers(false)`; `gatherFollowersSync` snaps followers to the hero and fires `onGatherFollowers(true)`.
-- `HeroFollower.saveState`/`loadState` round-trips rendering+location; a string follower id not found warns 142 (assert via `logger.catch`).
+ 用 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` 钩子)。
+ - 点位/事件:`event(x,y)`/`getPointEvent`;`getTile`/`getLocationData`;`setStaticDirection`/`setDynamicDirection`/`setZIndex`/`setFaceBinder`。
+ - 脏与比较:`dirty`/`markDirty`/`compareWith`。
+ - 异步门:`openDoor`/`closeDoor`(`closeDoor` 目标非空告警 46)用真实计时器 await。
+ - 不测 `saveState`/`loadState`(124 归 06-09)。
+ 注:若 `transferToDynamic` 的越图路径实际发出的码与语义不符(当前实现发 131 而 `transferToStatic` 发 128),在断言中触发并记录为 `#06-06-N` 待用户确认;若用户判定为缺陷则按 D-05 改为正确预期 + skip。
- pnpm exec vitest run packages-user/data-base/src/hero/items.test.ts packages-user/data-base/src/hero/follower.test.ts
- the focused run fails, or the async gather assertion does not complete through awaited movers
+ pnpm exec vitest run packages-user/data-base/src/map/mapLayer.test.ts
+ 运行失败,或 8/9/46/80/81/123/127/128/129/130/131 中任一码未被观测,或静态数组/动态转换断言失败
- - Both files pass with zero non-skipped failures.
- - items.test.ts asserts add/use/count/routing/save-load; follower.test.ts asserts add/remove/gather/links/save-load and warn 142.
- - `gatherFollowers` uses real timers and awaits the gathered controllers.
- - Every `it` has a preceding single-line Chinese comment.
+ - `mapLayer.test.ts` 零非跳过失败。
+ - 断言矩阵读写、静态数组(getMapData/putMapData/setMapRef)、动态转换语义、点位事件、脏/compareWith、异步门与全部列出码。
+ - 每个 `it` 前有单行中文注释。
- Item and follower behavior is covered and green.
+ MapLayer 矩阵、静态数组与动态转换(重点)覆盖并跑绿。
- Task 3: HeroMover async movement
- packages-user/data-base/src/hero/mover.test.ts
+ Task 3: tile/staticTile/dynamicTile/eventView/mover
+ 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
- packages-user/data-base/src/hero/mover.ts
- packages-user/data-common/src/common/mover.ts (ObjectMover)
- packages-user/data-common/src/common/mover.test.ts (abstract mover test pattern)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section C: mover)
+ 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/types.ts
+ packages-user/data-base/src/map/mapLifecycle.test.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(D 节 tile)
-Create `mover.test.ts` mirroring `data-common/src/common/mover.test.ts` but for `HeroMover`:
-- build an inline `IHeroLocation`-shaped tile + `IDataCommon` fake and a `Dir8FaceHandler`.
-- `config`/`getConfig` round-trips `noRoute`/`ignoreTerrain`/`autoSave`/`allowOutBound`.
-- with an inline fake `IHeroMoveTopImpl`: a Step move writes the next location through `setPos`; `CannotMove` calls `cannotEnter` (when terrain not ignored) and stops without moving; `Hit` calls `hit` and stops; `allowOutBound` permits an out-of-bound step while the default blocks it; `ignoreTerrain` skips pass/hit/enter/leave callbacks.
-- missing top implementation: `onMoveStart` warns 144 and `onStepStart` returns `HeroMoveCode.Stop` (assert via a subclass observable behavior).
-- `onStepSettled` calls `leave` then `enter` with before/current locators when terrain triggering is on.
-Use real timers + `await controller.onEnd`. Keep fixtures inline.
+ `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`)。
+
+ `mover.test.ts`:经一个 `DynamicTile` 构造其 `DynamicTileMover` 并驱动控制器:成功步进、非 `Success` 移动码触发码 126(`logger.catch`)、`onMoveStart`/`onStepSettled` 生命周期顺序;真实计时器 + `await controller.onEnd`。
- pnpm exec vitest run packages-user/data-base/src/hero/mover.test.ts
- the focused run fails, or a move-code/position-writeback assertion does not match the implementation contract
+ 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
+ 运行失败,或 136/143/126 未被观测,或默认事件恢复/动态 tile 生命周期断言失败
- - `mover.test.ts` passes with zero non-skipped failures.
- - Asserts config round-trip, Step/CannotMove/Hit, out-of-bound handling, enter/leave ordering, and warn 144.
- - Uses real timers and `await controller.onEnd`; no fake timers.
- - Every `it` has a preceding single-line Chinese comment.
+ - 五个文件零非跳过失败。
+ - tile 断言默认事件恢复与点位联动;staticTile/dynamicTile 断言各自公开方法;eventView 断言视图操作与码 136;mover 断言生命周期与码 126。
+ - 异步 mover 用例用真实计时器并 await 控制器;无 fake timers。
+ - 每个 `it` 前有单行中文注释。
- HeroMover async movement behavior is covered and green.
+ 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 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -189,45 +220,57 @@ Use real timers + `await controller.onEnd`. Keep fixtures inline.
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅修改/新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-06-01 | Tampering | tests under `packages-user/data-base/src/hero` | low | mitigate | Only tracked in-repo modules; synthetic fixtures; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-06-02 | Denial of service | async mover/gather loops | medium | mitigate | Async assertions await controller `onEnd` under the existing 30s test timeout; no unbounded polling. |
-| T-06-06-03 | Information disclosure | fixtures | low | accept | Synthetic item/equipment numbers only; no real game data (D-02). |
-| T-06-06-SC | Tampering | package installs | high | mitigate | No new dependency; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-06-01 | Tampering | `data-base/src/map` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成地图;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-06-02 | Denial of service | 大矩阵/异步门 | low | mitigate | fixture 用 1x1..3x2 小矩阵,运行开销可忽略;异步门 await 控制器,30s 超时兜底。 |
+| T-06-06-03 | Information disclosure | fixture | low | accept | 合成 tile id/事件,无真实游戏数据(D-02)。 |
+| T-06-06-SC | Tampering | 包安装 | high | mitigate | 不新增依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for all 5 files pass; `pnpm test:ci` stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-06-N` entry in `06-TEST-FINDINGS.md`.
+- 八个文件聚焦运行通过;`pnpm test:ci` 全量绿(既有 map 测试保持绿)。
+- `git status` 仅显示 `*.test.ts` 变更;无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-06-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-06 小节(21 个码:在 19 个基础上新增 60/61);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前向用户确认;执行后暂停并用中文分条汇报。
-- 5 hero storage/movement behavior test files exist and pass.
-- Equipment, equip store, items, followers, and the async hero mover are asserted.
+- 8 个 `data-base/src/map` 行为测试文件存在/扩展并通过。
+- 地图矩阵、点事件、图层、脏标记、resize/crop、静态图块、动态转换、静态数组全部断言。
+- 未测存读档。
Test 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`
-- `packages-user/data-base/src/hero/follower.test.ts`
-- `packages-user/data-base/src/hero/mover.test.ts`
+- `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`
+- `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`
Inline helper symbols:
-- `equipment.test.ts`: `createState`, `createEnv`, `createItem`
-- `equipStore.test.ts`: `createState`, `createEnv`, `createItem`, `FakeSorter`
-- `items.test.ts`: `createState`, `createEnv`, `createItem`
-- `follower.test.ts`: `createState`, `createLocation`, `createController`
-- `mover.test.ts`: `TestTile`, `FakeTopImpl`, `createMover`
+- `mapState.test.ts`: `createMapState`、`createRawMap`
+- `gameMap.test.ts`: `createGameMap`、`createState`、`createLayer`
+- `mapLayer.test.ts`: `createMapState`、`createLayerFixture`
+- `tile.test.ts`: `createMapState`、`createTiles`
+- `staticTile.test.ts`: `createMapState`、`createStaticTile`
+- `dynamicTile.test.ts`: `createMapState`、`createDynamicTile`
+- `eventView.test.ts`: `createView`
+- `mover.test.ts`: `createMoverFixture`
+
+Planning artifacts:
+- `06-COVERAGE-MAP.md` create-or-append 06-06 小节(21 个码)
diff --git a/.planning/phases/06-unit-tests/06-07-PLAN.md b/.planning/phases/06-unit-tests/06-07-PLAN.md
index 7c6fbd4..5b8450a 100644
--- a/.planning/phases/06-unit-tests/06-07-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-07-PLAN.md
@@ -2,56 +2,55 @@
phase: 06-unit-tests
plan: 07
type: execute
-wave: 1
-depends_on: []
+wave: 2
+depends_on: [06-02, 06-04, 06-06]
files_modified:
- - packages-user/data-base/src/map/gameMap.test.ts
- - packages-user/data-base/src/map/mapState.test.ts
- - packages-user/data-base/src/map/mapLayer.test.ts
- - packages-user/data-base/src/map/tile.test.ts
-autonomous: true
+ - packages-user/data-state/test/enemyCombination.test.ts
+ - packages-user/data-state/test/replayPlayback.test.ts
+autonomous: false
requirements: [TEST-01]
estimate:
- tokens: 56000
- raw_tokens: 56000
+ tokens: 52000
+ raw_tokens: 52000
tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - MapState creates/looks up/iterates maps, validates raw map data, and honors active status/area activation
- - GameMap manages layers, aliases, background, dirty state, and layer-only save aggregation
- - MapLayer reads/writes blocks, manages static/dynamic tiles and point events, tracks dirty state, and resizes/crops correctly
- - Static/dynamic tiles restore default events, expose their raw data, and round-trip their save state
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:本计划驱动的顶层/系统层组合入口与录像播放入口的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法排除(D-30 / D-40)。"
+ - "code 全覆盖:录像执行错误 2001、2002、2003、2004、2005、2006、2007、2008 各至少一条触发断言,经 `logger.catch` / `logger.error` spy 观测(D-31 / D-40)。"
+ - "① 伤害组合:顶层多特殊属性组合(同时存在多个 special 时的伤害计算)+ 系统层光环/各效果组合(D-25/D-26)经真实 API 驱动并断言最终 `{damage,turn}`。"
+ - "② 小地图场景:用真实 API(`createCoreState` + 地图/怪物/勇士公开接口)构造小地图场景 + 一段录像步并正常播放(合成数据,D-02)。"
+ - "③ 二次录制比对:首次播放完毕后录像系统记录一遍录像步,断言二次录像与原录像**逐条完全相等**(步数 + 每步 code + 各 param 的 type/value),随后再次播放检验。"
+ - "本计划允许为录像重置**最小使用**存档数据,但不测存读档本身(D-32)。"
+ - "执行前必须先向用户确认(依赖用户先接通录像记录)(D-40)。"
artifacts:
- - path: packages-user/data-base/src/map/gameMap.test.ts
- provides: GameMap coverage
- - path: packages-user/data-base/src/map/mapState.test.ts
- provides: MapState coverage
- - path: packages-user/data-base/src/map/mapLayer.test.ts
- provides: MapLayer coverage
- - path: packages-user/data-base/src/map/tile.test.ts
- provides: static/dynamic tile coverage
+ - path: packages-user/data-state/test/enemyCombination.test.ts
+ provides: 顶层伤害组合 + 系统层光环/效果组合覆盖
+ - path: packages-user/data-state/test/replayPlayback.test.ts
+ provides: 小地图录像播放 + 二次录制比对 + 2001–2008 覆盖
key_links:
- - "all four files reuse the inline `createMapState` fixture + `vi.hoisted` global stub shape from `data-base/src/map/mapLifecycle.test.ts` and assert all-compression key fields (D-13)"
- - "mapState.test.ts mirrors `eventPath.test.ts` fromRaw validation and logger-warning paths"
+ - "enemyCombination.test.ts 扩展 `data-state/test/dataClosure.test.ts` 的真实 `createCoreState` + `enemyManager.addPrefab` + `MainDamageCalculator` 形状到多 special/组合路径"
+ - "replayPlayback.test.ts 复用 `data-state/test/nodeReplay.test.ts` 的逐步回放 harness 与 `dataClosure.test.ts` 的 `waitForEnded` 有界等待"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-07 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只新增 `*.test.ts`"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "异步路径只用真实计时器,禁用 `vi.useFakeTimers()`(D-04)"
+ - "不测 `saveState`/`loadState` 本身;仅允许为录像重置最小使用存档数据(D-32)"
---
-Expand map coverage in `packages-user/data-base/src/map` beyond the existing `eventPath`/`eventView`/`mapLifecycle` tests (D-18): `MapState`, `GameMap`, `MapLayer`, and the tile classes.
+顶层集成(D-40):① 伤害系统的属性/加成组合(顶层多特殊属性 + 系统层光环/效果组合,D-25/D-26);② 用真实 API 构造小地图场景 + 一段录像步并正常播放;③ 二次录制验证:首次播放后系统记录一遍录像步,判定二次录像与原录像逐条完全相等,并再次播放检验。`error 2001–2008` 归本计划。
-Phase constraints. D-04: async paths (async door open/close) use real timers and `await` controller completion, never fake timers, while pure computation asserts synchronously; D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command.
+Purpose: 这是唯一验证多系统接线(战斗上下文 + 流程 + 计算器;地图 + 勇士 + 录像)的阶段工作。
-Purpose: D-18 requires a fuller map-system pass (matrix, point events, layers, dirty flags, resize/crop). The existing tests cover point-event lifecycle and tile defaults; this plan covers map registry/activation, per-map layer/alias/background/dirty, the layer matrix and dynamic-tile index, and tile default-event restoration.
+**执行前必须先向用户确认**(D-40:依赖用户先接通录像记录)。
-Output: 4 new co-located `*.test.ts` files.
+Output: 2 个 `data-state/test/` 集成测试文件。
@@ -62,118 +61,131 @@ Output: 4 new co-located `*.test.ts` files.
@.planning/phases/06-unit-tests/06-CONTEXT.md
@.planning/phases/06-unit-tests/06-PATTERNS.md
+@.planning/phases/03-data-completion/03-REPLAY-CONTRACT.md
+@.planning/phases/03-data-completion/03-REPLAY-DIAGNOSTICS.md
@.planning/codebase/TESTING.md
@dev.md
-@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/tile.ts
-@packages-user/data-base/src/map/staticTile.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
-@packages-user/data-base/src/map/eventPath.test.ts
-@packages-user/data-base/src/map/eventView.test.ts
+@packages-user/data-state/src/core.ts
+@packages-user/data-state/src/enemy/calculator.ts
+@packages-user/data-state/src/enemy/aura.ts
+@packages-user/data-state/src/enemy/mapDamage.ts
+@packages-user/data-state/src/replay/commands.ts
+@packages-user/data-system/src/combat/context.ts
+@packages-user/data-system/src/combat/combat.ts
+@packages-user/data-state/test/dataClosure.test.ts
+@packages-user/data-state/test/nodeReplay.test.ts
+@packages-user/data-state/test/fixtures/closed-loop.ts
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures inline and minimal (D-02/D-03); use the `vi.hoisted` global stub + dynamic module bag when the import chain requires it.
-- Warning paths via `logger.catch` / `vi.spyOn(logger, ...)`.
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-07-N`; `pnpm test:ci` stays green.
+- 只写测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03);可复用 `createCoreState()`,必要时引用 `closed-loop.ts`。
+- 异步路径用真实计时器 + 有界等待(`waitForEnded`),禁用 fake timers(D-04)。
+- 警告/错误路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
+- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-07-N`;`pnpm test:ci` 保持全绿。
+- **本计划执行前必须先向用户确认录像记录已接通(D-40);确认前不得开始写/跑用例。**
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-07 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
-
- Task 1: MapState registry and raw validation
- packages-user/data-base/src/map/mapState.test.ts
+
+ Task 1: 执行前门禁 — 用户确认录像记录已接通
+ packages-user/data-state/test/replayPlayback.test.ts(尚未创建,仅作占位说明)
+ 录像记录在 CoreState 操作路径中的接通,以及驱动小地图录像播放/二次录制所需的公开入口。
+
+ 1) 确认播放时每步会记入 `ReplaySystem.route`(录像记录已接通)。
+ 2) 确认 `CoreState` 暴露构造小地图场景 + 播放 + 二次录制所需的公开入口。
+ 3) 确认录像重置所需的最小状态入口可用。
+
+ 用户回复「录像记录已接通,可以开始」则继续 Task 2/3;否则暂停本计划等待用户改代码。
+ .planning/phases/06-unit-tests/06-CONTEXT.md(D-40)
+ packages-user/data-state/src/core.ts(replaySystem 接线与公开入口)
+ packages-user/data-state/src/replay/commands.ts(录像指令与 error 2001–2008)
+
+
+ 本计划依赖用户先在具体操作中接通录像记录,否则二次录制比对无法进行(D-40)。开始本计划前,向用户展示并确认:
+ - 录像记录是否已在 CoreState 操作路径中接通(播放时会把每步记入 `ReplaySystem.route`);
+ - `CoreState` 是否暴露驱动小地图场景 + 播放 + 二次录制所需的公开入口;
+ - 录像重置所需的最小状态入口是否可用。
+ 用户确认后再继续 Task 2/3;若未接通,暂停本计划并等待用户改代码,不得自行修改核心代码。
+
+
+ 用户明确回复「录像记录已接通,可以开始」,或不通过则暂停本计划
+
+
+ - 用户给出明确开始确认;否则本计划保持暂停状态。
+
+ 录像记录接通得到用户确认,或计划暂停等待用户改代码。
+
+
+
+ Task 2: 顶层伤害组合 + 系统层光环/效果组合
+ packages-user/data-state/test/enemyCombination.test.ts
+
+ 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-state/src/enemy/mapDamage.ts
+ packages-user/data-system/src/combat/context.ts
+ packages-user/data-system/src/combat/combat.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(A 节 combatClosure)
+
+
+ 用真实 `createCoreState()` 与真实顶层实现驱动:
+ - 顶层多特殊属性组合:一个怪物同时带多个 special(如 坚固 + 连击 + 吸血 等)时,按真实 `MainDamageCalculator` 断言组合后的 `{damage,turn}`(组合语义,非单分支)。
+ - 系统层组合:经真实 `EnemyContext` + 真实 `CommonAura`/`GuardAura` + 真实 final effect,覆盖 光环基础效果 ↔ 常规查询效果、光环特殊效果 ↔ 特殊查询效果、final-effect 阶段顺序、同/跨优先级顺序(D-25);属性→伤害联动(D-26)。
+ - 支援/guard 递归与未知 guard 的 warn 137 经 `logger.catch` 观测。
+ 若某组合需要的公开入口未暴露,按 D-07 暂停并向用户提问,不修改核心代码。
+
+
+ pnpm exec vitest run packages-user/data-state/test/enemyCombination.test.ts
+ 运行失败,或组合后的伤害/阶段顺序断言与真实实现不符
+
+
+ - `enemyCombination.test.ts` 零非跳过失败。
+ - 断言至少一组多 special 伤害组合与一组系统层光环/效果组合(含阶段顺序)。
+ - 每个 `it` 前有单行中文注释。
+
+ 顶层与系统层伤害/效果组合覆盖并跑绿。
+
+
+
+ Task 3: 小地图录像播放 + 二次录制逐条比对 + error 2001–2008
+ packages-user/data-state/test/replayPlayback.test.ts
+
+ packages-user/data-state/test/nodeReplay.test.ts
+ packages-user/data-state/test/dataClosure.test.ts
+ packages-user/data-state/test/fixtures/closed-loop.ts
+ packages-user/data-state/src/replay/commands.ts
packages-user/data-base/src/map/mapState.ts
- packages-user/data-base/src/map/types.ts
- packages-user/data-base/src/map/eventPath.test.ts (fromRaw validation + warning paths)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section D: mapState)
+ .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md
+ .planning/phases/03-data-completion/03-REPLAY-DIAGNOSTICS.md
+ .planning/phases/06-unit-tests/06-PATTERNS.md(B 节 replayIntegration)
-Build the inline `createMapState` fixture (TileStore + FaceManager + RoleFaceBinder + DirectionMapper fake `IDataCommon`) from 06-PATTERNS.md and cover `MapState`:
-- `createMap(id, w, h)` registers a map; `getMap` returns it and null for unknown; `fromRaw` builds a map from raw data and returns null for invalid raw (missing floor, invalid dimensions) with the documented logger warning.
-- active/inactive iteration: `setMapActiveStatus`, `isMapActive`, `getActiveMap`, `iterateActiveMaps`, `iterateInactiveMaps`, `iterateAllMaps`; `fromRaw` maps start inactive.
-- `setMapList` ordering and `getMap` resolution; `setArea`/`activeArea`/`deactiveArea`/`useAutoActivitor`/`notifyEnterFloor` change the active set as documented.
-- `compareWith(ref)` marks each map against its reference (`Uint32Array` per zIndex), marking missing references dirty.
-- `canPass`/`shouldHit` delegate to the active map/predicate as documented (use an inline fake predicate).
+ 用真实 API 构造小地图场景(合成数据)+ 一段录像步并播放:
+ - 小地图:用真实 `createCoreState()` 的 map 公开接口构造极小地图(1..3 格),放置怪物/勇士,绑定必要状态。
+ - 播放:记录一小段有效录像,创建 sandbox 并用有界等待(`waitForEnded`)播放完毕;断言关键状态到达预期。
+ - 二次录制:首次播放完毕后录像系统记录一遍录像步;断言二次录像与原录像**逐条完全相等**:步数相同、每步 code 相同、每步各 param 的 type/value 相同(严格逐条比较,不做整体 deep equal 之外的结构忽略);随后再次播放二次录像并断言同样结束。
+ - error 2001–2008:分别触发参数数量不匹配(2001)、参数类型不匹配(2002)、移动前未停止(2003)、移动控制器缺失(2004)、teleport 无路径(2005)、use-item 失败(2006)、equip 失败(2007)、unequip 失败(2008),经 `logger.catch`/`logger.error` spy 观测。
+ - 仅允许为录像重置最小使用存档数据,不测存读档本身(D-32)。
+ 有界循环,无 fake timers。
- pnpm exec vitest run packages-user/data-base/src/map/mapState.test.ts
- the focused run fails, or a fromRaw/activation/compareWith assertion does not match the documented behavior
+ pnpm exec vitest run packages-user/data-state/test/replayPlayback.test.ts
+ 运行失败,或 2001–2008 中任一码未被观测,或二次录制逐条比对失败
- - `mapState.test.ts` passes with zero non-skipped failures.
- - Asserts create/get/fromRaw validation, active-state iteration, area activation, compareWith, and canPass/shouldHit delegation.
- - Every `it` has a preceding single-line Chinese comment.
+ - `replayPlayback.test.ts` 零非跳过失败。
+ - 断言小地图场景播放完成、二次录像与原录像逐条相等(步数 + code + 各 param type/value)、二次播放完成。
+ - 断言 2001–2008 全部触发。
+ - 每个 `it` 前有单行中文注释。
- MapState registry, validation, and activation behavior is covered and green.
-
-
-
- Task 2: GameMap layers, aliases, dirty, save aggregation
- packages-user/data-base/src/map/gameMap.test.ts
-
- packages-user/data-base/src/map/gameMap.ts
- packages-user/data-base/src/map/types.ts
- packages-user/data-base/src/map/mapLifecycle.test.ts (save aggregation + compression loop)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section D: gameMap)
-
-
-Build `GameMap` with an inline `IDataCommon` and cover:
-- `addLayer`/`removeLayer`/`hasLayer` and the `onUpdateLayer` hook; `setLayerAlias`/`getLayerByAlias`/`getLayerAlias`, with a duplicate alias warning 84.
-- `setBackground`/`getBackground` and the `onChangeBackground` hook; `setActiveStatus` toggles `active`.
-- `setEventLayer`: rejects a layer not in `layerList` with warn 131; accepts an added layer; accepts null.
-- `dirty()` is true when any layer or the map self-flag is dirty; `markDirty(true/false)`.
-- `resizeLayer(w, h, keepBlock)`: calls `resize` when `keepBlock`, else `resize2`.
-- `saveState(compression)`: omits empty layers via the `isEmptyLayerSave` rule and includes a layer that has only point events or only static/dynamic blocks; `loadState` restores background and layers and clears the self dirty flag.
-- `compareWith(map of arrays)`: marks a layer dirty when its reference is absent and compares otherwise.
-
-
- pnpm exec vitest run packages-user/data-base/src/map/gameMap.test.ts
- the focused run fails, or the save-aggregation/empty-layer rule or warn 84/131 is not observed
-
-
- - `gameMap.test.ts` passes with zero non-skipped failures.
- - Asserts layer/alias lifecycle, background/event-layer, dirty, resize, save aggregation, and warn codes 84/131.
- - Every `it` has a preceding single-line Chinese comment.
-
- GameMap layer/alias/dirty/save behavior is covered and green.
-
-
-
- Task 3: MapLayer matrix + tiles (static/dynamic default events)
- packages-user/data-base/src/map/mapLayer.test.ts, packages-user/data-base/src/map/tile.test.ts
-
- packages-user/data-base/src/map/mapLayer.ts
- 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/types.ts
- packages-user/data-base/src/map/mapLifecycle.test.ts
- .planning/phases/06-unit-tests/06-PATTERNS.md (section D: mapLayer, tile)
-
-
-`mapLayer.test.ts` (fill beyond `mapLifecycle.test.ts`): `setBlock`/`getBlock`/`removeBlock` and `inMap`; `getMapData`/`putMapData`/`setMapRef`/`getMapRef` matrix read/write; `createDynamic` indexes the tile and `getDynamicTilesAt`/`iterateDynamicTiles`/`updateDynamicTile` reflect it; `transferToDynamic`/`transferToStatic`/`transferToStaticIfSafe` preserve or discard events per the keep flag; `getLocationData` resolves a locator to a tile; `setZIndex` and `setFaceBinder`; `event`/`getPointEvent` point-event access; `dirty`/`markDirty`/`compareWith`; `openDoor`/`closeDoor` async completion with real timers. Reuse the inline `createMapState` fixture; keep all-compression save assertions to explicit key fields (D-13).
-
-`tile.test.ts`: for a static and a dynamic tile: `num`/`raw`/`set` reflect the tile store data; changing `set` restores that tile's default `events` and marks the tile event view pure; `tileEvent()` is the tile's own view (dirty false after a default-restoring change) and `pointEvent()` returns the layer's point-event view for the tile's locator (or null out of map); `setFaceDirection` maps a direction through the face binder and returns the resulting num; `saveState`/`loadState` round-trip a dynamic tile's raw num and overridden events, and a static tile round-trips through the layer. Hotspot the default-event restoration into a dedicated `it`.
-
-
- pnpm exec vitest run packages-user/data-base/src/map/mapLayer.test.ts packages-user/data-base/src/map/tile.test.ts
- the focused run fails, or the default-event restoration / dynamic-tile index assertion does not match the documented behavior
-
-
- - Both files pass with zero non-skipped failures.
- - mapLayer.test.ts asserts matrix read/write, dynamic-tile index, transfer semantics, point events, dirty/compareWith, and async doors.
- - tile.test.ts asserts default-event restoration, point-event linkage, face direction, and save/load.
- - Every `it` has a preceding single-line Chinese comment.
-
- MapLayer matrix/tiles and tile default-event behavior is covered and green.
+ 小地图录像播放、二次录制比对与 2001–2008 覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(2001–2008)写入 `06-COVERAGE-MAP.md` 的 06-07 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -183,43 +195,44 @@ Build `GameMap` with an inline `IDataCommon` and cover:
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-07-01 | Tampering | tests under `packages-user/data-base/src/map` | low | mitigate | Only tracked in-repo modules; synthetic maps; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-07-02 | Denial of service | large map matrices | low | mitigate | Fixtures use tiny 1x1..3x2 matrices, keeping runtime negligible. |
-| T-06-07-03 | Information disclosure | fixtures | low | accept | Synthetic tile ids/events only; no real game data (D-02). |
-| T-06-07-SC | Tampering | package installs | high | mitigate | No new dependency; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-07-01 | Tampering | `data-state/test` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成 fixture;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-07-02 | Denial of service | 录像播放/二次录制循环 | medium | mitigate | 所有 await 循环有界(`waitForEnded` 迭代上限),30s 测试超时兜底。 |
+| T-06-07-03 | Information disclosure | fixture | low | accept | 合成状态,无真实存档或密钥(D-02)。 |
+| T-06-07-SC | Tampering | 包安装 | high | mitigate | 不新增依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for all 4 files pass; `pnpm test:ci` stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-07-N` entry in `06-TEST-FINDINGS.md`.
+- 两个文件聚焦运行通过;`pnpm test:ci` 全量绿。
+- `git status` 仅新增 `*.test.ts`;无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-07-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-07 小节(2001–2008);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前必须先向用户确认录像记录已接通;执行后暂停并用中文分条汇报。
-- 4 map behavior test files exist and pass.
-- D-18's fuller map coverage (matrix, point events, layers, dirty, resize/crop, tile defaults) is asserted.
+- 2 个集成测试文件存在并通过(在用户确认录像记录接通之后)。
+- 顶层伤害组合、系统层光环/效果组合、小地图录像播放、二次录制逐条比对、2001–2008 全部断言。
Test 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`
-- `packages-user/data-base/src/map/tile.test.ts`
+- `packages-user/data-state/test/enemyCombination.test.ts`
+- `packages-user/data-state/test/replayPlayback.test.ts`
Inline helper symbols:
-- `mapState.test.ts`: `createMapState`, `createRawMap`
-- `gameMap.test.ts`: `createGameMap`, `createState`, `createLayer`
-- `mapLayer.test.ts`: `createMapState`, `createLayerFixture`
-- `tile.test.ts`: `createMapState`, `createTiles`
+- `enemyCombination.test.ts`: `createMultiSpecialEnemy`、`createHandler`、`createAuraFixture`
+- `replayPlayback.test.ts`: `waitForEnded`、`createSmallMapScene`、`assertReplayEqual`、`triggerReplayError`
+
+Planning artifacts:
+- `06-COVERAGE-MAP.md` create-or-append 06-07 小节
diff --git a/.planning/phases/06-unit-tests/06-08-PLAN.md b/.planning/phases/06-unit-tests/06-08-PLAN.md
index 522d400..dce8e94 100644
--- a/.planning/phases/06-unit-tests/06-08-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-08-PLAN.md
@@ -5,45 +5,59 @@ type: execute
wave: 1
depends_on: []
files_modified:
- - packages-user/data-state/test/combatClosure.test.ts
- - packages-user/data-state/test/saveablesRoundTrip.test.ts
+ - packages-user/data-base/src/flag/system.test.ts
+ - 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
+ - packages-user/data-common/src/common/mover.test.ts
autonomous: true
requirements: [TEST-01]
estimate:
- tokens: 42000
- raw_tokens: 42000
- tasks: 2
+ tokens: 56000
+ raw_tokens: 56000
+ tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - A full battle path through a real `CoreState` (enemyManager + EnemyContext + CombatFlow + MainDamageCalculator) produces deterministic damage for a synthetic enemy
- - All 4 registered saveables (@system/hero, @system/flags, @system/maps, @system/enemy) round-trip same-instance across NoCompression/LowCompression/HighCompression with explicit key-field assertions
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "接口全覆盖:`FlagSystem`/`FlagCommonField`、`getFaceMovement`/`degradeFace`/`nextFaceDirection`/`fromDirectionString`、`MapLocIndexer`、`FaceManager`/`Dir4FaceHandler`/`Dir8FaceHandler`、`RoleFaceBinder`、`ObjectMover` 的每个公开方法至少一条正常用例;名称含 legacy 的接口/方法排除(D-19 / D-20 / D-30 / D-41)。"
+ - "code 全覆盖:本计划可达的码 43(bind 未知 main)、44(bind 主朝向冲突)、111(对非数值 flag 字段 add)各至少一条触发断言(D-31)。"
+ - "`FlagSystem` 公开接口(`occupied`/`insertField`/`getField`/`getOrInsert`/`getOrInsertComputed`/`deleteField`/`setFieldValue`/`addFieldValue`/`getFieldValue`/`getFieldValueDefaults`)行为与文档一致;`saveState`/`loadState` 归 06-09(flag `saveState()` 无压缩参数)。"
+ - "`data-common/src/common` 工具(utils/indexer/faceManager+face/mover)行为与文档一致;D-20 要求:主要测试未触及的辅助函数才补最小直接单测。"
artifacts:
- - path: packages-user/data-state/test/combatClosure.test.ts
- provides: combat integration through CoreState coverage
- - path: packages-user/data-state/test/saveablesRoundTrip.test.ts
- provides: saveables round-trip coverage
+ - path: packages-user/data-base/src/flag/system.test.ts
+ provides: FlagSystem 覆盖
+ - path: packages-user/data-common/src/common/utils.test.ts
+ provides: 朝向工具覆盖
+ - path: packages-user/data-common/src/common/indexer.test.ts
+ provides: MapLocIndexer 覆盖
+ - path: packages-user/data-common/src/common/faceManager.test.ts
+ provides: FaceManager + Dir4/Dir8 handler 覆盖
+ - path: packages-user/data-common/src/common/face.test.ts
+ provides: RoleFaceBinder 覆盖(码 43/44)
+ - path: packages-user/data-common/src/common/mover.test.ts
+ provides: ObjectMover 扩展覆盖(既有文件)
key_links:
- - "combatClosure.test.ts extends the existing `data-state/test/dataClosure.test.ts` shape (real `createCoreState`, `enemyManager.addPrefab`, `MainDamageCalculator`) to the context+flow path"
- - "saveablesRoundTrip.test.ts enters through `CoreState.getSaveableContent(id)` (data-state/src/core.ts:486) for the ids registered at core.ts:237-240 and never touches `saveSystem.init`/IndexedDB (D-10)"
+ - "flag/system.test.ts 扩展 `data-state/test/dataClosure.test.ts:120-132` 的 flag 往返形状到完整公开表面(去掉 save/load)"
+ - "common 相关文件沿用同目录 `mover.test.ts` 样板;`mover.test.ts` 为既有文件扩展,保留既有覆盖"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-08 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它 wave 1 计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只改/增 `*.test.ts`"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "异步路径只用真实计时器 + `await controller.onEnd`,禁用 `vi.useFakeTimers()`(D-04)"
+ - "不测任何 `saveState`/`loadState`(D-32)"
---
-Cover the two integration seams that cross layers: the combat closure through `CoreState`, and saveables-level same-instance round-trip for all 4 registered saveables across all 3 compression levels (D-01, D-10..D-13, D-15).
+覆盖两个辅助模块:全局 flag 系统 `packages-user/data-base/src/flag`(D-19)与通用接口 `packages-user/data-common/src/common`(utils / indexer / faceManager + face / mover)(D-20 / D-41)。
-Phase constraints. D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command; D-11: the saveables round-trip covers the 4 registered ids (`@system/hero`, `@system/flags`, `@system/maps`, `@system/enemy`) across all compression levels (`NoCompression`/`LowCompression`/`HighCompression`); D-12: the round-trip is same-instance (`saveState` then keep mutating then `loadState`, so state returns to the save point).
+Purpose: D-19 要求 flag 公开接口行为;D-20/D-41 要求 common 由主要测试自然覆盖,并对 tools/indexer/faceManager+face/mover 补直接单测。存读档集中到 06-09。
-Purpose: this is the only phase work that validates a multi-system wiring (combat context + flow + calculator) and the complete saveables contract, without introducing a CoreState end-to-end save/load path or IndexedDB.
-
-Output: 2 new integration test files under `data-state/test/`.
+Output: 6 个 `*.test.ts`(其中 `mover.test.ts` 为既有文件扩展)。
@@ -56,89 +70,121 @@ Output: 2 new integration test files under `data-state/test/`.
@.planning/phases/06-unit-tests/06-PATTERNS.md
@.planning/codebase/TESTING.md
@dev.md
-@packages-user/data-state/src/core.ts
+@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/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/mover.ts
+@packages-user/data-common/src/common/mover.test.ts
+@packages-user/data-common/src/common/types.ts
@packages-user/data-state/test/dataClosure.test.ts
-@packages-user/data-state/test/fixtures/closed-loop.ts
-@packages-user/data-system/src/combat/context.ts
-@packages-user/data-system/src/combat/combat.ts
-@packages-user/data-state/src/enemy/calculator.ts
-@packages-user/data-common/src/save/types.ts
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures inline and minimal (D-02/D-03); reuse `createCoreState()`.
-- Async paths use real timers + awaited sandbox/controller completion; no fake timers (D-04).
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-08-N`; `pnpm test:ci` stays green.
+- 只改测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03)。
+- 警告路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
+- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-08-N`;`pnpm test:ci` 保持全绿。
+- 不测存读档(D-32)。
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-08 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
- Task 1: Combat closure through CoreState
- packages-user/data-state/test/combatClosure.test.ts
+ Task 1: FlagSystem 完整公开表面
+ packages-user/data-base/src/flag/system.test.ts
+ 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-state/test/dataClosure.test.ts
- packages-user/data-system/src/combat/context.ts
- packages-user/data-system/src/combat/combat.ts
- packages-user/data-state/src/enemy/calculator.ts
- .planning/phases/06-unit-tests/06-PATTERNS.md (section A: combatClosure)
+ .planning/phases/06-unit-tests/06-PATTERNS.md(F 节 flag)
-Create `combatClosure.test.ts` driving the full combat path through a real `createCoreState()`:
-- add a synthetic prefab via `state.enemyManager.addPrefab(...)` with inline enemy attribute helpers (mirror the `dataClosure.test.ts` inline `createEnemy`), then create an enemy and register it in `state.enemyContext` with `resize` + `setEnemyAt`.
-- bind the hero: use `state.hero` (or an inline fake `IReadonlyHeroAttribute`) via `state.enemyContext.bindHero(...)` and `state.damageSystem`/`state.combatFlow` if exposed by `CoreState`; otherwise bind `CombatFlow`/`DamageSystem` directly against `state.enemyContext`.
-- assert `enemyContext.getEnemyByLocator`/`getEnemyLocatorByView`/`getViewByComputed` resolve the registered enemy.
-- run `combatFlow.battle(view)` (or `battleComputed`) and assert `MainDamageCalculator` via the production `DamageSystem`: deterministic `damage`/`turn`, and that the returned `info.handler` references the computed enemy + real locator + bound hero.
-- cover one guard/support scenario end-to-end if the context supports it, asserting warn 137 for a missing guard.
-Use real `CoreState` wiring; do not re-implement the calculator. If a wiring assumption is wrong (an interface is not exposed), STOP and record it as a `#06-08-N` finding with a skipped correct-expectation test rather than editing core code.
+ 创建 `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()` 无压缩参数)。
- pnpm exec vitest run packages-user/data-state/test/combatClosure.test.ts
- the focused run fails, or the handler identity / deterministic damage assertion does not hold
+ pnpm exec vitest run packages-user/data-base/src/flag/system.test.ts
+ 聚焦运行失败,或码 111 未被观测,或某公开方法缺少正常用例
- - `combatClosure.test.ts` passes with zero non-skipped failures.
- - Asserts context lookup, a real battle through the production damage path, handler identity, and one guard path (or a recorded skip if unsupported).
- - Every `it` has a preceding single-line Chinese comment.
+ - `system.test.ts` 零非跳过失败。
+ - 断言 D-19 列出的完整公开表面与码 111。
+ - 每个 `it` 前有单行中文注释。
- The combat path is proven end-to-end through CoreState and green.
+ FlagSystem 覆盖并跑绿。
- Task 2: Saveables same-instance round-trip (4 ids × 3 compressions)
- packages-user/data-state/test/saveablesRoundTrip.test.ts
+ Task 2: 朝向工具 + 位置索引 + FaceManager/handlers + RoleFaceBinder
+ 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
- packages-user/data-state/src/core.ts (addSaveableContent/getSaveableContent, ids @system/hero|flags|maps|enemy)
- packages-user/data-state/test/fixtures/closed-loop.ts (snapshot envelope)
- packages-user/data-base/src/map/mapLifecycle.test.ts (all-compression loop + explicit key fields)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section E: saveablesRoundTrip)
+ 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 节)
-Create `saveablesRoundTrip.test.ts` through `CoreState.getSaveableContent(id)` only:
-- for each of the 4 registered ids (`@system/hero`, `@system/flags`, `@system/maps`, `@system/enemy`) and each `SaveCompression` (`NoCompression`, `LowCompression`, `HighCompression`):
- - set a known key field on the saveable to a known value;
- - call `getSaveableContent(id)!.saveState(compression)`;
- - mutate the key field to a different value;
- - call `loadState(snapshot, compression)`;
- - assert ONLY the explicit key fields (D-13): hero base attribute value, flag field value, a map layer block/point-event value, an enemy prefab attribute value. Never assert a whole-object deep equality, derived fields, caches, or the serialization container shape.
- - additionally assert the snapshot is detached (mutating the live object after save does not change the snapshot's key field).
-- cover `getSaveableContent` returning null for a non-registered id.
- - Keep `CoreState` construction Node-safe via `createCoreState()`; do NOT call `saveSystem.init` and do NOT add `fake-indexeddb` (D-10).
- - The 4 ids x 3 compressions matrix is D-11, and the same-instance form (`saveState` then keep mutating then `loadState`, asserting the state returns to the save point) is D-12.
+ `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)。
- pnpm exec vitest run packages-user/data-state/test/saveablesRoundTrip.test.ts
- the focused run fails, or a round-trip assertion for any id/compression pair is missing or uses a whole-object deep equality
+ 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
+ 运行失败,或 43/44 未被观测,或某方向/索引映射断言缺失或错误
- - `saveablesRoundTrip.test.ts` passes with zero non-skipped failures.
- - Covers all 4 ids × 3 compressions with explicit key-field assertions and a snapshot-detachment check.
- - Asserts `getSaveableContent` null for an unknown id; no IndexedDB and no `fake-indexeddb`.
- - Every `it` has a preceding single-line Chinese comment.
+ - 四个文件零非跳过失败。
+ - utils 覆盖全部方向/字符串;indexer 覆盖往返与宽度变化;faceManager 覆盖注册表与两个 handler 全表面;face.test 覆盖 RoleFaceBinder 与码 43/44。
+ - 每个 `it` 前有单行中文注释。
- All registered saveables round-trip same-instance across all compressions and are green (D-11, D-12).
+ 朝向工具、位置索引、FaceManager/handlers、RoleFaceBinder 覆盖并跑绿。
+
+
+
+ Task 3: ObjectMover 扩展覆盖
+ packages-user/data-common/src/common/mover.test.ts
+
+ packages-user/data-common/src/common/mover.ts
+ packages-user/data-common/src/common/mover.test.ts
+ packages-user/data-common/src/common/types.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(共享样板 5)
+
+
+ 在既有 `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(契约检测点)。
+ - `push` 追加多步按序执行。
+ - 既有断言不变;仅新增缺失覆盖。无 fake timers。
+
+
+ pnpm exec vitest run packages-user/data-common/src/common/mover.test.ts
+ 运行失败,或既有用例被破坏,或某公开 mover 方法仍无正常用例
+
+
+ - `mover.test.ts` 零非跳过失败且既有覆盖保持。
+ - `ObjectMover` 全部列出公开方法至少一条正常用例;异步路径用真实计时器 + await。
+ - 每个新增 `it` 前有单行中文注释。
+
+ ObjectMover 公开表面扩展覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(43/44/111)写入 `06-COVERAGE-MAP.md` 的 06-08 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -148,39 +194,53 @@ Create `saveablesRoundTrip.test.ts` through `CoreState.getSaveableContent(id)` o
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅修改/新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-08-01 | Tampering | tests under `data-state/test` | low | mitigate | Only tracked in-repo modules; synthetic fixtures; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-08-02 | Denial of service | asynchronous closure replay | medium | mitigate | Any replay/await loop is bounded; the existing 30s test timeout is the backstop. |
-| T-06-08-03 | Information disclosure | fixtures | low | accept | Synthetic state only; no real save files or secrets (D-02). |
-| T-06-08-SC | Tampering | package installs | high | mitigate | No new dependency, explicitly no `fake-indexeddb`; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-08-01 | Tampering | `data-base/src/flag`、`data-common/src/common` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成 fixture;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-08-02 | Denial of service | mover 异步循环 | low | mitigate | 异步断言 await 控制器 `onEnd`,30s 超时兜底。 |
+| T-06-08-03 | Information disclosure | fixture | low | accept | 合成 flag 值/方向,无真实游戏数据(D-02)。 |
+| T-06-08-SC | Tampering | 包安装 | high | mitigate | 不新增依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for both files pass; `pnpm test:ci` stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-08-N` entry in `06-TEST-FINDINGS.md`.
+- 六个文件聚焦运行通过;`pnpm test:ci` 全量绿。
+- `git status` 仅显示 `*.test.ts` 变更;无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-08-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-08 小节(43/44/111);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前向用户确认;执行后暂停并用中文分条汇报。
-- 2 integration test files exist and pass.
-- Combat closure and the 4×3 saveables round-trip are asserted with explicit key fields.
+- 6 个辅助行为测试文件存在/扩展并通过。
+- D-19 的 flag 全表面与 D-20/D-41 的 common(utils/indexer/faceManager+face/mover)覆盖。
+- 未测存读档。
Test files:
-- `packages-user/data-state/test/combatClosure.test.ts`
-- `packages-user/data-state/test/saveablesRoundTrip.test.ts`
+- `packages-user/data-base/src/flag/system.test.ts`
+- `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`
+- `packages-user/data-common/src/common/mover.test.ts`(扩展)
Inline helper symbols:
-- `combatClosure.test.ts`: `createEnemy`, `addEnemy`, `createHandler`
-- `saveablesRoundTrip.test.ts`: `SAVEABLE_IDS`, `COMPRESSIONS`, `mutateKeyField`
+- `system.test.ts`: `createField`
+- `utils.test.ts`: `ALL_DIRECTIONS`
+- `indexer.test.ts`: `createIndexer`
+- `faceManager.test.ts`: `createHandler`
+- `face.test.ts`: `createBinder`
+- `mover.test.ts`: `TestTile`、`createMover`(沿用既有)
+
+Planning artifacts:
+- `06-COVERAGE-MAP.md` create-or-append 06-08 小节
diff --git a/.planning/phases/06-unit-tests/06-09-PLAN.md b/.planning/phases/06-unit-tests/06-09-PLAN.md
index 22ca93a..6980a57 100644
--- a/.planning/phases/06-unit-tests/06-09-PLAN.md
+++ b/.planning/phases/06-unit-tests/06-09-PLAN.md
@@ -2,56 +2,70 @@
phase: 06-unit-tests
plan: 09
type: execute
-wave: 1
-depends_on: []
+wave: 2
+depends_on: [06-03, 06-05, 06-06, 06-08]
files_modified:
- - packages-user/data-base/src/flag/system.test.ts
- - 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
-autonomous: true
+ - packages-user/data-base/src/enemy/saveLoad.test.ts
+ - packages-user/data-base/src/hero/saveLoad.test.ts
+ - 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
+ - .planning/phases/06-unit-tests/06-SAVE-EXCLUSIONS.md
+autonomous: false
requirements: [TEST-01]
estimate:
- tokens: 40000
- raw_tokens: 40000
+ tokens: 68000
+ raw_tokens: 68000
tasks: 3
confidence: low
must_haves:
truths:
- - "FLAGGED ASSUMPTION (unverified): TEST-01 is treated as satisfied by behavior unit tests gated by `pnpm test:ci`; no ROADMAP edge verifier resolved it (spec-less probe unclassified/unresolved), so user confirmation is required."
- - FlagSystem exposes its full public surface (occupied/insert/get/getOrInsert/getOrInsertComputed/delete/set/add/get/defaults) and save/load round-trips field values
- - getFaceMovement/degradeFace/nextFaceDirection/fromDirectionString return the documented directions
- - MapLocIndexer converts location<->index consistently for a configured width
- - FaceManager and the Dir4/Dir8 handlers implement degrade/movement/move/opposite/next/mapDirection/mapMovement
+ - "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
+ - "存读档集中:本计划是唯一测试 `saveState`/`loadState` 的计划;其它计划一律不测(D-32 / D-42)。"
+ - "接口全覆盖:所有覆盖系统内含 `saveState`/`loadState` 的类(Enemy/special、EnemyManager、HeroAttribute/modifier、HeroLocation、HeroState、HeroEquipment、EquipmentState/HeroEquipsStore、HeroItems、HeroFollower、HeroRendering、MapState、GameMap、MapLayer、StaticTile、DynamicTile、ReplayArray、FlagSystem)的这两个方法至少一条正常用例;名称含 legacy 的接口/方法排除(D-30 / D-42)。"
+ - "code 全覆盖:本计划可达的码 55(MapStore load 缺 reference)、58(equipment store load 找不到 maxUid)、59(equipment load 找不到 item raw data)、112(重复 saveable id)、113(bindSaveableExecuter 目标未添加)、119(loadState 缺 prefab)、120(loadState 缺 special)、122(MapStore load 楼层缺失)、124(MapLayer load 引用缺失)各至少一条触发断言(D-31 / D-42)。"
+ - "往返形式为**同实例恢复**:对可存档对象 `saveState` → 继续修改状态 → `loadState` → 关键字段回到存档点(D-12)。"
+ - "压缩档全覆盖:`NoCompression`/`LowCompression`/`HighCompression` 三档(按各 `saveState`/`loadState` 签名适用者)(D-11 / D-42)。"
+ - "「状态相同」只用**关键字段显式断言**,不比较派生字段/缓存/序列化容器结构,也不做整体深度相等(D-13)。"
+ - "推导并写出**排除清单**(存档无关/元数据字段,如最后存档时间、dirty、缓存),落到 `06-SAVE-EXCLUSIONS.md`(D-42)。"
+ - "CoreState 顶层验证:`getSaveableContent(id)` 对 4 个已注册 id(`@system/hero`/`flags`/`maps`/`enemy`)逐个往返(D-11),并验证 `addSaveableContent`/`bindSaveableExecuter` 守卫(112/113);若用户已新增公开 save/load 入口,则一并经该入口验证。"
+ - "执行前必须先向用户确认(依赖用户调整 CoreState 可存档内容并新增公开 save/load 入口,例如录像存档目前未计入但应计入)(D-42)。"
artifacts:
- - path: packages-user/data-base/src/flag/system.test.ts
- provides: FlagSystem coverage
- - path: packages-user/data-common/src/common/utils.test.ts
- provides: face utility coverage
- - path: packages-user/data-common/src/common/indexer.test.ts
- provides: MapLocIndexer coverage
- - path: packages-user/data-common/src/common/faceManager.test.ts
- provides: FaceManager + handlers coverage
+ - path: packages-user/data-base/src/enemy/saveLoad.test.ts
+ provides: Enemy/special/EnemyManager 存读档覆盖(119/120)
+ - path: packages-user/data-base/src/hero/saveLoad.test.ts
+ provides: hero 各子系统存读档覆盖(58/59)
+ - path: packages-user/data-base/src/map/saveLoad.test.ts
+ provides: 地图各层/tile 存读档覆盖(55/122/124)
+ - path: packages-user/data-common/src/replay/saveLoad.test.ts
+ provides: ReplayArray 存读档覆盖
+ - path: packages-user/data-base/src/flag/saveLoad.test.ts
+ provides: FlagSystem 存读档覆盖
+ - path: packages-user/data-state/test/saveablesRoundTrip.test.ts
+ provides: CoreState 顶层 4 saveables × 3 压缩档往返(112/113)
+ - path: .planning/phases/06-unit-tests/06-SAVE-EXCLUSIONS.md
+ provides: 存读档排除清单(存档无关/元数据字段)
key_links:
- - "flag/system.test.ts extends the existing flag round-trip in `data-state/test/dataClosure.test.ts:120-132` to the full public surface; note flag `saveState()` takes no compression argument"
- - "the common tests only exercise helpers the main suites do not (D-20); where a helper is already covered through combat/map/hero, keep the direct test minimal"
+ - "saveablesRoundTrip.test.ts 经 `CoreState.getSaveableContent(id)`(`data-state/src/core.ts:486`)进入,对应 `core.ts:237-240` 注册的 4 个 id;不接触 `saveSystem.init`/IndexedDB(D-10)"
+ - "各 saveLoad.test.ts 用 inline 合成对象/小地图/小录像数组;三档压缩循环参照 `data-base/src/map/mapLifecycle.test.ts:203-234` 的显式关键字段形状"
+ - "`06-COVERAGE-MAP.md` 为共享 create-or-append 文件:写 06-09 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节;幂等且与其它计划顺序无关,不重写他节"
prohibitions:
- - "FLAGGED/UNVERIFIED: no production/core source file is created or modified in this phase"
- - "FLAGGED/UNVERIFIED: no coverage provider is installed and no coverage threshold is enforced"
- - "FLAGGED/UNVERIFIED: no `fake-indexeddb` or CoreState end-to-end save/load path is introduced"
- - "FLAGGED/UNVERIFIED: a suspected-bug test is never weakened into passing; it is written to the correct expectation and marked it.skip/it.todo"
- - "FLAGGED/UNVERIFIED: no shared cross-file fixture/factory helper is created; fixtures stay inline per test file"
- - "FLAGGED/UNVERIFIED: no fake timers are used for async action tests"
+ - "不创建或修改任何生产/核心源码,只新增 `*.test.ts` 与规划产物"
+ - "不安装覆盖率工具(D-09)"
+ - "不引入 `fake-indexeddb`,不做 IndexedDB 端到端存档(D-10)"
+ - "疑似 bug 按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
+ - "fixture 全部文件内联,不建跨文件 helper(D-02/D-03)"
+ - "不使用 fake timers(D-04)"
+ - "不做整体深度相等;不比较派生/缓存/序列化容器结构(D-13)"
---
-Cover the two auxiliary modules: the global flag system `packages-user/data-base/src/flag` (D-19) and the common helpers `packages-user/data-common/src/common` (D-20).
+存读档独立系统(D-42):覆盖所有含 `saveState`/`loadState` 的类逐类同实例往返 + `CoreState` 顶层验证;仅断言关键状态字段,排除存档无关/元数据字段;压缩档全覆盖。
-Phase constraints. D-05: a suspected bug becomes a test written to the correct expectation and marked `it.skip`/`it.todo` with a Chinese comment pointing into `06-TEST-FINDINGS.md`, keeping `pnpm test:ci` green; D-08: verification uses the fixed non-watch `pnpm test:ci` command.
+**执行前必须先向用户确认**(D-42:依赖用户调整 CoreState 可存档内容,例如录像存档目前未计入但应计入,并新增公开 save/load 入口)。
-Purpose: D-19 asks for the flag system's public surface (many interfaces, simple logic). D-20 asks that `common` be covered naturally by the main suites, adding minimal direct tests only for helpers the main suites do not touch (`getFaceMovement`/`degradeFace`/`nextFaceDirection`/`fromDirectionString`, `MapLocIndexer`, `FaceManager`).
-
-Output: 4 new co-located `*.test.ts` files.
+Output: 6 个 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`。
@@ -64,111 +78,157 @@ Output: 4 new co-located `*.test.ts` files.
@.planning/phases/06-unit-tests/06-PATTERNS.md
@.planning/codebase/TESTING.md
@dev.md
-@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/utils.ts
-@packages-user/data-common/src/common/indexer.ts
-@packages-user/data-common/src/common/faceManager.ts
-@packages-user/data-common/src/common/mover.test.ts
+@packages-user/data-common/src/save/types.ts
+@packages-user/data-state/src/core.ts
@packages-user/data-state/test/dataClosure.test.ts
+@packages-user/data-state/test/fixtures/closed-loop.ts
+@packages-user/data-base/src/map/mapLifecycle.test.ts
+@packages-user/data-base/src/enemy/enemy.ts
+@packages-user/data-base/src/enemy/manager.ts
+@packages-user/data-base/src/hero/equipStore.ts
+@packages-user/data-base/src/hero/state.ts
+@packages-user/data-base/src/map/mapState.ts
+@packages-user/data-base/src/map/mapLayer.ts
+@packages-user/data-common/src/replay/array.ts
-- Tests only. Do not modify any production file.
-- Every `it(...)` is immediately preceded by a single-line Chinese comment (dev.md).
-- Fixtures inline and minimal (D-02/D-03).
-- Warning paths via `logger.catch` / `vi.spyOn(logger, ...)`.
-- Suspected bugs: correct expectation + `it.skip`/`it.todo` + Chinese comment anchored `#06-09-N`; `pnpm test:ci` stays green.
+- 只写测试,绝不修改任何生产文件。
+- 每个 `it(...)` 前必须有一行中文注释(dev.md)。
+- fixture 内联且最小(D-02/D-03);用 `SaveCompression` 三档。
+- 警告/错误路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
+- 疑似 bug:正确预期 + `it.skip`/`it.todo` + 中文注释锚定 `#06-09-N`;`pnpm test:ci` 保持全绿。
+- **执行前必须先向用户确认(D-42)CoreState 可存档内容与公开 save/load 入口已就绪;未确认前不得开始。**
+- `06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-09 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节。
+- **执行协议(D-33):执行前向用户确认;执行后暂停并用中文分条汇报。**
-
- Task 1: FlagSystem full public surface
- packages-user/data-base/src/flag/system.test.ts
+
+ Task 1: 执行前门禁 — 用户确认 CoreState 可存档内容与公开入口已调整
+ packages-user/data-state/test/saveablesRoundTrip.test.ts(尚未创建,仅作占位说明)
+ 调整后的 CoreState 可存档内容(含录像存档计入)与新增的公开 save/load 入口。
+
+ 1) 确认 4 个 saveable(`@system/hero`/`flags`/`maps`/`enemy`)及其新增内容已就绪。
+ 2) 确认是否提供公开 save/load 入口(有则一并验证,无则仅走 `getSaveableContent(id)` 的 saveables 层)。
+ 3) 确认录像存档是否已计入。
+
+ 用户回复「存读档入口已就绪,可以开始」则继续 Task 2/3;否则暂停本计划等待用户改代码。
+ .planning/phases/06-unit-tests/06-CONTEXT.md(D-42)
+ packages-user/data-state/src/core.ts(saveables 注册与 saveableContent 入口)
+ packages-user/data-common/src/save/types.ts(ISaveableContent/ISaveSystem 契约)
+
+
+ 本计划依赖用户先调整 CoreState 的可存档内容(例如录像存档目前未计入但应计入),并新增公开 save/load 入口(D-42)。开始前向用户展示并确认:
+ - 4 个 saveable(`@system/hero`/`flags`/`maps`/`enemy`)及其新增内容是否已就绪;
+ - 是否已提供公开 save/load 入口(若有,本计划将一并经该入口验证;若无,则仅走 `getSaveableContent(id)` 的 saveables 层);
+ - 录像存档是否已计入。
+ 用户确认后再继续 Task 2/3;若未就绪,暂停本计划等待用户改代码,不得自行修改核心代码。
+
+
+ 用户明确回复「存读档入口已就绪,可以开始」,或不通过则暂停本计划
+
+
+ - 用户给出明确开始确认;否则本计划保持暂停状态。
+
+ CoreState 可存档内容与入口就绪得到用户确认,或计划暂停等待用户改代码。
+
+
+
+ Task 2: enemy + hero 各类存读档往返(含压缩档与码 119/120/58/59)
+ 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
+
+ packages-user/data-base/src/enemy/enemy.ts
+ packages-user/data-base/src/enemy/manager.ts
+ packages-user/data-base/src/enemy/special.ts
+ 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/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/rendering.ts
+ .planning/phases/06-unit-tests/06-PATTERNS.md(C/E 节)
+
+
+ `enemy/saveLoad.test.ts`:对 `Enemy`、`CommonSerializableSpecial`/`NonePropertySpecial`、`EnemyManager` 分别同实例往返(三档压缩):
+ - Enemy:属性关键字段 + special 关键值;`loadState` 时实例上存在但存档缺失的 special 经 `logger.catch` 观测码 120;`saveState` 返回分离克隆(改返回对象不影响实例)。
+ - EnemyManager:`saveState` 只序列化脏 code;`loadState` 缺 prefab 经 `logger.catch` 观测码 119。
+ - 只断言关键状态字段(D-13)。
+
+ `hero/saveLoad.test.ts`:对 hero 各子系统同实例往返(三档压缩):
+ - `HeroAttribute`/`ValueModifier`/`PercentageModifier`:base/值关键字段恢复;modifier 存盘开关生效。
+ - `HeroLocation`:x/y/floorId/direction 四字段恢复。
+ - `HeroState`:attribute base 值、location、modifier 值关键字段恢复;save 禁用 modifier 不持久化。
+ - `HeroEquipment`/`EquipmentState`/`HeroEquipsStore`/`HeroItems`/`HeroFollower`/`HeroRendering`:各自关键字段恢复;equipStore `loadState` 缺 item raw 观测码 59、空 equipments 观测码 58(`logger.catch`)。
+ - 只断言关键字段(D-13)。
+
+ `06-SAVE-EXCLUSIONS.md`:逐类推导并写出**排除清单**——列出**不**在断言范围内的存档无关/元数据字段(如最后存档时间、dirty/脏标记、缓存/派生字段、序列化容器结构、副本容量等),并注明依据(为什么与存档语义无关)。此文档作为 06-09 与后续维护的对照基准。
+
+
+ pnpm exec vitest run packages-user/data-base/src/enemy/saveLoad.test.ts packages-user/data-base/src/hero/saveLoad.test.ts
+ 运行失败,或 119/120/58/59 中任一码未被观测,或某类缺少同实例往返断言
+
+
+ - 两个文件零非跳过失败。
+ - enemy/hero 每个含存读档的类至少一条同实例往返;断言码 119/120/58/59。
+ - 只用关键字段显式断言,无整体深度相等。
+ - `06-SAVE-EXCLUSIONS.md` 存在并逐类列出排除字段与依据。
+ - 每个 `it` 前有单行中文注释。
+
+ enemy/hero 存读档往返与排除清单覆盖并跑绿。
+
+
+
+ Task 3: map + replay + flag 存读档往返(码 55/122/124)与 CoreState 顶层验证(码 112/113)
+ 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
+
+ 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/staticTile.ts
+ 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-base/src/flag/field.ts
- packages-user/data-base/src/flag/types.ts
- packages-user/data-state/test/dataClosure.test.ts (flag round-trip)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section F: flag)
+ 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 节)
-Create `flag/system.test.ts` (include the `vi.hoisted` `Map.getOrInsertComputed` polyfill when the import chain requires it) covering the full `FlagSystem` surface:
-- `occupied` false then true after `insertField`/`setFieldValue`.
-- `insertField` returns a `FlagCommonField` whose `get`/`set`/`toStructured` behave as documented; `getField` returns null for an unknown field; `getOrInsert` inserts a default and returns the same instance on the next call.
-- `getOrInsertComputed` receives the key and inserts a computed default; `deleteField` removes it.
-- `setFieldValue` overwrites; `addFieldValue` adds to a numeric field and warns 111 when the current value is not a number (assert via `logger.catch`); `getFieldValue` returns undefined for an unknown field; `getFieldValueDefaults` returns the default and inserts it.
-- `saveState()` takes no compression argument; round-trip: set a known value, save, mutate, load, and assert the explicit key field plus `occupied` (D-13). Mutate the live field after save and assert the snapshot is detached.
+ `map/saveLoad.test.ts`:对 `MapState`、`GameMap`、`MapLayer`、`StaticTile`、`DynamicTile` 同实例往返(三档压缩):
+ - MapState:`loadState` 在非 `NoCompression` 且未 `compareWith` 时观测码 55;缺楼层观测码 122。
+ - MapLayer:关键块/点位事件恢复;引用缺失观测码 124;三档压缩循环参照 `mapLifecycle.test.ts` 的显式关键字段。
+ - StaticTile/DynamicTile:关键 num 与覆盖事件恢复。
+ - 只断言关键字段(D-13)。
+
+ `replay/saveLoad.test.ts`:`ReplayArray.saveState`/`loadState` 同实例往返:命令与参数(含 boolean/整数/bigint/string/数组)读回一致;不比较内部容量/元数据。
+
+ `flag/saveLoad.test.ts`:`FlagSystem.saveState()`(无压缩参数)/`loadState` 同实例往返:已知字段值 + `occupied` 恢复;`saveState` 返回分离克隆。
+
+ `saveablesRoundTrip.test.ts`:经 `CoreState.getSaveableContent(id)`:
+ - 对 4 个 id(`@system/hero`/`flags`/`maps`/`enemy`)× 3 档 `SaveCompression`:设已知关键字段 → `saveState` → 改字段 → `loadState` → 只断言关键字段(hero base 属性、flag 字段、map 层块/点位事件、enemy prefab 属性)。不做整体深度相等。
+ - 断言快照分离:save 后改活对象不影响快照关键字段。
+ - 未知 id 返回 null。
+ - 守卫:重复 `addSaveableContent` 经 `logger.catch` 观测码 112;对未添加对象 `bindSaveableExecuter` 观测码 113。
+ - 若用户已提供公开 save/load 入口,则经该入口再跑一遍等价往返。
+ - Node-safe `createCoreState()`;不 `saveSystem.init`、不加 `fake-indexeddb`(D-10)。
- pnpm exec vitest run packages-user/data-base/src/flag/system.test.ts
- the focused run fails, or the warn 111 / save-load key-field assertion is not observed
+ 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
+ 运行失败,或 55/112/113/122/124 中任一码未被观测,或 4×3 往返矩阵缺失某组合
- - `system.test.ts` passes with zero non-skipped failures.
- - Asserts the full public surface listed in D-19, warn 111, and an explicit-key-field save/load round-trip.
- - Every `it` has a preceding single-line Chinese comment.
+ - 四个文件零非跳过失败。
+ - map/replay/flag 各类同实例往返;断言码 55/122/124。
+ - saveablesRoundTrip 覆盖 4 id × 3 压缩档,显式关键字段 + 快照分离 + 未知 id null + 码 112/113。
+ - 每个 `it` 前有单行中文注释。
- FlagSystem behavior is covered and green.
-
-
-
- Task 2: Face utilities + location indexer
- packages-user/data-common/src/common/utils.test.ts, packages-user/data-common/src/common/indexer.test.ts
-
- packages-user/data-common/src/common/utils.ts
- packages-user/data-common/src/common/indexer.ts
- packages-user/data-common/src/common/types.ts (FaceDirection)
- packages-user/data-common/src/common/mover.test.ts (same-directory boilerplate)
- .planning/phases/06-unit-tests/06-PATTERNS.md (section G)
-
-
-`utils.test.ts`: assert `getFaceMovement` for every `FaceDirection` (including `Unknown -> {0,0}`), `degradeFace` for all diagonals and the custom `unknown` argument, `nextFaceDirection` for 4-way clockwise/counter-clockwise and 8-way clockwise/counter-clockwise plus `Unknown` passthrough, and `fromDirectionString` for every string plus an unknown string. These are pure functions; synchronous asserts.
-
-`indexer.test.ts`: with `MapLocIndexer` and `setWidth(w)`, assert `locToIndex(x,y)` and `locaterToIndex({x,y})` agree, `indexToLocator` is the inverse for in-range indices, and a wider width changes the row stride. Use a `vi.hoisted` stub only if the import chain requires it.
-
-Per D-20, keep these minimal and only where the main suites do not already exercise the helper.
-
-
- pnpm exec vitest run packages-user/data-common/src/common/utils.test.ts packages-user/data-common/src/common/indexer.test.ts
- the focused run fails, or a direction/index mapping assertion is missing or wrong
-
-
- - Both files pass with zero non-skipped failures.
- - utils.test.ts covers all four helpers across every direction/string; indexer.test.ts covers round-trip and width changes.
- - Every `it` has a preceding single-line Chinese comment.
-
- Face utilities and the location indexer are covered and green.
-
-
-
- Task 3: FaceManager + Dir4/Dir8 handlers
- packages-user/data-common/src/common/faceManager.test.ts
-
- packages-user/data-common/src/common/faceManager.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 (section G)
-
-
-Create `faceManager.test.ts`:
-- `FaceManager.register`/`registerById` then `get`/`getById` return the handler; unknown group/id return null (registry shape).
-- `Dir8FaceHandler`: `degrade` passthrough; `movement` for every direction and for an out-of-range input (`ZERO_DESCRIPTOR`); `move` scales and supports negative counts; `opposite` for each direction and `Unknown -> Unknown`; `next` clockwise/counter-clockwise and `Unknown` passthrough; `mapDirection`/`mapMovement` include `Unknown` with `{0,0}`.
-- `Dir4FaceHandler`: `degrade` maps diagonals to their 4-way direction and unknown values to `Unknown`; `movement`/`move`/`opposite`/`next`/`mapDirection`/`mapMovement` behave for the 4-way set.
-Keep fixtures inline. If a handler's documented descriptor is contradicted by the implementation, follow the skip+findings rule.
-
-
- pnpm exec vitest run packages-user/data-common/src/common/faceManager.test.ts
- the focused run fails, or a handler mapping/registry assertion is missing or wrong
-
-
- - `faceManager.test.ts` passes with zero non-skipped failures.
- - Asserts the registry and both handlers' full `IFaceHandler` surface.
- - Every `it` has a preceding single-line Chinese comment.
-
- FaceManager and both direction handlers are covered and green.
+ 地图/录像/flag 存读档往返与 CoreState 顶层验证覆盖并跑绿;完成后以 create-or-append 方式将本计划的 code→模块→用例行(55/58/59/112/113/119/120/122/124)写入 `06-COVERAGE-MAP.md` 的 06-09 小节:先检查表头,不存在则先创建,再只追加本小节,不重写他节。
@@ -178,43 +238,55 @@ Keep fixtures inline. If a handler's documented descriptor is contradicted by th
| Boundary | Description |
|----------|-------------|
-| none | In-repo Node Vitest tests only; no runtime input surface, network, DOM, or filesystem write is introduced. |
+| none | 仅新增仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入(不碰 IndexedDB)。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-06-09-01 | Tampering | tests under `data-base/src/flag` and `data-common/src/common` | low | mitigate | Only tracked in-repo modules; synthetic fixtures; no package install; gate `pnpm test:ci` plus review that only `*.test.ts` changed. |
-| T-06-09-02 | Information disclosure | fixtures | low | accept | Synthetic flag values/directions only; no real game data (D-02). |
-| T-06-09-03 | Repudiation | global stub leakage | low | accept | Any `vi.stubGlobal` is torn down in `afterAll`; tests are independent. |
-| T-06-09-SC | Tampering | package installs | high | mitigate | No new dependency; if an install becomes necessary, stop for a package-legitimacy checkpoint. |
+| T-06-09-01 | Tampering | `data-base`/`data-common`/`data-state/test` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成快照;不安装包、不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
+| T-06-09-02 | Information disclosure | 快照 fixture | low | accept | 合成状态,无真实存档文件或密钥(D-02)。 |
+| T-06-09-03 | Repudiation | 全局 stub 泄漏 | low | accept | 任何 `vi.stubGlobal` 在 `afterAll` 拆除;用例独立。 |
+| T-06-09-SC | Tampering | 包安装(尤其 `fake-indexeddb`) | high | mitigate | 明确不引入 `fake-indexeddb` 或任何新依赖;若确需安装,停止并先走包合法性 checkpoint。 |
-- Focused runs for all 4 files pass; `pnpm test:ci` stays green.
-- `git status` shows only new `*.test.ts`; no core file modified.
-- Any `it.skip`/`it.todo` has a matching `#06-09-N` entry in `06-TEST-FINDINGS.md`.
+- 六个测试文件聚焦运行通过;`pnpm test:ci` 全量绿。
+- `git status` 仅新增 `*.test.ts` 与 `06-SAVE-EXCLUSIONS.md`;无核心文件改动。
+- 任何 `it.skip`/`it.todo` 均有 `#06-09-N` 条目。
+- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-09 小节(9 个码);表头不存在则先创建;幂等、顺序无关,不重写他节。
+- **执行协议**:执行前必须先向用户确认 CoreState 可存档内容与入口就绪;执行后暂停并用中文分条汇报。
-- 4 auxiliary behavior test files exist and pass.
-- D-19's full flag surface and D-20's minimal direct common coverage are asserted.
+- 6 个存读档测试文件存在并通过(在用户确认入口就绪之后)。
+- 每个含 `saveState`/`loadState` 的类至少一条同实例往返;4 saveable × 3 压缩档覆盖。
+- 排除清单 `06-SAVE-EXCLUSIONS.md` 落盘。
+- 9 个可达码全部触发断言。
Test files:
-- `packages-user/data-base/src/flag/system.test.ts`
-- `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-base/src/enemy/saveLoad.test.ts`
+- `packages-user/data-base/src/hero/saveLoad.test.ts`
+- `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`
Inline helper symbols:
-- `system.test.ts`: `createField`
-- `utils.test.ts`: `ALL_DIRECTIONS`
-- `indexer.test.ts`: `createIndexer`
-- `faceManager.test.ts`: `createHandler`
+- `enemy/saveLoad.test.ts`: `createEnemy`、`SAVE_COMPRESSIONS`
+- `hero/saveLoad.test.ts`: `createState`、`createHeroState`、`SAVE_COMPRESSIONS`
+- `map/saveLoad.test.ts`: `createMapState`、`SAVE_COMPRESSIONS`
+- `replay/saveLoad.test.ts`: `createArray`、`createRoute`
+- `flag/saveLoad.test.ts`: `createFlags`
+- `saveablesRoundTrip.test.ts`: `SAVEABLE_IDS`、`COMPRESSIONS`、`mutateKeyField`
+
+Planning artifacts:
+- `.planning/phases/06-unit-tests/06-SAVE-EXCLUSIONS.md`
+- `06-COVERAGE-MAP.md` create-or-append 06-09 小节