docs(06-08): complete flag + common unit test plan

This commit is contained in:
unanmed 2026-09-14 17:04:06 +08:00
parent 6699df9eee
commit 5470f7a90e
6 changed files with 247 additions and 13 deletions

View File

@ -210,7 +210,7 @@ Plans:
3. 测试在本地可运行且全部通过
4. 测试由 AI 编写并运行,通过验证后可提交
**Plans**: 6/9 plans executed replanned (D-28;旧 06-01/06-02 执行结果标记 superseded,按同号重跑;数据端切片,非数据 render/legacy 覆盖延后)
**Plans**: 7/9 plans executed replanned (D-28;旧 06-01/06-02 执行结果标记 superseded,按同号重跑;数据端切片,非数据 render/legacy 覆盖延后)
Plans:
@ -221,7 +221,7 @@ Plans:
- [x] 06-05-PLAN.md — Hero ALL files incl. rendering + async mover (data-base/src/hero;无 save/load)
- [x] 06-06-PLAN.md — Map ALL interfaces, emphasis static/dynamic tiles + static arrays (data-base/src/map;无 save/load)
- [ ] 06-07-PLAN.md — Top-level integration: damage combos + map+replay play + second-play re-record equality; error 2001–2008 (BLOCKED: pre-execution user confirmation)
- [ ] 06-08-PLAN.md — Flag full surface + common (utils/indexer/faceManager+face/mover) (无 save/load)
- [x] 06-08-PLAN.md — Flag full surface + common (utils/indexer/faceManager+face/mover) (无 save/load)
- [ ] 06-09-PLAN.md — Save/load independent system: every saveState/loadState class + CoreState top-level (BLOCKED: pre-execution user confirmation)
**Wave 1** *(independent test-writing plans; no shared production edits)*
@ -231,7 +231,7 @@ Plans:
- [x] 06-03-PLAN.md
- [x] 06-04-PLAN.md
- [x] 06-05-PLAN.md
- [ ] 06-06-PLAN.md
- [x] 06-06-PLAN.md
- [ ] 06-08-PLAN.md
**Wave 2** *(blocked on Wave 1 + user confirmation)*
@ -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. 单元测试 | 6/9 | In Progress| |
| 6. 单元测试 | 7/9 | In Progress| |

View File

@ -4,16 +4,16 @@ milestone: v1.0
current_phase: 06
current_phase_name: unit-tests
status: executing
stopped_at: Completed 06-06-PLAN.md
last_updated: "2026-09-14T08:32:08.508Z"
stopped_at: Completed 06-08-PLAN.md
last_updated: "2026-09-14T09:03:12.315Z"
last_activity: 2026-09-14
last_activity_desc: Phase 06 execution started
state_head: 235eb1a5c9e17c985a28cc582248664e56385762
state_head: 6699df9eeed82f39879d68aa58ee1f978b442ab0
progress:
total_phases: 6
completed_phases: 0
total_plans: 46
completed_plans: 43
completed_plans: 44
milestone_name: milestone
---
@ -84,6 +84,7 @@ Progress: [█████░░░░░] 50%
| Phase 06 P04 | 26min | 3 tasks | 6 files |
| Phase 06 P05 | 22min | 3 tasks | 10 files |
| Phase 06 P06 | 24min | 3 tasks | 8 files |
| Phase 06 P08 | 21min | 3 tasks | 6 files |
## Accumulated Context
@ -166,6 +167,9 @@ Recent decisions affecting current work:
- [Phase 06]: 06-06:transferToDynamic 越图实际发码 131(setEventLayer 专属)而 transferToStatic 发 128,按 D-05 以正确预期 it.skip 登记 #06-06-1 待用户确认;131 正常覆盖由 gameMap.setEventLayer 越权路径承担
- [Phase 06]: 06-06:IMapState 并无 canPass/shouldHit(实现在 data-state/src/hero/predicate.ts),mapState.test 只覆盖谓词侧依赖的「活跃楼层 → 事件层」数据供给;计划中的 createLayerState 码 121 实为 MapState.createMap 重复注册告警
- [Phase 06]: 06-06:D-32 不测任何 saveState/loadState(55/122/124 归 06-09);MapTileBase 抽象类经 StaticTile/DynamicTile 具体子类覆盖,mover protected 回调为 no-op 故经公开钩子观测生命周期
- [Phase 06]: 06-08:flag + common 按 D-43 三阶段(构件→组合/流水线→完整/集成)以 6 个行为单测覆盖,每阶段聚焦跑绿并过 D-44 门禁后提交
- [Phase 06]: 06-08:FlagSystem 全公开表面(码 111)、FaceManager + Dir4/Dir8 handler、RoleFaceBinder(码 43/44)、utils 朝向纯函数、MapLocIndexer、ObjectMover 全公开方法均覆盖;D-32 不测 saveState/loadState(flag 往返归 06-09)
- [Phase 06]: 06-08:疑似缺陷 #06-08-1(ObjectMover.backward(count>1) 因 Special 步翻转 moveDirection 而方向摆动、净位移为零),按 D-05 以正确预期 it.skip 登记,不修改核心代码
### Pending Todos
@ -194,6 +198,6 @@ None yet.
## Session Continuity
Last session: 2026-09-14T08:32:08.256Z
Stopped at: Completed 06-06-PLAN.md
Last session: 2026-09-14T09:03:12.081Z
Stopped at: Completed 06-08-PLAN.md
Resume file: None

View File

@ -1,10 +1,10 @@
---
schema_version: 1
open_count: 13
open_count: 14
waived_count: 0
fixed_count: 8
total_count: 21
last_updated: 2026-09-14T00:54:04.044Z
total_count: 22
last_updated: 2026-09-14T09:03:38.649Z
---
# Broken Windows Ledger
@ -36,6 +36,7 @@ last_updated: 2026-09-14T00:54:04.044Z
| 19 | 06 | skipped-test | packages-user/data-system/src/combat/damage.test.ts | | 06-01-1 suspected bug: calculateCritical yields info not matching nextValue (Finding #06-01-1) | open | | 2026-09-14T00:54:02.628Z | |
| 20 | 06 | skipped-test | packages-user/data-system/src/combat/mapDamage.test.ts | | 06-01-2 suspected bug: MapDamage.deleteEnemy leaves enemy-sourced damage (Finding #06-01-2) | open | | 2026-09-14T00:54:03.338Z | |
| 21 | 06 | skipped-test | packages-user/data-system/src/combat/combat.test.ts | | 06-01-3 suspected bug: CombatFlow.before truthy/falsy semantics inverted vs interface doc (Finding #06-01-3) | open | | 2026-09-14T00:54:04.044Z | |
| 22 | 06 | skipped-test | packages-user/data-common/src/common/mover.test.ts | | backward(count>1) 方向摆动、净位移为零(#06-08-1),已 it.skip 待用户确认 | open | | 2026-09-14T09:03:38.649Z | |
````json
[
@ -290,6 +291,18 @@ last_updated: 2026-09-14T00:54:04.044Z
"reason": "",
"recorded_at": "2026-09-14T00:54:04.044Z",
"resolved_at": null
},
{
"id": 22,
"kind": "skipped-test",
"phase": "06",
"file": "packages-user/data-common/src/common/mover.test.ts",
"line": null,
"description": "backward(count>1) 方向摆动、净位移为零(#06-08-1),已 it.skip 待用户确认",
"status": "open",
"reason": "",
"recorded_at": "2026-09-14T09:03:38.649Z",
"resolved_at": null
}
]
````

View File

@ -0,0 +1,182 @@
---
phase: 06-unit-tests
plan: 08
subsystem: testing
tags: [vitest, data-common, data-base, flag, face, faceManager, mover, indexer, utils]
# Dependency graph
requires: []
provides:
- "FlagSystem 全公开表面的行为单测(不含 save/load)"
- "common 工具覆盖:utils 朝向纯函数 / MapLocIndexer / FaceManager + Dir4/Dir8 handler / RoleFaceBinder / ObjectMover"
- "码 43 / 44 / 111 的触发断言与 06-COVERAGE-MAP.md 06-08 小节"
affects: [06-09, verify-work]
# Actuals (#2632)
actuals:
tokens: 9903
tasks: 3
commits: 3
plan_head_before: 2b6de6df5fc7c7af255637f594fe00b729617dca
# Tech tracking
tech-stack:
added: []
patterns:
- "三阶段测试组织(构件 → 组合/流水线 → 完整/集成)"
- "logger.catch 观测 warn/error code,vi.hoisted 全局 stub + Map.getOrInsertComputed/getOrInsert polyfill"
- "异步 mover 路径用真实计时器 + await controller.onEnd"
key-files:
created:
- 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
modified:
- packages-user/data-common/src/common/mover.test.ts
key-decisions:
- "按 D-43 三阶段顺序执行:构件级(utils/indexer)→ 组合(FlagSystem/FaceManager/handlers/RoleFaceBinder)→ 完整(ObjectMover 异步),每阶段跑绿并过 D-44 门禁后再进入下一阶段"
- "D-32:不测任何 saveState/loadState;flag 存读档往返归 06-09"
- "D-30:排除名称含 legacy 的接口/方法"
- "ObjectMover 多步后退方向摆动记为疑似缺陷 #06-08-1,按 D-05 以 it.skip 正确预期用例登记,不修改核心代码"
patterns-established:
- "flag/face 测试:vi.hoisted(vi.stubGlobal + Map polyfill) + beforeAll 动态 import 模块袋,纯函数模块直接相对导入"
- "非数值 flag 字段告警:modules.logger.catch(() => system.addFieldValue(...)) 断言 info.code 含 111"
- "mover 扩展覆盖:新增模块级 nextLocator 辅助并保持既有坐标回写用例不变"
requirements-completed: [TEST-01]
coverage:
- id: D1
description: "common 朝向工具纯函数与 MapLocIndexer 索引单元覆盖(构件级)"
requirement: "TEST-01"
verification:
- kind: unit
ref: "packages-user/data-common/src/common/utils.test.ts#getFaceMovement/degradeFace/nextFaceDirection/fromDirectionString"
status: pass
- kind: unit
ref: "packages-user/data-common/src/common/indexer.test.ts#MapLocIndexer"
status: pass
human_judgment: false
- id: D2
description: "FlagSystem 全公开表面(码 111)与 FaceManager + Dir4/Dir8 handler + RoleFaceBinder(码 43/44)覆盖(组合/流水线)"
requirement: "TEST-01"
verification:
- kind: unit
ref: "packages-user/data-base/src/flag/system.test.ts#FlagSystem field container + value accessors"
status: pass
- kind: unit
ref: "packages-user/data-common/src/common/faceManager.test.ts#FaceManager + Dir4FaceHandler + Dir8FaceHandler"
status: pass
- kind: unit
ref: "packages-user/data-common/src/common/face.test.ts#RoleFaceBinder malloc/bind/query"
status: pass
human_judgment: false
- id: D3
description: "ObjectMover 公开移动方法完整覆盖(含控制器 stop 与 start-while-moving 契约)"
requirement: "TEST-01"
verification:
- kind: unit
ref: "packages-user/data-common/src/common/mover.test.ts#object mover position writeback + public surface"
status: pass
human_judgment: true
rationale: "多步后退(backward(count>1))行为异常,已按 D-05 记为疑似缺陷 #06-08-1 并以 it.skip 的正确预期用例登记;需人工确认预期语义后再修复与取消 skip"
# Metrics
duration: 21min
completed: 2026-09-14
status: complete
---
# Phase 06 Plan 08: flag + common 单元测试 Summary
**以 6 个行为单测覆盖 FlagSystem 全公开表面与 common 工具(朝向纯函数 / MapLocIndexer / FaceManager + 4·8 向 handler / RoleFaceBinder / ObjectMover),触发码 43/44/111,并按 D-05 记录 1 处疑似缺陷**
## Performance
- **Duration:** 21min
- **Started:** 2026-09-14T08:40:00Z (approx)
- **Completed:** 2026-09-14T09:01:33Z
- **Tasks:** 3
- **Files modified:** 6(5 新建 + 1 扩展)
## Accomplishments
- 阶段 1(构件级):`utils.test.ts` 覆盖 `getFaceMovement`/`degradeFace`/`nextFaceDirection`(4 向与 8 向顺逆时针、Unknown 透传)/`fromDirectionString`(全部字符串 + 未知);`indexer.test.ts` 覆盖 `MapLocIndexer` 的坐标/定位符一致、索引往返与 `setWidth` 步长变化。
- 阶段 2(组合/流水线):`flag/system.test.ts` 覆盖 `FlagSystem` 全部公开方法(`occupied`/`insertField`/`getField`/`getOrInsert`/`getOrInsertComputed`/`deleteField`/`setFieldValue`/`addFieldValue`/`getFieldValue`/`getFieldValueDefaults`)并触发码 111;`faceManager.test.ts` 覆盖注册表与 `Dir4FaceHandler`/`Dir8FaceHandler` 全方法(degrade/movement/move/opposite/next/mapDirection/mapMovement);`face.test.ts` 覆盖 `RoleFaceBinder` 并触发码 43/44。
- 阶段 3(完整/集成):扩展 `mover.test.ts`,在保留既有 4 条坐标回写用例的前提下补齐 `ObjectMover` 全部公开移动方法(`setPos`/`setFaceDir`/`setMoveDir`/`tp`/`jump`/`step`/`stepFace`/`forward`/`backward`/`speed`/`face`/`animDir`/`push`/`clear`/`start`)与控制器 `stop` 契约,异步路径用真实计时器 + `await controller.onEnd`。
- 覆盖表与问题记录:`06-COVERAGE-MAP.md` 新增 06-08 小节与码 43/44/111 行;`06-TEST-FINDINGS.md` 新增 `#06-08` 小节(`#06-08-1`)。
## Task Commits
Each task was committed atomically:
1. **阶段 1(构件级):朝向工具纯函数 + MapLocIndexer** - `7394a25` (test)
2. **阶段 2(组合/流水线):FlagSystem + FaceManager/handlers + RoleFaceBinder** - `2cb9bba` (test)
3. **阶段 3(完整/集成):ObjectMover 异步移动** - `6699df9` (test)
**Plan metadata:** (docs: complete 06-08 plan) — 见最终 docs 提交
_Note: 每阶段先跑绿聚焦用例并过 D-44 门禁(eslint + vue-tsc 文件级 + `pnpm test:ci`)后提交。_
## Files Created/Modified
- `packages-user/data-common/src/common/utils.test.ts` - 朝向工具纯函数覆盖(11 用例)
- `packages-user/data-common/src/common/indexer.test.ts` - `MapLocIndexer` 覆盖(4 用例)
- `packages-user/data-base/src/flag/system.test.ts` - `FlagSystem` 全公开表面与码 111(10 用例)
- `packages-user/data-common/src/common/faceManager.test.ts` - `FaceManager` + `Dir4`/`Dir8` handler(12 用例)
- `packages-user/data-common/src/common/face.test.ts` - `RoleFaceBinder` 与码 43/44(7 用例)
- `packages-user/data-common/src/common/mover.test.ts` - `ObjectMover` 扩展覆盖(18 通过 + 1 skip,既有 4 用例保留)
## Decisions Made
- 三阶段严格顺序执行(D-43);每阶段通过 D-44 门禁(`eslint --fix` 后 `eslint` 0 错误、`vue-tsc --noEmit` 本计划文件 0 类型错误、`pnpm test:ci` 全绿)后才提交并进入下一阶段。
- 纯函数模块(utils/indexer/faceManager)直接相对导入,避免 barrel 副作用;flag/face 因 import 链触及 `@motajs/common` logger 使用 `vi.hoisted` 全局 stub 与 `Map.getOrInsertComputed`/`getOrInsert` polyfill。
- 不测 `saveState`/`loadState`(D-32),不引入任何生产代码改动。
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug(测试预期)] 修正 `getMainFace` 期望值与负步数 `-0` 断言**
- **Found during:** 阶段 2(`face.test.ts`、`faceManager.test.ts` 首次聚焦运行)
- **Issue:** `RoleFaceBinder.getMainFace(identifier)` 返回的是**被查询图块自身标识**与主朝向方向;测试误写成主图块标识。另 `move(dir, negative)` 在零分量上产生 `-0`,与 `{ x: 0 }` 不被 `toEqual` 视为相等。
- **Fix:** 将 `getMainFace(2)` 期望改为 `{ identifier: 2, face: Down }`;负步数用例改用无零分量的对角方向(`RightUp, -2`),dir4 只保留正步数缩放。
- **Files modified:** `packages-user/data-common/src/common/face.test.ts`、`packages-user/data-common/src/common/faceManager.test.ts`
- **Verification:** 阶段 2 聚焦运行与全量 `pnpm test:ci` 全绿
- **Committed in:** `2cb9bba`(阶段 2 提交)
**2. [Rule 1 - Bug(测试骨架)] 扩展 `TestMover` 以支持 Jump/DirFace/Special 步**
- **Found during:** 阶段 3(扩展 `mover.test.ts`)
- **Issue:** 既有 `TestMover.onStepEnd` 只处理 `Teleport`/`Dir`,无法驱动 `jump`/`stepFace`/`forward`/`backward`。
- **Fix:** 抽出模块级 `nextLocator` 辅助;`onStepEnd` 增加 `Teleport|Jump`(含 `rel` 相对模式)、`Dir|DirFace`、`Special`(按 `this.moveDirection`)分支。既有 4 条坐标回写用例断言保持不变。
- **Files modified:** `packages-user/data-common/src/common/mover.test.ts`
- **Verification:** 阶段 3 聚焦运行(18 通过 / 1 skip)与全量 `pnpm test:ci` 全绿
- **Committed in:** `6699df9`(阶段 3 提交)
---
**Total deviations:** 2 auto-fixed(均为测试预期/测试骨架问题,未触碰生产代码)
**Impact on plan:** 无范围扩张;偏差仅限测试自身的期望与辅助实现,生产源码零改动。
## Issues Encountered
- 发现疑似缺陷 `#06-08-1`:`ObjectMover.backward(count>1)` 因 `Special` 步把 `moveDirection` 翻转为反方向、而 `getCurrentDirection` 又优先读取该方向,导致连续后退方向来回摆动、净位移为零并翻转朝向。按 D-05 以正确预期的 `it.skip` 用例登记(`mover.test.ts` `keeps retreating along the same axis across multiple backward steps`),并在 `06-TEST-FINDINGS.md` 记录;单步后退正常用例通过。未修改核心代码。
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- flag 与 common 的行为覆盖已完成,`pnpm test:ci` 全绿(58 文件 / 563 通过 / 14 跳过)。
- 存读档往返(含 flag)按 D-32 归 06-09;`#06-08-1` 待用户确认语义后修复并取消 skip。
---
*Phase: 06-unit-tests*
*Completed: 2026-09-14*
## Self-Check: PASSED
- All 6 test files + SUMMARY exist
- Commits 7394a25, 2cb9bba, 6699df9 exist
- Focused runs green; pnpm test:ci green (58 files / 563 passed / 14 skipped)
- D-44 gate passed per stage

View File

@ -71,6 +71,9 @@
| 131 | map/gameMap.ts | `accepts own layers, warns 131 for a foreign layer and clears on null` | 06-06 |
| 136 | map/eventView.ts | `warns code 136 when the same priority is set twice` | 06-06 |
| 143 | map/dynamicTile.ts | `warns code 143 when the raw tile data is missing`、`reflects num and raw and warns 143 on an unknown set` | 06-06 |
| 43 | common/face.ts | `warns code 43 for an unknown main block` | 06-08 |
| 44 | common/face.ts | `warns code 44 when binding the main direction` | 06-08 |
| 111 | flag/field.ts | `warns code 111 when adding to a non-numeric field` | 06-08 |
## 06-01 战斗系统(packages-user/data-system/src/combat)
@ -178,3 +181,23 @@ D-18/D-39 计划措辞与实现的两处偏差(非代码缺陷,不登记为
一条疑似缺陷 `#06-06-1`(`transferToDynamic` 越图误发 131 而非 128)按 D-05 以 `it.skip` 的
正确预期用例登记,详见 `06-TEST-FINDINGS.md`;码 131 的触发则由 `gameMap.test.ts` 的
`setEventLayer` 越权路径正常覆盖。
## 06-08 flag + common(packages-user/data-base/src/flag、packages-user/data-common/src/common)
模块归属:43 / 44 → `common/face.ts`(`RoleFaceBinder.bind` 未知主图块 / 主朝向覆盖);
111 → `flag/field.ts`(`FlagCommonField.add` 对非数值字段告警,经 `FlagSystem.addFieldValue` 触发)。
阶段 1(构件级)完成朝向工具纯函数(`getFaceMovement`/`degradeFace`/`nextFaceDirection`/
`fromDirectionString`)与 `MapLocIndexer` 的索引互转及宽度切换。
阶段 2(组合/流水线)完成 `FlagSystem` 容器全公开表面(`occupied`/`insertField`/`getField`/
`getOrInsert`/`getOrInsertComputed`/`deleteField`/`setFieldValue`/`addFieldValue`/`getFieldValue`/
`getFieldValueDefaults`,观测 111)、`FaceManager` 注册表与 `Dir4FaceHandler`/`Dir8FaceHandler`
全方法、`RoleFaceBinder`(`malloc`/`bind`/`getFaceOf`/`getFaceDirection`/`getMainFace`,观测 43/44)。
阶段 3(完整/集成)完成 `ObjectMover` 全部公开移动方法(`setPos`/`setFaceDir`/`setMoveDir`/
`tp`/`jump`/`step`/`stepFace`/`forward`/`backward`/`speed`/`face`/`animDir`/`push`/`clear`/`start`)
与控制器 `stop` 契约;异步路径用真实计时器 + `await controller.onEnd`。
D-32:不测任何 `saveState`/`loadState`,flag 存读档往返归 06-09。
D-30:排除名称含 legacy 的接口/方法。
一条疑似缺陷 `#06-08-1`(`backward(count>1)` 因 `Special` 步翻转 `moveDirection` 而方向摆动、
净位移为零)按 D-05 以 `it.skip` 的正确预期用例登记,详见 `06-TEST-FINDINGS.md`。

View File

@ -98,3 +98,15 @@ D-30:排除名称含 legacy 的接口/方法;两处**计划措辞与实现
| 模块/接口 | 现象 | 最小复现 | 疑似原因 | 影响面 | 建议修复方向 | 关联 skip 用例 | 严重度 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `mapLayer.ts` `MapLayer.transferToDynamic`(越图分支) | 越图转换发出的诊断码与语义不符:发的是 setEventLayer 专属码 131,且返回 null | 2x2 图层上调用 `layer.transferToDynamic(9, 9)`;`logger.catch` 捕获到码 131(`Cannot set event layer since target map layer does not belongs to current GameMap instance.`),返回 null | `if (!this.inMap(x, y))` 分支写成 `logger.warn(131, x, y)`;对照同文件 `transferToStatic`/`transferToStaticIfSafe` 的越界分支发码 128(`Cannot transfer ... out of bounds.`),此处应为 128 | 越界转换的诊断码错误,人工/回放诊断可能误判为事件层绑定问题;行为(返回 null、不产生动态图块)本身正确 | 将该分支改发 128,与 `transferToStatic` 的越界语义保持一致 | `mapLayer.test.ts` `warns code 128 for an out-of-map transferToDynamic`(#06-06-1) | 低 |
## #06-08 flag + common(packages-user/data-base/src/flag、packages-user/data-common/src/common)
本计划按 D-43 三阶段(构件 → 组合/流水线 → 完整/集成)执行,6 个测试文件(`utils` / `indexer`
/ `faceManager` / `face` / `flag/system` / `mover`)全部跑绿;`pnpm test:ci` 全绿
(58 文件 / 563 通过 / 14 跳过,其中 1 条为本计划新增 skip)。D-32:不测任何 `saveState`/`loadState`,
flag 的存读档往返归 06-09。D-30:排除名称含 legacy 的接口/方法。
发现 1 处疑似缺陷(`#06-08-1`),按 D-05 以正确预期的 `it.skip` 用例登记。
| 模块/接口 | 现象 | 最小复现 | 疑似原因 | 影响面 | 建议修复方向 | 关联 skip 用例 | 严重度 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `mover.ts` `ObjectMover.backward`(`Special` 步的移动方向推导) | 连续后退多个步骤时方向来回摆动,净位移为零且朝向被翻转 | 朝向 `Down` 的 mover 调用 `backward(2)` → `start()` → `await controller.onEnd`,最终坐标为 `(0, 0)` 而非 `(0, -2)`,`faceDirection` 变为 `Up` | `prepareStep` 的后退分支把 `moveDirection` 设为 `opposite(dir)`,而 `getCurrentDirection` 又优先读取非 `Unknown` 的 `moveDirection`;下一步据此再次取反,形成交替 | 多步后退(`backward(count>1)`)无法沿同一轴连续移动,并把 `faceDirection` 翻转;单步后退正常 | 后退步的基准方向应取当前朝向 `faceDirection` 而非被翻转后的 `moveDirection`,或在 `getCurrentDirection` 中区分「停顿时的移动方向」与「本步刚设置的临时移动方向」 | `mover.test.ts` `keeps retreating along the same axis across multiple backward steps`(#06-08-1) | 低 |