diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a6777bb..ef12ea3 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -210,13 +210,13 @@ Plans: 3. 测试在本地可运行且全部通过 4. 测试由 AI 编写并运行,通过验证后可提交 -**Plans**: 2/9 plans executed replanned (D-28;旧 06-01/06-02 执行结果标记 superseded,按同号重跑;数据端切片,非数据 render/legacy 覆盖延后) +**Plans**: 3/9 plans executed replanned (D-28;旧 06-01/06-02 执行结果标记 superseded,按同号重跑;数据端切片,非数据 render/legacy 覆盖延后) Plans: - [x] 06-01-PLAN.md — Combat L2 (data-system/src/combat) + EnemyContext aura pipeline / effect combos / full interface & code coverage (D-21..D-27) - [x] 06-02-PLAN.md — enemy top-level BASIC functionality only (data-state/src/enemy;单分支,无组合,无 save/load) -- [ ] 06-03-PLAN.md — Enemy data model full public surface except legacy (data-base/src/enemy;无 save/load) +- [x] 06-03-PLAN.md — Enemy data model full public surface except legacy (data-base/src/enemy;无 save/load) - [ ] 06-04-PLAN.md — Replay focus ReplayArray ops + encode/decode + system/sandbox/decorators (data-common/src/replay;无 save/load,完整播放→06-07) - [ ] 06-05-PLAN.md — Hero ALL files incl. rendering + async mover (data-base/src/hero;无 save/load) - [ ] 06-06-PLAN.md — Map ALL interfaces, emphasis static/dynamic tiles + static arrays (data-base/src/map;无 save/load) @@ -227,7 +227,7 @@ Plans: **Wave 1** *(independent test-writing plans; no shared production edits)* - [x] 06-01-PLAN.md -- [ ] 06-02-PLAN.md +- [x] 06-02-PLAN.md - [ ] 06-03-PLAN.md - [ ] 06-04-PLAN.md - [ ] 06-05-PLAN.md @@ -251,4 +251,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. 单元测试 | 2/9 | In Progress| | +| 6. 单元测试 | 3/9 | In Progress| | diff --git a/.planning/STATE.md b/.planning/STATE.md index 5593790..2f80f2b 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-02-PLAN.md -last_updated: "2026-09-14T05:36:57.734Z" +stopped_at: Completed 06-03-PLAN.md +last_updated: "2026-09-14T06:43:21.137Z" last_activity: 2026-09-14 last_activity_desc: Phase 06 execution started -state_head: e72231d9fab00224a566c1a235de058396d7ebc3 +state_head: 7012aa49fce154f2fc48c1da3b1719229c0fd709 progress: total_phases: 6 completed_phases: 0 total_plans: 46 - completed_plans: 38 + completed_plans: 40 milestone_name: milestone --- @@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-09-10) ## Current Position Phase: 06 (unit-tests) — EXECUTING -Plan: 5 of 9 +Plan: 6 of 9 Status: Ready to execute Last activity: 2026-09-14 — Phase 06 execution started @@ -80,6 +80,7 @@ Progress: [█████░░░░░] 50% | Phase 06 P02 | 9min | 3 tasks | 6 files | | Phase 06 P01 | 13min | 3 tasks | 6 files | | Phase 06 P02 | 8min | 3 tasks | 6 files | +| Phase 06 P03 | 13min | 3 tasks | 5 files | ## Accumulated Context @@ -148,6 +149,10 @@ Recent decisions affecting current work: - [Phase 06]: 06-01:战斗系统 Layer-2 按 D-43 三阶段重跑(构件→流水线→集成),EnemyContext 全公开方法 + 三范围光环 + 四阶段顺序 + 两条刷新路径 + 15 个可达 code 全覆盖;D-26 属性→伤害联动经真实 EnemyContext + fake calculator 验证。 - [Phase 06]: 06-01:新增疑似缺陷 #06-01-4(重复 buildup 未重置计算后怪物导致属性累加),按 D-05 写成正确预期 it.skip 并登记 06-TEST-FINDINGS.md;连同既有 #06-01-1..3 共 4 条只记录不修复。 - [Phase 06]: 06-02(重跑):按 D-43 三阶段复核,补齐 GuardAuraConverter.convert 与 GuardAura 能力/applySpecial 正常用例以满足 D-30 公开方法全覆盖;三阶段聚焦运行与 pnpm test:ci 全绿(257 passed / 4 skipped) +- [Phase 06]: 06-03:enemy 数据模型按 D-43 三阶段(构件 → 组合/流水线 → 完整/集成)执行,每阶段聚焦跑绿并过 D-44 门禁后再进入下一阶段 +- [Phase 06]: 06-03:manager 脏集合用 Reflect.get(manager, 'dirtySet') 观测,不调用 saveState/loadState,严格遵守 D-32 +- [Phase 06]: 06-03:registerSpecial 覆盖语义只能经被排除的 legacy 转换路径观测,故只覆盖注册/重复注册不报错的最小正常用例(D-30 优先) +- [Phase 06]: 06-03:createEnemy/createEnemyById 未走复用映射,按 D-05 写 it.skip 正确预期并登记 #06-03-1,不修改核心代码 ### Pending Todos @@ -176,6 +181,6 @@ None yet. ## Session Continuity -Last session: 2026-09-14T05:36:57.495Z -Stopped at: Completed 06-02-PLAN.md +Last session: 2026-09-14T06:43:20.891Z +Stopped at: Completed 06-03-PLAN.md Resume file: None diff --git a/.planning/phases/06-unit-tests/06-03-SUMMARY.md b/.planning/phases/06-unit-tests/06-03-SUMMARY.md new file mode 100644 index 0000000..92fb2de --- /dev/null +++ b/.planning/phases/06-unit-tests/06-03-SUMMARY.md @@ -0,0 +1,179 @@ +--- +phase: 06-unit-tests +plan: 03 +subsystem: testing +tags: [vitest, data-base, enemy, enemy-manager, special, dirty-tracking, unit-test] + +# Dependency graph +requires: + - phase: 06-unit-tests + provides: 06-01/06-02 combat + top-level enemy behavior tests; D-43/D-44 staged execution conventions +provides: + - Enemy attribute units (get/set/add/cloneAttributes) + - Enemy special CRUD composition + clone/copyFrom independence (warn 96) + - CommonSerializableSpecial / NonePropertySpecial unit + clone/deepEqualsTo + - EnemyManager registry/prefab/reuse/modify/comparer dirty lifecycle (error 53, warn 117/118) + - 06-COVERAGE-MAP 06-03 section (codes 53/96/117/118) +affects: [06-09-save-load, verify-work, audit-milestone] + +# Actuals (#2632) — pairs with the plan's `estimate` to calibrate future estimates. +actuals: + tokens: 7026 + tasks: 3 + commits: 3 + +# Tech tracking +tech-stack: + added: [] + patterns: + - "动态 import + vi.hoisted 全局 stub harness,经 logger.catch 断言 warn/error 码" + - "EnemyManager 脏集合经 Reflect.get 白盒观测,避免触碰 D-32 的 saveState/loadState" + - "疑似缺陷按正确预期写 it.skip 并锚定 #06-03-N" + +key-files: + created: + - packages-user/data-base/src/enemy/enemy.test.ts + - packages-user/data-base/src/enemy/special.test.ts + - packages-user/data-base/src/enemy/manager.test.ts + modified: + - .planning/phases/06-unit-tests/06-COVERAGE-MAP.md + - .planning/phases/06-unit-tests/06-TEST-FINDINGS.md + +key-decisions: + - "06-03:enemy 数据模型按 D-43 三阶段(构件 → 组合/流水线 → 完整/集成)执行,每阶段聚焦跑绿并过 D-44 门禁后再进入下一阶段" + - "06-03:manager 脏集合用 Reflect.get(manager, 'dirtySet') 观测,不调用 saveState/loadState,严格遵守 D-32" + - "06-03:registerSpecial 覆盖语义只能经被排除的 legacy 转换路径观测,故只覆盖注册/重复注册不报错的最小正常用例(D-30 优先)" + - "06-03:createEnemy/createEnemyById 未走复用映射,按 D-05 写 it.skip 正确预期并登记 #06-03-1,不修改核心代码" + +patterns-established: + - "阶段门禁:每阶段 聚焦跑 + eslint + vue-tsc 文件级 + pnpm test:ci 全绿后才提交" + - "D-30 边界:legacy 命名接口/方法仅作协作对象,不纳入断言" + - "D-32 边界:存读档不测,脏跟踪经内部集合观测而非 saveState" + +requirements-completed: [TEST-01] + +# Coverage metadata (#1602) +coverage: + - id: D1 + description: "Enemy 属性单元(getAttribute/setAttribute/addAttribute 单键独立、cloneAttributes 深拷贝非别名)" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-base/src/enemy/enemy.test.ts#Enemy attribute units" + status: pass + human_judgment: false + - id: D2 + description: "Enemy special CRUD 组合与 clone/copyFrom 独立性(含重复 code 告警 96)" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-base/src/enemy/enemy.test.ts#Enemy special composition" + status: pass + human_judgment: false + - id: D3 + description: "CommonSerializableSpecial / NonePropertySpecial 单方法单元、clone 独立性与 deepEqualsTo 判定" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-base/src/enemy/special.test.ts#CommonSerializableSpecial units, NonePropertySpecial units, Special clone and deep equality" + status: pass + human_judgment: false + - id: D4 + description: "EnemyManager 注册表/prefab/reuse/modify/comparer 脏跟踪(error 53、warn 117/118)" + requirement: TEST-01 + verification: + - kind: unit + ref: "packages-user/data-base/src/enemy/manager.test.ts#EnemyManager registry and attribute defaults, prefab CRUD, reuse mapping, modifyPrefabAttribute, comparer and dirty tracking" + status: pass + human_judgment: false + - id: D5 + description: "复用映射经 createEnemy/createEnemyById 解析到来源模板(疑似缺陷 #06-03-1,it.skip)" + verification: + - kind: unit + ref: "packages-user/data-base/src/enemy/manager.test.ts#creates enemies for reused codes and ids through the reuse mapping (skipped)" + status: unknown + human_judgment: true + rationale: "实现未走复用映射并返回 null,属疑似缺陷;按 D-05 只记录不修复,需人工确认修复方向后再转回归用例" + +# Metrics +duration: 13min +completed: 2026-09-14 +status: complete +--- + +# Phase 06 Plan 03: enemy 数据模型单测 Summary + +**enemy 数据模型三层行为单测:`Enemy` 属性/special CRUD 与 clone/copyFrom、`CommonSerializableSpecial`/`NonePropertySpecial` 单元与深比较、`EnemyManager` 注册表/prefab/reuse/modify/comparer 脏跟踪(覆盖 error 53、warn 96/117/118),并登记 1 处疑似缺陷 #06-03-1** + +## Performance + +- **Duration:** 13 min +- **Started:** 2026-09-14T06:27:23Z +- **Completed:** 2026-09-14T06:40:30Z +- **Tasks:** 3 (three D-43 stages) +- **Files modified:** 5 (3 new test files + 2 shared planning docs) + +## Accomplishments + +- 阶段 1(构件级):`enemy.test.ts` 属性单元 + `special.test.ts` 单方法单元,4 + 5 用例跑绿。 +- 阶段 2(组合/流水线):special CRUD 组合、warn 96、clone/copyFrom 独立性、`deepEqualsTo` 判定,9 + 8 用例跑绿。 +- 阶段 3(完整/集成):`EnemyManager` 注册表/prefab/reuse/modify/comparer 脏跟踪,17 通过 + 1 跳过。 +- 三阶段全部通过 D-44 门禁(eslint 0 错误、vue-tsc 文件级 0 类型错误、`pnpm test:ci` 全绿)。 +- 以 create-or-append 写入 `06-COVERAGE-MAP.md` 06-03 小节(code 53/96/117/118)并登记 `06-TEST-FINDINGS.md` #06-03。 + +## Task Commits + +Each stage was committed atomically (normal hooks enabled, no `--no-verify`): + +1. **阶段 1(构件级)** - `965868c` (test) +2. **阶段 2(组合/流水线)** - `ff75271` (test) +3. **阶段 3(完整/集成)** - `7012aa4` (test) + +**Plan metadata:** final `docs(06-03): complete enemy data model unit test plan` commit (see STATE.md). + +## Files Created/Modified + +- `packages-user/data-base/src/enemy/enemy.test.ts` - Enemy 属性单元 + special CRUD/clone/copyFrom(9 用例) +- `packages-user/data-base/src/enemy/special.test.ts` - CommonSerializableSpecial/NonePropertySpecial 单元 + clone/deepEqualsTo(8 用例) +- `packages-user/data-base/src/enemy/manager.test.ts` - EnemyManager 注册表/prefab/reuse/modify/脏跟踪(17 通过 + 1 skip) +- `.planning/phases/06-unit-tests/06-COVERAGE-MAP.md` - 06-03 code→模块→用例小节(53/96/117/118) +- `.planning/phases/06-unit-tests/06-TEST-FINDINGS.md` - #06-03 疑似缺陷登记 + +## Decisions Made + +- D-43 三阶段顺序执行,每阶段聚焦跑绿 + D-44 门禁后才进入下一阶段;三阶段均无阻断性 bug。 +- D-32:`EnemyManager` 脏集合改经 `Reflect.get(manager, 'dirtySet')` 观测,完全避开 `saveState`/`loadState`(119/120 归 06-09)。 +- D-30:`registerSpecial` 的覆盖语义只能经被排除的 legacy 转换路径观测,改为覆盖「注册/重复注册不报错」的最小正常用例;假 `IEnemyLegacyBridge` 仅作构造必需协作对象。 +- D-05:`createEnemy`/`createEnemyById` 未走复用映射,按正确预期写 `it.skip` 并登记 `#06-03-1`,不修改核心代码。 + +## Deviations from Plan + +1. **阶段 3 `registerSpecial` 覆盖语义未断言(约束冲突,无代码改动)** + - **Found during:** 阶段 3(EnemyManager 注册表) + - **Issue:** 计划 action 要求断言「同 code 后注册者胜」,但该注册表唯一消费方是被 D-30 排除的 legacy 转换路径(`fromLegacyEnemy`/`addPrefabFromLegacy`),无可观测的公开路径。 + - **Resolution:** 按 D-30 优先,改为断言「注册与重复注册均不产生告警/报错」的最小正常用例,并在 FINDINGS 与 COVERAGE-MAP 注明。 + - **Impact:** 无功能风险;`registerSpecial` 的覆盖语义留待 legacy 路径解禁或获得只读取值接口后补测。 + +其余无偏差——三个测试文件与计划列出的人工 fixture 符号(`createEnemy`、`createSpecial`、`makeConfig`、`bridge`、`createPrefab`、`createManager`)一致;未新增/修改任何 `packages-user/*/src` 生产文件。 + +## Issues Encountered + +- 一次临时验证 `#06-03-1` 时误用 PowerShell `Set-Content -NoNewline` 重写 `manager.test.ts`,导致中文注释编码损坏;已用 `Write` 工具整文件重写恢复,随后改用 `Edit` 工具临时取消/恢复 `it.skip`。最终文件编码与格式经 eslint/prettier 门禁确认无碍。 +- 疑似缺陷 `#06-03-1`:`createEnemy(100)` / `createEnemyById('slime-reuse')` 在注册复用映射后返回 `null`(`getPrefab`/`getPrefabById` 能正确解析)。临时取消 skip 运行确认为真实行为后恢复 skip;详见 `06-TEST-FINDINGS.md`。 + +## D-44 Gate Evidence + +- 阶段 1-3:`pnpm exec eslint --fix` 后 `pnpm exec eslint <改动文件>` 均 0 错误。 +- 阶段 1-3:`pnpm exec vue-tsc --noEmit` 输出按文件路径过滤 `data-base/src/enemy/{enemy,special,manager}.test.ts` 均 0 类型错误(仓库全局既有无关错误不在范围)。 +- 聚焦运行:阶段 1 `2 files / 9 passed`;阶段 2 `2 files / 17 passed`;阶段 3 `1 file / 17 passed + 1 skipped`。 +- `pnpm test:ci`:基线 29 文件 / 257 通过 / 4 跳过 → 最终 32 文件 / 291 通过 / 5 跳过(新增 34 通过 + 1 计划 skip)。 + +## Next Phase Readiness + +- 06-03 三阶段全部完成,`data-base/src/enemy`(除 legacy 与存读档)公开接口已覆盖。 +- 存读档相关(`saveState`/`loadState`、code 119/120)集中留给 06-09。 +- `#06-03-1` 待人工确认后修复并取消 skip,转为回归用例。 + +--- +*Phase: 06-unit-tests* +*Completed: 2026-09-14* diff --git a/.planning/phases/06-unit-tests/06-COVERAGE-MAP.md b/.planning/phases/06-unit-tests/06-COVERAGE-MAP.md index 4659dfe..9349963 100644 --- a/.planning/phases/06-unit-tests/06-COVERAGE-MAP.md +++ b/.planning/phases/06-unit-tests/06-COVERAGE-MAP.md @@ -22,6 +22,10 @@ | 140 | combat/combat.ts | `sorts scripts by descending priority and rejects duplicates` | 06-01 | | 141 | combat/combat.ts | `warns 141 when the damage context cannot produce damage info` | 06-01 | | 137 | enemy/calculator.ts | `warns 137 when a guard locator has no enemy` | 06-02 | +| 53 | enemy/manager.ts | `logs error 53 for non-serializable default attribute values` | 06-03 | +| 96 | enemy/enemy.ts | `warns 96 and keeps the existing special on a duplicate code` | 06-03 | +| 117 | enemy/manager.ts | `warns 117 on a repeat compareWith and refreshes the dirty set` | 06-03 | +| 118 | enemy/manager.ts | `warns 118 and marks dirty when no comparer is attached` | 06-03 | ## 06-01 战斗系统(packages-user/data-system/src/combat) @@ -43,3 +47,20 @@ 阶段 3(完整/集成)完成 mapDamage 五视图 + `MainMapDamageConverter` + `MainMapDamageReducer`。 本计划只覆盖顶层实现**基本功能**(D-35),跨特殊属性/光环的组合语义见 06-07; 无 `it.skip`/`it.todo`,`06-TEST-FINDINGS.md` 无 `#06-02-N` 条目。 + +## 06-03 enemy 数据模型(packages-user/data-base/src/enemy) + +模块归属:96 → `enemy/enemy.ts`(`Enemy.addSpecial` 重复 special); +53 / 117 / 118 → `enemy/manager.ts`(`setAttributeDefaults` 非法默认值 / `compareWith` 多次调用 / +无 comparer 的 `updateDirty`)。 + +阶段 1(构件级)完成 `Enemy` 属性单元与 special 单方法单元; +阶段 2(组合/流水线)完成 special CRUD 组合与 `clone`/`copyFrom`/`deepEqualsTo` 独立性; +阶段 3(完整/集成)完成 `EnemyManager` 注册表 / prefab / reuse / modify / comparer 脏跟踪,并观测 53/117/118。 + +D-32:本计划不测 `saveState`/`loadState`,其专属码 119/120 与存读档往返归 06-09。 +D-30:排除一切名称含 legacy 的接口/方法(`fromLegacyEnemy`/`addPrefabFromLegacy`/`IEnemyLegacyBridge`); +假桥接仅作必需协作对象。`registerSpecial` 的覆盖语义只能经被排除的 legacy 转换路径观测, +故仅覆盖「注册/重复注册不报错」的最小正常用例。 +一条疑似缺陷 `#06-03-1`(`createEnemy`/`createEnemyById` 未走复用映射)按 D-05 以 `it.skip` +的正确预期用例登记,详见 `06-TEST-FINDINGS.md`。 diff --git a/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md b/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md index efefefc..9651bca 100644 --- a/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md +++ b/.planning/phases/06-unit-tests/06-TEST-FINDINGS.md @@ -37,3 +37,15 @@ | 模块/接口 | 现象 | 最小复现 | 疑似原因 | 影响面 | 建议修复方向 | 关联 skip 用例 | 严重度 | | --- | --- | --- | --- | --- | --- | --- | --- | | (无) | — | — | — | — | — | — | — | + +## #06-03 怪物数据模型(data-base/src/enemy) + +本计划按 D-43 三阶段(构件 → 组合/流水线 → 完整/集成)执行,发现 1 处疑似 bug(`#06-03-1`), +按 D-05 以 `it.skip` 的正确预期用例登记;`pnpm test:ci` 全绿(32 文件 / 291 通过 / 5 跳过, +其中 1 条为本计划新增 skip)。D-32:不测 `saveState`/`loadState`,119/120 归 06-09。 +D-30:`registerSpecial` 的覆盖语义只能经被排除的 legacy 转换路径观测,故仅覆盖 +「注册/重复注册不报错」的最小正常用例。 + +| 模块/接口 | 现象 | 最小复现 | 疑似原因 | 影响面 | 建议修复方向 | 关联 skip 用例 | 严重度 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `manager.ts` `EnemyManager.createEnemy` / `createEnemyById` | 注册复用映射后,按复用 code/id 创建怪物返回 `null` | `addPrefab(code=1, id='slime')` → `reusePrefab(1, 100, 'slime-reuse')` → `createEnemy(100)` 返回 `null`,而 `getPrefab(100)` 能解析到来源模板 | `createEnemy`/`createEnemyById` 直接 `prefabByCode.get(code)`/`prefabById.get(id)`,未像 `getPrefab`/`getPrefabById`/`internalGetPrefab` 那样先经 `reuseByCode`/`reuseById` 解析 | 复用映射(面朝方向图块复用同一模板)对应的 code/id 无法生成怪物;当前生产代码尚无调用方,属潜在功能缺口 | 在 `createEnemy`/`createEnemyById` 中改用 `internalGetPrefab`(或先查复用映射再取模板) | `manager.test.ts` `creates enemies for reused codes and ids through the reuse mapping`(#06-03-1) | 中 | diff --git a/.planning/state.json b/.planning/state.json index f2528ba..9cd4340 100644 --- a/.planning/state.json +++ b/.planning/state.json @@ -39,5 +39,5 @@ "label": "Advance to the next step", "reason": "Phase 06 of 6 · executing" }, - "updated_at": "2026-09-14T05:36:10.349Z" + "updated_at": "2026-09-14T06:42:22.824Z" }