diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 5f4e872..c23b8be 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -210,12 +210,12 @@ Plans: 3. 测试在本地可运行且全部通过 4. 测试由 AI 编写并运行,通过验证后可提交 -**Plans**: 1/9 plans executed (data-layer slice; non-data render/legacy coverage deferred) +**Plans**: 2/9 plans executed (data-layer slice; non-data render/legacy coverage deferred) Plans: - [x] 06-01-PLAN.md — Combat L2 (data-system/src/combat) behavior tests + phase tracer -- [ ] 06-02-PLAN.md — Combat L3 (data-state/src/enemy) calculator/aura/special/mapDamage tests +- [x] 06-02-PLAN.md — Combat L3 (data-state/src/enemy) calculator/aura/special/mapDamage tests - [ ] 06-03-PLAN.md — Enemy data model (data-base/src/enemy) tests - [ ] 06-04-PLAN.md — Replay (data-common/src/replay) tests + replay integration - [ ] 06-05-PLAN.md — Hero core (attribute/modifier/location/state) tests @@ -226,7 +226,7 @@ Plans: **Wave 1** *(all plans; independent new test files, no shared production edits)* -- [ ] 06-01-PLAN.md +- [x] 06-01-PLAN.md - [ ] 06-02-PLAN.md - [ ] 06-03-PLAN.md - [ ] 06-04-PLAN.md @@ -248,4 +248,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 3. 数据端完成 | 19/19 | Complete | 2026-09-12 | | 4. 渲染适配与双布局 | 0/TBD | Not started | - | | 5. Legacy 移植 | 0/TBD | Not started | - | -| 6. 单元测试 | 1/9 | In Progress| | +| 6. 单元测试 | 2/9 | In Progress| | diff --git a/.planning/STATE.md b/.planning/STATE.md index 4190fad..a2f5029 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,16 +4,16 @@ milestone: v1.0 current_phase: 06 current_phase_name: unit-tests status: executing -stopped_at: Completed 06-01-PLAN.md -last_updated: "2026-09-14T00:53:17.655Z" +stopped_at: Completed 06-02-PLAN.md +last_updated: "2026-09-14T01:09:15.227Z" last_activity: 2026-09-14 last_activity_desc: Phase 06 execution started -state_head: 6c8f85a2be68d9696844ee863a8bb6787b6fde45 +state_head: 7b956705d3ecf72553561c3f124039a9100ad3be progress: total_phases: 6 completed_phases: 0 total_plans: 46 - completed_plans: 38 + completed_plans: 39 milestone_name: milestone --- @@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-09-10) ## Current Position Phase: 06 (unit-tests) — EXECUTING -Plan: 2 of 9 +Plan: 3 of 9 Status: Ready to execute Last activity: 2026-09-14 — Phase 06 execution started @@ -77,6 +77,7 @@ Progress: [█████░░░░░] 50% | Phase 03 P18 | 14min | 3 tasks | 8 files | | Phase 03 P19 | 8min | 2 tasks | 6 files | | Phase 06 P01 | 9min | 3 tasks | 6 files | +| Phase 06 P02 | 9min | 3 tasks | 6 files | ## Accumulated Context @@ -140,6 +141,8 @@ Recent decisions affecting current work: - [quick 260913-qtq]: 事件注册收口为单一 createEventRegistrations(),不做分类包装;共享工具放 event/utils.ts 且不经 index 导出。 - [Phase 06]: 06-01:combat Layer-2 以行为单测覆盖(DamageContext/DamageSystem/MapDamage/EnemyContext/CombatFlow),三处疑似 bug 只记录不修复,按 D-05 写成 it.skip 正确预期用例并登记 06-TEST-FINDINGS.md #06-01-1..3 - [Phase 06]: 06-01:Node 测试须同时 polyfill Map.getOrInsert 与 getOrInsertComputed(EnemyContext 使用前者) +- [Phase 06]: 06-02:enemy 顶层实现以 6 个同目录行为单测覆盖(calculator/final/comparer/aura/special/mapDamage),fixture 全部 inline,未修改任何生产代码 +- [Phase 06]: 06-02:未发现疑似 bug,06-TEST-FINDINGS.md 无新增 #06-02-N;BetweenDamageView 方向去重语义与 plan 措辞不符但实现正确,仅记录澄清 ### Pending Todos @@ -168,6 +171,6 @@ None yet. ## Session Continuity -Last session: 2026-09-14T00:53:17.425Z -Stopped at: Completed 06-01-PLAN.md +Last session: 2026-09-14T01:09:14.983Z +Stopped at: Completed 06-02-PLAN.md Resume file: None diff --git a/.planning/phases/06-unit-tests/06-02-SUMMARY.md b/.planning/phases/06-unit-tests/06-02-SUMMARY.md new file mode 100644 index 0000000..321c28c --- /dev/null +++ b/.planning/phases/06-unit-tests/06-02-SUMMARY.md @@ -0,0 +1,179 @@ +--- +phase: 06-unit-tests +plan: 02 +subsystem: testing +tags: [vitest, unit-test, data-state, enemy, damage-calculator, aura, special-registry, map-damage, node] + +requires: + - phase: 06-unit-tests + provides: "Node Vitest harness pattern (vi.hoisted global stub + dynamic module bag + inline fakes + logger.catch) and the shared 06-TEST-FINDINGS.md sink" +provides: + - "Top-level combat implementation coverage: MainDamageCalculator (all special branches), MainEnemyFinalEffect, MainEnemyComparer, CommonAura/GuardAura, registerSpecials, and the map-damage views/converter/reducer" + - "Reusable inline fixture shapes for data-state enemy tests (fake enemy/hero/state/context/view)" +affects: [06-unit-tests, 06-03, 06-08] + +actuals: + tokens: 16356 + tasks: 3 + commits: 4 + plan_head_before: 50d868760aab1f00544424d01b1982978d0aa344 + +tech-stack: + added: [] + patterns: + - "Inline per-file fake IEnemy/IReadonlyEnemy/IReadonlyHeroAttribute/IStateBase/IEnemyContext; data-layer interfaces never mocked" + - "vi.stubGlobal('core', { flags }) with a mutable handle returned from vi.hoisted for the BetweenDamageView core read (restored per assertion)" + - "Focused vitest run per task, then pnpm test:ci as the phase gate" + +key-files: + created: + - 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/aura.test.ts + - packages-user/data-state/src/enemy/special.test.ts + - packages-user/data-state/src/enemy/mapDamage.test.ts + modified: [] + +key-decisions: + - "06-02:enemy 顶层实现以行为单测覆盖,6 个同目录 *.test.ts 全部保持 inline fixture(D-02/D-03),未修改任何生产代码" + - "06-02:MainDamageCalculator 逐个特殊分支断言精确 { damage, turn },支援路径额外验证 inGuard 不泄漏(两次顶层计算相等 + 支援怪自身 guard 不被递归)" + - "06-02:registerSpecials 断言代码 0-27 各注册一次且守卫默认值为空集合;仅对 6/25/1 调用 getSpecialName/getDescription,避免触发 core.values.* 全局读取" + - "06-02:plan 对 BetweenDamageView 的描述『两个 delta 都为正』与实现不符(实现是排除向左/向上以去重),按实现实际语义断言(右/下命中),属描述措辞而非代码缺陷,未登记 findings" + - "06-02:未发现疑似 bug,06-TEST-FINDINGS.md 无新增 #06-02-N 条目,无 it.skip/it.todo" + +requirements-completed: [TEST-01] + +coverage: + - id: D1 + description: "MainDamageCalculator 行为覆盖:基础伤害/回合、无法破防与无敌、魔攻、连击 4/5、多段、支援递归与告警 137、先攻、破甲、反击、净化、吸血(含 add)、负伤 flag 夹取、固伤、仇恨、取整、攻击/其他属性临界上界" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-state/src/enemy/calculator.test.ts#MainDamageCalculator base and defeat branches" + status: pass + human_judgment: false + - id: D2 + description: "MainEnemyFinalEffect 与 MainEnemyComparer 行为覆盖:坚固下限提升、模仿复制、无属性不变、优先级 0;基础属性与特殊属性数量/代码/数值深比较" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-state/src/enemy/final.test.ts#MainEnemyFinalEffect" + status: pass + - kind: unit + ref: "packages-user/data-state/src/enemy/comparer.test.ts#MainEnemyComparer" + status: pass + human_judgment: false + - id: D3 + description: "光环与特殊属性注册覆盖:CommonAura 范围选择与加成结算、GuardAura 3x3 范围与守卫定位符添加/自身跳过/缺来源跳过;registerSpecials 代码 0-27 各一次、守卫默认值、名称与描述生成" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-state/src/enemy/aura.test.ts#CommonAura apply" + status: pass + - kind: unit + ref: "packages-user/data-state/src/enemy/special.test.ts#registerSpecials" + status: pass + human_judgment: false + - id: D4 + description: "地图伤害覆盖:领域/阻击/激光/夹击/捕捉五种视图的范围参数与伤害信息、转换器按特殊属性顺序组装视图与激光朝向查找、合并器求和/最大伤害类型/额外信息并集" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-state/src/enemy/mapDamage.test.ts#MainMapDamageReducer" + status: pass + human_judgment: false + - id: D5 + description: "FLAGGED ASSUMPTION:TEST-01 是否由 `pnpm test:ci` 门禁下的行为单测满足,需用户确认(ROADMAP 侧无边界 verifier 解析该规格)" + requirement: TEST-01 + verification: [] + human_judgment: true + rationale: "计划 frontmatter 明确将该假设标为 unverified;只有用户能确认该验收口径,自动化测试无法自证需求定义。" + +duration: 9min +completed: 2026-09-14 +status: complete +--- + +# Phase 06 Plan 02: Enemy Top-Level Implementation Unit Tests Summary + +**Behavior unit tests for `data-state/src/enemy` — MainDamageCalculator every special branch, final effect, comparer, aura converters, special registry 0-27, and the five map-damage views plus converter/reducer, all green under the fixed `pnpm test:ci` gate** + +## Performance + +- **Duration:** 9 min +- **Started:** 2026-09-14T08:58:34Z +- **Completed:** 2026-09-14T09:07:44Z +- **Tasks:** 3 +- **Files modified:** 6 created (all `*.test.ts`), 0 production files touched + +## Accomplishments + +- `calculator.test.ts` (22 tests) proves exact `{ damage, turn }` for the baseline, the two defeat paths (no break-through / 无敌 without cross), and every special branch: 魔攻, 2/3连击, 多段, 支援 guard recursion + warn 137 + `inGuard` non-leakage, 先攻, 破甲, 反击, 净化, 吸血 (with/without `add`), 负伤 flag clamping, 固伤, 仇恨, flooring, and both `getCriticalLimit` paths (坚固 → Infinity). +- `aura.test.ts` (13 tests) covers `CommonAuraConverter`/`GuardAuraConverter` selection, `FullRange`/`RectRange`/`ManhattanRange` choice and params, buff math, the 3×3 guard range, guard-locator registration, own-locator skip and missing-source no-op. +- `special.test.ts` (6 tests) calls `registerSpecials` on an inline fake manager and asserts codes 0-27 registered exactly once, the `guard` empty-set default, and name/description generation for a serializable special (code 6), the halo (code 25) and a none-property special (code 1). +- `mapDamage.test.ts` (19 tests) drives all five views (`Zone`, `Repulse`, `Laser`, `Between`, `Ambush`), the converter's per-special view order + laser face lookup, and the reducer's sum / max-damage type / catch+repulse union, including the `core.flags.betweenAttackMax` branch. +- `final.test.ts` (5) and `comparer.test.ts` (5) cover the final-effect and comparison contracts. +- Full suite: `pnpm test:ci` → **29 files, 223 passed, 3 skipped** (the 3 skips are the pre-existing `#06-01-N` findings). No new skips. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: MainDamageCalculator + final effect + comparer** - `5b76cf4` (test) +2. **Task 2: Auras + special registration** - `b36a4ee` (test) +3. **Task 3: Map damage views, converter, reducer** - `7b95670` (test) + +**Plan metadata:** metadata docs commit follows this SUMMARY (3 task commits + 1 metadata commit = 4 total from `plan_head_before`). + +## Files Created/Modified + +- `packages-user/data-state/src/enemy/calculator.test.ts` - MainDamageCalculator branch coverage (22 tests) +- `packages-user/data-state/src/enemy/final.test.ts` - MainEnemyFinalEffect (5 tests) +- `packages-user/data-state/src/enemy/comparer.test.ts` - MainEnemyComparer (5 tests) +- `packages-user/data-state/src/enemy/aura.test.ts` - CommonAura/GuardAura + converters (13 tests) +- `packages-user/data-state/src/enemy/special.test.ts` - registerSpecials registry (6 tests) +- `packages-user/data-state/src/enemy/mapDamage.test.ts` - map damage views/converter/reducer (19 tests) + +## Decisions Made + +- Kept every fixture inline per file (D-03); no cross-file factory helper was introduced. +- Extended the 06-01 harness shape (`vi.hoisted` global/`Map` polyfill stubs + `beforeAll` dynamic module bag + `logger.catch` for warn 137) and added a mutable `core` stub for the `BetweenDamageView` global read. +- Asserted `BetweenDamageView` by its actual semantics — the right/down adjacent locator qualifies and left/up is excluded (an anti-double-count bias), rather than the plan's parenthetical "both deltas positive". +- No suspected bugs surfaced, so `06-TEST-FINDINGS.md` gains no `#06-02-N` entry and no `it.skip`/`it.todo` was added (D-05 not triggered). + +## Deviations from Plan + +None - plan executed exactly as written, except for one documentation clarification recorded below (no code change and no test weakening). + +### Clarification (not a code deviation) + +**BetweenDamageView qualifier wording** +- **Found during:** Task 3 (map damage views) +- **Plan text:** "only the forward-adjacent locator (delta sum 1 and both deltas positive) qualifies". +- **Implementation:** rejects only `deltaX <= 0 && deltaY <= 0`, so the right `(dx=1,dy=0)` and down `(dx=0,dy=1)` neighbours qualify while left/up do not. This deliberate direction bias prevents the mirrored source enemy from double-counting the same pair. +- **Action:** Tests assert the implementation's actual (correct) semantics; no `it.skip` and no findings entry. Recorded here for reviewer awareness. + +## Issues Encountered + +- Windows/PowerShell surfaces pnpm's Browserslist age notice on stderr as a non-terminating `NativeCommandError`; it does not affect the run. Focused runs and the full `pnpm test:ci` are green. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- The `data-state/src/enemy` top-level implementation is behavior-covered and green; later plans (06-03 `data-base/src/enemy`, 06-08 `CoreState` integration) can reuse these inline fixture shapes. +- Open input for the user: confirm the flagged TEST-01 acceptance assumption (D5 above) and triage the `#06-01-N` findings; no core code was modified in this phase. + +## Self-Check: PASSED + +- Created files verified present: the 6 `data-state/src/enemy/*.test.ts` files and `06-02-SUMMARY.md`. +- Task commits verified in history: `5b76cf4`, `b36a4ee`, `7b95670`. +- `git diff --name-only` from `plan_head_before` lists only the 6 new `*.test.ts` files; no production source changed. +- Focused runs and full `pnpm test:ci` green (29 files, 223 passed, 3 documented pre-existing skips). + +--- +*Phase: 06-unit-tests* +*Completed: 2026-09-14* diff --git a/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md b/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md index b5cd2c9..d2ef3dd 100644 --- a/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md +++ b/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md @@ -26,3 +26,13 @@ | `damage.ts` `DamageContext.calculateCritical` / `findNextCritical` | 产出的临界点 `info` 与 `nextValue` 不对应 | 假计算器 `damage = 100 - atk * 10`、`getCriticalLimit = 10`、当前 `atk = 0`;首个临界点 `nextValue = 1`(正确伤害应为 90),但 `info.damage = 0`、`damageDiff = -100` | `findNextCritical` 在 `middleInfo.damage < referenceDamage` 分支只更新 `right`,从不更新 `targetInfo`,`targetInfo` 一直停留在初始 `upperLimit` 处的计算结果 | 临界点查询(数值提示、战斗模拟)展示的伤害与对应属性值不匹配 | 在 `middleInfo.damage < referenceDamage` 分支同步 `targetInfo = middleInfo`,或二分结束后按最终 `right` 重新计算一次 | `damage.test.ts` `reports the damage info matching the yielded critical value`(#06-01-1) | 中 | | `mapDamage.ts` `MapDamage.deleteEnemy` | 删除怪物后,该怪物产生的有来源地图伤害仍然存在 | 注册一个怪物视图并转换出伤害(假视图伤害 7)→ `getSeparatedDamage` 有 1 条 → `deleteEnemy(view)` → `getSeparatedDamage` 仍有 1 条(正确应为 0) | `viewStore` 与 `damageStore` 只在读/删路径出现,全文件没有任何 `.set(...)` 写入,因此 `deleteEnemy` / `removeEnemyAffecting` 中 `viewStore.get(viewItem)` 恒为 `undefined`,清理循环被整体跳过;`sourcedDamage` 点位与 `affectedBy` 都不会被移除 | 怪物死亡/离场后地图伤害不消失,`getSeparatedDamage` / `getReducedDamage` 继续返回幽灵伤害,并会污染后续 `refreshIndex` 的重新聚合 | 在 `refreshEnemy` / `refreshEnemyAndClearCache` 建立 sourced 伤害时同步写入 `viewStore.set(viewItem, { damages, enemy })` 与 `damageStore.set(damage, { sourceView, sourceEnemy, index })`,或改为在 `deleteEnemy` 中直接遍历 `sourcedDamage` 的 `affectedBy` 反向清理 | `mapDamage.test.ts` `removes enemy-sourced damage when the enemy is deleted`(#06-01-2) | 中 | | `combat.ts` `CombatFlow.combatFlow` / `ICombatScript.before` | 实现与接口文档语义相反:接口声明返回 `false` 放弃战斗,实现却在返回**真值**时短路 | 加载一个 `onBeforeCombat` 钩子,脚本 `before` 返回 `false`;`battle()` 仍会执行该钩子(正确实现应直接返回并放弃) | `const skip = await script.before(...); if (skip) return damage;` 以真值短路,与文档「返回 false 会立刻停止并放弃此次战斗」相反;文档或实现其一有误 | 所有战斗脚本的短路约定:按接口文档编写的脚本会得到相反效果,可能漏战或误战 | 先与接口设计者确认语义,再二选一统一:以 `true` 短路为准则修正接口注释,以文档为准则改为 `if (!skip) return damage;` | `combat.test.ts` `abandons the battle when the before script returns false`(#06-01-3) | 中 | + +## #06-02 顶层战斗实现(data-state/src/enemy) + +本计划未发现疑似 bug,无 `it.skip` / `it.todo`,`pnpm test:ci` 全绿(29 文件 / 223 通过 / 3 跳过)。 + +仅有一处**计划措辞与实现不符**(非代码缺陷,不登记为 bug):`mapDamage.ts` 的 `BetweenDamageView.getDamageWithoutCheck` 计划描述为「两个 delta 都为正才命中」,实现实际是排除 `deltaX <= 0 && deltaY <= 0`,即向右/向下相邻格命中、向左/向上不命中。该方向偏置使相邻两只怪中只有一只的视图产出伤害,从而避免同一对怪被重复计算,属实现的有意去重。测试按实现实际语义断言(`packages-user/data-state/src/enemy/mapDamage.test.ts`),无跳过用例。 + +| 模块/接口 | 现象 | 最小复现 | 疑似原因 | 影响面 | 建议修复方向 | 关联 skip 用例 | 严重度 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| (无) | — | — | — | — | — | — | — | diff --git a/.planning/state.json b/.planning/state.json index 781f544..2ab0295 100644 --- a/.planning/state.json +++ b/.planning/state.json @@ -31,7 +31,7 @@ { "number": "6", "name": "单元测试", - "status": "pending" + "status": "in_progress" } ], "next": { @@ -39,5 +39,5 @@ "label": "Advance to the next step", "reason": "Phase 06 of 6 · executing" }, - "updated_at": "2026-09-14T00:53:00.361Z" + "updated_at": "2026-09-14T01:08:51.893Z" }