docs(06-11): fix G-06-06-A keepEvent plan to pre-warm source static tile

This commit is contained in:
unanmed 2026-09-15 09:49:57 +08:00
parent 174a1d5160
commit 26458ff534

View File

@ -0,0 +1,265 @@
---
phase: 06-unit-tests
plan: 11
type: execute
wave: 3
depends_on: [06-05, 06-06]
files_modified:
- packages-user/data-base/src/hero/equipment.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
- .planning/phases/06-unit-tests/06-COVERAGE-MAP.md
autonomous: true
requirements: [TEST-01]
estimate:
tokens: 60000
raw_tokens: 60000
tasks: 3
confidence: low
must_haves:
truths:
- "TEST-01 由数据端行为单测满足,门禁为固定非 watch 命令 `pnpm test:ci` 全绿(D-08)。"
- "G-06-05-A:勇士多个不同槽位(如 weapon 数值槽 0 + armor 数值槽 1)同时装备后,断言合并后的最终属性(各槽位修饰器叠加)与卸下其中一件后其余修饰器保留(D-17 深化)。"
- "G-06-06-A:静态→动态→移动若干格→静态 全链路串联用例;前置须先 `layer.getTile(0, 0)` 物化源格静态图块,使 `transferToDynamic` 的动态图块真正继承源格默认事件 `{10:'base-event'}`(否则 `StaticTile` 在 `setBlock(0, x, y)` 归零之后才构造,其 `num()` 已为 0、默认事件为空,动态图块将不含 `base-event`);`keepEvent` 为 true 与 false 两种分支均在**移动之后**的往返中验证:true 保留动态专属事件并与写回图块 num 的默认事件合并为 `[[10,'base-event'],[30,'moved-event']]` 且事件脏,false 丢弃动态专属事件并按写回图块 num 重新推导默认事件 `[[10,'base-event']]`(而非目标格原块 2 的 `[[20,'alternate-event']]`)且事件不脏,并断言原格清空、目标格写回移动进来图块的 num、动态索引更新。"
- "G-06-06-B:`MapState.createMap('F1',2,2)` 返回的地图上**写入并读取内容**(图层、块、点位事件、事件层绑定、脏标记),证明 createMap 生成的地图可用于内容生成。"
- "G-06-06-C:同一地图内多个不同 zIndex 图层并存,各自块数据独立、zIndex 正确、按 zIndex 的 `compareWith` 参考比较分别生效。"
- "接口覆盖:经既有公开接口驱动(`HeroEquipment`/`HeroEquipsStore`/`HeroAttribute`、`MapState`/`GameMap`/`MapLayer`/`DynamicTile`),名称含 legacy 的接口/方法排除(D-30)。"
- "code 覆盖(模块可达):本计划不引入新码;复用并再确认既有可达码(如 84/121/127/128/129/131)的覆盖不被削弱(D-31)。"
- "测试形态(D-01):纯数据模型单测(fake / 显式依赖注入,延续 Phase 3 D-03);不引入真实游戏数据(D-02)。"
- "阶段化(D-43):计划按 构件→组合→完整/集成 三阶段组织,取消 tracer-first;每阶段跑绿后才进入下一阶段;某阶段发现阻断性 bug 时暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。"
- "质量门禁(D-44):每个计划执行后、提交前,改动文件 `eslint --fix` 后 `eslint <改动文件>` 0 错误;本计划改动的测试文件在 `pnpm exec vue-tsc --noEmit` 输出中按文件路径过滤 0 类型错误(全局既有无关错误不在范围);`pnpm test:ci` 全绿;任一不通过不得提交。"
- "执行节奏(D-33):本计划执行前先向用户确认;执行完成后暂停,用中文分条简要汇报验证结果,详细结果写入共用的 `06-TEST-FINDINGS.md`。"
- "增量补测(D-46):只新增/扩展 `*.test.ts`,不改动已执行的 06-01..06-09 计划与任何生产/核心源码;异步移动只用真实计时器 + `await controller.onEnd`(D-04)。"
- "疑似缺陷按正确预期编写并标记 `it.skip`/`it.todo`,并按 D-06 格式登记到共用的 `06-TEST-FINDINGS.md`(本计划预期无需新增 skip)。"
artifacts:
- path: packages-user/data-base/src/hero/equipment.test.ts
provides: G-06-05-A 多槽位同时装备的合并最终属性断言
- path: packages-user/data-base/src/map/mapState.test.ts
provides: G-06-06-B createMap 生成地图的写入/读取内容断言
- path: packages-user/data-base/src/map/gameMap.test.ts
provides: G-06-06-C 多个不同 zIndex 图层并存的独立读写断言
- path: packages-user/data-base/src/map/mapLayer.test.ts
provides: G-06-06-A 静态→动态→移动→静态 全链路与移动后 keepEvent 断言
- path: .planning/phases/06-unit-tests/06-COVERAGE-MAP.md
provides: 阶段级 code → 模块 → 用例 映射表(本计划 create-or-append 06-11 小节)
key_links:
- "equipment.test.ts 复用既有 `createEnv`/`createItem`/`registerItem`,经 `setSlots(['weapon','armor'])` + `store.add(num)` + `equip(uid, slot)` + `getFinalAttribute` 驱动;装备/卸下路径已桩化 `replaySystem`,无需真实录像"
- "mapState.test.ts 复用既有 `createFixture`(注入 `TileStore`/`FaceManager`/`RoleFaceBinder` 的 `IDataCommon` 假对象)与 `MapState.createMap`;图层/块/事件经 `IGameMap.addLayer`、`setLayerAlias`、`setEventLayer`、`setBlock`、`event(x,y)` 读写"
- "gameMap.test.ts 复用既有 `createFixture` 与 `GameMap.addLayer`/`setZIndex`/`compareWith`;多图层并存经捕获 `addLayer` 返回值与 `setLayerAlias` 断言"
- "mapLayer.test.ts 复用既有 `createFixture(blocks)` 与 `MapLayer.transferToDynamic`/`transferToStatic`;移动用 `DynamicTile.step(FaceDirection.Right)` + `await controller.onEnd`(真实计时器),与既有 `dynamicTile.test.ts` 的 `step` 用例同形状"
- "`.planning/phases/06-unit-tests/06-COVERAGE-MAP.md` 为共享 create-or-append 文件(幂等、顺序无关):写 06-11 小节前先检查表头(`| code | 模块 | 用例 | 计划 |`)是否存在,不存在则先创建,再只追加/更新本小节,绝不重写他节"
prohibitions:
- "不创建或修改任何生产/核心源码,只新增/扩展 `*.test.ts` 与规划产物"
- "不改动 06-01..06-09 的 PLAN.md"
- "不测试 `saveState`/`loadState`(集中到 06-13)(D-32)"
- "不安装覆盖率工具(D-09)"
- "不建立跨文件共享 fixture/factory helper,fixture 全部文件内联(D-02/D-03)"
- "异步移动只用真实计时器,禁用 `vi.useFakeTimers()`(D-04)"
- "疑似 bug 不得弱化为通过;按正确预期编写并标记 `it.skip`/`it.todo`(D-05)"
---
<objective>
按 D-43 阶段化(构件 → 组合 → 完整/集成)补齐 06-05(勇士)与 06-06(地图)人工评审发现的覆盖缺口(D-46):
- **G-06-05-A** 多槽位同时装备的最终属性未验证(`hero/equipment.test.ts`)
- **G-06-06-A** 静态→动态→移动→静态 全链路与移动后 keepEvent 未串联验证(`map/mapLayer.test.ts`)
- **G-06-06-B** createMap 创建的地图上生成内容未验证(`map/mapState.test.ts`)
- **G-06-06-C** 多个不同 zIndex 图层并存未验证(`map/gameMap.test.ts`)
Purpose: 现有用例各自覆盖单槽装备、单图层、createMap 注册、单次转换/单次移动,缺少「多槽合并」「多图层并存」「createMap 后内容生成」「移动后再转静态」的组合断言。本计划只补测试,不改任何生产代码。
Output: 4 个既有 `*.test.ts` 的扩展与 `06-COVERAGE-MAP.md` 的 06-11 小节。
阶段结构(D-43):阶段 1 构件级(G-06-05-A 多槽装备 + G-06-06-B createMap 内容生成)→ 阶段 2 组合(G-06-06-C 多 zIndex 图层并存)→ 阶段 3 完整/集成(G-06-06-A 静态→动态→移动→静态 全链路)。每阶段跑绿后再进入下一阶段。
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/phases/06-unit-tests/06-CONTEXT.md
@.planning/phases/06-unit-tests/06-COVERAGE-GAPS.md
@.planning/phases/06-unit-tests/06-PATTERNS.md
@.planning/phases/06-unit-tests/06-TEST-FINDINGS.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/attribute.ts
@packages-user/data-base/src/hero/equipment.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/dynamicTile.ts
@packages-user/data-base/src/map/staticTile.ts
@packages-user/data-base/src/map/types.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/dynamicTile.test.ts
@packages/common/src/logger.json
</context>
<execution_rules>
- 只写测试,绝不修改 `packages-user/*/src` 下的非 `*.test.ts` 文件。
- 每个 `it(...)` 之前必须有一行中文注释说明覆盖内容(dev.md 注释规范),测试范围变化时同步更新。
- 异步移动用真实计时器 + `await controller.onEnd`,禁用 `vi.useFakeTimers()`(D-04)。
- fixture 内联且最小(D-02/D-03),沿用目标文件既有 helper,不新造跨文件 helper。
- 警告/错误路径经 `logger.catch` / `vi.spyOn(logger, ...)`。
- 疑似 bug:按正确预期编写、标记 `it.skip`/`it.todo`、中文注释锚定 `#06-XX-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`。**
- **阶段门禁(D-43):本计划按 构件 → 组合 → 完整/集成 三阶段顺序执行,每阶段跑绿后才进入下一阶段;若某阶段发现阻断性 bug 使后续阶段无法运行,暂停并按 D-05/D-07 向用户汇报、等确认,不得跳过或弱化。**
- **质量门禁(D-44,每个计划执行后、提交前必过)**:对改动文件运行 `eslint --fix`,并确保 `eslint <改动文件>` **0 错误**;对**本计划改动的测试文件**运行 `pnpm exec vue-tsc --noEmit`,并按输出文件路径过滤,确保这些文件路径下 **0 类型错误**(全局既有无关错误,如 `client-modules`/`legacy-plugin-data`,不在本门禁范围);`pnpm test:ci` **全绿**。任一不通过则**不得提交**。
</execution_rules>
<tasks>
<task type="auto">
<name>阶段 1(构件级):G-06-05-A 多槽位同时装备合并属性 + G-06-06-B createMap 内容生成</name>
<files>packages-user/data-base/src/hero/equipment.test.ts, packages-user/data-base/src/map/mapState.test.ts</files>
<read_first>
packages-user/data-base/src/hero/equipment.test.ts(既有 `createEnv`/`createItem`/`registerItem` 与单槽最终属性断言 15/22/10)
packages-user/data-base/src/hero/equipment.ts(`setSlots`/`equip`/`unequip`/`getEquipped`/`equipped`/`compareEquip`)
packages-user/data-base/src/hero/equipStore.ts(`HeroEquipsStore.add`/`get`)
packages-user/data-base/src/hero/attribute.ts(`getFinalAttribute`)
packages-user/data-base/src/map/mapState.test.ts(既有 `createFixture`/`createRaw` 与 `createMap` 注册用例)
packages-user/data-base/src/map/mapState.ts(`createMap`/`fromRaw`/`getMap`)
packages-user/data-base/src/map/gameMap.ts(`addLayer`/`setLayerAlias`/`setEventLayer`/`dirty`)
packages-user/data-base/src/map/types.ts(`IMapLayer.setBlock`/`getBlock`/`event`)
.planning/phases/06-unit-tests/06-COVERAGE-GAPS.md(G-06-05-A、G-06-06-B)
.planning/phases/06-unit-tests/06-PATTERNS.md(C / D 节)
</read_first>
<action>
1) G-06-05-A:在 `equipment.test.ts` 的「equip and unequip」分区新增用例。`createEnv()`;`setSlots(['weapon', 'armor'])`;`registerItem(createItem(10, 'sword', [0], [['atk', 5]]))` 与 `registerItem(createItem(11, 'shield', [1], [['def', 3]]))`;`const sword = store.add(10)`、`const shield = store.add(11)`;`equipment.equip(sword, 0)` 与 `equipment.equip(shield, 1)`。断言:`getEquipped(0) === sword`、`getEquipped(1) === shield`、`equipped(sword)` 与 `equipped(shield)` 均为 true、`attribute.getFinalAttribute('atk') === 15`(10 + 5)、`attribute.getFinalAttribute('def') === 8`(5 + 3);再 `equipment.unequip(0)` 后断言 `atk === 10` 且 `def === 8`(另一槽修饰器保留)。可补一件带百分比修饰器的第三槽以覆盖 value+percentage 合并,但保持最小。
2) G-06-06-B:在 `mapState.test.ts` 的楼层注册分区新增用例。`const mapState = createFixture()`;`const map = mapState.createMap('F1', 2, 2)`;`const layer = map.addLayer()`;`map.setLayerAlias(layer, 'event')`;`map.setEventLayer(layer)`;`layer.setBlock(5, 0, 0)`;`layer.setZIndex(0)`;`layer.event(1, 0)!.set(9, 'gen-event')`。断言:`mapState.getMap('F1') === map`、`map.getLayerByAlias('event') === layer`、`map.eventLayer === layer`、`layer.getBlock(0, 0) === 5`、`layer.getBlock(1, 0) === 0`、`layer.event(1, 0)!.get()` 等于 `new Map([[9, 'gen-event']])`、`map.dirty() === true`;证明 createMap 返回的地图可直接用于内容生成与读取。
两条用例前各写一行中文注释。若某断言与实现不符,按 D-05 写正确预期 + `it.skip` 并登记 `06-TEST-FINDINGS.md` 的 `#06-11-N`,不得弱化。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/map/mapState.test.ts</automated>
<fails_when>两个文件运行失败(非 skip),或 G-06-05-A 未断言合并后的 atk/def 与卸下后保留,或 G-06-06-B 未在 createMap 的地图上断言块/事件/事件层/脏标记,或既有装备与 mapState 用例回归失败</fails_when>
</verify>
<acceptance_criteria>
- `equipment.test.ts` 与 `mapState.test.ts` 零非跳过失败。
- G-06-05-A:两个不同槽位同时装备的合并最终属性与单槽卸下后的保留均有显式断言。
- G-06-06-B:createMap 后写入/读取块、点位事件、事件层绑定与脏标记均有显式断言。
- 每个新增 `it` 前有单行中文注释。
- D-44 门禁通过:改动文件 `eslint --fix` 后 `eslint <改动文件>` 0 错误;本计划改动的测试文件在 `pnpm exec vue-tsc --noEmit` 输出中按文件路径过滤 0 类型错误;`pnpm test:ci` 全绿。
</acceptance_criteria>
<done>阶段 1 的 G-06-05-A 与 G-06-06-B 覆盖全部跑绿后才进入阶段 2。若发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认。</done>
</task>
<task type="auto">
<name>阶段 2(组合):G-06-06-C 多个不同 zIndex 图层并存与独立读写</name>
<files>packages-user/data-base/src/map/gameMap.test.ts</files>
<read_first>
packages-user/data-base/src/map/gameMap.test.ts(既有 `createFixture` 与 addLayer/别名/compareWith 用例)
packages-user/data-base/src/map/gameMap.ts(`addLayer`/`setLayerAlias`/`getLayerByAlias`/`compareWith`/`removeLayer`)
packages-user/data-base/src/map/mapLayer.ts(`setZIndex`/`getBlock`/`setBlock`/`dirty`/`compareWith`)
.planning/phases/06-unit-tests/06-COVERAGE-GAPS.md(G-06-06-C)
.planning/phases/06-unit-tests/06-PATTERNS.md(D 节)
</read_first>
<action>
在 `gameMap.test.ts` 的图层生命周期或脏标记分区新增用例。`const { map } = createFixture()`;`const low = map.addLayer()`、`const mid = map.addLayer()`、`const high = map.addLayer()`;`map.setLayerAlias(low, 'low')`、`map.setLayerAlias(mid, 'mid')`、`map.setLayerAlias(high, 'high')`;`low.setZIndex(1)`、`mid.setZIndex(5)`、`high.setZIndex(9)`;`low.setBlock(3, 0, 0)`、`mid.setBlock(4, 1, 0)`、`high.setBlock(5, 0, 1)`。断言:三个图层均可由别名取回且互不相同;各 `zIndex` 为 1/5/9;各自 `getBlock` 只反映自己写入的值(其余格为 0),即三份块数据互不影响;`map.dirty() === true`。再断言按 zIndex 的参考比较分别生效:`map.compareWith(new Map([[1, new Uint32Array([3, 0, 0, 0])], [5, new Uint32Array([0, 4, 0, 0])]]))`——两把参考分别**逐字节等于** low(索引 0=3)与 mid(索引 1=4)的当前内容,因此 low/mid 由 `MapLayer.compareWith` 的 `isEqualToRef` 判定为干净(`low.dirty() === false`、`mid.dirty() === false`);high(zIndex 9,内容 `[0, 0, 5, 0]`)因参考 Map 中缺 key 9 走 `GameMap.compareWith` 的 `layer.markDirty(true)` 分支而被标脏(`high.dirty() === true`),证明多 zIndex 图层在参考比较中按 zIndex 独立处理。**参考数组必须等于各图层当前内容(含非零格),若传全零参考会把 low/mid 判脏。**
写一行中文注释。若参考比较语义与断言不符,按 D-05 写正确预期 + `it.skip` 并登记 `#06-11-N`,不得弱化。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/gameMap.test.ts</automated>
<fails_when>该文件运行失败(非 skip),或未断言三个不同 zIndex 图层并存、各自块数据独立,或按 zIndex 的 compareWith 独立判定未断言,或既有 gameMap 用例回归失败</fails_when>
</verify>
<acceptance_criteria>
- `gameMap.test.ts` 零非跳过失败。
- G-06-06-C:三个不同 zIndex 图层并存、各自块数据独立、按 zIndex 的参考比较分别生效均有显式断言。
- 每个新增 `it` 前有单行中文注释。
- D-44 门禁通过:改动文件 `eslint --fix` 后 `eslint <改动文件>` 0 错误;本计划改动的测试文件在 `pnpm exec vue-tsc --noEmit` 输出中按文件路径过滤 0 类型错误;`pnpm test:ci` 全绿。
</acceptance_criteria>
<done>阶段 2 的 G-06-06-C 覆盖在阶段 1 跑绿的基础上全部跑绿后才进入阶段 3。若发现阻断性 bug,暂停并按 D-05/D-07 向用户汇报、等确认。</done>
</task>
<task type="auto">
<name>阶段 3(完整/集成):G-06-06-A 静态→动态→移动→静态 全链路与移动后 keepEvent(含覆盖表写入)</name>
<files>packages-user/data-base/src/map/mapLayer.test.ts, .planning/phases/06-unit-tests/06-COVERAGE-MAP.md</files>
<read_first>
packages-user/data-base/src/map/mapLayer.test.ts(既有 `createFixture`、transferToDynamic/transferToStatic/keepEvent 用例)
packages-user/data-base/src/map/mapLayer.ts(`transferToDynamic`/`transferToStatic`/`syncStaticEvent`/`getDynamicTilesAt`/`getBlock`/`getTile`)
packages-user/data-base/src/map/dynamicTile.ts(`step`/`mover`/`x`/`y`/`tileEvent`)
packages-user/data-base/src/map/dynamicTile.test.ts(`step` + `await controller.onEnd` 既有写法)
packages-user/data-base/src/map/staticTile.ts(构造函数 `restoreDefaultEvents`/`num`/`set`/`toDynamic`/`tileEvent`)
packages-user/data-base/src/map/types.ts(`transferToDynamic`/`transferToStatic` 的 `keepEvent` 语义)
.planning/phases/06-unit-tests/06-COVERAGE-GAPS.md(G-06-06-A)
.planning/phases/06-unit-tests/06-PATTERNS.md(D 节)
</read_first>
<action>
在 `mapLayer.test.ts` 的动态转换分区新增两条串联用例(真实计时器 + `await controller.onEnd`)。
共同前置(两条用例相同):`const { layer } = createFixture([1, 2, 1, 2])`;**紧接着 `layer.getTile(0, 0)` 物化源格静态图块**——`StaticTile` 的构造函数即执行 `restoreDefaultEvents()` 并读取当时的 `num()`;若不在 `transferToDynamic` 之前物化,`transferToDynamic` 会先 `setBlock(0, 0, 0)` 把源格归零、再 `createDynamic`,此刻才惰性构造的 `StaticTile.num()` 已为 0,读不到源格默认事件,动态图块因而为空事件(与既有 `transfers a dynamic tile back to static keeping events` 用例先 `layer.getTile(0, 0)` 的写法保持一致);随后 `const tile = layer.transferToDynamic(0, 0)!`(静态 1 → 动态,静态格清空,动态事件继承 `{10:'base-event'}`);`const controller = tile.step(FaceDirection.Right)`,断言 `controller !== null` 且 `tile.mover.moving === true`,`await controller!.onEnd` 后断言 `tile.x === 1`、`tile.y === 0`、`layer.getDynamicTilesAt(1, 0)` 含 tile、`layer.getDynamicTilesAt(0, 0)` 为空;此时目标格 (1,0) 的静态块仍为 2(非空),随后写入动态专属事件 `tile.tileEvent().set(30, 'moved-event')`(使动态事件为 `{10:'base-event', 30:'moved-event'}`)。
1) keepEvent = true:用 `const { ret: restored } = logger.catch(() => layer.transferToStatic(tile, true)!)` 包裹(目标格非空触发既有告警 129,属预期,不做告警断言)。断言 `restored.num() === 1`、`layer.getBlock(1, 0) === 1`(写回的是动态图块自身的 num=1,不是目标格原来的 2)、`layer.getBlock(0, 0) === 0`、`restored.tileEvent().get()` **恰等于** `new Map([[10, 'base-event'], [30, 'moved-event']])`(默认事件 + 动态专属事件全部保留)、`restored.tileEvent().dirty() === true`、动态索引 `getDynamicTilesAt(1, 0)` 为空。
2) keepEvent = false(移动之后):同样用 `logger.catch` 包裹 `layer.transferToStatic(tile, false)`。语义关键点:`transferToStatic` 先 `setBlock(tile.num(), x, y)` 把**动态图块自己的 num=1** 写到 (1,0),随后 `syncStaticEvent` 调 `staticTile.set(staticTile.num())` → `restoreDefaultEvents()` 按**写回后的新块 num(1)**重新推导默认事件,因此得到的是图块 1 的默认 `[[10, 'base-event']]`,而**不是**目标格 (1,0) 原始块 2 的默认 `[[20, 'alternate-event']]`。断言 `restored.num() === 1`、`layer.getBlock(1, 0) === 1`、`layer.getBlock(0, 0) === 0`、`restored.tileEvent().get()` **恰等于** `new Map([[10, 'base-event']])`(动态专属 `30:'moved-event'` 被丢弃)、`restored.tileEvent().get().has(30) === false`、`restored.tileEvent().dirty() === false`、动态索引 `getDynamicTilesAt(1, 0)` 为空。两条用例仅凭 `30:'moved-event'` 的保留/丢弃区分 keepEvent 语义。
两条用例前各写一行中文注释。若往返后位置/事件/索引与预期不符(含既有 `#06-06-1` 之外的越界码问题),按 D-05 写正确预期 + `it.skip` 并登记 `06-TEST-FINDINGS.md` 的 `#06-11-N`(注明是否与既有 `#06-06-1` 同源),不得弱化。
完成后以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 的 06-11 小节:本计划**不引入新码**,先确认表头存在(不存在则创建),再追加 `## 06-11 勇士与地图缺口补测` 小节,列出 G-06-05-A、G-06-06-A/B/C → 用例名 → 计划 06-11 的映射,并注明「本计划无新增码,复用既有可达码」;只追加/更新本小节,绝不重写他节。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-base/src/map/mapLayer.test.ts</automated>
<fails_when>该文件运行失败(非 skip),或 G-06-06-A 未串联 静态→动态→移动→静态 全链路,或 keepEvent true/false 未在移动之后验证,或 06-11 小节未写入 `06-COVERAGE-MAP.md`,或既有 mapLayer 用例回归失败</fails_when>
</verify>
<acceptance_criteria>
- `mapLayer.test.ts` 零非跳过失败。
- G-06-06-A:两条用例的共同前置在 `transferToDynamic` 之前执行 `layer.getTile(0, 0)` 物化源格静态图块,使动态图块实际继承 `{10:'base-event'}`;一条用例串联静态→动态→移动→静态(keepEvent true,断言 `restored.tileEvent().get()` **恰等于** `[[10,'base-event'],[30,'moved-event']]` 且 `restored.tileEvent().dirty() === true`);另一条验证移动后 keepEvent false 的事件丢弃与**按写回图块 num 重推**的默认事件回退(`restored.tileEvent().get()` **恰等于** `[[10,'base-event']]`,既非 `[[30,'moved-event']]`、也非目标格原块 2 的 `[[20,'alternate-event']]`,且 `restored.tileEvent().dirty() === false`);位置、块、事件、动态索引均有断言。
- 移动使用真实计时器 + `await controller.onEnd`。
- `06-COVERAGE-MAP.md` 存在 06-11 小节(create-or-append,未重写他节),列出 G-06-05-A、G-06-06-A/B/C 的用例映射。
- 每个新增 `it` 前有单行中文注释。
- D-44 门禁通过:改动文件 `eslint --fix` 后 `eslint <改动文件>` 0 错误;本计划改动的测试文件在 `pnpm exec vue-tsc --noEmit` 输出中按文件路径过滤 0 类型错误;`pnpm test:ci` 全绿。
</acceptance_criteria>
<done>阶段 3 的 G-06-06-A 覆盖在阶段 1/2 跑绿的基础上全部跑绿,且 06-11 小节已写入 `06-COVERAGE-MAP.md`。完成后暂停并用中文分条汇报;详细结果写入 `06-TEST-FINDINGS.md`。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| none | 仅新增/扩展仓库内 Node Vitest 测试;无运行时输入面、网络、DOM 或文件写入。 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-06-11-01 | Tampering | `data-base/src/hero` 与 `data-base/src/map` 下测试文件 | low | mitigate | 只导入已跟踪模块、合成 fixture;不改核心源码;门禁 `pnpm test:ci` + 复核仅 `*.test.ts` 变更。 |
| T-06-11-02 | Information disclosure | 合成 fixture | low | accept | 合成属性/小地图,无真实数据或密钥(D-02)。 |
| T-06-11-03 | Repudiation | 全局 stub 泄漏 | low | accept | 既有文件已在 `vi.hoisted`/`afterAll` 管理 stub;新增用例不引入新 stub。 |
| T-06-11-SC | Tampering | 包安装 | high | mitigate | 明确不引入任何新依赖;若确需安装,停止并先走包合法性 checkpoint。 |
</threat_model>
<verification>
- 四个测试文件聚焦运行通过;`pnpm test:ci` 全量绿。
- `git status` 仅显示四个 `*.test.ts` 与 `06-COVERAGE-MAP.md` 的变更;无核心文件改动。
- 任何 `it.skip`/`it.todo` 均有 `#06-11-N` 条目(预期无需新增)。
- 以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-11 小节;表头不存在则先创建;幂等、顺序无关,不重写他节。
- **执行协议**:执行前必须先向用户确认;执行后暂停并用中文分条汇报。
</verification>
<success_criteria>
- G-06-05-A、G-06-06-A、G-06-06-B、G-06-06-C 均有可跑绿的新增用例。
- 多槽合并属性、createMap 内容生成、多 zIndex 图层并存、静态→动态→移动→静态 全链路(keepEvent 两分支)均被显式断言。
- 无生产代码改动,`pnpm test:ci` 全绿。
</success_criteria>
<artifacts_this_plan_produces>
Test files (extended):
- `packages-user/data-base/src/hero/equipment.test.ts`(新增多槽同时装备合并属性用例)
- `packages-user/data-base/src/map/mapState.test.ts`(新增 createMap 内容生成用例)
- `packages-user/data-base/src/map/gameMap.test.ts`(新增多 zIndex 图层并存用例)
- `packages-user/data-base/src/map/mapLayer.test.ts`(新增静态→动态→移动→静态 两条 keepEvent 用例)
Planning artifacts:
- `06-COVERAGE-MAP.md` create-or-append 06-11 小节
</artifacts_this_plan_produces>
<output>
完成时创建 `.planning/phases/06-unit-tests/06-11-SUMMARY.md`,以 create-or-append 方式写入 `06-COVERAGE-MAP.md` 06-11 小节;疑似 bug 追加到 `06-TEST-FINDINGS.md` 的 `#06-11-N` 小节(若有)。执行完成后暂停并用中文分条汇报。
</output>