From 9114a652ee772bce66152ca19576ebd108a3ad60 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Fri, 23 Oct 2026 21:23:52 +0800 Subject: [PATCH] docs(10): plan advanced I mover and face-handler units --- .planning/ROADMAP.md | 9 +- .../phases/10-docs-restructure/10-28-PLAN.md | 186 ++++++++++++++++++ .../phases/10-docs-restructure/10-29-PLAN.md | 182 +++++++++++++++++ .../phases/10-docs-restructure/10-30-PLAN.md | 148 ++++++++++++++ .../phases/10-docs-restructure/10-31-PLAN.md | 170 ++++++++++++++++ .../phases/10-docs-restructure/10-32-PLAN.md | 176 +++++++++++++++++ 6 files changed, 870 insertions(+), 1 deletion(-) create mode 100644 .planning/phases/10-docs-restructure/10-28-PLAN.md create mode 100644 .planning/phases/10-docs-restructure/10-29-PLAN.md create mode 100644 .planning/phases/10-docs-restructure/10-30-PLAN.md create mode 100644 .planning/phases/10-docs-restructure/10-31-PLAN.md create mode 100644 .planning/phases/10-docs-restructure/10-32-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 165b8e9..c5ac10a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -554,7 +554,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → **Goal:** 重构过老、结构不佳、新人难懂的项目文档,面向多背景读者(完全新手 / 不懂代码的作者 / 有基础的作者 / AI Agent)提供分层指南。本阶段采用「伞阶段 + 增量派发」:每次只规划执行一个已派发的具体小任务。**第一批已派发 = 中级教程(`docs/intermediate/`,24 篇中文 markdown)+ 在 VitePress 站点注册它们 + 旧内容清空(D-04 并入本批)**(D-01..D-44)。**第二批已派发 = 高级教程 I(`docs/advanced/`,7 篇数据端文档)+ 站点注册 + 独立读者人格审阅 + 用户主导修订循环**(D-53..D-57)。**第三批已完成 = 高级教程 II「深入定制」(`docs/advanced/ii/`,10 篇数据端文档)+ 站点注册 + 独立读者人格审阅 + 用户主导修订循环**(D-67,2026-10-23 收尾)。 **Requirements**: (无专属需求 ID;以 `10-CONTEXT.md` 的 D-01..D-74 为需求基线,验收锚在 D-09:用户人工审阅 + 链接全部有效 + 构建通过) **Depends on:** Phase 9 -**Plans:** 24/27 completed(第一批:中级教程 24 篇 + 站点注册 + D-09 复审修订;第二批:高级 I 7 篇 + 注册 + 读者审阅 + 用户修订循环;第三批:高级 II 10 篇 + 注册 + 读者审阅 + 用户修订循环)。**未完成 3 个**:`10-06`(中级全批验收)、`10-11`(中级读者审阅)、`10-12`(中级最终验收)——其范围已被后续 D-09 修订与高级 I/II 期间的最终状态覆盖,缺独立 SUMMARY,待补跑或并入后续验收。 +**Plans:** 24/32 completed(第一批:中级教程 24 篇 + 站点注册 + D-09 复审修订;第二批:高级 I 7 篇 + 注册 + 读者审阅 + 用户修订循环;第三批:高级 II 10 篇 + 注册 + 读者审阅 + 用户修订循环;第四批:高级 I 补写 2 篇 + 注册 + 读者审阅 + 用户修订循环,待执行)。**未完成 3 个**(第一批遗留):`10-06`(中级全批验收)、`10-11`(中级读者审阅)、`10-12`(中级最终验收)——其范围已被后续 D-09 修订与高级 I/II 期间的最终状态覆盖,缺独立 SUMMARY,待补跑或并入后续验收。 Plans: - [x] 10-01-PLAN.md — Wave 0:旧内容清空 + 站点注册(删除 `docs/guide`、`docs/dev`、`docs/logger`、`docs/api` 与首页 `docs/index.md`;清理 `config.ts` 旧 nav/sidebar 键并解除 apiSidebar/api 依赖;`api.ts` 加缺失目录守卫;新增 nav 入口 + `sidebar['/intermediate/']` 4 组/24 条;报告并等待 checkpoint) @@ -584,9 +584,16 @@ Plans: - [x] 10-25-PLAN.md — 高级 II 导览 + 侧边栏注册(`/advanced/ii/` 九篇、分组名待用户确认)+ 互链与全量构建 - [x] 10-26-PLAN.md — 高级 II 独立读者人格审阅(高代码能力人格,不同于高级 I)→ 问题清单 → 迭代修正(≤3 轮)→ 复审清零 - [x] 10-27-PLAN.md — 高级 II 用户主导修订循环 → 用户确认 → 最终格式 / 构建 / SUMMARY 收尾 +- [ ] 10-28-PLAN.md — 高级 I 补写:移动 Mover(三件套 / 移动计划与执行 / 控制器 / 步骤类型 / 钩子 / 自定义移动器) +- [ ] 10-29-PLAN.md — 高级 I 补写:FaceHandler 系列(`FaceDirection` / `FaceGroup` / `IFaceHandler` / 内置处理器 / 自定义处理器 / `IRoleFaceBinder`) +- [ ] 10-30-PLAN.md — 高级 I 导览 7 → 9 + 侧边栏注册 + 两篇互链收口 + 全量构建 +- [ ] 10-31-PLAN.md — 两篇补写内容的独立读者审阅(沿用高级 I 人格)→ 问题清单 → 迭代修正(≤3 轮)→ 复审清零 +- [ ] 10-32-PLAN.md — 两篇补写内容的用户主导修订循环 → 用户确认 → 最终格式 / 构建 / SUMMARY 收尾 **说明:** 每篇中级教程遵循 `10-CONTEXT.md` D-21..D-44 的逐篇要点与阅读顺序(4/8/9/3)。**旧内容删除已并入本批(D-04,Wave 0 / 10-01)**:删除 `docs/guide/*`、`docs/dev/*`、`docs/logger/*`、`docs/api/*` 与首页 `docs/index.md`,并清理 `docs/.vitepress/config.ts` 的旧 `nav`/`sidebar` 键。文档改动为 `docs/intermediate/*.md`(新增)、`docs/.vitepress/config.ts` 与 `docs/.vitepress/api.ts`(缺失目录守卫,保留按包生成机制)。构建命令 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`;`init.ts` 在 `docs/api/` 缺失时安全跳过。本机 PowerShell 禁止 `pnpm.ps1`,命令经 `cmd /c "..."` 执行。 **高级 I 说明:** 第二批(10-13..10-20)只写**数据端**内容,不含渲染端;七篇文件名与侧边栏显示名以各计划 Task 0 的用户确认为准(D-13)。指向高级 II / III 的内容一律使用代码 span 或 HTML 注释占位,不写真实链接(D-47)。每篇保留「怎么验证」小节,并按 D-48 六步 / D-49 改内置优先 / D-50 模板独立 / D-51 落点与副作用 / D-52 加粗边界执行。构建命令同上;10-18 负责站点注册与全量链接收口,10-19 / 10-20 分别执行独立读者人格审阅与用户主导修订循环,**用户明确确认后方可收尾**(D-46 第 2 阶段 / D-57)。 **高级 II 说明(已完成):** 第三批(10-21..10-27)按 D-67 / D-53 定位为「深入定制」**难度层**(难度标签,非领域分类),只写**数据端**内容;已建 `docs/advanced/ii/` **10 篇**(导览 + 8 个主题 + 点事件与图块事件)。写作按 D-59 库文档风格、D-60 简洁、D-61 主干与边界分离、D-62/D-70 代码块完整、D-63 模板顺序、D-64 只在需实现接口时给模板、D-65 注释充足、D-66 并列功能分开示例、D-69 内部实现章节后置;读者水平按 D-68(已理解绝大多数常用语法,认真阅读后能读明白一部分简单与中等复杂度系统,不预设资深开发者)。侧边栏分组名「高级 II · 深入定制」。渲染端 / 编辑器 / 状态栏 / 怪物图鉴按 D-47 非断链占位;高级 III 只用代码 span 或 `TODO(advanced-III)` 注释。10-25 负责导览与站点注册,10-26 执行独立读者审阅,10-27 执行用户主导修订循环,**用户已明确确认**(D-46 第 2 阶段)并于 2026-10-23 收尾。构建命令同上。 + +**高级 I 补写说明(第四批,待执行):** 按 D-75 / D-71,为高级 I 补两篇数据端文档——`docs/advanced/mover.md`(移动 Mover)与 `docs/advanced/face-handler.md`(FaceHandler 系列)。写作按高级 I 读者水平(D-57:了解基本语法、代码能力一般、不熟悉引擎内部架构)与 D-59..D-74 库文档风格;渲染端 / 编辑器按 D-47 非断链占位。10-28 / 10-29 并行写两篇(互引先用代码 span),10-30 负责导览 7 → 9、侧边栏注册与互链收口,10-31 执行独立读者审阅(沿用 10-19 已确认的高级 I 人格),10-32 执行用户主导修订循环,**用户明确确认后方可收尾**。文件名、页面标题、侧边栏显示名与示例标识符名以各计划 Task 0 的用户确认为准(D-13)。D-75 写的 `IMoveController` 与源码 `IMoverController` 不一致,以源码为准。 diff --git a/.planning/phases/10-docs-restructure/10-28-PLAN.md b/.planning/phases/10-docs-restructure/10-28-PLAN.md new file mode 100644 index 0000000..b1eddf7 --- /dev/null +++ b/.planning/phases/10-docs-restructure/10-28-PLAN.md @@ -0,0 +1,186 @@ +--- +phase: 10-docs-restructure +plan: 28 +type: execute +wave: 1 +depends_on: [] +files_modified: + - docs/advanced/mover.md + - .planning/phases/10-docs-restructure/10-28-SUMMARY.md +autonomous: false +requirements: [] +user_setup: [] +estimate: + tokens: 26000 + raw_tokens: 17000 + tasks: 3 + confidence: low +must_haves: + truths: + - "`docs/advanced/mover.md` 存在,kebab-case、CRLF、单 H1「移动 Mover」" + - "讲清三件套的分工:`IObjectMovable`(被移动对象)、`IObjectMover`(移动器)、`IMoverController`(一次执行的句柄);说明「先排计划、再执行」是本篇的主线" + - "讲清链式规划接口:`step` / `stepFace` / `forward` / `backward` / `face` / `tp` / `jump` / `speed` / `animDir` / `push` / `clear`,并说明 `speed` 与 `animDir` 只设置后续步骤的参数、`face` 只转向、`forward` 以移动方向为基准、`backward` 以当前朝向的反方向为基准、`tp` / `jump` 的 `rel` 含义" + - "讲清执行模型与异步语义:`start()` 取队列快照并清空待执行队列、已在移动时返回 `null`、`start()` 会把 `moveDirection` 重置为 `Unknown`、`moving` / `done` / `onEnd`、`controller.push` / `insert` / `stop` 的时机、执行期间用 `mover` 上的链式方法追加不会进入当前这一次执行(只能用 `controller.push`)" + - "讲清 `IObjectMoverHooks` 七个钩子:`onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onSetPos` / `onSetFaceDir` / `onSetMoveDir`;`code` 由 `onStepStart` 传递到 `onStepEnd`;异步钩子由 `Promise.all` 并发等待" + - "给出可实现的扩展路径:`ObjectMover` 子类的完整模板(五个抽象方法 + `[需改]`)与一个最小可移动对象 + 最小移动器示例;示例为 class + new,注释充足(D-65)" + - "纯内部实现(执行流程源码顺序)单独成节、放在靠后位置,并以「此章节讲解一下移动器的内部执行流程」开头(D-69)" + - "数据端 only;渲染端 / 编辑器内容按 D-47 用非断链占位;与 `face-handler.md` 的互引先用代码 span,真实链接由 10-30 统一收口" + - "不改 `config.ts`、生产代码与渲染端内容;构建 green、`build complete`、dead link = 0" + artifacts: + - "docs/advanced/mover.md" + - ".planning/phases/10-docs-restructure/10-28-SUMMARY.md" + key_links: + - "D-75 第 1 项:高级 I 补写「移动 Mover」,补齐 D-71 记录的两个缺口之一" + - "`packages-user/data-common/src/common/mover.ts` 是唯一权威来源;`ObjectMover` 的执行流程、控制器语义与钩子签名逐条以它为准" + - "向上接 `./hooks-in-depth.md`(`IObjectMoverHooks` 属于该篇讲的多对象钩子族)、`./replay-consistency.md`(`@shouldReplay` 标在步进方法上)、`./ii/static-vs-dynamic-tiles.md`(动态图块的移动器)" +--- + + +编写高级 I 补写篇目之一:`docs/advanced/mover.md`(移动 Mover)。讲清移动器的三件套分工、移动计划怎么排、执行与异步等待怎么用、步骤类型、控制器语义、`IObjectMoverHooks` 钩子,以及如何通过继承 `ObjectMover` 扩展出自己的移动器。 + +Purpose: 中级教程只讲「获取勇士位置」(`hero-location`),移动位置留给高级;高级 II 的寻路、动态图块又反复出现「拿到移动器、排几步、等它走完」。缺这一篇,读者看到 `state.hero.location.mover` 只能猜。D-75 明确要求补上,且要讲「怎么用、怎么改/扩展」。 +Output: 1 个新 markdown 文件(`docs/advanced/mover.md`)。 + + + +@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md +@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md + + + +@.planning/phases/10-docs-restructure/10-CONTEXT.md +@docs/advanced/index.md +@docs/advanced/hooks-in-depth.md +@docs/advanced/replay-consistency.md +@docs/advanced/ii/static-vs-dynamic-tiles.md +@docs/advanced/ii/pathfinding.md +@docs/intermediate/hero-location.md +@packages-user/data-common/src/common/mover.ts +@packages-user/data-common/src/common/types.ts +@packages-user/data-base/src/map/mover.ts +@packages-user/data-base/src/hero/mover.ts +@packages-user/data-base/src/hero/types.ts +@packages-user/data-base/src/map/dynamicTile.ts +@packages/common/src/utils/types.ts + +**写作规范(高级 I,逐条遵守):** +- 读者水平按 D-57:了解基本语法(变量、函数、类、`import`),代码能力一般,不熟悉引擎内部架构;不预设高代码水平,也不当成完全不懂代码。 +- 按一般库文档方式写(D-59):概念 → 完整示例(含解释性注释)→ 接入位置;不用中级教程的「复制什么 / 改什么」六步。 +- 简洁优先(D-60):中级已讲过的(位置 `x` / `y`、`hero.location`)不重复;能删就删。 +- 主干与边界分离(D-61):特殊情况集中到文末「边界与注意」。 +- 代码块结构完整(D-62 / D-70):完整类 / 完整函数,不写脱离上下文的成员片段。 +- 模板组织顺序(D-63):先讲理念 → 引导语 → 模板 → 解释模板;只有需要读者自己实现接口时才给模板(D-64)。 +- 示例注释越多越好(D-65);并列功能分开示例(D-66)。 +- 纯内部实现章节靠后,并以「此章节讲解一下 XXX 的内部实现」开头(D-69)。 +- 注释与实现不一致时以方法注释为准,并在对话中向用户报告,不把实现写进注释语义(D-72)。 +- 假定编辑器已完成(D-73);引用其他文档的具体章节用 `./xxx.md#标题`(D-74)。 +- 数据端 only;渲染端 / 编辑器相关内容按 D-47 用代码 span 或 HTML 注释占位,不写真实链接、不写 `src/client`。 +- 加粗边界:`**` 相邻处不得是标点(D-52)。 + +**源码事实(写作时逐条核对,勿凭印象):** +- 三件套都在 `packages-user/data-common/src/common/mover.ts`:`IObjectMovable`(`x` / `y` / `setPos` / `getCurrentFaceDirection`)、`IObjectMover`(链式规划 + 状态 + `start`)、`IMoverController`(`done` / `onEnd` / `push` / `insert` / `stop`)。 +- **命名更正:** D-75 写作 `IMoveController`,源码实际是 `IMoverController`。文档按源码写,并在 Task 0 向用户说明(D-13 / D-72)。 +- 步骤联合类型 `ObjectMoveStep` 有 8 种:`Dir` / `DirFace` / `Speed` / `Face` / `Special` / `AnimDir` / `Teleport` / `Jump`;对应的 `type` 常量来自 `ObjectMoveType`;`ObjectSpecialStep` 有 `Forward` / `Backward`;`ObjectAnimDirection` 有 `Forward` / `Backward`。 +- `step(dir, count)` 追加 `Dir`;`stepFace(move, face, count)` 追加 `DirFace`;`forward(count)` / `backward(count)` 追加 `Special`;`face(dir)` 追加 `Face`;`speed(value)` 追加 `Speed`(单位 ms/格,默认 `currentSpeed = 100`);`animDir(dir)` 追加 `AnimDir`;`tp(x, y, rel)` 追加 `Teleport`;`jump(x, y, rel)` 追加 `Jump`;`push(steps)` 一次追加数组;`clear()` 清空尚未执行的队列。 +- `start()`:已在移动时返回 `null`;否则把 `moveQueue` 切片成局部队列、调用 `clear()` 清空内部队列、`shouldStop = false`、`moveDirection` 重置为 `Unknown`,返回控制器并驱动执行。 +- 执行顺序(`moveProgress`):`onMoveStart`(子类)→ 钩子 `onMoveStart`(`Promise.all`)→ 每个步骤:`prepareStep`(先同步更新内部状态)→ `onStepStart`(返回 `code`)→ 钩子 `onStepStart` → `onStepEnd`(返回落点 `ITileLocator`)→ 坐标变化时 `tile.setPos` → `onStepSettled` → 钩子 `onStepEnd` → 全部步骤结束后 `onMoveEnd` → 钩子 `onMoveEnd`。 +- 控制器:`push` / `insert` 只在仍在移动、未 `stop`、未 `done` 时生效;`insert` 把步骤放在「当前步之后」;`stop()` 把 `shouldStop` 置真并返回 `onEnd`,当前步会走完再停;`done` 在整次执行结束时置真,`onEnd` 同时兑现。 +- **执行期间的坑:** `moveProgress` 用的是 `start()` 时的局部队列快照;执行期间再调用 `mover.step` / `mover.face` 等只会写回内部 `moveQueue`,不会被当前这一次执行消费。要在执行中追加,只能用 `controller.push` / `controller.insert`。 +- `prepareStep` 的语义:`Dir` 同时写移动方向与朝向;`DirFace` 分开写;`Face` 只写朝向;`Special` 前进用当前朝向、后退用 `faceHandler.opposite(当前朝向)` 并保持朝向不变;`Speed` 写 `currentSpeed`;`AnimDir` 写 `currAnimDir`。 +- `setPos` / `setFaceDir` / `setMoveDir` 立即生效、不排队,并分别触发 `onSetPos` / `onSetFaceDir` / `onSetMoveDir`;这三个钩子是同步的(返回 `void`)。 +- 参考实现:`packages-user/data-base/src/hero/mover.ts`(`HeroMover` 继承 `ObjectMover`,实现边检、通行判断、撞击、步前步后事件,并在步进方法上标 `@shouldReplay`)、`packages-user/data-base/src/map/mover.ts`(`DynamicTileMover`,最小实现:只处理 8 种步骤的落点)。 +- 现成入口:`state.hero.location.mover`(`IHeroMover`,另有 `config` / `getConfig` / `useTopImplementation`)、动态图块的 `mover` 字段;`IHeroMover` 与 `HeroMoveCode` 在 `packages-user/data-base/src/hero/types.ts`。移动器拿到的朝向处理器是 `IFaceHandler`(`packages/common/src/utils/types.ts`),本篇只说明「构造时要传一个」,细节由 10-29 的 `FaceHandler 系列` 承担。 +- 通行性不在移动器里:`HeroMover` 把判断交给顶层实现对象(`IHeroMoveTopImpl` 的 `predicate` / `inBound`)。自定义移动器只负责「这一步落到哪」,通行判定属于调用方。 +- 录像:`HeroMover` / `DynamicTileMover` 在会改状态的步进方法上标了 `@shouldReplay`;自定义移动器若要让移动进录像,同样标在改状态的方法上(详见 `./replay-consistency.md`)。 + + + + + + Task 0: 向用户汇报本计划并等待「可以执行」 + 向用户汇报:**要做什么**(新增 `docs/advanced/mover.md`,讲移动器三件套、移动计划、执行与异步等待、步骤类型、控制器、钩子与自定义移动器)、**为什么**(D-75 / D-71 要求补「移动 Mover」;中级只讲获取位置,高级 II 的寻路与动态图块又反复用到移动器,缺这篇读者只能猜)、**大致怎么做**(先讲三件套与「先排计划再执行」的主线,再讲链式接口与步骤类型、执行与控制器语义、状态与钩子,然后给 `ObjectMover` 子类模板与最小可移动对象示例,最后是内部执行流程(D-69 靠后)、边界与注意、「怎么验证」)、**影响范围**(仅新增 1 个 `docs/advanced/*.md`;不改 `config.ts`、生产代码与渲染端内容;侧边栏注册由 10-30 负责)。请用户确认三件事:① 文件名 `mover.md` 与页面标题「移动 Mover」(D-13);② 示例中会出现的标识符名:被移动对象 `SimpleTile`、移动器 `SimpleMover`、结果码 `SimpleMoveCode`(如不同意请给替代名);③ **命名更正**:D-75 写的 `IMoveController` 在源码里是 `IMoverController`,文档将以源码为准。汇报中明确:本计划的修改方式为**派发子代理执行**,范围严格限于 `docs/advanced/mover.md` 与 `.planning/.../10-28-SUMMARY.md`,执行者不内联修改文档。停下等待用户明确回复「可以执行」四个字;未收到前不得写任何文件。 + 本计划内容:高级 I 补写篇目「移动 Mover」。 + 阅读上面的四段说明,确认文件名 / 标题 / 示例标识符名与 `IMoverController` 的命名更正;如不同意请给出替代。 + 回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。 + + 用户已回复「可以执行」,且未对文件名 / 页面标题 / 示例标识符名提出替代。 + + 用户回复「可以执行」。 + + + + Task 1: 编写 mover.md 正文 + docs/advanced/mover.md + (1) 新建 `docs/advanced/mover.md`,单 H1 为「移动 Mover」。(2) 开头用两三段说清问题:对象要从一格走到另一格;移动器把「想怎么走」和「实际怎么走」分开——先用链式调用排出移动计划,`start()` 后才按步执行,每一步落点由子类决定;并说明本篇讲数据端的移动器,画面上的移动表现由渲染端负责(按 D-47 用代码 span 占位,不写 `src/client`)。(3) 正文按以下顺序组织,每节只讲一件事: +① `## 三个角色`:表格列 `IObjectMovable`(被移动对象:`x` / `y` / `setPos` / `getCurrentFaceDirection`)、`IObjectMover`(移动器:规划 + 执行 + 状态)、`IMoverController`(一次执行的句柄:`done` / `onEnd` / `push` / `insert` / `stop`);说明继承关系是「对象实现 `IObjectMovable`,移动器持有对象」。 +② `## 拿到现成的移动器`:`state.hero.location.mover`(`IHeroMover`,额外提供 `config` / `getConfig` / `useTopImplementation`)与动态图块的 `mover` 字段;说明这两个都是引擎已经建好的实例,不要重复 `new`;链接 `../intermediate/hero-location.md` 与 `./ii/static-vs-dynamic-tiles.md`。 +③ `## 排一个移动计划`:给链式接口表(方法 / 追加的步骤类型 / 作用),逐条讲:`step` 一步一方向并同步朝向;`stepFace` 移动与朝向分开;`forward` 沿当前移动方向;`backward` 沿当前朝向的反方向且朝向不变;`face` 只转向不移动;`tp` 瞬移;`jump` 跳跃(数据端只体现为直接落到目标格);`speed` 设置每格耗时(ms);`animDir` 设置动画播放方向;`push` 追加一组步骤;`clear` 清空尚未执行的队列。明确 `speed` / `animDir` 只是写状态,本身不产生移动,由消费方(渲染端)读取。 +④ `## 执行与等待`:`start()` 返回控制器或 `null`(已在移动);用 `controller.onEnd` 等待整次移动结束,或用 `controller.done` 查询;给一个完整示例:勇士先向右走两格、转向下、再走一格,`await controller.onEnd` 后打印坐标。示例含解释性注释、标注 `[需改]`。同时点明 `start()` 会把 `moveDirection` 重置为 `Unknown`,所以 `forward()` 之前必须先有 `step` / `stepFace`。 +⑤ `## 执行中追加、插入与停止`:`controller.push` 追加到队列末尾、`controller.insert` 插到当前步之后、`controller.stop()` 返回 `onEnd` 且当前步走完后才停;说明停止后 `done` 变真,想继续走要重新排计划并 `start()`。**并给出关键边界**:执行期间调用移动器自身的 `step` / `face` 等方法只会写回内部待执行队列,不会被当前这次执行消费;执行中要追加,只能用控制器。 +⑥ `## 步骤类型`:`ObjectMoveStep` 联合类型表(`type` / 字段 / 效果),八种逐一对应 `ObjectMoveType`;说明自定义移动器必须自己处理这些 `type`,未识别的类型应当原样返回当前坐标。 +⑦ `## 移动器状态`:`moving` / `faceDirection` / `moveDirection` / `currAnimDir` / `currentSpeed` / `faceHandler`;说明 `setPos` / `setFaceDir` / `setMoveDir` 是立即生效、不排队,并会触发对应钩子;`faceDirection` 是朝向、`moveDirection` 是移动方向,二者在 `stepFace` / `backward` 下会不同。 +⑧ `## 钩子`:`IObjectMoverHooks` 表(钩子 / 触发时机 / 参数 / 是否异步);讲清 `code` 由 `onStepStart` 的返回值一路传到 `onStepEnd`;`onStepStart` 时坐标仍是移动前,`onStepEnd` 时坐标已更新;异步钩子由 `Promise.all` 并发等待,不要依赖彼此的先后(链接 `./hooks-in-depth.md`);`onSetPos` / `onSetFaceDir` / `onSetMoveDir` 只由对应的 setter 触发。 +⑨ `## 实现自己的移动器`:先讲什么时候需要自己写(现有 `HeroMover` / `DynamicTileMover` 不满足需求时),再给「模板:一个自定义移动器」——`class SimpleMover extends ObjectMover` 的完整类骨架,构造里接收对象与朝向处理器,实现 `onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onStepSettled` 五个抽象方法,并用 `const enum SimpleMoveCode` 定义自己的结果码;模板后用文字解释每个方法的职责、`onStepEnd` 返回落点而坐标由基类写入、`onStepSettled` 在坐标更新之后运行、五个方法都是 `protected` 不能被外部调用;用 `[需改]` 标出需要按需求修改的部分。 +⑩ `## 示例:一个最小可移动对象`:给 `class SimpleTile implements IObjectMovable`(`x` / `y`、`setPos`、`getCurrentFaceDirection`)与一个只处理四方向与瞬移的最小移动器,演示「方向 → 偏移 → 落点」的完整链路;说明通行判断不在移动器里,由调用方负责(`HeroMover` 就是交给顶层实现对象的)。 +⑪ `## 与录像、存档的关系`:`HeroMover` / `DynamicTileMover` 把 `@shouldReplay` 标在会改状态的步进方法上,所以移动会进录像;自定义移动器同理(链接 `./replay-consistency.md`)。位置本身是存档内容,移动过程不产生存档条目。 +⑫ `## 内部执行流程`(D-69,靠后):以小标题开头写「此章节讲解一下移动器的内部执行流程」,然后按源码顺序讲 `start` → 队列快照 → `prepareStep` → 步内五个阶段 → 钩子并发等待;只讲与使用相关的时序,不逐行翻译源码。 +⑬ `## 边界与注意`(D-61):集中列 `start` 返回 `null`、`moveDirection` 被重置、执行期间链式方法无效、`stop` 不立刻中断、`setPos` 不触发步进钩子、`speed` / `animDir` 只是状态、`faceHandler` 必须与对象方向体系一致、无人读取 `currentSpeed` 时速度不产生可见效果(渲染端占位)等。 +⑭ `## 怎么验证`:给可操作的自检——排三步后打印移动前后的 `x` / `y`、`controller.done`、钩子打印顺序;执行中 `controller.push` 一步看是否被消费;`stop()` 后看是否停在当前步末尾。 +(4) 提到朝向处理器时只用代码 span 写 `FaceHandler 系列`,不写指向 `face-handler.md` 的相对链接(该页由并行计划 10-29 创建,链接由 10-30 统一收口);提到高级 III / 编辑器内容一律用代码 span 或 HTML 注释占位。不要写 `src/client`、不要写「后续再讲」式元叙述、不要给中级教程的六步实操。完成后跑 prettier `--write`。Commit:`docs(10-28): 编写移动 Mover 文档`。 + + $ErrorActionPreference='Stop'; $p='docs/advanced/mover.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(? + non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "MISSING-DETAIL", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK" + + 文件存在、CRLF、单 H1;三件套、链式接口、执行与控制器、步骤类型、状态、钩子、模板与最小示例、内部执行流程(D-69)、边界与注意、「怎么验证」齐全;无渲染端内容;无死链。 + + + + Task 2: 源码核对、格式与构建收口 + docs/advanced/mover.md, .planning/phases/10-docs-restructure/10-28-SUMMARY.md + 逐条核对正文与源码,确认没有把语义写反:① `packages-user/data-common/src/common/mover.ts`——五个抽象方法的签名与调用顺序、`prepareStep` 对 `Special` 的朝向/移动方向处理(后退用 `opposite` 且朝向不变)、`start` 对队列的切片与清空、`moveDirection` 重置、`shouldStop` 与 `stop` 的兑现时机、`done` / `onEnd` 的设置点、`push` / `insert` 的生效条件、`onStepEnd` 后先 `setPos` 再 `onStepSettled`。② `packages-user/data-base/src/hero/mover.ts`——`@shouldReplay` 标在哪些方法上、通行判断交给顶层实现、`cannotEnter` / `hit` 后调用 `controller.stop()`(并说明此处不能 `await`)。③ `packages-user/data-base/src/map/mover.ts`——最小实现如何只处理落点。④ `packages-user/data-base/src/hero/types.ts`——`IHeroMover` 的 `config` / `getConfig` / `useTopImplementation`、`IHeroMoverConfig` 三个字段、`HeroMoveCode` 四个值。⑤ `packages/common/src/utils/types.ts`——`IFaceHandler` 方法名与 `IFaceDescriptor` 形状,确认正文只写「构造时要传朝向处理器」而不展开 FaceHandler 细节。若发现源码注释与实现不一致(例如某方法的行为与注释描述不符),**只向用户报告,不写进文档、不擅自记录为问题**(AGENTS.md / D-72)。然后跑 prettier `--write`,再跑构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认 green、`build complete`、dead link = 0。写 `.planning/phases/10-docs-restructure/10-28-SUMMARY.md`:章节结构、引用的源码落点、核对到的关键语义、构建与格式结论、向用户报告的不一致(若无可写「无」)。Commit:`docs(10-28): 核对源码并收口移动文档`。 + + $ErrorActionPreference='Stop'; $p='docs/advanced/mover.md'; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); foreach($needle in @('onStepSettled','内部执行流程','ObjectSpecialStep','rel','Promise.all','stop')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-AFTER-CHECK $needle" } }; $sum='.planning/phases/10-docs-restructure/10-28-SUMMARY.md'; if(-not(Test-Path -LiteralPath $sum)){ Write-Error "MISSING $sum" }; $s=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $sum)); foreach($needle in @('mover.md','构建','源码')){ if($s -notmatch [regex]::Escape($needle)){ Write-Error "SUMMARY-MISSING $needle" } }; $ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/mover.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'GATE-OK' + non-zero exit, or output contains "MISSING", "MISSING-AFTER-CHECK", "SUMMARY-MISSING", "PRETTIER-FAIL", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "GATE-OK" + + 正文语义与 `mover.ts` / `hero/mover.ts` / `map/mover.ts` / `hero/types.ts` 逐条一致;注释不一致已向用户报告且未写入文档;prettier 通过;构建 green、`build complete`、dead link = 0;SUMMARY 已写。 + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 | +| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 | +| 执行者 → 子代理 | 写作子代理只改 `mover.md`,不触碰配置与生产代码 | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-10-82 | Tampering / XSS | `mover.md` 正文与代码块 | low | mitigate | 只写文本、代码块、表格;不引入原始 `