mirror of
https://github.com/motajs/template.git
synced 2026-09-24 01:30:18 +08:00
docs(04-04): revise plan for D-29 IFaceManager injection
This commit is contained in:
parent
e3880fb756
commit
b59710ac7d
@ -9,6 +9,7 @@ depends_on:
|
||||
- 04-03
|
||||
files_modified:
|
||||
- packages-user/client-modules/src/render/map/extension/hero.ts
|
||||
- packages-user/client-modules/src/render/map/extension/types.ts
|
||||
- packages-user/client-modules/src/render/map/extension/manager.ts
|
||||
autonomous: false
|
||||
requirements:
|
||||
@ -21,28 +22,31 @@ estimate:
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "本计划的**第一个 block 点**是 Task 0 的 `blocking-human` 汇报关卡:先向用户汇报要解决的问题(04-03 人工审查反馈 D-24..D-28)、大致解决方案(含逐调用替换映射表)、**拟新增 / 删除 / 改写的成员命名清单**(两个钩子类名 + `IFaceHandler` 构造器参数名 + `onSetPos` 归属 + D-18/D-22 多余方法归属)、范围边界与禁令、验证方式与运行时不可得的诚实降级,然后必须**停下等待**用户明确回复「可以执行」四个字;收到该回复前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
|
||||
- "**D-24 落地**:`hero.ts` 中不再出现已弃用工具 `getFaceMovement` / `degradeFace` / `nextFaceDirection`(词边界判定),改为 `IFaceHandler<FaceDirection>` 的 `movement(dir)` / `degrade(dir)` / `next(dir)`;`MapHeroRenderer` 构造器新增 `IFaceHandler<FaceDirection>` 参数,并在 `manager.ts` 的 `new MapHeroRenderer(...)` 处由 `state.mover.faceHandler` 注入(`IObjectMover.faceHandler`,`data-common/src/common/mover.ts:251`),不使用全局 `state` 取朝向处理器(D-23)"
|
||||
- "**D-24 的 `degradeFace(dir, FaceDirection.Down)` 回退细节已显式处理**(`hero.ts:357` 的 `addFollower`):`faceHandler.degrade(dir)` 之后当结果为 `FaceDirection.Unknown` 时显式回退为 `FaceDirection.Down`(`IFaceHandler.degrade` 自身对 Unknown 返回 Unknown);**对 `Unknown` 的回退与旧 helper 的 `unknown` 形参等价,但注入的 `Dir8FaceHandler.degrade` 对斜向输入是恒等**(旧 helper 会折叠为正交:`faceManager.ts:95-97` vs `utils.ts:41-52`),该差异已在 Task 0 汇报并由用户裁定"
|
||||
- "本计划的**第一个 block 点**是 Task 0 的 `blocking-human` 汇报关卡:先向用户汇报要解决的问题(04-03 人工审查反馈 D-24..D-28 与用户对 D-24 注入方式的修订 D-29)、大致解决方案(含逐调用替换映射表与修正后的语义映射:`degrade` / `next` 走 `Dir4FaceHandler`、`movement` 走 `hero.mover.faceHandler`)、**拟新增 / 删除 / 改写的成员命名清单**(两个钩子类名 + `IFaceManager` 构造器参数名 / 字段名与派生的四向处理器字段名 + `onSetPos` 归属 + D-18/D-22 多余方法归属)、**`IMapExtensionManager.addHero` 的接口签名变更(新增 `faceManager: IFaceManager` 形参)单独列为待批准项**、`addHero` 既有 jsDoc 是否新增 `@param` 行的处理、范围边界与禁令、验证方式与运行时不可得的诚实降级,然后必须**停下等待**用户明确回复「可以执行」四个字;收到该回复前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
|
||||
- "**D-24 / D-29 落地**:`hero.ts` 中不再出现已弃用工具 `getFaceMovement` / `degradeFace` / `nextFaceDirection`(词边界判定);`degrade` / `next` 改走由注入的 `IFaceManager` 取出的四向处理器(`faceManager.get<FaceDirection>(FaceGroup.Dir4)`,`Dir4FaceHandler`),`movement` 改走勇士自身的 `hero.mover.faceHandler`(`IObjectMover.faceHandler`,`data-common/src/common/mover.ts:251`,Dir8);`MapHeroRenderer` 构造器新增 `IFaceManager` 参数并保存为 `faceManager` 字段、在构造体内派生四向处理器字段(`!` 非空断言,见 assumptions 的 lint-clean 说明);`IMapExtensionManager.addHero` 新增 `faceManager: IFaceManager` 形参(D-29 注入路径 B,Task 0 待用户批准),`manager.ts` 的 `addHero` 把该形参转发给 `new MapHeroRenderer(...)`;不使用全局 `state` 取朝向处理器(D-23)"
|
||||
- "**D-24 的 `degradeFace(dir, FaceDirection.Down)` 回退细节已显式处理**(`hero.ts:357` 的 `addFollower`):`dir4.degrade(dir)` 之后当结果为 `FaceDirection.Unknown` 时显式回退为 `FaceDirection.Down`(`IFaceHandler.degrade` 自身对 Unknown 返回 Unknown)。注入的 `Dir4FaceHandler.degrade`(`faceManager.ts:170-172`)与弃用 `degradeFace`(`utils.ts:37-54`)**等价**:斜向折叠相同(`LeftUp/LeftDown→Left`、`RightUp/RightDown→Right`)、正交恒等、`Unknown→Unknown`,故加上显式 Down 回退后与旧 helper 的 `unknown` 形参逐字等价"
|
||||
- "**D-25 落地**:`MapHeroHook` 拆为**两个内部类**(`MapHeroLocationHook` 实现 `Partial<IHeroLocationHooks>`,`MapHeroMoverHook` 实现 `Partial<IObjectMoverHooks<IHeroLocation>>`),各自用 `hero.addHook(...)` / `hero.mover.addHook(...)` 注册到自己的 hookable 并各持一个 `IHookController` 字段(沿用既有 `locationController` / `moverController`);`onSetPos` **只在 `MapHeroLocationHook` 上实现一次**(`mover.setPos` 会先 `tile.setPos`,location 触发面是超集,见 `mover.ts:522-527`),因此同一次定位不会触发两次;`onSetPos` **无条件** `this.hero.setPosition(x, y)`,**不存在** `mover.moving` 守卫"
|
||||
- "**D-26 落地**:`MapHeroMoverHook.onStepEnd` 的 `ObjectMoveType.AnimDir` 分支不再跳过——除开头既有的 `mover.currAnimDir` 设置外,该分支按本步的 `step.dir`(`IObjectMoveAnimDir.dir: ObjectAnimDirection`,`mover.ts:103-108`)调用 `setHeroAnimateDirection`"
|
||||
- "**D-27 落地(范围仅限 hero.ts)**:`hero.ts` 不再出现 `mutate-animate`(词边界判定),改为从 `@motajs/animate` 引入 `ExcitationCurve2D`(`packages/animate/src/types.ts:12`,barrel `packages/animate/src/index.ts:4`),`generateJumpFn` 返回类型由 `TimingFn<2>` 改为 `ExcitationCurve2D`;二者结构等价(均为 `(progress: number) => [number, number]`,见 `node_modules/mutate-animate/index.d.ts:12-14`),故 `IMovingBlock.moveRelative(curve: TimingFn<2>, ...)`(`render/map/moving.ts:162-166`)仍可接收;**`moving.ts` / `maps/types.ts` / 其它文件的存量 `mutate-animate` 使用一律不动**"
|
||||
- "**D-28 落地(存量 `state` 保留)**:`hero.ts` 的 `import { state } from '@user/data-state';` 与其 `state.roleFace.getFaceOf(...)` 三处调用(`hero.ts:235` / `:358` / `:417`)**逐字保留**,本计划不删除、不替换、不据此阻塞;`state` 仍被引用即视为符合 D-28"
|
||||
- "**D-18 / D-22 成员逐字保留**:`setImage` / `setAlpha` / `setFollowerAlpha` / `addFollower` / `removeFollower` / `removeAllFollowers` 与钩子方法 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` 均不被删除、不被改写语义(贴图 / 不透明度按 D-18 待用户先改数据端;跟随者按 D-22 暂缓)"
|
||||
- "**契约面零变更**:`types.ts` **不被修改**(`IMapHeroRenderer` / `IMapExtensionManager` 的成员集合沿用 04-03 收口结果,本计划不再动它);无任何新增公共接口成员;D-24 的新参数只出现在 `MapHeroRenderer` 构造器(不在任何接口里)"
|
||||
- "**范围外零改动**:`extension/types.ts`、`action/move.ts` 与全部移动控制 API(D-19)、`core.*` / legacy(D-12)、`getHeroStatusOn` 类裸函数(D-11)、任何数据端文件与 `packages/**` 均未被本计划改动;`mutate-animate` 的其余存量使用(D-27 范围外)一动不动"
|
||||
- "**验证可运行且不臆造通过**:改动前先记录 `pnpm exec vue-tsc --noEmit` 的 `client-modules` 基线错误集(当日实际值,逐行原样入 SUMMARY);改动后该错误集必须与基线**完全一致**(不多不少),且三个在范围文件命中 0 条类型错误;`pnpm exec eslint` 对两个被改文件 0 错误;静态门禁(弃用 helper、`mutate-animate`、`IFaceHandler` 注入、两钩子类分别注册、`onSetPos` 无 `moving` 守卫、AnimDir 已处理、D-18/D-22 保留、D-28 保留)、CRLF 门禁、基线感知范围门禁与并发改动基线门禁全部转绿。渲染运行时无测试设施、且 `MapExtensionManager.addHero` 当前**未被接线**(`client.ts:151-155` 已注释),故本计划的实质验证为「编译 + 契约 + 静态」三类机器门禁,另附人工 UAT 代码复核;**不得**以任何无法运行的门禁冒充通过"
|
||||
- "**契约面仅一处经批准的签名变更**:`extension/types.ts` 的 `IMapExtensionManager.addHero` 新增 `faceManager: IFaceManager` 形参(D-29,Task 0 待用户批准);`IMapHeroRenderer` 与其余所有接口成员集合不变,无任何新增 / 删除成员;`addHero` 既有 jsDoc 默认逐字保留(是否新增 `@param faceManager` 行由用户在 Task 0 裁定,AGENTS.md 既有注释保护)"
|
||||
- "**范围外零改动**:`action/move.ts` 与全部移动控制 API(D-19)、`core.*` / legacy(D-12)、`getHeroStatusOn` 类裸函数(D-11)、任何数据端文件与 `packages/**` 均未被本计划改动;`mutate-animate` 的其余存量使用(D-27 范围外)一动不动;`client.ts` 的 `addHero` 接线继续保持注释、未启用"
|
||||
- "**验证可运行且不臆造通过**:改动前先记录 `pnpm exec vue-tsc --noEmit` 的 `client-modules` 基线错误集(当日实际值,逐行原样入 SUMMARY);改动后该错误集必须与基线**完全一致**(不多不少),且三个在范围文件命中 0 条类型错误;`pnpm exec eslint` 对三个被改文件 0 错误;静态门禁(弃用 helper、`mutate-animate`、`FaceGroup.Dir4` / `faceManager.get(` / `hero.mover.faceHandler.movement` / 3 参 `addHero` 签名、两钩子类分别注册、`onSetPos` 无 `moving` 守卫、AnimDir 已处理、D-18/D-22 保留、D-28 保留)、CRLF 门禁、基线感知范围门禁(放行三个在范围文件)与并发改动基线门禁全部转绿。渲染运行时无测试设施、且 `MapExtensionManager.addHero` 当前**未被接线**(`client.ts:151-155` 已注释),故本计划的实质验证为「编译 + 契约 + 静态」三类机器门禁,另附人工 UAT 代码复核;**不得**以任何无法运行的门禁冒充通过"
|
||||
- "用户并发的未提交改动(以 Task 1 起始 `git status --porcelain` 当日实测为准,排除 `packages-user/client-modules/src/render/map/extension/` 与 `.planning/phases/04-render-adaptation/` 两个前缀)在 Task 1 起始记录基线(porcelain 条目 + 各文件内容 sha256 摘要),Task 3 复验逐条与内容未变;**不回滚 / 不暂存 / 不提交 / 不修改**它们。注意:04-03 SUMMARY 记录的旧基线(`client-base/src/load/*` 等)已在 `4e64953 chore: 内部接口改名` 落库,本计划**不沿用旧列表**,一律以 Task 1 当日实测为准"
|
||||
- "REND-01 / REND-02 在本 run 后仍保持 Pending:本计划只落地「04-03 勇士渲染修正」一个子切片,未实施其余渲染适配与移动端 / 桌面端双布局(见 assumptions 的两条 FLAGGED ASSUMPTION)"
|
||||
artifacts:
|
||||
- path: packages-user/client-modules/src/render/map/extension/hero.ts
|
||||
provides: "`MapHeroRenderer` 持有注入的 `IFaceHandler<FaceDirection>` 并以 `movement` / `degrade` / `next` 取代三个弃用 helper;`MapHeroHook` 拆为 `MapHeroLocationHook` / `MapHeroMoverHook` 两个内部类分别注册;`onSetPos` 无条件设置且无 `moving` 守卫;`onStepEnd` 的 AnimDir 分支按 `step.dir` 设置动画方向;`mutate-animate` 改为 `@motajs/animate` 的 `ExcitationCurve2D`;D-18 / D-22 / D-28 成员逐字保留"
|
||||
provides: "`MapHeroRenderer` 持有注入的 `IFaceManager`(`faceManager` 字段)与派生的四向处理器字段(`faceManager.get<FaceDirection>(FaceGroup.Dir4)`),以 `dir4.degrade` / `dir4.next`(Dir4)与 `hero.mover.faceHandler.movement`(Dir8)取代三个弃用 helper;`MapHeroHook` 拆为 `MapHeroLocationHook` / `MapHeroMoverHook` 两个内部类分别注册;`onSetPos` 无条件设置且无 `moving` 守卫;`onStepEnd` 的 AnimDir 分支按 `step.dir` 设置动画方向;`mutate-animate` 改为 `@motajs/animate` 的 `ExcitationCurve2D`;D-18 / D-22 / D-28 成员逐字保留"
|
||||
- path: packages-user/client-modules/src/render/map/extension/types.ts
|
||||
provides: "`IMapExtensionManager.addHero` 新增 `faceManager: IFaceManager` 形参(D-29);成员集合无增删、`IMapHeroRenderer` 与其余成员逐字保留"
|
||||
- path: packages-user/client-modules/src/render/map/extension/manager.ts
|
||||
provides: "`addHero` 把 `state.mover.faceHandler` 作为第四个实参注入 `new MapHeroRenderer(this.renderer, layer, state, state.mover.faceHandler)`;无其它改动"
|
||||
provides: "`addHero(state, layer, faceManager)` 把 `faceManager` 形参转发给 `new MapHeroRenderer(this.renderer, layer, state, faceManager)`;`addHero` 之外的方法无改动"
|
||||
key_links:
|
||||
- "`manager.ts` 的 `addHero(state: IHeroLocation, layer)` → `new MapHeroRenderer(this.renderer, layer, state, state.mover.faceHandler)` ↔ `hero.ts` 构造器 `readonly faceHandler: IFaceHandler<FaceDirection>`(`packages/common/src/utils/types.ts:152`)"
|
||||
- "`types.ts` 的 `IMapExtensionManager.addHero(state, layer, faceManager: IFaceManager)` ↔ `manager.ts` 的 `addHero(state, layer, faceManager)` → `new MapHeroRenderer(this.renderer, layer, state, faceManager)` ↔ `hero.ts` 构造器 `readonly faceManager: IFaceManager`(D-29 注入路径 B)"
|
||||
- "`IFaceManager.get<T extends number>(group: number): IFaceHandler<T> | null`(`data-common/src/common/faceManager.ts:32`)↔ `FaceGroup.Dir4` / `Dir8`(`:6-11`)↔ `hero.ts` 构造体 `faceManager.get<FaceDirection>(FaceGroup.Dir4)!` 派生的 `Dir4FaceHandler`(`faceManager.ts:169-193`);`core.ts:115-120` 当日注册 `FaceGroup.Dir4`(`Dir4FaceHandler`)与 `FaceGroup.Dir8`(`Dir8FaceHandler`)"
|
||||
- "`IObjectMover.faceHandler: IFaceHandler<FaceDirection>`(`data-common/src/common/mover.ts:251`)↔ `IHeroMover<T> extends IObjectMover<T>`(`data-base/src/hero/types.ts:399`)↔ `IHeroLocation.mover: IHeroMover<this>`(`data-base/src/hero/types.ts:303-314`)"
|
||||
- "`IFaceHandler.movement(dir): IFaceDescriptor {x, y}`(`common/src/utils/types.ts:164`)/ `degrade(dir): T`(`:158`,Unknown→Unknown)/ `next(dir, anticlockwise?)`(`:186`,默认顺时针)↔ `hero.ts` 原来对三个弃用 helper 的 7 处调用(`:132` / `:202` / `:230` / `:357` / `:364` / `:365` / `:453`)"
|
||||
- "`IFaceHandler.degrade(dir): T`(Unknown→Unknown)/ `next(dir, anticlockwise?)`(默认顺时针)由 `Dir4FaceHandler`(`faceManager.ts:169-193`)实现,与弃用 `degradeFace`(`utils.ts:37-54`,含 `unknown` 形参)/ `nextFaceDirection(face8=false)`(`utils.ts:64-158`,90° 顺时针)等价(`next` 仅正交输入等价)↔ `hero.ts` 的 `:132` / `:202` / `:357` / `:453`;`IFaceHandler.movement(dir): IFaceDescriptor {x, y}` 由 `hero.mover.faceHandler`(Dir8,`faceManager.ts:94-127`)实现,与弃用 `getFaceMovement`(`utils.ts:8-29`)等价 ↔ `hero.ts` 的 `:230` / `:364` / `:365`"
|
||||
- "`IHeroLocationHooks.onSetPos(x, y)`(`data-base/src/hero/types.ts:283`)由 `HeroLocation.setPos` 触发(`data-base/src/hero/location.ts:59-63`);`IObjectMoverHooks.onSetPos(x, y, tile, mover)`(`mover.ts:216`)由 `ObjectMover.setPos` 触发(`mover.ts:522-527`,其中先 `this.tile.setPos(...)` → 亦触发 location 钩子)——两接口共享 `onSetPos` 是本计划拆分钩子类的原因"
|
||||
- "`IObjectMoverHooks.onStepEnd` 触发时序(`mover.ts:659-687`):`onStepStart`(坐标未变)→ 内部 `onStepEnd` 计算 → `tile.setPos(loc)`(触发 location `onSetPos`)→ `onStepSettled` → `onStepEnd` 钩子(坐标已更新)"
|
||||
- "`ObjectMoveType.AnimDir` 步形状 `IObjectMoveAnimDir { type, dir: ObjectAnimDirection }`(`mover.ts:103-108`);`prepareStep` 对 AnimDir 写 `mover.currAnimDir = step.dir`(`mover.ts:513-515`)"
|
||||
@ -51,22 +55,22 @@ must_haves:
|
||||
assumptions:
|
||||
- "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付「04-03 勇士渲染修正」这一个子切片,验收判据取自 04-CONTEXT 的 D-24..D-28 与 `04-HERO-MOVER-INTERFACE.md` 的 `#04-02-R-01..12`。用户须在推进其余渲染适配前补齐 REND-01 的边界判据。"
|
||||
- "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局实现,也不发明其验收阈值。"
|
||||
- "**执行前必须复核(未确定项,不猜):** Task 1 步骤 (0a) 必须在动手前打开当日 HEAD 的基准文件逐条核对:① `IFaceHandler<T extends number>` 七成员、`movement` 返回 `IFaceDescriptor`、`degrade` 对 Unknown 返回 Unknown;② `IObjectMover.faceHandler` 存在且类型为 `IFaceHandler<FaceDirection>`;③ `IHeroLocation.mover` 为 `IHeroMover<this>` 且 `HeroLocation` 由 `IFaceHandler<FaceDirection>` 构造 `HeroMover`;④ 三个弃用 helper 仍导出且签名如记录(「待替换物尚在」的核对,不是要求保留它们);⑤ `@motajs/animate` 导出 `ExcitationCurve2D`、`mutate-animate` 的 `TimingFn<2>` 与 `MovingBlock.moveRelative` 的形参形态;⑥ `IObjectMoveAnimDir.dir`、`prepareStep` 对 AnimDir 的处理、`moveProgress` 的钩子时序。任一不同即**停下汇报并修订本计划**,不得按记忆改代码。"
|
||||
- "**`IFaceHandler` 语义映射(Task 0 陈述、Task 1 注入复核 + 编译佐证;不声称逐字等价):** 当日实际装配链路注入的是 **`Dir8FaceHandler`**(不是文档示例里的 `Dir4FaceHandler`):`core.ts:116` `const dir8 = new Dir8FaceHandler()` → `:143` `new HeroState(this, dir8, heroAttribute)` → `state.ts:55` `new HeroLocation(state, defaultLoc, faceHandler)` → `location.ts:42` `new HeroMover(this, faceHandler)` → `hero.mover.faceHandler`(`mover.ts:251`)。`docs/dev/hero/hero-mover.md:74` 的「本样式中为 `Dir4FaceHandler`」是设计文档里的样式描述,**不是**现行装配事实;一律以 `core.ts` 实际装配为准。对 `hero.ts` 实际用到的三个方法逐一说明(Task 0 须原样汇报差异,**不得**声称等价):① **`movement` 等价**——`getFaceMovement` 与 `Dir8FaceHandler.movement` 均覆盖八方向且 `Unknown→{0,0}`(`faceManager.ts:47-57,99-101` ↔ `utils.ts:8-29`),全输入域一致(`:230` / `:364` / `:365`);② **`degrade` 不等价**——`degradeFace(dir, unknown=Unknown)` 把斜向折叠为正交(`LeftUp/LeftDown→Left`、`RightUp/RightDown→Right`,`utils.ts:41-52`),而 `Dir8FaceHandler.degrade` 是**恒等**(`return dir as FaceDirection`,`faceManager.ts:95-97`);正交与 `Unknown` 一致,**斜向输入**下结果不同(`:132` / `:202` / `:357`);③ **`next` 不等价**——`nextFaceDirection(dir, false, false)` 是 90° 顺时针(`Up→Right`,`utils.ts:135-156`),而 `Dir8FaceHandler.next` 走 `DIR8_CW` 是 **45°** 顺时针(`Up→RightUp`,`faceManager.ts:60-69,113-118`);仅 `turn()` **无参**路径(`hero.ts:451-453`)可达,而仓库内 `turn(...)` 的调用点(`hero.ts:506` / `:511` / `:530`)均显式传参,故该分叉当前**无内部调用者**、属潜性差异(契约上仍可被外部以无参调用触达);④ `:357` 的 `degradeFace(last.nextDirection, FaceDirection.Down)` 在 `Dir8` 恒等语义下,斜向 `last.nextDirection` 不再被折叠、仅 `Unknown` 回退为 `Down`——本计划按 D-24 照常替换并在 Task 0 如实汇报该差异,由用户裁定。"
|
||||
- "**执行前必须复核(未确定项,不猜):** Task 1 步骤 (0a) 必须在动手前打开当日 HEAD 的基准文件逐条核对:① `IFaceHandler<T extends number>` 七成员、`movement` 返回 `IFaceDescriptor`、`degrade` 对 Unknown 返回 Unknown;② `IFaceManager.get<T extends number>(group: number): IFaceHandler<T> | null` 与 `FaceGroup.Dir4` / `Dir8`(`data-common/src/common/faceManager.ts:6-39`),且当日装配把 `Dir4FaceHandler` 注册到 `FaceGroup.Dir4`、`Dir8FaceHandler` 注册到 `FaceGroup.Dir8`(`data-state/src/core.ts:115-120`);③ `Dir4FaceHandler.degrade` 斜向折叠 + Unknown→Unknown、`next` 先 degrade 再 90° 顺时针(`faceManager.ts:169-193`),与弃用 helper 的等价关系成立;④ `IObjectMover.faceHandler` 存在且类型为 `IFaceHandler<FaceDirection>`;⑤ `IHeroLocation.mover` 为 `IHeroMover<this>` 且 `HeroLocation` 由 `IFaceHandler<FaceDirection>` 构造 `HeroMover`;⑥ 三个弃用 helper 仍导出且签名如记录(「待替换物尚在」的核对,不是要求保留它们);⑦ `@motajs/animate` 导出 `ExcitationCurve2D`、`mutate-animate` 的 `TimingFn<2>` 与 `MovingBlock.moveRelative` 的形参形态;⑧ `IObjectMoveAnimDir.dir`、`prepareStep` 对 AnimDir 的处理、`moveProgress` 的钩子时序。任一不同即**停下汇报并修订本计划**,不得按记忆改代码。"
|
||||
- "**语义映射(Task 0 陈述、Task 1 注入复核 + 编译佐证;此为 D-29 修订后的结论,取代先前「注入 `Dir8FaceHandler`、`degrade`/`next` 不等价」的汇报):** 注入的 `IFaceManager` 当日由 `core.ts:115-120` 装配:`const dir4 = new Dir4FaceHandler()` / `const dir8 = new Dir8FaceHandler()`,`faceManager.register(FaceGroup.Dir4, dir4)`(`:117`)与 `faceManager.register(FaceGroup.Dir8, dir8)`(`:119`)。对 `hero.ts` 实际用到的三个方法逐一说明:① **`degrade` 等价**——`Dir4FaceHandler.degrade`(`faceManager.ts:170-172`)与弃用 `degradeFace`(`utils.ts:37-54`)对斜向折叠一致(`LeftUp/LeftDown→Left`、`RightUp/RightDown→Right`)、正交恒等、`Unknown→Unknown`(`:132` / `:202`);`:357` 因 `IFaceHandler.degrade` 无 `unknown` 形参而对 `Unknown` 显式回退 `FaceDirection.Down`,加上回退后等价。② **`movement` 等价**——`hero.mover.faceHandler`(Dir8)的 `movement`(`faceManager.ts:99-101`)与弃用 `getFaceMovement`(`utils.ts:8-29`)均覆盖八方向且 `Unknown→{0,0}`(`faceManager.ts:47-57`),全输入域一致(`:230` / `:364` / `:365`)。③ **`next` 仅正交输入等价**——`Dir4FaceHandler.next`(`faceManager.ts:188-193`)先 `degrade` 再 90° 顺时针,与弃用 `nextFaceDirection(dir, false, false)`(`utils.ts:135-156`)对正交输入一致(`Up→Right`);**斜向输入分叉**:旧 helper 在斜向间 90° 旋转(`LeftUp→RightUp`,`utils.ts:145-146`),`Dir4` 先退化到正向后旋转(`LeftUp→Left→Up`,`faceManager.ts:190-192`)。该分叉仅 `turn()` **无参**路径(`hero.ts:451-453`)可达,而仓库内 `turn(...)` 的调用点(`hero.ts:506` / `:511` / `:530`)均显式传参,故当前**无内部调用者**、属潜性差异;且 `heroEntity.direction` 期望为正交朝向。④ `hero.mover.faceHandler` 当日装配为 `Dir8FaceHandler`(`core.ts:116` → `:143` `new HeroState(this, dir8, ...)` → `state.ts:55` → `location.ts:42`),仅用于 `movement`;`docs/dev/hero/hero-mover.md:74` 的 `Dir4FaceHandler` 样式描述与 D-29「贴图四向」一致,但 `movement` 依据的仍是 `mover.faceHandler`(Dir8)。"
|
||||
- "**运行时 UAT 不可得(诚实降级):** `MapExtensionManager.addHero` 目前无任何调用点(`client.ts:151-155` 被注释),故勇士渲染拓展在运行时并未启用;本计划不接线 `client.ts`。因此本计划的运行结果无法在渲染环境观察,验收依赖「scoped 类型门禁 + 契约 / 静态门禁 + 人工代码复核」,运行时渲染 UAT 顺延到启用接线的增量。"
|
||||
- "**数据端实现可能被用户并发修改(D-05):** 接口签名视为稳定,实现可变;本计划只读引用数据端接口,不修改任何数据端文件。若执行当日数据端接口签名与规划基线不一致,按 Task 1 步骤 (0a) 停下汇报,不按旧形状适配。"
|
||||
- "**成员命名需用户同意:** 本计划引入 / 删除的渲染端成员名(两个钩子类 `MapHeroLocationHook` / `MapHeroMoverHook`、`MapHeroRenderer` 的新构造器参数 / 字段 `faceHandler`、被删除的类名 `MapHeroHook`,以及 `onSetPos` 与 D-18/D-22 多余方法的归属)须在 Task 0 汇报中获得用户同意后才可落地(AGENTS.md「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」);用户未同意即改名为用户指定的写法,或停下修订计划。"
|
||||
- "**成员命名需用户同意:** 本计划引入 / 删除的渲染端成员名(两个钩子类 `MapHeroLocationHook` / `MapHeroMoverHook`、`MapHeroRenderer` 的新构造器参数 / 字段 `faceManager: IFaceManager`、由 `faceManager.get<FaceDirection>(FaceGroup.Dir4)` 派生的四向处理器字段(拟名 `dir4`)、被删除的类名 `MapHeroHook`,以及 `onSetPos` 与 D-18/D-22 多余方法的归属,外加 `IMapExtensionManager.addHero` 的新形参名 `faceManager`)须在 Task 0 汇报中获得用户同意后才可落地(AGENTS.md「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」);用户未同意即改名为用户指定的写法,或停下修订计划。"
|
||||
prohibitions:
|
||||
- "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理(AGENTS.md 的第一个 block 点约束;不因触发方式或 `yolo` / `auto_advance` 模式而放宽)。"
|
||||
- "不得修改任何数据端文件(`packages-user/data-common` / `data-base` / `data-system` / `data-state`),也不得新增数据端公共接口;本计划只从数据端 import 现有类型并读取 `state.mover.faceHandler`。"
|
||||
- "不得改动 `packages-user/client-modules/src/render/map/extension/types.ts`(本计划的构造器参数不在任何接口里,无需改契约面);若编译链强制要求触碰它,必须停下汇报并修订本计划(不得自行扩面)。"
|
||||
- "不得修改任何数据端文件(`packages-user/data-common` / `data-base` / `data-system` / `data-state`),也不得新增数据端公共接口;本计划只从数据端 import 现有类型 / 枚举(`IFaceHandler` / `IFaceManager` / `FaceGroup` 等)并读取 `hero.mover.faceHandler` 与经 `addHero` 传入的 `IFaceManager`。"
|
||||
- "不得改动 `types.ts` 中除 `IMapExtensionManager.addHero` 签名(新增 `faceManager: IFaceManager` 形参,D-29 已批准)以外的任何内容:不得增删 / 改写其它接口成员与 jsDoc,不得触碰 `IMapHeroRenderer`;若编译链要求更多契约面改动,必须停下汇报并修订本计划(不得自行扩面)。"
|
||||
- "不得改动 `packages-user/client-modules/src/action/move.ts` 及任何移动控制 API(D-19:`HeroKeyMover` / `IMoveController` 队列 / `oneStep` 由用户手动处理;`#04-02-G-02` 不在本计划)。"
|
||||
- "不得删除 / 替换 / 阻塞 `hero.ts` 的存量 `state` 使用与 `import { state } from '@user/data-state'`(D-28)。"
|
||||
- "不得改写贴图 / 不透明度相关代码的语义:`setImage` / `setAlpha` / `hero.image`(贴图别名读取)/ `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 一律保留(D-18)。"
|
||||
- "不得适配跟随者:`IHeroFollower` / `IHeroFollowersController` / 跟随者钩子与渲染方法(`addFollower` / `removeFollower` / `removeAllFollowers` / `setFollowerAlpha` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`)保持原样(D-22 暂缓)。"
|
||||
- "不得把 `mutate-animate` → `@motajs/animate` 的迁移扩散到 `hero.ts` 之外(D-27 范围;`moving.ts` / `maps/types.ts` / `components/**` / `legacy-ui` 等的存量使用一律不动)。"
|
||||
- "不得引入 `core.*` / `client.*` / legacy 全局适配(D-12);不得处理 `getHeroStatusOn` 类裸函数(D-11)。"
|
||||
- "不得新增任何渲染端公共接口成员(`IMapHeroRenderer` / `IMapExtensionManager` 的成员集合只减不增,且本计划不改 `types.ts`);不得让渲染端出现允许外部驱动渲染状态的新方法。"
|
||||
- "不得新增 / 删除任何渲染端公共接口成员(`IMapHeroRenderer` / `IMapExtensionManager` 的成员集合不变;`types.ts` 本次只改 `addHero` 的形参,不增删成员);不得让渲染端出现允许外部驱动渲染状态的新方法。"
|
||||
- "不得修改任何既有 jsDoc 注释的内容;被 D-25 拆分 / 重命名所涉及的方法与其配套 jsDoc 属「移动 / 删除代码」而非「改写注释」,须在 Task 0 汇报中经用户确认。"
|
||||
- "不得改动 `files_modified` 之外的源码;不得回滚 / 暂存 / 提交用户并发的未提交改动。"
|
||||
- "不得新增依赖、不得修改 `package.json` / `pnpm-lock.yaml`(`@motajs/animate` 已是 `client-modules` 的 workspace 依赖,`packages-user/client-modules/package.json:4`);出现安装需求即暂停并要求用户确认包合法性。"
|
||||
@ -74,10 +78,10 @@ must_haves:
|
||||
---
|
||||
|
||||
<objective>
|
||||
Phase 4 第四个增量 —— **勇士渲染修正:04-03 人工审查反馈落地**。按用户对 04-03 产出的裁定 D-24..D-28,修正 `render/map/extension/hero.ts`(以及注入所需的 `.../manager.ts`)里的五处问题。**本计划是代码改动计划**,改动面严格限定为两个文件。
|
||||
Phase 4 第四个增量 —— **勇士渲染修正:04-03 人工审查反馈落地**。按用户对 04-03 产出的裁定 D-24..D-28,以及用户对 D-24 注入方式的修订 D-29,修正 `render/map/extension/hero.ts`、注入链所需的 `.../manager.ts` 与契约签名所需的 `.../types.ts` 里的问题。**本计划是代码改动计划**,改动面严格限定为三个文件。
|
||||
|
||||
**要解决的问题(五条,全部来自用户对 04-03 的人工审查):**
|
||||
1. **D-24 仍在用弃用工具**:`hero.ts` 使用 `@user/data-common` 的已弃用 `getFaceMovement` / `degradeFace` / `nextFaceDirection`(import `:2`/`:4`/`:7`,使用 `:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453`);数据端已提供 `IFaceHandler`(`packages/common/src/utils/types.ts:152`)。
|
||||
1. **D-24 / D-29 仍在用弃用工具**:`hero.ts` 使用 `@user/data-common` 的已弃用 `getFaceMovement` / `degradeFace` / `nextFaceDirection`(import `:2`/`:4`/`:7`,使用 `:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453`);数据端已提供 `IFaceHandler`(`packages/common/src/utils/types.ts:152`)与 `IFaceManager`(`data-common/src/common/faceManager.ts:13`)。用户 D-29 裁定:**贴图只有四向**,`degrade` / `next` 必须走四向处理器(`Dir4FaceHandler`,经 `faceManager.get(FaceGroup.Dir4)`),`movement` 仍走勇士自身的八向 `hero.mover.faceHandler`;因构造器参数已多,**直接注入 `IFaceManager`**,并经**注入路径 B** 给 `addHero` 新增 `faceManager` 形参。
|
||||
2. **D-25 钩子重复调用 + 陈旧守卫**:`IHeroLocationHooks` 与 `IObjectMoverHooks` 共享 `onSetPos`,单个 `MapHeroHook` 同时注册到两个 hookable 会重复触发;且 `onSetPos` 带 `if (mover.moving) return` 守卫,使渲染在移动中不跟随数据端终点位置(渲染不再客观描述数据端状态)。
|
||||
3. **D-26 AnimDir 未设**:`onStepEnd` 的 `ObjectMoveType.AnimDir` 分支是 no-op,未按步的 `dir` 设置动画播放方向。
|
||||
4. **D-27 用了 `mutate-animate`**:`hero.ts:19` import `TimingFn`,`generateJumpFn`(`:255`)返回 `TimingFn<2>`。
|
||||
@ -87,26 +91,28 @@ Phase 4 第四个增量 —— **勇士渲染修正:04-03 人工审查反馈
|
||||
|
||||
| 旧调用(hero.ts) | 替换 | 说明 |
|
||||
|---|---|---|
|
||||
| `degradeFace(dir)` `:132` / `:202` | `this.faceHandler.degrade(dir)` | 对 `Unknown` 与正交输入一致;**斜向输入不等价**——旧 helper 折叠为正交,注入的 `Dir8FaceHandler.degrade` 恒等(`faceManager.ts:95-97` vs `utils.ts:41-52`) |
|
||||
| `degradeFace(dir, FaceDirection.Down)` `:357` | `this.faceHandler.degrade(dir)`,随后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果` | `IFaceHandler.degrade` 无 `unknown` 形参,Down 回退须显式保留;`Unknown→Down` 与旧 helper 等价,但斜向输入下 `Dir8` 恒等(旧 helper 会折叠),**非逐字等价** |
|
||||
| `getFaceMovement(dir)` `:230` / `:364` / `:365` | `this.faceHandler.movement(dir)` | 返回 `IFaceDescriptor { x, y }`,解构用法不变;全输入域等价 |
|
||||
| `nextFaceDirection(dir)` `:453` | `this.faceHandler.next(dir)` | 均为顺时针,但步长不同:旧 helper(`face8=false`)为 90°(`Up→Right`),注入的 `Dir8FaceHandler.next` 为 45°(`Up→RightUp`);仅 `turn()` 无参路径可达,仓库内无内部调用者 |
|
||||
| `degradeFace(dir)` `:132` / `:202` | `this.dir4.degrade(dir)` | **等价**——`Dir4FaceHandler.degrade`(`faceManager.ts:170-172`)与旧 helper(`utils.ts:37-54`)斜向折叠相同(`LeftUp/LeftDown→Left`、`RightUp/RightDown→Right`)、正交恒等、`Unknown→Unknown` |
|
||||
| `degradeFace(dir, FaceDirection.Down)` `:357` | `this.dir4.degrade(dir)`,随后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果` | `IFaceHandler.degrade` 无 `unknown` 形参,Down 回退须显式保留;加上回退后与旧 helper 的 `unknown` 形参**等价**(`Dir4FaceHandler.degrade` 同样折叠斜向) |
|
||||
| `getFaceMovement(dir)` `:230` / `:364` / `:365` | `this.hero.mover.faceHandler.movement(dir)` | 返回 `IFaceDescriptor { x, y }`,解构用法不变;**全输入域等价**(勇士自身 `mover.faceHandler` 为 Dir8) |
|
||||
| `nextFaceDirection(dir)` `:453` | `this.dir4.next(dir)` | 对正交输入**等价**(先 degrade 再 90° 顺时针,`Up→Right`);**斜向输入分叉**——旧 helper 在斜向间 90° 旋转(`LeftUp→RightUp`),`Dir4` 先退化再旋转(`LeftUp→Left→Up`)。仅 `turn()` 无参路径可达,仓库内无内部调用者,且 `heroEntity.direction` 期望为正交朝向 |
|
||||
|
||||
- **D-24 注入**:`MapHeroRenderer` 构造器新增 `IFaceHandler<FaceDirection>` 参数(拟名 `faceHandler`);`manager.ts` 的 `new MapHeroRenderer(...)` 由 `state.mover.faceHandler` 注入(`IObjectMover.faceHandler`,`data-common/src/common/mover.ts:251`;`state` 是 `addHero` 的 `IHeroLocation` 形参,不是全局单例,符合 D-23)。
|
||||
(表中 `this.dir4` 为构造体内由 `faceManager.get<FaceDirection>(FaceGroup.Dir4)` 派生的四向处理器字段,字段名待 Task 0 用户批准。)
|
||||
|
||||
- **D-24 / D-29 注入**:`MapHeroRenderer` 构造器新增第 4 个参数 `faceManager: IFaceManager`(拟名 `faceManager`,保存为同名字段),并在构造体内派生四向处理器:`const dir4 = faceManager.get<FaceDirection>(FaceGroup.Dir4)!;`(字段名待 Task 0 批准;`!` 为已核实的 lint-clean 处理,见 assumptions)。`IMapExtensionManager.addHero` 新增 `faceManager: IFaceManager` 形参(**接口变更,D-29 已批准**,Task 0 单独列出待用户确认);`manager.ts` 的 `addHero(state, layer, faceManager)` 把该形参转发给 `new MapHeroRenderer(this.renderer, layer, state, faceManager)`(**注入路径 B**;未来接线处传 `this.faceManager`,本计划不接线)。不取全局 `state` / `client` 单例(D-23)。
|
||||
- **D-25 拆分**:把 `MapHeroHook` 拆成 `MapHeroLocationHook`(`Partial<IHeroLocationHooks>`;实现 `onSetPos` **且只实现一次**,另承载 D-18/D-22 的非接口多余方法)与 `MapHeroMoverHook`(`Partial<IObjectMoverHooks<IHeroLocation>>`;实现 `onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir`,**不**实现 `onSetPos`);`hero.addHook(...)` / `hero.mover.addHook(...)` 分别注册,各持 `locationController` / `moverController` 字段;`onSetPos(x, y)` 无条件 `this.hero.setPosition(x, y)`(删除 `mover.moving` 守卫)。
|
||||
- **D-26**:`onStepEnd` 的 `case ObjectMoveType.AnimDir:` 改为 `this.hero.setHeroAnimateDirection(step.dir); break;`(开头既有的 `mover.currAnimDir` 设置保留)。
|
||||
- **D-27**:`hero.ts` 的 `import { TimingFn } from 'mutate-animate';` 改为 `import { ExcitationCurve2D } from '@motajs/animate';`,`generateJumpFn` 返回类型改为 `ExcitationCurve2D`。
|
||||
- **D-28**:`import { state } from '@user/data-state';` 与三处 `state.roleFace.getFaceOf(...)` 一字不动。
|
||||
- **契约面**:`types.ts` **不改**(新参数只在构造器里,不进任何接口);无新增公共接口成员。
|
||||
- **契约面**:`types.ts` 仅改 `IMapExtensionManager.addHero` 的签名(新增 `faceManager: IFaceManager` 形参,D-29);`IMapHeroRenderer` 与其它成员集合不变,无新增 / 删除成员;`addHero` 既有 jsDoc 默认逐字保留(是否新增 `@param` 行由用户在 Task 0 裁定)。
|
||||
- **范围硬边界**:贴图 / 不透明度(D-18)、跟随者(D-22)、`action/move.ts` 与移动控制(D-19)、`core.*`(D-12)、裸函数(D-11)、D-27 的其余存量迁移一律不动。
|
||||
|
||||
**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「要解决的问题 / 大致解决方案(含逐调用替换映射表)/ 拟新增·删除·改写的成员命名清单 / `onSetPos` 与 D-18·D-22 多余方法的归属 / Down 回退处理 / 范围边界与禁令 / 验证方式的诚实降级」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式或配置模式如何均不得自动放行。故本计划 `autonomous: false`。
|
||||
**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「要解决的问题 / 大致解决方案(含逐调用替换映射表与修正后的语义映射)/ **`IMapExtensionManager.addHero` 的接口签名变更** / 拟新增·删除·改写的成员命名清单 / `onSetPos` 与 D-18·D-22 多余方法的归属 / Down 回退处理 / `addHero` 的 jsDoc 处理 / 范围边界与禁令 / 验证方式的诚实降级」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式或配置模式如何均不得自动放行。故本计划 `autonomous: false`。
|
||||
|
||||
**显式延后(本计划不涉及、不实施):** `client.ts` 的 `addHero` 接线启用、贴图与不透明度来源(D-18)、跟随者位置 / 动画归属(D-22)、移动控制(D-19)、D-27 范围外的 `mutate-animate` 存量迁移、其余 ① 错配与 ③ 多余旧路径、移动端 / 桌面端双布局(REND-02)。
|
||||
|
||||
Purpose: 04-03 让勇士渲染在编译与契约上对齐了数据端,但仍留着「弃用 helper、钩子重复触发 + 陈旧守卫、AnimDir no-op、`mutate-animate` 依赖」四处实质问题。本计划把它们一次修平,使后续渲染适配与双布局建立在一个语义正确、依赖一致的地基上。
|
||||
|
||||
Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可复跑的 scoped 类型门禁、静态门禁(D-24/D-25/D-26/D-27)、D-18/D-22 与 D-28 保留门禁、并发改动基线门禁与 CRLF 门禁结果 + `.planning/phases/04-render-adaptation/04-04-SUMMARY.md`。Task 0 为只读汇报关卡,不写任何文件。
|
||||
Output: `hero.ts` / `types.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可复跑的 scoped 类型门禁、静态门禁(D-24/D-25/D-26/D-27/D-29)、D-18/D-22 与 D-28 保留门禁、基线感知范围门禁、并发改动基线门禁与 CRLF 门禁结果 + `.planning/phases/04-render-adaptation/04-04-SUMMARY.md`。Task 0 为只读汇报关卡,不写任何文件。
|
||||
|
||||
## Source Audit(多源覆盖审计)
|
||||
|
||||
@ -116,14 +122,15 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
| REQ | REND-01 | 渲染端适配新数据层接口 | 04-04 | COVERED(子切片;需求保持 Pending) | D-24 把勇士渲染从弃用 helper 改到 `IFaceHandler`(新数据层接口),D-25/D-26 修正钩子语义;验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
|
||||
| REQ | REND-02 | 渲染端同时支持移动端与桌面端布局 | 04-04 | COVERED(不涉及) | 本 run 范围不含双布局;REND-02 验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
|
||||
| RESEARCH | — | (本阶段无 RESEARCH.md) | — | N/A | Phase 4 未产出 RESEARCH.md;等价输入为 04-01 / 04-02 账本与 04-03 产出 |
|
||||
| CONTEXT | D-24 | 弃用 helper 全改 `IFaceHandler`,构造器注入 | 04-04 | COVERED | Task 1(tracer):注入 + 7 处替换 + Down 回退显式保留 |
|
||||
| CONTEXT | D-24 | 弃用 helper 全改 `IFaceHandler`(经 `IFaceManager` 构造器注入) | 04-04 | COVERED | Task 1(tracer):`degrade` / `next` 走 `Dir4FaceHandler`(经 `IFaceManager`)、`movement` 走 `hero.mover.faceHandler`(Dir8);7 处替换 + Down 回退显式保留 |
|
||||
| CONTEXT | D-29 | `degrade` / `next` 用 `Dir4FaceHandler`(贴图四向),构造器改注入 `IFaceManager`,注入路径 B 改 `addHero` 签名 | 04-04 | COVERED | Task 1:`faceManager.get<FaceDirection>(FaceGroup.Dir4)` 派生四向处理器;`types.ts` / `manager.ts` 的 `addHero` 增 `faceManager` 形参并转发 |
|
||||
| CONTEXT | D-25 | 钩子实现拆两类,`onSetPos` 直接设置 | 04-04 | COVERED | Task 2:`MapHeroLocationHook` / `MapHeroMoverHook` 分别注册;`onSetPos` 无条件 |
|
||||
| CONTEXT | D-26 | 补上 `AnimDir` 设置 | 04-04 | COVERED | Task 2:AnimDir 分支按 `step.dir` 设置动画方向 |
|
||||
| CONTEXT | D-27 | 不用 `mutate-animate`,改 `@motajs/animate`(仅 hero.ts) | 04-04 | COVERED | Task 3:`TimingFn<2>` → `ExcitationCurve2D`;其余存量使用不动 |
|
||||
| CONTEXT | D-28 | 存量 `state` 使用暂不处理 | 04-04 | COVERED(保留) | `state.roleFace.getFaceOf` 三处与 import 逐字保留;设保留门禁 |
|
||||
| CONTEXT | D-18 | `HeroRendering` 暂不处理(贴图 / 不透明度) | 04-04 | COVERED(排除 + 保留门禁) | 贴图 / 不透明度成员逐字保留;不重设来源 |
|
||||
| CONTEXT | D-19 | 移动控制由用户手动修改 | 04-04 | COVERED(排除) | `action/move.ts` 与移动控制 API 不在 `files_modified` |
|
||||
| CONTEXT | D-20 | 勇士渲染完全被动 | 04-04 | COVERED(沿用) | 沿用 04-03 的被动契约;本计划不新增任何公共接口成员 |
|
||||
| CONTEXT | D-20 | 勇士渲染完全被动 | 04-04 | COVERED(沿用) | 沿用 04-03 的被动契约;本计划不新增 / 删除任何公共接口成员(`addHero` 仅增一个转发形参) |
|
||||
| CONTEXT | D-21 | 走现有 `IObjectMoverHooks` / `IHeroLocationHooks`,不新增数据端接口 | 04-04 | COVERED(沿用) | 两个钩子类仍只注册到现有 hookable,不新增数据端接口 |
|
||||
| CONTEXT | D-22 | 跟随者暂缓 | 04-04 | COVERED(排除 + 保留门禁) | 跟随者族成员与钩子方法逐字保留 |
|
||||
| CONTEXT | D-23 | 不使用 `client` / `state` 全局单例(仅约束新增 / 后续) | 04-04 | COVERED | 新增代码只经构造器参数取 `faceHandler`,不取全局单例;存量 `state` 由 D-28 保留 |
|
||||
@ -162,7 +169,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:decision" gate="blocking-human">
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报 D-24..D-28 的解决方案、逐调用替换映射表、成员命名清单与 `onSetPos` / D-18·D-22 方法归属,等待用户回复「可以执行」</name>
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报 D-24..D-29 的解决方案(含 `IFaceManager` 注入与 `addHero` 签名变更)、逐调用替换映射表、成员命名清单与 `onSetPos` / D-18·D-22 方法归属,等待用户回复「可以执行」</name>
|
||||
<precondition>用户对 04-03 的人工审查裁定 D-24..D-28 已写入 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(以文件内实际存在 D-24..D-28 为准)</precondition>
|
||||
<files>(只读汇报,不修改任何文件)</files>
|
||||
<read_first>
|
||||
@ -173,7 +180,9 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
- packages-user/client-modules/src/render/map/extension/hero.ts(现状 import `:1-21`、`addHeroMoving` `:110-140`、`setImage` `:199-206`、`moveEntity` `:225-248`、`generateJumpFn` `:255`、`addFollower` `:351-386`、`turn` `:451-460`、`MapHeroHook` `:468-552`)
|
||||
- packages-user/client-modules/src/render/map/extension/manager.ts:24-32(`addHero` 的 `new MapHeroRenderer(this.renderer, layer, state)`)
|
||||
- packages/common/src/utils/types.ts:145-197(`IFaceDescriptor` / `IFaceHandler` 七成员)
|
||||
- packages-user/data-common/src/common/utils.ts:1-64(三个弃用 helper 的现行签名,尤以 `degradeFace` 的 `unknown` 默认为 Unknown、`nextFaceDirection` 的 `face8=false` 默认)
|
||||
- packages-user/data-common/src/common/utils.ts:1-64(三个弃用 helper 的现行签名,尤以 `degradeFace` 的 `unknown` 默认为 Unknown、`nextFaceDirection` 的 `face8=false` 默认与斜向旋转台阶)
|
||||
- packages-user/data-common/src/common/faceManager.ts:6-39,94-202(`FaceGroup` / `IFaceManager.get`;`Dir4FaceHandler.degrade` 斜向折叠 + `next` 90° 顺时针,对照 `Dir8FaceHandler`)
|
||||
- packages-user/data-state/src/core.ts:112-144(`Dir4FaceHandler` 注册到 `FaceGroup.Dir4`、`Dir8FaceHandler` 注册到 `FaceGroup.Dir8`;`dir8` 注入 `HeroState`)
|
||||
- packages-user/data-common/src/common/mover.ts:103-108,216,235-251,513-527,646-687(`IObjectMoveAnimDir` / `IObjectMoverHooks` / `IObjectMover.faceHandler` / `ObjectMover.setPos` / `moveProgress` 时序)
|
||||
- packages-user/data-base/src/hero/types.ts:277-321,399-417(`IHeroLocationHooks` / `IHeroLocation` / `IHeroMover`)
|
||||
- packages/animate/src/types.ts:7-12 与 node_modules/mutate-animate/index.d.ts:12-14(`ExcitationCurve2D` vs `TimingFn<2>`)
|
||||
@ -182,20 +191,21 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
**停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
|
||||
|
||||
① **要解决的问题(来自用户对 04-03 的人工审查,五条)**:
|
||||
- D-24:`hero.ts` 仍在用已弃用 helper `getFaceMovement` / `degradeFace` / `nextFaceDirection`(import `:2`/`:4`/`:7`,使用 `:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453`),数据端现行接口是 `IFaceHandler`。
|
||||
- D-24:`hero.ts` 仍在用已弃用 helper `getFaceMovement` / `degradeFace` / `nextFaceDirection`(import `:2`/`:4`/`:7`,使用 `:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453`),数据端现行接口是 `IFaceHandler` / `IFaceManager`。用户随后以 **D-29** 修订注入方式:**贴图只有四向**,`degrade` / `next` 必须用四向处理器(`Dir4FaceHandler`,经 `faceManager.get(FaceGroup.Dir4)`),`movement` 仍用勇士自身 `mover.faceHandler`(Dir8);因构造器参数已多,**直接注入 `IFaceManager`**,并经**注入路径 B** 给 `addHero` 新增 `faceManager` 形参(改 `types.ts` 接口)。
|
||||
- D-25:`IHeroLocationHooks` 与 `IObjectMoverHooks` 共享 `onSetPos`,单个 `MapHeroHook` 同注册到两个 hookable 会**重复触发**;且 `onSetPos` 的 `if (mover.moving) return` 守卫让渲染在移动中不跟随数据端终点位置(违背「渲染客观描述数据端」)。
|
||||
- D-26:`onStepEnd` 的 `ObjectMoveType.AnimDir` 分支是 no-op,不按步的 `dir` 设置动画播放方向。
|
||||
- D-27:`hero.ts:19` import `mutate-animate` 的 `TimingFn`,`generateJumpFn`(`:255`)返回 `TimingFn<2>`。
|
||||
- D-28:`hero.ts` 的 `state.roleFace.getFaceOf(...)`(`:235`/`:358`/`:417`)是存量单例使用——本计划**保留**(D-23 只约束新增代码)。
|
||||
|
||||
② **大致解决方案(两个文件)+ 逐调用替换映射表(须逐行汇报)**:
|
||||
- `manager.ts`:`new MapHeroRenderer(this.renderer, layer, state, state.mover.faceHandler)`(第 4 实参为新增)。
|
||||
- `hero.ts`:构造器新增 `IFaceHandler<FaceDirection>` 参数并保存为字段;映射表:
|
||||
`degradeFace(dir)` `:132`/`:202` → `faceHandler.degrade(dir)`(对 `Unknown` 与正交输入一致;斜向输入不等价——旧 helper 折叠为正交,`Dir8FaceHandler.degrade` 恒等);
|
||||
`degradeFace(dir, FaceDirection.Down)` `:357` → `faceHandler.degrade(dir)` 后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果`(Down 回退显式保留;`Unknown→Down` 等价,斜向输入下 `Dir8` 恒等故不等价);
|
||||
`getFaceMovement(dir)` `:230`/`:364`/`:365` → `faceHandler.movement(dir)`(`IFaceDescriptor { x, y }`;全输入域等价);
|
||||
`nextFaceDirection(dir)` `:453` → `faceHandler.next(dir)`(均为顺时针,但旧 helper 为 90°、`Dir8FaceHandler.next` 为 45°;仅 `turn()` 无参路径可达,仓库内无内部调用者);随后删除三个弃用 import。
|
||||
**须逐条汇报语义映射(不声称等价):** ① 注入的处理器是 **`Dir8FaceHandler`**(装配链 `core.ts:116` `dir8` → `:143` `new HeroState(this, dir8, ...)` → `state.ts:55` → `location.ts:42` → `hero.mover.faceHandler`);`docs/dev/hero/hero-mover.md:74` 的 `Dir4FaceHandler` 只是文档里的样式描述,不是现行装配,**不作为等价依据**。② `movement` 全输入域等价(八方向 + `Unknown→{0,0}`)。③ `degrade` 不等价:`Dir8FaceHandler.degrade` 是恒等(`faceManager.ts:95-97`),旧 `degradeFace` 折叠斜向(`utils.ts:41-52`)——正交与 `Unknown` 一致,斜向不同。④ `next` 不等价:`Dir8FaceHandler.next` 走 `DIR8_CW` 为 45°(`Up→RightUp`,`faceManager.ts:60-69,113-118`),旧 `nextFaceDirection(face8=false)` 为 90°(`Up→Right`,`utils.ts:135-156`)。⑤ 以上差异按 D-24 照常替换,须由用户在关卡上确认接受、或指定补偿方式。
|
||||
② **大致解决方案(三个文件)+ 逐调用替换映射表(须逐行汇报)**:
|
||||
- `types.ts`:`IMapExtensionManager.addHero` 新增 `faceManager: IFaceManager` 形参(**接口签名变更,D-29 批准,须用户在本关卡明确确认**);`addHero` 既有 jsDoc 默认逐字保留,是否新增 `@param faceManager` 行请用户裁定。
|
||||
- `manager.ts`:`addHero(state, layer, faceManager)` 把该形参转发给 `new MapHeroRenderer(this.renderer, layer, state, faceManager)`(第 4 实参为新增;**注入路径 B**,未来接线处传 `this.faceManager`,本计划不接线)。
|
||||
- `hero.ts`:构造器新增 `IFaceManager` 参数(拟名 `faceManager`)并保存为字段,构造体内派生四向处理器 `const dir4 = faceManager.get<FaceDirection>(FaceGroup.Dir4)!;`(字段名待批准);映射表:
|
||||
`degradeFace(dir)` `:132`/`:202` → `dir4.degrade(dir)`(**等价**:四向处理器折叠斜向、正交恒等、`Unknown→Unknown`);
|
||||
`degradeFace(dir, FaceDirection.Down)` `:357` → `dir4.degrade(dir)` 后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果`(Down 回退显式保留;加上回退后与旧 helper 的 `unknown` 形参等价);
|
||||
`getFaceMovement(dir)` `:230`/`:364`/`:365` → `hero.mover.faceHandler.movement(dir)`(`IFaceDescriptor { x, y }`;八方向,全输入域等价);
|
||||
`nextFaceDirection(dir)` `:453` → `dir4.next(dir)`(对正交输入等价:先 degrade 再 90° 顺时针;斜向输入分叉见下);随后删除三个弃用 import。
|
||||
**须逐条汇报修正后的语义映射:** ① 注入的 `IFaceManager` 当日装配为 `Dir4FaceHandler` → `FaceGroup.Dir4`、`Dir8FaceHandler` → `FaceGroup.Dir8`(`core.ts:115-120`)。② **`degrade` 等价**——`Dir4FaceHandler.degrade`(`faceManager.ts:170-172`)与旧 `degradeFace`(`utils.ts:37-54`)斜向折叠一致、正交恒等、`Unknown→Unknown`。③ **`movement` 等价**——`hero.mover.faceHandler` 为 `Dir8FaceHandler`(`core.ts:116` → `:143`),其 `movement` 与旧 `getFaceMovement` 全输入域一致。④ **`next` 仅正交等价**——`Dir4FaceHandler.next` 与旧 `nextFaceDirection(face8=false)` 对正交输入均为 90° 顺时针(`Up→Right`);**斜向分叉**:旧 helper 在斜向间旋转(`LeftUp→RightUp`),`Dir4` 先退化再旋转(`LeftUp→Left→Up`);仅 `turn()` 无参路径可达,仓库内无内部调用者,`heroEntity.direction` 期望正交。⑤ 以上映射为修正后的结论(取代先前「注入 `Dir8FaceHandler`、`degrade`/`next` 不等价」的汇报),请用户确认。
|
||||
- D-25:把 `MapHeroHook` 拆成 `MapHeroLocationHook`(`Partial<IHeroLocationHooks>`)与 `MapHeroMoverHook`(`Partial<IObjectMoverHooks<IHeroLocation>>`);前者实现 `onSetPos(x, y)`(无条件 `this.hero.setPosition(x, y)`,**无 `moving` 守卫**)并承载 D-18/D-22 的非接口多余方法;后者实现 `onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir`,**刻意不实现 `onSetPos`**(location 触发面是超集:`mover.setPos` 先 `tile.setPos`,见 `mover.ts:522-527`),从而同一次定位只触发一次;构造体内分别 `hero.addHook(new MapHeroLocationHook(this))` / `hero.mover.addHook(new MapHeroMoverHook(this))` 并各自 `load()`,`destroy()` 各自 `unload()`。
|
||||
- D-26:`MapHeroMoverHook.onStepEnd` 的 AnimDir 分支改为 `this.hero.setHeroAnimateDirection(step.dir); break;`(开头既有的 `mover.currAnimDir` 设置保留)。
|
||||
- D-27:`import { ExcitationCurve2D } from '@motajs/animate';` 取代 `import { TimingFn } from 'mutate-animate';`,`generateJumpFn` 返回类型改 `ExcitationCurve2D`;二者结构等价(`(progress: number) => [number, number]`),`moveRelative(curve: TimingFn<2>, ...)` 仍可接收;仅限 `hero.ts`。
|
||||
@ -203,32 +213,33 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
|
||||
③ **成员命名清单(须用户同意,AGENTS.md 命名规则)**:
|
||||
- **新增**内部类 `MapHeroLocationHook`(实现 `Partial<IHeroLocationHooks>`)与 `MapHeroMoverHook`(实现 `Partial<IObjectMoverHooks<IHeroLocation>>`);**删除**内部类名 `MapHeroHook`(其逻辑分散到上述两类)。
|
||||
- **新增** `MapHeroRenderer` 构造器参数 / 只读字段 `faceHandler`(类型 `IFaceHandler<FaceDirection>`);备选名请用户当场指定(如 `face` / `directionHandler`,但 `face` 与数据端语义易混,推荐 `faceHandler`,与 `IObjectMover.faceHandler` 同名)。
|
||||
- **新增** `MapHeroRenderer` 构造器参数 / 只读字段 `faceManager`(类型 `IFaceManager`,与 `IMapExtensionManager.addHero` 的新形参同名);**新增**由 `faceManager.get<FaceDirection>(FaceGroup.Dir4)` 派生的四向处理器字段(拟名 `dir4`,类型 `IFaceHandler<FaceDirection>`;备选名 `dir4Handler` / `faceHandler4`);以及 `IMapExtensionManager.addHero` 的新形参名 `faceManager`。备选名请用户当场指定。
|
||||
- **`!` 非空断言处理**:`faceManager.get<FaceDirection>(FaceGroup.Dir4)!`——本项目 eslint 未启用 `@typescript-eslint/no-non-null-assertion`(仅启用 `no-extra-non-null-assertion` / `no-non-null-asserted-optional-chain`,已核实),故 `!` 为 lint-clean;且 `FaceManager` 当日必注册 `FaceGroup.Dir4`,不引入自造 logger 码。若用户更倾向 `logger` 守卫,请在回复中指定。
|
||||
- **重新使用**(04-03 已获批,本计划不变)`locationController` / `moverController` 两个 `IHookController` 字段。
|
||||
- **方法归属**:`onSetPos` 归 `MapHeroLocationHook` 且只出现一次;`onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir` 归 `MapHeroMoverHook`;D-18/D-22 的多余方法 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` 归 `MapHeroLocationHook`(它们不属于任何现有钩子接口,也不注册到任何数据端对象,仅逐字保留)——请用户确认该归属,或指定其它归属(如全部放 `MapHeroMoverHook`,或维持原类另立第三容器)。
|
||||
- 名字均为**内部类 / 内部字段**,不进任何公共接口;`IMapHeroRenderer` / `IMapExtensionManager` 成员集合不变。
|
||||
|
||||
④ **Down 回退处理(D-24 唯一需裁定的细节)**:`:357` 的 `degradeFace(last.nextDirection, FaceDirection.Down)` 要求 Unknown 回退为 Down,而 `IFaceHandler.degrade` 对 Unknown 返回 Unknown。本计划主张在调用点显式写 `degraded === FaceDirection.Unknown ? FaceDirection.Down : degraded`(最贴近旧语义、不引入新接口)。请用户确认,或指定其它处理(如抽一个渲染端私有小函数)。
|
||||
④ **Down 回退处理(D-24 细节)**:`:357` 的 `degradeFace(last.nextDirection, FaceDirection.Down)` 要求 Unknown 回退为 Down,而 `IFaceHandler.degrade` 对 Unknown 返回 Unknown。本计划主张在调用点显式写 `degraded === FaceDirection.Unknown ? FaceDirection.Down : degraded`;由于 `Dir4FaceHandler.degrade` 与旧 helper 的斜向折叠一致,加上该回退后**与旧语义等价**。请用户确认,或指定其它处理(如抽一个渲染端私有小函数)。
|
||||
|
||||
⑤ **范围边界与禁令**:只改 `hero.ts` 与 `manager.ts`;**不改 `types.ts`**(新参数只在构造器里,契约面无需变动);不新增数据端公共接口、不改任何数据端文件;不动 `action/move.ts` 与移动控制(D-19);不动贴图 / 不透明度(D-18)、跟随者(D-22)、`core.*`(D-12)、裸函数(D-11);D-27 不扩散到 `hero.ts` 之外;不删除存量 `state`(D-28);不接线 `client.ts`;不回滚 / 不暂存 / 不提交用户并发未提交改动。
|
||||
⑤ **范围边界与禁令**:只改 `hero.ts`、`types.ts`、`manager.ts`;`types.ts` **仅**改 `IMapExtensionManager.addHero` 签名(新增 `faceManager: IFaceManager` 形参,D-29 批准),不增删 / 不改其它成员与 jsDoc;不新增数据端公共接口、不改任何数据端文件;不动 `action/move.ts` 与移动控制(D-19);不动贴图 / 不透明度(D-18)、跟随者(D-22)、`core.*`(D-12)、裸函数(D-11);D-27 不扩散到 `hero.ts` 之外;不删除存量 `state`(D-28);不接线 `client.ts`(`addHero` 接线保持注释);不回滚 / 不暂存 / 不提交用户并发未提交改动。
|
||||
|
||||
⑥ **验证方式与诚实降级(须原样告知)**:渲染端无测试设施,且 `MapExtensionManager.addHero` 当前无调用点(`client.ts:151-155` 被注释),故运行时渲染无法观察。本计划的运行结果 = **scoped 类型门禁**(`pnpm exec vue-tsc --noEmit` 三个在范围文件 0 条,且 `client-modules` 错误集与当日记录基线完全一致)+ **静态门禁**(弃用 helper 清零、`IFaceHandler` 注入、两钩子类分别注册、`onSetPos` 无守卫、AnimDir 已处理、`mutate-animate` 清零且 `@motajs/animate` 就位)+ **D-18/D-22 与 D-28 保留门禁** + **eslint / CRLF 门禁** + **基线感知范围门禁 + 并发改动基线门禁**;另附**人工代码复核**。**不冒充任何无法运行的门禁。**
|
||||
⑥ **验证方式与诚实降级(须原样告知)**:渲染端无测试设施,且 `MapExtensionManager.addHero` 当前无调用点(`client.ts:151-155` 被注释),故运行时渲染无法观察。本计划的运行结果 = **scoped 类型门禁**(`pnpm exec vue-tsc --noEmit` 三个在范围文件 0 条,且 `client-modules` 错误集与当日记录基线完全一致)+ **静态门禁**(弃用 helper 清零、`FaceGroup.Dir4` / `faceManager.get(` / `hero.mover.faceHandler.movement` / 3 参 `addHero` 签名就位、两钩子类分别注册、`onSetPos` 无守卫、AnimDir 已处理、`mutate-animate` 清零且 `@motajs/animate` 就位)+ **D-18/D-22 与 D-28 保留门禁** + **eslint / CRLF 门禁** + **基线感知范围门禁(放行三个在范围文件)+ 并发改动基线门禁**;另附**人工代码复核**。**不冒充任何无法运行的门禁。**
|
||||
|
||||
⑦ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
|
||||
|
||||
⑧ **失败语义(等同硬门禁,非自动命令)**:用户未回复「可以执行」即继续,或回复的是其它任何内容(含修订意见、沉默、`yolo` / `auto_advance` 模式下的自动推进、或子代理/执行器的自行放行),一律视为**未通过本关卡**:必须立即停止执行,不派发任何执行子代理,保持只读、不写任何文件,直到用户在对话中明确回复「可以执行」四个字后方可继续。
|
||||
|
||||
⑨ **若用户给出修订意见**(含不同意命名 / 不同意方法归属 / 不同意 Down 回退):退出本计划、修订 `04-04-PLAN.md` 后重新汇报,不得自行改道执行。
|
||||
⑨ **若用户给出修订意见**(含不同意命名 / 不同意方法归属 / 不同意 Down 回退 / 不同意 `addHero` 签名变更或 jsDoc 处理):退出本计划、修订 `04-04-PLAN.md` 后重新汇报,不得自行改道执行。
|
||||
</action>
|
||||
<decision>是否按本计划(两文件勇士渲染修正:D-24 注入 `IFaceHandler` 并替换弃用 helper、D-25 拆分钩子类且 `onSetPos` 无条件、D-26 补 AnimDir、D-27 换 `@motajs/animate`、D-28 保留存量 `state`)开始执行;并确认 ③ 的成员命名与归属、④ 的 Down 回退处理</decision>
|
||||
<decision>是否按本计划(三文件勇士渲染修正:D-24/D-29 注入 `IFaceManager` 并以 `Dir4FaceHandler` 做 `degrade`/`next`、以 `hero.mover.faceHandler` 做 `movement`,含 `addHero` 签名变更、D-25 拆分钩子类且 `onSetPos` 无条件、D-26 补 AnimDir、D-27 换 `@motajs/animate`、D-28 保留存量 `state`)开始执行;并确认 ③ 的成员命名与归属、④ 的 Down 回退处理、`addHero` 的 jsDoc 处理</decision>
|
||||
<context>
|
||||
AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认,且任何新增 / 改写的成员命名必须先经用户同意。本计划改的是渲染端勇士本体的钩子接线与内部命名,属于「接口 / 命名设计归用户」的边界;`.planning/config.json` 为 `mode: yolo`,因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`。`onSetPos` 的归属与 D-18/D-22 多余方法的归属若不先裁决,执行者会在「避免重复触发」与「逐字保留」之间自行猜测。
|
||||
</context>
|
||||
<options>
|
||||
<option id="execute">
|
||||
<name>可以执行</name>
|
||||
<pros>按本计划两个文件落地:注入 `IFaceHandler`、替换三个弃用 helper(含 Down 回退)、拆分两个钩子类、`onSetPos` 无条件、补 AnimDir、换 `@motajs/animate`;命名与归属均按 ③/④ 汇报内容执行</pros>
|
||||
<cons>无(`types.ts` 不动,契约面不变)</cons>
|
||||
<pros>按本计划三个文件落地:注入 `IFaceManager` 并以 `Dir4FaceHandler` 替换 `degrade`/`next`、以 `hero.mover.faceHandler` 替换 `movement`(含 `addHero` 签名变更与 Down 回退)、拆分两个钩子类、`onSetPos` 无条件、补 AnimDir、换 `@motajs/animate`;命名与归属均按 ③/④ 汇报内容执行</pros>
|
||||
<cons>`types.ts` 的 `addHero` 签名按 D-29 变更(经用户批准;成员集合不变)</cons>
|
||||
</option>
|
||||
<option id="execute-renamed">
|
||||
<name>可以执行,但改名 / 改归属</name>
|
||||
@ -247,32 +258,33 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 用户回复中明确包含「可以执行」四个字
|
||||
- 汇报内容涵盖:要解决的问题(D-24..D-28 五条)、逐调用替换映射表(旧 helper 调用行 → `IFaceHandler` 方法 → 语义映射与差异说明,含注入的 `Dir8FaceHandler` 与 `degrade` / `next` 差异)、成员命名清单(新增 `MapHeroLocationHook` / `MapHeroMoverHook` / `faceHandler`;删除 `MapHeroHook`;方法归属)、Down 回退处理、范围边界与禁令、验证方式与运行时不可得的诚实降级
|
||||
- 汇报内容涵盖:要解决的问题(D-24..D-28 与 D-29 修订)、逐调用替换映射表(旧 helper 调用行 → `Dir4FaceHandler` 的 `degrade`/`next` 与 `hero.mover.faceHandler` 的 `movement` → 修正后的语义映射与等价性)、**`IMapExtensionManager.addHero` 的接口签名变更**(单独列出待批准)、成员命名清单(新增 `MapHeroLocationHook` / `MapHeroMoverHook` / `faceManager` / 派生的 `dir4`;删除 `MapHeroHook`;方法归属)、Down 回退处理、`addHero` 的 jsDoc 处理、范围边界与禁令、验证方式与运行时不可得的诚实降级
|
||||
- 汇报中已原样告知关卡性质:执行者/子代理必须停下等待,不因触发方式或 yolo / auto 模式自动放行
|
||||
</acceptance_criteria>
|
||||
<done>用户明确回复「可以执行」(如需改名 / 改归属 / 改回退处理,已在回复中给出);在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理</done>
|
||||
</task>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1(tracer):D-24 端到端最小切片 —— `manager.ts` 注入 `state.mover.faceHandler` → `MapHeroRenderer` 构造器保存 → 7 处弃用 helper 全部改走 `IFaceHandler`</name>
|
||||
<files>packages-user/client-modules/src/render/map/extension/manager.ts, packages-user/client-modules/src/render/map/extension/hero.ts, .planning/phases/04-render-adaptation/04-04-SUMMARY.md</files>
|
||||
<name>Task 1(tracer):D-24 / D-29 端到端最小切片 —— `types.ts` / `manager.ts` 注入 `faceManager` → `MapHeroRenderer` 构造器保存 `IFaceManager` 并派生 `Dir4FaceHandler` → 7 处弃用 helper 改走 `dir4` 与 `hero.mover.faceHandler`</name>
|
||||
<files>packages-user/client-modules/src/render/map/extension/types.ts, packages-user/client-modules/src/render/map/extension/manager.ts, packages-user/client-modules/src/render/map/extension/hero.ts, .planning/phases/04-render-adaptation/04-04-SUMMARY.md</files>
|
||||
<read_first>
|
||||
- AGENTS.md(命名规则 / 注释规则 / 第一个 block 点)与 dev.md(双端分离;注释规范;换行 CRLF)
|
||||
- packages-user/client-modules/src/render/map/extension/hero.ts(整文件 552 行:import `:1-21`;`addHeroMoving` `:110-140`;`setImage` `:199-206`;`moveEntity` `:225-248`;`generateJumpFn` `:255`;`addFollower` `:351-386`;`turn` `:451-460`;`MapHeroHook` `:468-552`)
|
||||
- packages-user/client-modules/src/render/map/extension/types.ts:1-50(`IMapExtensionManager`;本任务的 `addHero` 签名变更目标,成员集合须不变)
|
||||
- packages-user/client-modules/src/render/map/extension/manager.ts(`addHero` `:24-32`)
|
||||
- packages/common/src/utils/types.ts:145-197(`IFaceDescriptor` / `IFaceHandler`:`degrade` / `movement` / `move` / `opposite` / `next` / `mapDirection` / `mapMovement`)
|
||||
- packages-user/data-common/src/common/utils.ts:1-64(三个弃用 helper 的现行签名与语义,作为替换语义映射的对照,须核对斜向降级与旋转步长差异)
|
||||
- packages-user/data-common/src/common/mover.ts:103-108,166-232,235-251,513-527,646-687(`IObjectMoveAnimDir` / 钩子成员 / `IObjectMover.faceHandler` / `setPos` / `moveProgress` 时序,供 Task 2 参照,本任务只确认 `faceHandler` 来源)
|
||||
- packages-user/data-base/src/hero/types.ts:277-321,399-417(`IHeroLocationHooks` / `IHeroLocation` / `IHeroMover<T>`)
|
||||
- packages-user/data-base/src/hero/location.ts:33-45(`HeroLocation` 以 `IFaceHandler<FaceDirection>` 构造 `HeroMover`)
|
||||
- packages-user/data-common/src/common/faceManager.ts:94-127(`Dir8FaceHandler` 的 `degrade`(恒等)/ `movement`(八方向 + `Unknown`)/ `next`(`DIR8_CW` 45°)实现);对照 `:129-202` 的 `Dir4FaceHandler` 以区分两者
|
||||
- packages-user/data-common/src/common/faceManager.ts:6-39,94-202(`FaceGroup` / `IFaceManager.get<T extends number>`;`Dir8FaceHandler` `:94-127` 与 `Dir4FaceHandler` `:169-193`——D-29 的 `degrade`/`next` 走后者、`movement` 走前者)
|
||||
- packages-user/data-state/src/core.ts:112-144(`dir8 = new Dir8FaceHandler()` `:116` → `new HeroState(this, dir8, heroAttribute)` `:143`:当日实际注入处理器的来源,须现场复核)
|
||||
- packages-user/data-base/src/hero/state.ts:43-63 与 packages-user/data-base/src/hero/mover.ts:38-42(`faceHandler` 由 `HeroState` → `HeroLocation` → `HeroMover` 的装配链)
|
||||
- packages-user/client-modules/src/action/move.ts:1-6(**只读**:确认 D-19 范围外,不得改动)
|
||||
</read_first>
|
||||
<action>
|
||||
(0) **执行前核对与基线(只读前置,必须先做)**:
|
||||
(0a) **数据端现行接口复核(消费未确定项,不猜)**:打开当日 HEAD 的基准文件逐条确认,并把结论(每条一行:结论 + `file:line`)写入 `04-04-SUMMARY.md` 的「执行前核对」段:① `IFaceHandler<T extends number>` 七成员(`degrade` / `movement` / `move` / `opposite` / `next` / `mapDirection` / `mapMovement`)存在,`movement(dir)` 返回 `IFaceDescriptor { readonly x; readonly y }`,`degrade` 对 Unknown 返回 Unknown(`packages/common/src/utils/types.ts:152-197`);② `IObjectMover.faceHandler: IFaceHandler<FaceDirection>` 存在(`data-common/src/common/mover.ts:251`);③ `IHeroMover<T> extends IObjectMover<T>`(`data-base/src/hero/types.ts:399`)且 `IHeroLocation.mover` 类型为 `IHeroMover<this>`(`:303-314`);④ `HeroLocation` 由传入的 `IFaceHandler<FaceDirection>` 构造 `HeroMover`(`data-base/src/hero/location.ts:33-45`);⑤ 三个弃用 helper 仍导出且签名如记录(`data-common/src/common/utils.ts:8` `getFaceMovement`、`:37` `degradeFace`、`:64` `nextFaceDirection`)——这是「待替换物尚在」的核对;⑥ `hero.ts` 当前 7 处调用行号仍为 `:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453`,import 行仍为 `:2`/`:4`/`:7`;⑦ **注入处理器复核(Task 0 前提的现场校验)**:打开 `data-state/src/core.ts:112-144`、`data-base/src/hero/state.ts:43-63`、`data-base/src/hero/location.ts:33-45`、`data-base/src/hero/mover.ts:38-42`,确认 `hero.mover.faceHandler` 的当日实际来源;按现状应为 `Dir8FaceHandler`(`core.ts:116` `dir8` → `:143` `new HeroState(this, dir8, ...)`)。把结论(处理器类名 + 装配链 `file:line`)写入 SUMMARY 的「执行前核对」段。**若实测不是 `Dir8FaceHandler`(例如被改为 `Dir4FaceHandler` 或其它实现),立即停止、不写任何代码,向用户汇报并按新处理器复核 `degrade`/`next`/`movement` 差异后修订本计划**(不得按 Task 0 的 `Dir8` 前提继续,也不得沿用文档示例)。**任一不同:立即停止,不写任何代码,向用户汇报差异并修订本计划。**
|
||||
(0a) **数据端现行接口复核(消费未确定项,不猜)**:打开当日 HEAD 的基准文件逐条确认,并把结论(每条一行:结论 + `file:line`)写入 `04-04-SUMMARY.md` 的「执行前核对」段:① `IFaceHandler<T extends number>` 七成员(`degrade` / `movement` / `move` / `opposite` / `next` / `mapDirection` / `mapMovement`)存在,`movement(dir)` 返回 `IFaceDescriptor { readonly x; readonly y }`,`degrade` 对 Unknown 返回 Unknown(`packages/common/src/utils/types.ts:152-197`);② `IFaceManager` 的 `get<T extends number>(group: number): IFaceHandler<T> | null` 与 `FaceGroup.Dir4` / `Dir8` 存在(`data-common/src/common/faceManager.ts:6-39`);③ 当日装配把 `Dir4FaceHandler` 注册到 `FaceGroup.Dir4`、`Dir8FaceHandler` 注册到 `FaceGroup.Dir8`(`data-state/src/core.ts:115-120`);④ `Dir4FaceHandler.degrade`(`faceManager.ts:170-172`)折叠斜向、`Unknown→Unknown`、`next`(`:188-193`)先 degrade 再 90° 顺时针,与弃用 `degradeFace`(`utils.ts:37-54`)/ `nextFaceDirection(face8=false)`(`utils.ts:64-158`)的等价关系成立(`next` 仅正交等价);⑤ `IObjectMover.faceHandler: IFaceHandler<FaceDirection>` 存在(`data-common/src/common/mover.ts:251`);⑥ `IHeroMover<T> extends IObjectMover<T>`(`data-base/src/hero/types.ts:399`)且 `IHeroLocation.mover` 类型为 `IHeroMover<this>`(`:303-314`);⑦ `HeroLocation` 由传入的 `IFaceHandler<FaceDirection>` 构造 `HeroMover`(`data-base/src/hero/location.ts:33-45`),且当日 `hero.mover.faceHandler` 为 `Dir8FaceHandler`(`core.ts:116` `dir8` → `:143` `new HeroState(this, dir8, ...)` → `state.ts:55` → `location.ts:42`);⑧ 三个弃用 helper 仍导出且签名如记录(`data-common/src/common/utils.ts:8` `getFaceMovement`、`:37` `degradeFace`、`:64` `nextFaceDirection`)——这是「待替换物尚在」的核对;⑨ `hero.ts` 当前 7 处调用行号仍为 `:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453`,import 行仍为 `:2`/`:4`/`:7`。**任一不同(尤其 `FaceGroup.Dir4` 未注册、`faceManager.get(FaceGroup.Dir4)` 返回非 `Dir4FaceHandler`、或 `hero.mover.faceHandler` 不是 `Dir8FaceHandler`):立即停止、不写任何代码,向用户汇报差异并修订本计划**(不得按记忆或文档示例继续)。
|
||||
(0b) **并发改动基线**:执行 `node -e "const fs=require('fs'),c=require('crypto'),{execSync}=require('child_process');const keep=l=>!/packages-user\/client-modules\/src\/render\/map\/extension\//.test(l)&&!/\.planning\/phases\/04-render-adaptation\//.test(l);const out=execSync('git status --porcelain',{encoding:'utf8'}).replace(/\s+$/,'');const cur=(out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[]).filter(keep);const digest=c.createHash('sha256').update(cur.slice().sort().map(l=>{let h='MISSING';try{h=c.createHash('sha256').update(fs.readFileSync(l.slice(3))).digest('hex')}catch(e){}return l+' '+h}).join('\n')).digest('hex');console.log(cur.join('\n'));console.log('基线摘要 sha256='+digest)"`,把输出**原样**写入 SUMMARY,格式固定为:
|
||||
```
|
||||
<!-- CONCURRENT-BASELINE:START -->
|
||||
@ -289,41 +301,43 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
```
|
||||
同时确认三个在范围文件(`render/map/extension/{hero,types,manager}.ts`)**当前 0 条诊断**。**若当日基线与规划时记录不同,以当日实测为准记入 SUMMARY**(说明用户并发改动改变了基线),**不得静默沿用旧值**。
|
||||
(0d) 建立 `04-04-SUMMARY.md` 骨架(含上面三段 + 后续任务要填的空节),保持 CRLF。
|
||||
(1) **`manager.ts`(注入层)**:把 `addHero` 中的 `new MapHeroRenderer(this.renderer, layer, state)` 改为 `new MapHeroRenderer(this.renderer, layer, state, state.mover.faceHandler)`。不改 `addHero` 的签名 / jsDoc、不改 `heroMap` / `removeHero` / `addDoor` / `removeDoor` / `addText` / `removeText` / `destroy`,不新增 import。
|
||||
(2) **`hero.ts` import(D-24 部分)**:从 `@user/data-common` 的 import 中删除 `degradeFace` / `getFaceMovement` / `nextFaceDirection` 三个名字(其余 `FaceDirection` / `IObjectMover` / `IObjectMoverHooks` / `ObjectAnimDirection` / `ObjectMoveStep` / `ObjectMoveType` 本任务全部保留——都有既有使用点);把 `@motajs/common` 的 `import { IHookController, logger }` 扩为 `import { IFaceHandler, IHookController, logger }`。**本任务不要引入本任务用不到的名字**(eslint `@typescript-eslint/no-unused-vars` 为 error 且 `args: 'all'`,未用 import 直接报错;`--fix` 不会删除未使用 import)。
|
||||
(3) **`MapHeroRenderer` 构造器(D-24 注入)**:在 `readonly hero: IHeroLocation` 之后新增第 4 个参数 `readonly faceHandler: IFaceHandler<FaceDirection>`(字段名以 Task 0 用户确认为准)。不改构造体既有钩子注册逻辑(本任务**保留**单一 `MapHeroHook` 现状,Task 2 再拆)。
|
||||
(1) **`types.ts`(契约签名,D-29)**:新增 `import { IFaceManager } from '@user/data-common';`;把 `addHero(state: IHeroLocation, layer: IMapLayer): IMapHeroRenderer | null;` 改为 `addHero(state: IHeroLocation, layer: IMapLayer, faceManager: IFaceManager): IMapHeroRenderer | null;`。**仅改这一处签名**:不增删 / 不改 `IMapHeroRenderer` 与其它成员,不改既有 jsDoc(是否新增 `@param faceManager` 行以 Task 0 用户裁定为准)。
|
||||
(1b) **`manager.ts`(注入层)**:新增 `import { IFaceManager } from '@user/data-common';`;把 `addHero(state: IHeroLocation, layer: IMapLayer)` 改为 `addHero(state: IHeroLocation, layer: IMapLayer, faceManager: IFaceManager)`,并把 `new MapHeroRenderer(this.renderer, layer, state)` 改为 `new MapHeroRenderer(this.renderer, layer, state, faceManager)`。不改 `heroMap` / `removeHero` / `addDoor` / `removeDoor` / `addText` / `removeText` / `destroy`。
|
||||
(2) **`hero.ts` import(D-24 / D-29 部分)**:从 `@user/data-common` 的 import 中删除 `degradeFace` / `getFaceMovement` / `nextFaceDirection` 三个名字(其余 `FaceDirection` / `IObjectMover` / `IObjectMoverHooks` / `ObjectAnimDirection` / `ObjectMoveStep` / `ObjectMoveType` 本任务全部保留——都有既有使用点),并新增 `FaceGroup` 与 `IFaceManager`(两者本任务即消费);把 `@motajs/common` 的 `import { IHookController, logger }` 扩为 `import { IFaceHandler, IHookController, logger }`(`IFaceHandler` 用于派生字段类型)。**本任务不要引入本任务用不到的名字**(eslint `@typescript-eslint/no-unused-vars` 为 error 且 `args: 'all'`,未用 import 直接报错;`--fix` 不会删除未使用 import)。
|
||||
(3) **`MapHeroRenderer` 构造器(D-24 / D-29 注入)**:在 `readonly hero: IHeroLocation` 之后新增第 4 个参数 `readonly faceManager: IFaceManager`(字段名以 Task 0 用户确认为准);新增派生字段(拟名 `dir4`,类型 `IFaceHandler<FaceDirection>`,名字以 Task 0 为准),在构造体开头派生一次:`this.dir4 = faceManager.get<FaceDirection>(FaceGroup.Dir4)!;`(`!` 非空断言的 lint-clean 依据见 assumptions;**不得**在每次调用点重新 `get`)。不改构造体既有钩子注册逻辑(本任务**保留**单一 `MapHeroHook` 现状,Task 2 再拆)。
|
||||
(4) **7 处弃用 helper 替换(逐处对齐,行号为改动前锚点)**:
|
||||
(4a) `:132` `degradeFace(hero.getCurrentFaceDirection())` → `this.faceHandler.degrade(hero.getCurrentFaceDirection())`(`addHeroMoving`;正交与 `Unknown` 等价,斜向输入下 `Dir8` 恒等 vs 旧 helper 折叠——差异已在 Task 0 汇报)。
|
||||
(4b) `:202` `degradeFace(this.hero.getCurrentFaceDirection())` → `this.faceHandler.degrade(this.hero.getCurrentFaceDirection())`(`setImage`;同 4a 的斜向差异)。
|
||||
(4c) `:230` `const { x: dx, y: dy } = getFaceMovement(direction);` → `const { x: dx, y: dy } = this.faceHandler.movement(direction);`(`moveEntity`)。
|
||||
(4d) `:357` `const nowFace = degradeFace(last.nextDirection, FaceDirection.Down);` → 先 `const degraded = this.faceHandler.degrade(last.nextDirection);`,再 `const nowFace = degraded === FaceDirection.Unknown ? FaceDirection.Down : degraded;`(`addFollower`;**Down 回退显式保留**,`Unknown→Down` 与旧 helper 的 `unknown` 形参等价;但注入的 `Dir8FaceHandler.degrade` 对斜向 `last.nextDirection` 是恒等(旧 helper 会折叠为正交),斜向输入下不等价——差异已在 Task 0 汇报)。
|
||||
(4e) `:364` `const { x: dxn, y: dyn } = getFaceMovement(last.nextDirection);` → `this.faceHandler.movement(last.nextDirection)`(变量名与后续用法不变)。
|
||||
(4f) `:365` `const { x: dx, y: dy } = getFaceMovement(last.direction);` → `this.faceHandler.movement(last.direction)`。
|
||||
(4g) `:453` `nextFaceDirection(this.heroEntity.direction)` → `this.faceHandler.next(this.heroEntity.direction)`(`turn` 的无参路径)。**非等价,按 Task 0 汇报的差异执行**:旧 helper `face8=false` 为 90° 顺时针(`Up→Right`),`Dir8FaceHandler.next` 走 `DIR8_CW` 为 45° 顺时针(`Up→RightUp`);仓库内 `turn(...)` 的调用点(`:506`/`:511`/`:530`)均显式传参,该无参路径当前无内部调用者,差异属潜性;替换按 D-24 执行,若用户要求保持 90° 须在 Task 0 裁定。
|
||||
(4a) `:132` `degradeFace(hero.getCurrentFaceDirection())` → `this.dir4.degrade(hero.getCurrentFaceDirection())`(`addHeroMoving`;**等价**——`Dir4FaceHandler.degrade` 折叠斜向、正交恒等)。
|
||||
(4b) `:202` `degradeFace(this.hero.getCurrentFaceDirection())` → `this.dir4.degrade(this.hero.getCurrentFaceDirection())`(`setImage`;同 4a)。
|
||||
(4c) `:230` `const { x: dx, y: dy } = getFaceMovement(direction);` → `const { x: dx, y: dy } = this.hero.mover.faceHandler.movement(direction);`(`moveEntity`;勇士自身 `mover.faceHandler` 为 Dir8,等价)。
|
||||
(4d) `:357` `const nowFace = degradeFace(last.nextDirection, FaceDirection.Down);` → 先 `const degraded = this.dir4.degrade(last.nextDirection);`,再 `const nowFace = degraded === FaceDirection.Unknown ? FaceDirection.Down : degraded;`(`addFollower`;**Down 回退显式保留**;`Dir4FaceHandler.degrade` 与旧 helper 斜向折叠一致,加上回退后**等价**)。
|
||||
(4e) `:364` `const { x: dxn, y: dyn } = getFaceMovement(last.nextDirection);` → `this.hero.mover.faceHandler.movement(last.nextDirection)`(变量名与后续用法不变)。
|
||||
(4f) `:365` `const { x: dx, y: dy } = getFaceMovement(last.direction);` → `this.hero.mover.faceHandler.movement(last.direction)`。
|
||||
(4g) `:453` `nextFaceDirection(this.heroEntity.direction)` → `this.dir4.next(this.heroEntity.direction)`(`turn` 的无参路径)。**对正交输入等价**(先 degrade 再 90° 顺时针,`Up→Right`);**斜向输入分叉**——旧 helper `face8=false` 在斜向间旋转(`LeftUp→RightUp`),`Dir4FaceHandler.next` 先退化再旋转(`LeftUp→Left→Up`);仓库内 `turn(...)` 的调用点(`:506`/`:511`/`:530`)均显式传参,该无参路径当前无内部调用者,差异属潜性;`heroEntity.direction` 期望为正交朝向。
|
||||
**不得**改动这些方法里与其无关的分支 / 注释 / 变量名;**不得**改动 `:235`/`:358`/`:417` 的 `state.roleFace.getFaceOf(...)`(D-28 保留)。
|
||||
(5) **提交**:本任务改动是整体修正的中间态,**不单独提交**;Task 3 完成全部改动后再一次性原子提交(见 Task 3)。若执行环境强制每任务提交,提交信息用 `fix(04-04): inject IFaceHandler into hero renderer`,并与后续任务的提交在本次执行内连续完成。
|
||||
(6) 全程保持文件 CRLF;改完执行 `pnpm exec eslint --fix` 对这两个文件,再 `pnpm exec eslint` 必须 0 错误。
|
||||
(6) 全程保持三个在范围文件的 CRLF;改完执行 `pnpm exec eslint --fix` 对这三个文件,再 `pnpm exec eslint` 必须 0 错误。
|
||||
</action>
|
||||
<reversibility rating="reversible">本任务只把渲染端内部朝向计算从弃用 helper 换到注入的 `IFaceHandler`,并把新参数加在 `MapHeroRenderer` 构造器(不在任何接口里);不改变公共契约面,回退只需还原两个文件,故为 `reversible`。</reversibility>
|
||||
<reversibility rating="reversible">本任务把渲染端内部朝向计算从弃用 helper 换到注入的 `IFaceManager`(经 `faceManager.get<FaceDirection>(FaceGroup.Dir4)` 取四向处理器做 `degrade`/`next`,经 `hero.mover.faceHandler` 做 `movement`),并按 D-29(已获用户批准)为 `IMapExtensionManager.addHero` 增加 `faceManager` 形参——这是本计划唯一一处契约面变化,但仅增一个转发形参、成员集合不变,回退只需还原三个在范围文件,故为 `reversible`。</reversibility>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<26,stdio:['ignore','pipe','pipe']})}catch(e){out=String(e.stdout||'')+String(e.stderr||'')}const lines=out.split(/\r?\n/).filter(l=>/: error TS\d+/.test(l));const scope=lines.filter(l=>/render[\\/]map[\\/]extension[\\/](hero|types|manager)\.ts\(\d+,\d+\)/.test(l));if(scope.length){console.error('IN-SCOPE TYPE ERRORS '+scope.length+'\n'+scope.join('\n'));process.exit(1)}const norm=l=>{const m=l.match(/client-modules[\\/]src[\\/](.+)$/);return m?m[1].replace(/\s+$/,''):null};const cur=lines.map(norm).filter(Boolean).sort();const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-04-SUMMARY.md','utf8');const bm=t.match(/<!-- TYPE-BASELINE:START -->([\s\S]*?)<!-- TYPE-BASELINE:END -->/);if(!bm){console.error('NO TYPE BASELINE');process.exit(1)}const base=bm[1].split(/\r?\n/).map(s=>s.trim()).filter(Boolean).sort();const extra=cur.filter(x=>base.indexOf(x)<0);const missing=base.filter(x=>cur.indexOf(x)<0);if(extra.length||missing.length){console.error('CLIENT-MODULES ERROR SET CHANGED missing=['+missing.join(' ; ')+'] extra=['+extra.join(' ; ')+']');process.exit(1)}console.log('OK in-scope type errors 0; client-modules baseline '+cur.length)"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS"(三个在范围文件仍有类型错误)/ "CLIENT-MODULES ERROR SET CHANGED"(渲染端类型错误集相对基线增减)/ "NO TYPE BASELINE"(SUMMARY 未按固定格式记录基线)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';const src=['hero.ts','types.ts','manager.ts'].map(f=>fs.readFileSync(S+f,'utf8')).join('\n');const hasTok=(s,t)=>new RegExp('(?<![A-Za-z0-9_])'+t+'(?![A-Za-z0-9_])').test(s);const banned=['getFaceMovement','degradeFace','nextFaceDirection'];const hit=banned.filter(t=>hasTok(src,t));if(hit.length){console.error('DEPRECATED FACE HELPERS REMAIN '+hit.join(','));process.exit(1)}const miss=['IFaceHandler<FaceDirection>','faceHandler'].filter(t=>!src.includes(t));if(miss.length){console.error('FACE HANDLER INJECTION MISSING '+miss.join(','));process.exit(1)}const m=fs.readFileSync(S+'manager.ts','utf8');if(!/\.mover\.faceHandler/.test(m)){console.error('MANAGER NOT PASSING mover.faceHandler');process.exit(1)}const h=fs.readFileSync(S+'hero.ts','utf8');if(!/getCurrentFaceDirection\(\)/.test(h)){console.error('FACE DIRECTION READ LOST');process.exit(1)}console.log('OK D24 face handler injection (deprecated helpers 0; faceHandler wired)')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "DEPRECATED FACE HELPERS REMAIN"(三个弃用 helper 名仍以**独立 token**(词边界)出现在三个在范围文件)/ "FACE HANDLER INJECTION MISSING"(`IFaceHandler<FaceDirection>` 或 `faceHandler` 未出现)/ "MANAGER NOT PASSING mover.faceHandler"(注入点未接)/ "FACE DIRECTION READ LOST"(朝向读取口径丢失)</fails_when>
|
||||
<automated>pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/manager.ts</automated>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';const src=['hero.ts','types.ts','manager.ts'].map(f=>fs.readFileSync(S+f,'utf8')).join('\n');const hasTok=(s,t)=>new RegExp('(?<![A-Za-z0-9_])'+t+'(?![A-Za-z0-9_])').test(s);const banned=['getFaceMovement','degradeFace','nextFaceDirection'];const hit=banned.filter(t=>hasTok(src,t));if(hit.length){console.error('DEPRECATED FACE HELPERS REMAIN '+hit.join(','));process.exit(1)}const need=['FaceGroup.Dir4','faceManager.get<FaceDirection>(FaceGroup.Dir4)','this.hero.mover.faceHandler.movement(','IFaceHandler<FaceDirection>','IFaceManager'].filter(t=>!src.includes(t));if(need.length){console.error('D29 BINDINGS MISSING '+need.join(','));process.exit(1)}const ty=fs.readFileSync(S+'types.ts','utf8');if(!/addHero\(state: IHeroLocation, layer: IMapLayer, faceManager: IFaceManager\)/.test(ty)){console.error('ADD_HERO SIGNATURE NOT UPDATED');process.exit(1)}const m=fs.readFileSync(S+'manager.ts','utf8');if(!/new MapHeroRenderer\(this\.renderer, layer, state, faceManager\)/.test(m)){console.error('MANAGER NOT FORWARDING faceManager');process.exit(1)}const h=fs.readFileSync(S+'hero.ts','utf8');if(!/getCurrentFaceDirection\(\)/.test(h)){console.error('FACE DIRECTION READ LOST');process.exit(1)}console.log('OK D24 face handler injection (deprecated helpers 0; faceHandler wired)')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "DEPRECATED FACE HELPERS REMAIN"(三个弃用 helper 名仍以**独立 token**(词边界)出现在三个在范围文件)/ "D29 BINDINGS MISSING"(`FaceGroup.Dir4` / `faceManager.get<FaceDirection>(FaceGroup.Dir4)` / `this.hero.mover.faceHandler.movement(` / `IFaceHandler<FaceDirection>` / `IFaceManager` 有缺)/ "ADD_HERO SIGNATURE NOT UPDATED"(`types.ts` 的 `addHero` 未加 `faceManager` 形参)/ "MANAGER NOT FORWARDING faceManager"(`manager.ts` 未把 `faceManager` 转发给构造器)/ "FACE DIRECTION READ LOST"(朝向读取口径丢失)</fails_when>
|
||||
<automated>pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/types.ts packages-user/client-modules/src/render/map/extension/manager.ts</automated>
|
||||
<fails_when>non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用 import / 未使用变量)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';for(const f of ['hero.ts','manager.ts']){const s=fs.readFileSync(S+f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF LINE ENDINGS '+f);process.exit(1)}}console.log('OK CRLF')"</automated>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';for(const f of ['hero.ts','types.ts','manager.ts']){const s=fs.readFileSync(S+f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF LINE ENDINGS '+f);process.exit(1)}}console.log('OK CRLF')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」)</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `manager.ts` 的 `addHero` 以 `state.mover.faceHandler` 作为 `new MapHeroRenderer(...)` 的第四个实参;`addHero` 签名与 jsDoc 未变
|
||||
- `hero.ts` 构造器新增 `IFaceHandler<FaceDirection>` 参数(`faceHandler`),且 `@motajs/common` 的 import 含 `IFaceHandler`
|
||||
- 弃用 helper 的 7 处调用(`:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453` 对应位置)全部改走 `this.faceHandler.degrade` / `.movement` / `.next`;`:357` 的 Unknown→Down 回退显式保留;三个弃用名字不再出现在三个在范围文件的任何位置(含 import)
|
||||
- `types.ts` 的 `addHero` 已改为 `addHero(state: IHeroLocation, layer: IMapLayer, faceManager: IFaceManager): IMapHeroRenderer | null`(新增 `import { IFaceManager } from '@user/data-common'`);其余成员与既有 jsDoc 一字未动
|
||||
- `manager.ts` 的 `addHero(state, layer, faceManager)` 把 `faceManager` 转发为 `new MapHeroRenderer(this.renderer, layer, state, faceManager)` 的第四个实参;其余方法未动
|
||||
- `hero.ts` 构造器新增 `readonly faceManager: IFaceManager` 参数(第 4 个),并在构造体派生出四向处理器字段(`faceManager.get<FaceDirection>(FaceGroup.Dir4)!`,类型 `IFaceHandler<FaceDirection>`);`@motajs/common` 的 import 含 `IFaceHandler`,`@user/data-common` 的 import 含 `FaceGroup` 与 `IFaceManager`
|
||||
- 弃用 helper 的 7 处调用(`:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453` 对应位置)全部改走 `this.dir4.degrade` / `this.dir4.next`(Dir4)与 `this.hero.mover.faceHandler.movement`(Dir8);`:357` 的 Unknown→Down 回退显式保留;三个弃用名字不再出现在三个在范围文件的任何位置(含 import)
|
||||
- `:235`/`:358`/`:417` 的 `state.roleFace.getFaceOf(...)` 与 `import { state } from '@user/data-state'` 一字未动(D-28)
|
||||
- 本任务**未**触碰 `MapHeroHook` / 钩子注册 / 贴图 / 跟随者 / `types.ts`;未提交任何改动
|
||||
- 四条 `<automated>` 全部转绿:scoped 类型门禁(三个文件 0 条、`client-modules` 错误集 == SUMMARY 记录的 `TYPE-BASELINE`)、D-24 注入门禁、eslint 0 错误、CRLF 门禁
|
||||
- 本任务**未**触碰 `MapHeroHook` / 钩子注册 / 贴图 / 跟随者;未提交任何改动
|
||||
- 四条 `<automated>` 全部转绿:scoped 类型门禁(三个文件 0 条、`client-modules` 错误集 == SUMMARY 记录的 `TYPE-BASELINE`)、D-24/D-29 注入门禁、eslint 0 错误、CRLF 门禁
|
||||
</acceptance_criteria>
|
||||
<done>`state.mover.faceHandler` 经 `manager.ts` 注入到 `MapHeroRenderer`,7 处弃用 helper 全部改走 `IFaceHandler`(含 Down 回退显式保留),D-28 的 `state` 用法与 D-18/D-22 成员未动,四条机器门禁转绿</done>
|
||||
<done>`faceManager` 经 `types.ts` 契约与 `manager.ts` 注入到 `MapHeroRenderer`,7 处弃用 helper 全部改走 `dir4`(Dir4)与 `hero.mover.faceHandler`(Dir8)(含 Down 回退显式保留),D-28 的 `state` 用法与 D-18/D-22 成员未动,四条机器门禁转绿</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
@ -386,42 +400,42 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
</read_first>
|
||||
<action>
|
||||
(1) **D-27(仅 hero.ts)**:把 `import { TimingFn } from 'mutate-animate';` 改为 `import { ExcitationCurve2D } from '@motajs/animate';`(`@motajs/animate` 已是 `client-modules` 的 workspace 依赖,零依赖变更);把 `generateJumpFn(dx: number, dy: number): TimingFn<2>` 的返回类型改为 `ExcitationCurve2D`。函数体与返回值 `[x, y]` 一字不改(返回类型标注会让元组字面量被上下文类型化为 `[number, number]`)。确认 `hero.ts` 内再无 `TimingFn` / `mutate-animate` 引用;**不得**改动 `moving.ts` / `maps/types.ts` / 其它文件的存量 `mutate-animate` 使用(D-27 范围)。
|
||||
(2) **原子提交**:`git add` 仅 `packages-user/client-modules/src/render/map/extension/hero.ts` 与 `packages-user/client-modules/src/render/map/extension/manager.ts`,提交信息 `fix(04-04): align hero renderer with IFaceHandler and split hooks`(或执行环境约定的等价信息);**不得**把用户的并发未提交改动纳入提交,也不得提交 `types.ts`(本计划不改它)。
|
||||
(3) **范围门禁(基线感知,先跑,防止越界改动)**:把当前 `git status --porcelain` 的**生产路径**(`^packages(-user)?/`)与 SUMMARY 中 `CONCURRENT-BASELINE` 段记录的基线条目做差集;只有**基线之外**、且不属于 `hero.ts` / `manager.ts` 的生产改动才算越界。特别断言 `extension/types.ts`(本计划不改)、`action/move.ts`(D-19)、`client-base/**`、`data-common/**`、`data-base/**`、`data-system/**`、`data-state/**`、`packages/**` 均无基线之外的新改动。若出现基线之外的第三个生产文件改动:停止、汇报、修订计划(不得自行扩面)。
|
||||
(2) **原子提交**:`git add` 仅三个在范围文件 —— `packages-user/client-modules/src/render/map/extension/hero.ts`、`packages-user/client-modules/src/render/map/extension/types.ts`、`packages-user/client-modules/src/render/map/extension/manager.ts`,提交信息 `fix(04-04): align hero renderer with IFaceManager and split hooks`(或执行环境约定的等价信息);**不得**把用户的并发未提交改动纳入提交。
|
||||
(3) **范围门禁(基线感知,先跑,防止越界改动)**:把当前 `git status --porcelain` 的**生产路径**(`^packages(-user)?/`)与 SUMMARY 中 `CONCURRENT-BASELINE` 段记录的基线条目做差集;只有**基线之外**、且不属于 `hero.ts` / `types.ts` / `manager.ts` 三个在范围文件的生产改动才算越界。特别断言 `action/move.ts`(D-19)、`client-base/**`、`data-common/**`、`data-base/**`、`data-system/**`、`data-state/**`、`packages/**` 均无基线之外的新改动。若出现基线之外的第四个生产文件改动:停止、汇报、修订计划(不得自行扩面)。
|
||||
(4) **并发改动基线门禁**:用与 Task 1 步骤 (0b) 完全相同的固定命令重算(排除 `packages-user/client-modules/src/render/map/extension/` 与 `.planning/phases/04-render-adaptation/` 两个前缀),断言 porcelain 条目集合与内容摘要 `sha256` 均与 SUMMARY 的 `CONCURRENT-BASELINE` 段记录一致。
|
||||
(5) **复跑全部机器门禁**:scoped 类型门禁(三个在范围文件 0 条 + `client-modules` 错误集 == `TYPE-BASELINE`)、静态门禁 A(D-24/D-25/D-26/D-28)、静态门禁 B(D-27 + D-18/D-22 保留 + `IMapHeroRenderer` 契约保持)、`pnpm exec eslint` 对 hero.ts / manager.ts 0 错误、CRLF 门禁。
|
||||
(6) **人工复核(写进 SUMMARY 的「人工复核」段,逐条给结论)**:① D-24 的 7 处逐条对位(旧 helper 行 → 新 `faceHandler` 调用)与**语义映射结论(非等价声明)**:当日实测注入处理器类名(应为 `Dir8FaceHandler`,装配链 `core.ts:116` → `:143` → `state.ts:55` → `location.ts:42`)、`movement` 全输入域等价、`degrade` 斜向差异(`Dir8` 恒等 vs 旧 helper 折叠)、`next` 45° vs 90° 差异(含 `:357` 斜向不再折叠),并记录用户对差异的裁定;② `:357` 的 Unknown→Down 回退是否与旧 helper 的 `unknown` 语义一致(`Unknown` 一致,斜向按①记录差异);③ D-25:两个钩子类是否分别注册到 `hero` / `hero.mover`,`onSetPos` 是否只出现一次且无条件,并说明「`mover.setPos` → `tile.setPos`(location 钩子)→ `onSetPos` 一次」为何不会再重复触发;④ D-26:AnimDir 分支是否按 `step.dir` 设置,`Speed` 是否仍无渲染动作;⑤ D-27:`hero.ts` 是否已无 `mutate-animate`,`ExcitationCurve2D` 是否被 `moveRelative(curve: TimingFn<2>)` 的形参接受(以编译结果为准);⑥ D-28:`state.roleFace.getFaceOf` 三处与 `import { state } from '@user/data-state'` 是否未动;⑦ D-18/D-22 成员是否逐字保留(对照门禁输出);⑧ 是否新增任何公共接口成员(应为零)且 `types.ts` 是否未改;⑨ 运行时 UAT 不可得的理由已写明(`addHero` 未被接线,`client.ts:151-155` 仍为注释)。
|
||||
(7) **SUMMARY 定稿**:写 `.planning/phases/04-render-adaptation/04-04-SUMMARY.md`,含:改动两文件的清单与职责;D-24..D-28 的逐条处置对照表(含 7 处替换映射与 Down 回退处理);执行前核对七条结论(含注入处理器类名与装配链,带 `file:line`);执行前基线(`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`)与类型门禁基线(`TYPE-BASELINE` 段,逐行原样);全部机器门禁的实际输出摘要;人工复核九条;未实现 / 显式延后项清单(`client.ts` 接线、D-18 贴图来源、D-22 跟随者、D-19 移动控制、D-27 其余存量迁移、REND-02 双布局、其余 ① / ③ 项);以及 REND-01 / REND-02 仍为 Pending 与运行时 UAT 不可得的明确声明。全文 CRLF。
|
||||
(5) **复跑全部机器门禁**:scoped 类型门禁(三个在范围文件 0 条 + `client-modules` 错误集 == `TYPE-BASELINE`)、静态门禁 A(D-24/D-29/D-25/D-26/D-28)、静态门禁 B(D-27 + D-18/D-22 保留 + `IMapHeroRenderer` 契约保持)、`pnpm exec eslint` 对 hero.ts / types.ts / manager.ts 0 错误、CRLF 门禁。
|
||||
(6) **人工复核(写进 SUMMARY 的「人工复核」段,逐条给结论)**:① D-24 / D-29 的 7 处逐条对位(旧 helper 行 → 新调用)与**语义映射结论**:当日实测 `FaceGroup.Dir4` 已注册为 `Dir4FaceHandler`、`FaceGroup.Dir8` 已注册为 `Dir8FaceHandler`(`core.ts:115-120`),故 `degrade` / `next` 经 `faceManager.get<FaceDirection>(FaceGroup.Dir4)` 取四向处理器、`movement` 取勇士自身 `hero.mover.faceHandler`(Dir8);逐方法结论(对应替换行):`degrade` 与旧 `degradeFace` **等价**(斜向折叠相同、正交恒等、Unknown→Unknown;`:132`/`:202`);`movement` 与旧 `getFaceMovement` **全输入域等价**(`:230`/`:364`/`:365`);`next` 与旧 `nextFaceDirection(face8=false)` **仅正交输入等价**(均 90° 顺时针,如 Up→Right),斜向输入分叉(旧 helper 在斜向间 90° 旋转 LeftUp→RightUp,Dir4 先退化再旋转 LeftUp→Left→Up;仅无内部调用者的无参 `turn()` 路径可达),记录该潜性差异与 `heroEntity.direction` 期望正交;② `:357` 的 Unknown→Down 回退(对 `dir4.degrade(last.nextDirection)` 的结果显式判 `FaceDirection.Unknown`)是否与旧 helper 的 `unknown` 形参语义等价(`Dir4FaceHandler.degrade` 斜向折叠与旧 helper 一致,故加上回退后逐字等价);③ D-25:两个钩子类是否分别注册到 `hero` / `hero.mover`,`onSetPos` 是否只出现一次且无条件,并说明「`mover.setPos` → `tile.setPos`(location 钩子)→ `onSetPos` 一次」为何不会再重复触发;④ D-26:AnimDir 分支是否按 `step.dir` 设置,`Speed` 是否仍无渲染动作;⑤ D-27:`hero.ts` 是否已无 `mutate-animate`,`ExcitationCurve2D` 是否被 `moveRelative(curve: TimingFn<2>)` 的形参接受(以编译结果为准);⑥ D-28:`state.roleFace.getFaceOf` 三处与 `import { state } from '@user/data-state'` 是否未动;⑦ D-18/D-22 成员是否逐字保留(对照门禁输出);⑧ 是否未新增 / 删除任何公共接口成员(应为零;`types.ts` 仅 `addHero` 增一个 `faceManager` 形参,成员集合不变);⑨ 运行时 UAT 不可得的理由已写明(`addHero` 未被接线,`client.ts:151-155` 仍为注释)。
|
||||
(7) **SUMMARY 定稿**:写 `.planning/phases/04-render-adaptation/04-04-SUMMARY.md`,含:改动三个在范围文件的清单与职责;D-24..D-29 的逐条处置对照表(含 7 处替换映射、`Dir4FaceHandler`/`Dir8FaceHandler` 的语义等价结论与 Down 回退处理、`addHero` 签名变更);执行前核对结论(含 `FaceGroup.Dir4` / `Dir8` 注册与注入链,带 `file:line`);执行前基线(`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`)与类型门禁基线(`TYPE-BASELINE` 段,逐行原样);全部机器门禁的实际输出摘要;人工复核九条;未实现 / 显式延后项清单(`client.ts` 接线、D-18 贴图来源、D-22 跟随者、D-19 移动控制、D-27 其余存量迁移、REND-02 双布局、其余 ① / ③ 项);以及 REND-01 / REND-02 仍为 Pending 与运行时 UAT 不可得的明确声明。全文 CRLF。
|
||||
(8) **提交 SUMMARY**:`docs(04-04): record execution state`,仅含 `04-04-SUMMARY.md`。
|
||||
(9) **不做的事**:不接线 `client.ts`、不改 `action/move.ts`、不改 `types.ts`、不新增测试文件(渲染端无测试设施)、不改任何数据端文件、不修任何 ③ 多余旧路径或其它 ① 错配、不迁移 `hero.ts` 之外的 `mutate-animate`。
|
||||
(9) **不做的事**:不接线 `client.ts`、不改 `action/move.ts`、不新增测试文件(渲染端无测试设施)、不改任何数据端文件、不修任何 ③ 多余旧路径或其它 ① 错配、不迁移 `hero.ts` 之外的 `mutate-animate`。
|
||||
</action>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out;try{out=execSync('git status --porcelain',{encoding:'utf8'})}catch(e){console.error('GIT STATUS FAILED '+e.message);process.exit(1)}const allow=['packages-user/client-modules/src/render/map/extension/hero.ts','packages-user/client-modules/src/render/map/extension/manager.ts'];const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-04-SUMMARY.md','utf8');const bm=t.match(/<!-- CONCURRENT-BASELINE:START -->([\s\S]*?)<!-- CONCURRENT-BASELINE:END -->/);if(!bm){console.error('NO CONCURRENT BASELINE');process.exit(1)}const base=bm[1].split(/\r?\n/).map(s=>s.replace(/\s+$/,'')).filter(Boolean).map(s=>s.slice(3).replace(/^\"|\"$/g,''));const lines=out.split(/\r?\n/).filter(l=>l.trim().length>0);const prod=lines.map(l=>l.slice(3).replace(/^\"|\"$/g,'')).filter(p=>/^packages(-user)?\//.test(p));const extra=prod.filter(p=>allow.indexOf(p)<0&&base.indexOf(p)<0);if(extra.length){console.error('OUT-OF-SCOPE FILE MODIFIED '+extra.join(' ; '));process.exit(1)}console.log('OK scope (baseline-aware): out-of-scope extra 0')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 `files_modified` 之外、且不在并发基线内的生产文件改动,含 `extension/types.ts`、D-19 的 `action/move.ts`、任何数据端文件或 `packages/**` 的新改动;基线内的用户并发改动不触发)/ "GIT STATUS FAILED" / "NO CONCURRENT BASELINE"</fails_when>
|
||||
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out;try{out=execSync('git status --porcelain',{encoding:'utf8'})}catch(e){console.error('GIT STATUS FAILED '+e.message);process.exit(1)}const allow=['packages-user/client-modules/src/render/map/extension/hero.ts','packages-user/client-modules/src/render/map/extension/types.ts','packages-user/client-modules/src/render/map/extension/manager.ts'];const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-04-SUMMARY.md','utf8');const bm=t.match(/<!-- CONCURRENT-BASELINE:START -->([\s\S]*?)<!-- CONCURRENT-BASELINE:END -->/);if(!bm){console.error('NO CONCURRENT BASELINE');process.exit(1)}const base=bm[1].split(/\r?\n/).map(s=>s.replace(/\s+$/,'')).filter(Boolean).map(s=>s.slice(3).replace(/^\"|\"$/g,''));const lines=out.split(/\r?\n/).filter(l=>l.trim().length>0);const prod=lines.map(l=>l.slice(3).replace(/^\"|\"$/g,'')).filter(p=>/^packages(-user)?\//.test(p));const extra=prod.filter(p=>allow.indexOf(p)<0&&base.indexOf(p)<0);if(extra.length){console.error('OUT-OF-SCOPE FILE MODIFIED '+extra.join(' ; '));process.exit(1)}console.log('OK scope (baseline-aware): out-of-scope extra 0')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 `files_modified` 之外、且不在并发基线内的生产文件改动,含 D-19 的 `action/move.ts`、任何数据端文件或 `packages/**` 的新改动;基线内的用户并发改动不触发)/ "GIT STATUS FAILED" / "NO CONCURRENT BASELINE"</fails_when>
|
||||
<automated>node -e "const fs=require('fs'),c=require('crypto'),{execSync}=require('child_process');const keep=l=>!/packages-user\/client-modules\/src\/render\/map\/extension\//.test(l)&&!/\.planning\/phases\/04-render-adaptation\//.test(l);const out=execSync('git status --porcelain',{encoding:'utf8'}).replace(/\s+$/,'');const cur=(out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[]).filter(keep);const digest=c.createHash('sha256').update(cur.slice().sort().map(l=>{let h='MISSING';try{h=c.createHash('sha256').update(fs.readFileSync(l.slice(3))).digest('hex')}catch(e){}return l+' '+h}).join('\n')).digest('hex');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-04-SUMMARY.md','utf8');const bm=t.match(/<!-- CONCURRENT-BASELINE:START -->([\s\S]*?)<!-- CONCURRENT-BASELINE:END -->/);if(!bm){console.error('NO CONCURRENT BASELINE');process.exit(1)}const rec=bm[1].split(/\r?\n/).map(s=>s.replace(/\s+$/,'')).filter(Boolean).filter(keep);const dm=t.match(/基线摘要[^\r\n]*?sha256=([0-9a-f]{64})/);if(!dm){console.error('NO BASELINE DIGEST');process.exit(1)}const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length||gone.length){console.error('BASELINE ENTRY SET CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}if(dm[1]!==digest){console.error('BASELINE DIGEST CHANGED '+dm[1]+' vs '+digest);process.exit(1)}console.log('OK user concurrent edits unchanged entries '+cur.length)"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "NO BASELINE DIGEST" / "BASELINE ENTRY SET CHANGED" / "BASELINE DIGEST CHANGED"(用户并发的未提交改动被本计划回滚 / 暂存 / 提交 / 修改)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';const src=['hero.ts','types.ts','manager.ts'].map(f=>fs.readFileSync(S+f,'utf8')).join('\n');const hasTok=(s,t)=>new RegExp('(?<![A-Za-z0-9_])'+t+'(?![A-Za-z0-9_])').test(s);const bad=['getFaceMovement','degradeFace','nextFaceDirection','MapHeroHook'];const hit=bad.filter(t=>hasTok(src,t));if(hit.length){console.error('FORBIDDEN TOKENS REMAIN '+hit.join(','));process.exit(1)}const need=['IFaceHandler<FaceDirection>','faceHandler','MapHeroLocationHook','MapHeroMoverHook','hero.addHook','hero.mover.addHook','locationController','moverController','setHeroAnimateDirection(step.dir)'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('REQUIRED BINDINGS MISSING '+miss.join(','));process.exit(1)}const h=fs.readFileSync(S+'hero.ts','utf8');const pos=h.indexOf('onSetPos(');if(pos<0){console.error('NO onSetPos');process.exit(1)}const posBody=h.slice(pos,h.indexOf('\n }',pos)+6);if(hasTok(posBody,'moving')||!/setPosition\(/.test(posBody)){console.error('D25 onSetPos WRONG');process.exit(1)}const gf=(h.match(/state\.roleFace\.getFaceOf/g)||[]).length;if(gf<3||!h.includes(\"from '@user/data-state'\")){console.error('D28 STATE USAGE LOST getFaceOf='+gf);process.exit(1)}console.log('OK final gates D24 D25 D26 D28')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "FORBIDDEN TOKENS REMAIN"(弃用 helper / 旧单一钩子类名仍以独立 token 出现)/ "REQUIRED BINDINGS MISSING" / "D25 onSetPos WRONG"(`onSetPos` 仍有守卫或未直接设置位置)/ "D28 STATE USAGE LOST"(存量 `state.roleFace` 用法被误删)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';const src=['hero.ts','types.ts','manager.ts'].map(f=>fs.readFileSync(S+f,'utf8')).join('\n');const hasTok=(s,t)=>new RegExp('(?<![A-Za-z0-9_])'+t+'(?![A-Za-z0-9_])').test(s);const bad=['getFaceMovement','degradeFace','nextFaceDirection','MapHeroHook'];const hit=bad.filter(t=>hasTok(src,t));if(hit.length){console.error('FORBIDDEN TOKENS REMAIN '+hit.join(','));process.exit(1)}const need=['FaceGroup.Dir4','faceManager.get<FaceDirection>(FaceGroup.Dir4)','this.hero.mover.faceHandler.movement(','IFaceHandler<FaceDirection>','IFaceManager','MapHeroLocationHook','MapHeroMoverHook','hero.addHook','hero.mover.addHook','locationController','moverController','setHeroAnimateDirection(step.dir)'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('REQUIRED BINDINGS MISSING '+miss.join(','));process.exit(1)}const ty=fs.readFileSync(S+'types.ts','utf8');if(!/addHero\(state: IHeroLocation, layer: IMapLayer, faceManager: IFaceManager\): IMapHeroRenderer \| null;/.test(ty)){console.error('ADD_HERO SIGNATURE NOT UPDATED');process.exit(1)}const mg=fs.readFileSync(S+'manager.ts','utf8');if(!/new MapHeroRenderer\(this\.renderer, layer, state, faceManager\)/.test(mg)){console.error('MANAGER NOT FORWARDING faceManager');process.exit(1)}const h=fs.readFileSync(S+'hero.ts','utf8');const pos=h.indexOf('onSetPos(');if(pos<0){console.error('NO onSetPos');process.exit(1)}const posBody=h.slice(pos,h.indexOf('\n }',pos)+6);if(hasTok(posBody,'moving')||!/setPosition\(/.test(posBody)){console.error('D25 onSetPos WRONG');process.exit(1)}const gf=(h.match(/state\.roleFace\.getFaceOf/g)||[]).length;if(gf<3||!h.includes(\"from '@user/data-state'\")){console.error('D28 STATE USAGE LOST getFaceOf='+gf);process.exit(1)}console.log('OK final gates D24 D25 D26 D28')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "FORBIDDEN TOKENS REMAIN"(弃用 helper / 旧单一钩子类名仍以独立 token 出现)/ "REQUIRED BINDINGS MISSING"(`FaceGroup.Dir4` / `faceManager.get<FaceDirection>(FaceGroup.Dir4)` / `this.hero.mover.faceHandler.movement(` / `IFaceManager` / 钩子拆分绑定有缺)/ "ADD_HERO SIGNATURE NOT UPDATED"(`types.ts` 的 `addHero` 未加 `faceManager` 形参)/ "MANAGER NOT FORWARDING faceManager"(`manager.ts` 未把 `faceManager` 转发给构造器)/ "D25 onSetPos WRONG"(`onSetPos` 仍有守卫或未直接设置位置)/ "D28 STATE USAGE LOST"(存量 `state.roleFace` 用法被误删)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';const h=fs.readFileSync(S+'hero.ts','utf8');const hasTok=(s,t)=>new RegExp('(?<![A-Za-z0-9_])'+t+'(?![A-Za-z0-9_])').test(s);if(hasTok(h,'mutate-animate')){console.error('MUTATE-ANIMATE REMAINS IN hero.ts');process.exit(1)}if(!h.includes(\"from '@motajs/animate'\")||!h.includes('ExcitationCurve2D')){console.error('ANIMATE IMPORT MISSING');process.exit(1)}const need=['onSetImage(','onSetAlpha(','onSetFollowerAlpha(','onAddFollower(','onRemoveFollower(','onRemoveAllFollowers(','setImage(','setAlpha(','setFollowerAlpha(','addFollower(','removeFollower(','removeAllFollowers('];const miss=need.filter(x=>!h.includes(x));if(miss.length){console.error('D18 D22 MEMBERS REMOVED '+miss.join(','));process.exit(1)}const t=fs.readFileSync(S+'types.ts','utf8');const i=t.indexOf('export interface IMapHeroRenderer');const j=t.indexOf('export interface ',i+1);const seg=t.slice(i,j<0?t.length:j);const d18=['setImage(image: ITexture): void;','addFollower(image: number, id: string): void;','removeFollower(follower: string, animate: boolean): Promise<void>;','removeAllFollowers(): void;','setAlpha(alpha: number): void;','setFollowerAlpha(identifier: string, alpha: number): void;','destroy(): void;'];const d18Miss=d18.filter(x=>!seg.includes(x));if(d18Miss.length){console.error('D18 D22 CONTRACT MEMBERS MISSING '+d18Miss.join(' , '));process.exit(1)}console.log('OK final D27 + D18 D22 preservation')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "MUTATE-ANIMATE REMAINS IN hero.ts"(D-27 未落地)/ "ANIMATE IMPORT MISSING" / "D18 D22 MEMBERS REMOVED"(hero.ts 的 D-18/D-22 成员被误删)/ "D18 D22 CONTRACT MEMBERS MISSING"(`IMapHeroRenderer` 契约被误改)</fails_when>
|
||||
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<26,stdio:['ignore','pipe','pipe']})}catch(e){out=String(e.stdout||'')+String(e.stderr||'')}const lines=out.split(/\r?\n/).filter(l=>/: error TS\d+/.test(l));const scope=lines.filter(l=>/render[\\/]map[\\/]extension[\\/](hero|types|manager)\.ts\(\d+,\d+\)/.test(l));if(scope.length){console.error('IN-SCOPE TYPE ERRORS '+scope.length+'\n'+scope.join('\n'));process.exit(1)}const norm=l=>{const m=l.match(/client-modules[\\/]src[\\/](.+)$/);return m?m[1].replace(/\s+$/,''):null};const cur=lines.map(norm).filter(Boolean).sort();const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-04-SUMMARY.md','utf8');const bm=t.match(/<!-- TYPE-BASELINE:START -->([\s\S]*?)<!-- TYPE-BASELINE:END -->/);if(!bm){console.error('NO TYPE BASELINE');process.exit(1)}const base=bm[1].split(/\r?\n/).map(s=>s.trim()).filter(Boolean).sort();const extra=cur.filter(x=>base.indexOf(x)<0);const missing=base.filter(x=>cur.indexOf(x)<0);if(extra.length||missing.length){console.error('CLIENT-MODULES ERROR SET CHANGED missing=['+missing.join(' ; ')+'] extra=['+extra.join(' ; ')+']');process.exit(1)}console.log('OK final type gate: in-scope 0; client-modules baseline '+cur.length)"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS" / "CLIENT-MODULES ERROR SET CHANGED" / "NO TYPE BASELINE"(终验类型门禁未过)</fails_when>
|
||||
<automated>pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/manager.ts</automated>
|
||||
<automated>pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/types.ts packages-user/client-modules/src/render/map/extension/manager.ts</automated>
|
||||
<fails_when>non-zero exit 或输出含 "error"</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';for(const f of ['hero.ts','manager.ts']){const s=fs.readFileSync(S+f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF LINE ENDINGS '+f+' lone-lf='+(lf-crlf));process.exit(1)}}console.log('OK CRLF source files')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(两个源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」/ `.prettierrc` 的 `endOfLine: crlf`;SUMMARY 不在本机器门禁范围——04-03-SUMMARY 实测为混合行尾,故不对工具写出的 markdown 施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-04-SUMMARY.md','utf8');const need=['执行前核对','CONCURRENT-BASELINE:START','TYPE-BASELINE:START','人工复核','REND-01','REND-02','Pending','addHero','client.ts:151','@motajs/animate','IFaceHandler','MapHeroLocationHook','MapHeroMoverHook','AnimDir'];const miss=need.filter(x=>!t.includes(x));if(miss.length){console.error('SUMMARY MISSING SECTIONS '+miss.join(','));process.exit(1)}console.log('OK summary completeness')"</automated>
|
||||
<automated>node -e "const fs=require('fs');const S='packages-user/client-modules/src/render/map/extension/';for(const f of ['hero.ts','types.ts','manager.ts']){const s=fs.readFileSync(S+f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF LINE ENDINGS '+f+' lone-lf='+(lf-crlf));process.exit(1)}}console.log('OK CRLF source files')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(三个源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」/ `.prettierrc` 的 `endOfLine: crlf`;SUMMARY 不在本机器门禁范围——04-03-SUMMARY 实测为混合行尾,故不对工具写出的 markdown 施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-04-SUMMARY.md','utf8');const need=['执行前核对','CONCURRENT-BASELINE:START','TYPE-BASELINE:START','人工复核','REND-01','REND-02','Pending','addHero','client.ts:151','@motajs/animate','IFaceHandler','IFaceManager','faceManager','FaceGroup.Dir4','MapHeroLocationHook','MapHeroMoverHook','AnimDir'];const miss=need.filter(x=>!t.includes(x));if(miss.length){console.error('SUMMARY MISSING SECTIONS '+miss.join(','));process.exit(1)}console.log('OK summary completeness')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线段 / 人工复核 / 需求 Pending 声明 / 运行时不可得说明 / D-24..D-27 关键项)</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `hero.ts` 不再出现 `mutate-animate`(独立 token),`generateJumpFn` 返回 `ExcitationCurve2D`(`@motajs/animate`),且 `moving.ts` 等范围外文件未被改动(D-27)
|
||||
- `hero.ts` / `manager.ts` 已一个原子提交入库(`fix(04-04)`),提交未包含 `types.ts`、未包含用户并发改动
|
||||
- 范围门禁(基线感知)通过:除两个在范围文件与基线内的并发用户改动外,无其它生产文件改动(`extension/types.ts`、`action/move.ts`、任何数据端包、`client-base`、`packages` 均无基线之外的新改动)
|
||||
- `hero.ts` / `types.ts` / `manager.ts` 已一个原子提交入库(`fix(04-04)`),提交未包含用户并发改动
|
||||
- 范围门禁(基线感知)通过:除三个在范围文件与基线内的并发用户改动外,无其它生产文件改动(`action/move.ts`、任何数据端包、`client-base`、`packages` 均无基线之外的新改动)
|
||||
- 并发改动基线门禁通过:porcelain 条目集合与内容 `sha256` 均与 SUMMARY 记录一致
|
||||
- 静态门禁 A(D-24/D-25/D-26/D-28)、静态门禁 B(D-27 + D-18/D-22 保留 + `IMapHeroRenderer` 契约)、scoped 类型门禁(三文件 0 条、`client-modules` 错误集 == `TYPE-BASELINE`)、eslint 0 错误、CRLF 门禁全部转绿
|
||||
- `04-04-SUMMARY.md` 含执行前核对七条(含注入处理器类名与装配链,带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段、D-24..D-28 处置对照、门禁输出摘要、人工复核九条、未实现 / 延后项清单、REND-01 / REND-02 仍 Pending 与运行时 UAT 不可得的明确说明;并以 `docs(04-04)` 提交(仅含该 SUMMARY)
|
||||
- 静态门禁 A(D-24/D-29/D-25/D-26/D-28)、静态门禁 B(D-27 + D-18/D-22 保留 + `IMapHeroRenderer` 契约)、scoped 类型门禁(三文件 0 条、`client-modules` 错误集 == `TYPE-BASELINE`)、eslint 0 错误、CRLF 门禁全部转绿
|
||||
- `04-04-SUMMARY.md` 含执行前核对结论(含 `FaceGroup.Dir4` / `Dir8` 注册与注入链,带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段、D-24..D-29 处置对照、门禁输出摘要、人工复核九条、未实现 / 延后项清单、REND-01 / REND-02 仍 Pending 与运行时 UAT 不可得的明确说明;并以 `docs(04-04)` 提交(仅含该 SUMMARY)
|
||||
</acceptance_criteria>
|
||||
<done>D-27 落地并原子提交;七类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02)</done>
|
||||
<done>D-27 落地并原子提交(三个在范围文件);七类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02)</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@ -431,56 +445,56 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 渲染端契约面 → 下游调用方 | `IMapHeroRenderer` / `IMapExtensionManager` 是 `client-base` / `client-modules` 其它模块与用户代码消费的公共契约;本计划声称不改契约面,误改 `types.ts` 或误删 D-18/D-22 成员会直接扩大破坏面 |
|
||||
| 数据端接口 → 渲染端绑定 | 渲染端只读 import `data-common` / `data-base` 的类型并读取 `state.mover.faceHandler`;本计划不改数据端,接口一旦被用户并发改动,适配即按错误形状落地 |
|
||||
| 渲染端契约面 → 下游调用方 | `IMapHeroRenderer` / `IMapExtensionManager` 是 `client-base` / `client-modules` 其它模块与用户代码消费的公共契约;本计划按 D-29 仅对 `addHero` 做一处经批准的签名变更(新增 `faceManager` 形参),误改其它成员或误删 D-18/D-22 成员会直接扩大破坏面 |
|
||||
| 数据端接口 → 渲染端绑定 | 渲染端只读 import `data-common` / `data-base` 的类型,读取 `hero.mover.faceHandler`(Dir8)并经 `addHero` 接收 `IFaceManager`(取 `FaceGroup.Dir4` 的 `Dir4FaceHandler`);本计划不改数据端,接口一旦被用户并发改动,适配即按错误形状落地 |
|
||||
| 数据端钩子回调 → 渲染状态 | 现有钩子是唯一允许推进渲染状态的入口(D-20 / D-21);两个钩子类若归错 hookable 或 `onSetPos` 重复实现,会造成重复驱动 / 状态偏差 |
|
||||
| 执行者/子代理 → 执行前置关卡 | AGENTS.md 要求第一个 block 点先汇报并等待用户回复「可以执行」;`yolo` / `auto_advance` 或子代理派发都不是放行理由(Task 0 为 `blocking-human`、`autonomous: false`) |
|
||||
| 执行者 → 仓库工作树 | 用户会并发手工重构数据端与加载系统;本计划只允许改两个渲染端文件,且不得触碰用户的未提交改动 |
|
||||
| 执行者 → 仓库工作树 | 用户会并发手工重构数据端与加载系统;本计划只允许改三个渲染端文件,且不得触碰用户的未提交改动 |
|
||||
| 未接线状态 → 验收结论 | `addHero` 无调用点,运行时渲染不可观察;若不显式声明,「编译通过」会被误读为「勇士渲染已可用」 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-04-04-01 | Tampering | `IMapHeroRenderer` / `IMapExtensionManager` 公共契约(`types.ts`) | high | mitigate | `types.ts` 不在 `files_modified`;Task 1 的构造器参数不进任何接口;Task 3 的 D-18/D-22 契约门禁断言 `IMapHeroRenderer` 的 7 个保留成员逐字仍在、范围门禁断言 `types.ts` 无改动;prohibitions 明列不得新增接口成员 |
|
||||
| T-04-04-01 | Tampering | `IMapHeroRenderer` / `IMapExtensionManager` 公共契约(`types.ts`) | high | mitigate | `types.ts` 只在 `files_modified` 内、且只改 `addHero` 一处经批准的签名(新增 `faceManager` 形参,D-29);构造器参数不进任何接口;Task 3 的 D-18/D-22 契约门禁断言 `IMapHeroRenderer` 的 7 个保留成员逐字仍在,静态门禁断言成员集合无增删;prohibitions 明列不得新增 / 删除接口成员 |
|
||||
| T-04-04-02 | Repudiation | 钩子拆分 / 命名 / 映射的可追溯性 | medium | mitigate | 逐调用替换映射表与成员命名清单在 Task 0 汇报并经用户确认;Task 2 / Task 3 静态门禁断言旧单一钩子类名不复现、两类分别注册、`onSetPos` 只出现一次且无守卫、AnimDir 已处理;映射与归属写入 SUMMARY 人工复核段 |
|
||||
| T-04-04-03 | Elevation of Privilege | 执行前置关卡(AGENTS.md 要求的第一处 block 点) | high | mitigate | Task 0 为 `checkpoint:decision` + `gate="blocking-human"` 且本计划 `autonomous: false`;Task 0 的 action ⑦/⑧ 与 acceptance_criteria 明示未收到「可以执行」四个字即必须停止、不派发执行子代理 |
|
||||
| T-04-04-04 | Tampering | 用户并发的未提交改动 | high | mitigate | Task 1 步骤 (0b) 记录 porcelain 条目 + 各文件内容 `sha256` 摘要(写入 SUMMARY 的 `CONCURRENT-BASELINE` 段);Task 3 用同一固定命令复算并断言集合与摘要未变;prohibitions 明确不得回滚 / 暂存 / 提交 / 修改 |
|
||||
| T-04-04-05 | Spoofing | 「编译通过」被当作「运行时可用」 | medium | mitigate | 运行时 UAT 不可得(`addHero` 未被接线)作为 truths/assumptions 明写;SUMMARY 必须含运行时不可得说明(Task 3 门禁断言 `addHero` 与 `client.ts:151` 字样);严禁以不可运行的门禁冒充通过 |
|
||||
| T-04-04-06 | Elevation of Privilege | 范围蔓延(D-27 范围外的 `mutate-animate`、D-28 误删 `state`、D-18/D-19/D-22、`types.ts`) | medium | mitigate | 范围门禁(基线感知)只放行两个文件;D-18/D-22 保留门禁与 D-28 保留门禁;prohibitions 逐条列明;需要第三个文件时强制停下汇报并修订计划 |
|
||||
| T-04-04-06 | Elevation of Privilege | 范围蔓延(D-27 范围外的 `mutate-animate`、D-28 误删 `state`、D-18/D-19/D-22、`types.ts` 的越权改动) | medium | mitigate | 范围门禁(基线感知)只放行 `hero.ts` / `types.ts` / `manager.ts` 三个在范围文件;D-18/D-22 保留门禁与 D-28 保留门禁;prohibitions 逐条列明;需要第四个文件、或 `types.ts` 需要 `addHero` 之外的改动时强制停下汇报并修订计划 |
|
||||
| T-04-04-07 | Information Disclosure | 改动引用的接口名、文件行号与类型签名 | low | accept | 改动只含渲染端源码引用,不含凭据 / 密钥 / 用户数据;无需额外缓解 |
|
||||
| T-04-04-SC | Tampering | npm / pnpm 依赖安装 | high | accept | 本计划零依赖变更、不修改 `package.json` / `pnpm-lock.yaml`(`@motajs/animate` 已是 `client-modules` 的 workspace 依赖),Package Legitimacy Gate 不触发;若执行中出现安装需求,立即停止并汇报 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
<!-- plan-criteria-allow: R4 - 本计划判据区内的 `|` 全部是 node -e 脚本里 JS 源码的转义 / 正则交替 / 对象分隔,不是 shell 管道;脚本内 git 调用由 execSync 的 try/catch 捕获并以非零退出失败,不会把失败读成通过。注释中出现的弃用 helper 名与库名为 D-24..D-28 的裁定对象,已由下方 allow 标记豁免。 -->
|
||||
<!-- plan-criteria-allow: R4 - 本计划判据区内的 `|` 全部是 node -e 脚本里 JS 源码的转义 / 正则交替 / 对象分隔,不是 shell 管道;脚本内 git 调用由 execSync 的 try/catch 捕获并以非零退出失败,不会把失败读成通过。注释中出现的弃用 helper 名与库名为 D-24..D-29 的裁定对象,已由下方 allow 标记豁免。 -->
|
||||
<!-- planner-discipline-allow: getFaceMovement -->
|
||||
<!-- planner-discipline-allow: degradeFace -->
|
||||
<!-- planner-discipline-allow: nextFaceDirection -->
|
||||
<!-- planner-discipline-allow: mutate-animate -->
|
||||
<!-- planner-discipline-allow: MapHeroHook -->
|
||||
<!-- planner-discipline-allow: moving -->
|
||||
0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题(D-24..D-28)/ 大致解决方案(含逐调用替换映射表)/ 成员命名清单与方法归属 / Down 回退处理 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
|
||||
1. 执行前核对与基线(Task 1 步骤 0):七条数据端现行接口与注入处理器复核结论(含 `hero.mover.faceHandler` 实测类名与装配链 `file:line`)写入 SUMMARY;并发改动基线(`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`)与类型门禁基线(`TYPE-BASELINE` 段,逐行原样)写入 SUMMARY;任一与规划不一致即停止并修订计划;**不得沿用 04-03 SUMMARY 的旧并发列表**(已随 `4e64953` 落库)。
|
||||
0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题(D-24..D-28 与 D-29 修订)/ 大致解决方案(含逐调用替换映射表与修正后的语义映射)/ 成员命名清单与方法归属 / `addHero` 接口签名变更 / Down 回退处理 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
|
||||
1. 执行前核对与基线(Task 1 步骤 0):数据端现行接口与注入处理器复核结论(含 `FaceGroup.Dir4` / `Dir8` 的注册实测类名与 `hero.mover.faceHandler` 装配链 `file:line`)写入 SUMMARY;并发改动基线(`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`)与类型门禁基线(`TYPE-BASELINE` 段,逐行原样)写入 SUMMARY;任一与规划不一致即停止并修订计划;**不得沿用 04-03 SUMMARY 的旧并发列表**(已随 `4e64953` 落库)。
|
||||
2. scoped 类型门禁(Task 1 / 2 / 3):`pnpm exec vue-tsc --noEmit` 过滤到 `render/map/extension/{hero,types,manager}.ts` 必须 0 条诊断;`client-modules` 错误集必须与 SUMMARY 的 `TYPE-BASELINE` 完全一致(集合比较,顺序无关)。整仓其余既有诊断不属本计划,不得以「整仓非 0 退出」判定失败,也不得漏判范围文件的新诊断。
|
||||
3. 静态门禁 A(Task 1 / 2 / 3):三个在范围文件不出现**独立 token**(词边界)`getFaceMovement` / `degradeFace` / `nextFaceDirection` / `MapHeroHook`;必须出现 `IFaceHandler<FaceDirection>` / `faceHandler` / `MapHeroLocationHook` / `MapHeroMoverHook` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController` / `setHeroAnimateDirection(step.dir)`;`onSetPos` 方法体内不含 `moving` 且含 `setPosition(`;`hero.ts` 含 `state.roleFace.getFaceOf`(≥3)与 `from '@user/data-state'`。
|
||||
3. 静态门禁 A(Task 1 / 2 / 3):三个在范围文件不出现**独立 token**(词边界)`getFaceMovement` / `degradeFace` / `nextFaceDirection` / `MapHeroHook`;必须出现 `FaceGroup.Dir4` / `faceManager.get<FaceDirection>(FaceGroup.Dir4)` / `this.hero.mover.faceHandler.movement(` / `IFaceHandler<FaceDirection>` / `IFaceManager` / `MapHeroLocationHook` / `MapHeroMoverHook` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController` / `setHeroAnimateDirection(step.dir)`,且 `types.ts` 的 `addHero(state: IHeroLocation, layer: IMapLayer, faceManager: IFaceManager)` 与 `manager.ts` 的 `new MapHeroRenderer(this.renderer, layer, state, faceManager)` 就位;`onSetPos` 方法体内不含 `moving` 且含 `setPosition(`;`hero.ts` 含 `state.roleFace.getFaceOf`(≥3)与 `from '@user/data-state'`。
|
||||
4. 静态门禁 B(Task 3):`hero.ts` 不含 `mutate-animate`(独立 token)且含 `from '@motajs/animate'` 与 `ExcitationCurve2D`;`hero.ts` 的 D-18/D-22 钩子与实现方法逐字保留;`IMapHeroRenderer` 的 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy` 逐字保留。
|
||||
5. D-18 / D-22 / D-28 保留门禁(Task 2 / 3):贴图 / 不透明度 / 跟随者成员不被删除或改写;存量 `state.roleFace.getFaceOf` 三处与 `import { state } from '@user/data-state'` 未被移除(这是**正向**门禁:D-28 要求保留)。
|
||||
6. 并发改动基线门禁(Task 3):排除两个在范围文件与 `.planning/phases/04-render-adaptation/` 后,porcelain 条目集合与内容 `sha256` 与 SUMMARY 的 `CONCURRENT-BASELINE` 段记录一致(用户改动未被回滚 / 暂存 / 提交 / 修改)。
|
||||
7. 范围门禁(Task 3,基线感知):将当前 `git status --porcelain` 的生产路径(`^packages(-user)?/`)与 `CONCURRENT-BASELINE` 段做差集;只有基线之外、且不属于 `hero.ts` / `manager.ts` 的生产改动才判越界;`extension/types.ts`、`action/move.ts`、数据端各包、`client-base`、`packages` 均无基线之外的新改动。
|
||||
8. ESLint / CRLF 门禁(Task 1 / 2 / 3):`pnpm exec eslint` 对 `hero.ts` / `manager.ts` 0 错误;两个源文件无孤立 LF(`.prettierrc` 的 `endOfLine: crlf`)。SUMMARY 不施加该硬门禁(04-03-SUMMARY 实测为混合行尾),改由人工复核遵循仓库 markdown 惯例。
|
||||
9. 人工复核(Task 3):D-24 的 7 处替换逐条对位且**语义映射与差异已记录(非等价声明)**(含注入处理器实测类名 `Dir8FaceHandler` 与装配链、`movement` 全等、`degrade` 斜向差异、`next` 45° vs 90° 差异);Down 回退的 `Unknown` 语义一致;两钩子类分别注册且 `onSetPos` 只出现一次、无条件;AnimDir 按 `step.dir` 设置且 `Speed` 仍 no-op;D-27 仅在 `hero.ts` 生效且 `ExcitationCurve2D` 被编译接受;D-28 未动;D-18/D-22 逐字保留;无新增公共接口成员且 `types.ts` 未改;运行时 UAT 不可得的理由已写明。
|
||||
10. 提交:Task 3 的 `fix(04-04)` 仅含 `hero.ts` / `manager.ts`;`docs(04-04)` 仅含 SUMMARY;用户并发改动未被纳入任何提交。
|
||||
6. 并发改动基线门禁(Task 3):排除整个 `render/map/extension/` 目录(含三个在范围文件)与 `.planning/phases/04-render-adaptation/` 后,porcelain 条目集合与内容 `sha256` 与 SUMMARY 的 `CONCURRENT-BASELINE` 段记录一致(用户改动未被回滚 / 暂存 / 提交 / 修改)。
|
||||
7. 范围门禁(Task 3,基线感知):将当前 `git status --porcelain` 的生产路径(`^packages(-user)?/`)与 `CONCURRENT-BASELINE` 段做差集;只有基线之外、且不属于 `hero.ts` / `types.ts` / `manager.ts` 的生产改动才判越界;`action/move.ts`、数据端各包、`client-base`、`packages` 均无基线之外的新改动。
|
||||
8. ESLint / CRLF 门禁(Task 1 / 2 / 3):`pnpm exec eslint` 对 `hero.ts` / `types.ts` / `manager.ts` 0 错误;三个源文件无孤立 LF(`.prettierrc` 的 `endOfLine: crlf`)。SUMMARY 不施加该硬门禁(04-03-SUMMARY 实测为混合行尾),改由人工复核遵循仓库 markdown 惯例。
|
||||
9. 人工复核(Task 3):D-24 / D-29 的 7 处替换逐条对位且**语义映射已记录**(`FaceGroup.Dir4` / `Dir8` 注册实测,`degrade` 与旧 `degradeFace` 等价、`movement` 与旧 `getFaceMovement` 全输入域等价、`next` 与旧 `nextFaceDirection(face8=false)` 仅正交输入等价并说明斜向分叉);Down 回退的 `Unknown` 语义等价;两钩子类分别注册且 `onSetPos` 只出现一次、无条件;AnimDir 按 `step.dir` 设置且 `Speed` 仍 no-op;D-27 仅在 `hero.ts` 生效且 `ExcitationCurve2D` 被编译接受;D-28 未动;D-18/D-22 逐字保留;未新增 / 删除任何公共接口成员(`types.ts` 仅 `addHero` 增一个 `faceManager` 形参);运行时 UAT 不可得的理由已写明。
|
||||
10. 提交:Task 3 的 `fix(04-04)` 仅含 `hero.ts` / `types.ts` / `manager.ts`;`docs(04-04)` 仅含 SUMMARY;用户并发改动未被纳入任何提交。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报计划内容、逐调用替换映射表、成员命名清单与 `onSetPos` / D-18·D-22 方法归属、Down 回退处理后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行。
|
||||
- **D-24:** 勇士渲染已绑定注入的 `IFaceHandler<FaceDirection>`(`manager.ts` 传入 `state.mover.faceHandler`),三个弃用 helper(含 import)在三个在范围文件内以独立 token 判定彻底消失;7 处调用逐条改走 `degrade` / `movement` / `next`,`:357` 的 Unknown→Down 回退显式保留。
|
||||
- **D-24 / D-29:** 勇士渲染已绑定注入的 `IFaceManager`(`manager.ts` 转发 `addHero` 的 `faceManager` 实参);`degrade` / `next` 走 `faceManager.get<FaceDirection>(FaceGroup.Dir4)` 的 `Dir4FaceHandler`(贴图四向),`movement` 走勇士自身 `hero.mover.faceHandler`(Dir8);三个弃用 helper(含 import)在三个在范围文件内以独立 token 判定彻底消失;7 处调用逐条改走 `dir4` / `hero.mover.faceHandler.movement`,`:357` 的 Unknown→Down 回退显式保留;语义映射结论为 `degrade` 等价、`movement` 全输入域等价、`next` 仅正交输入等价(斜向分叉已记录)。
|
||||
- **D-25:** `MapHeroHook` 已拆为 `MapHeroLocationHook` / `MapHeroMoverHook` 两个内部类,各自注册到 `hero` / `hero.mover` 并各持一个 `IHookController`;`onSetPos` 只出现一次、无条件直接设置位置、无 `moving` 守卫,不再重复触发。
|
||||
- **D-26:** `onStepEnd` 的 `ObjectMoveType.AnimDir` 分支按 `step.dir` 设置动画播放方向(且在既有 `mover.currAnimDir` 处理之外)。
|
||||
- **D-27:** `hero.ts` 不再使用 `mutate-animate`,改用 `@motajs/animate` 的 `ExcitationCurve2D`;范围外文件的存量使用未被动。
|
||||
- **D-28:** 存量 `state.roleFace.getFaceOf` 三处与 `from '@user/data-state'` 逐字保留。
|
||||
- **契约面与范围:** `types.ts` 未改、无新增公共接口成员;D-18 / D-22 成员逐字保留;`action/move.ts`、数据端各包、`client.ts` 接线、`core.*` 均未被触碰;用户并发的未提交改动未被回滚 / 暂存 / 提交 / 修改。
|
||||
- **契约面与范围:** `types.ts` 仅有经批准的 `addHero` 签名变更(新增 `faceManager: IFaceManager` 形参),未新增 / 删除任何公共接口成员;D-18 / D-22 成员逐字保留;`action/move.ts`、数据端各包、`client.ts` 接线、`core.*` 均未被触碰;用户并发的未提交改动未被回滚 / 暂存 / 提交 / 修改。
|
||||
- **验证诚实且可运行:** scoped 类型门禁(三文件 0 条、`client-modules` 错误集 == 基线)、静态门禁 A/B、D-18/D-22 与 D-28 保留门禁、eslint / CRLF 门禁、范围门禁(基线感知)、并发改动基线门禁全部转绿且有实际输出;运行时渲染 UAT 不可得的原因(`addHero` 未被接线)在摘要中明写,未以任何不可运行的门禁冒充通过。
|
||||
- REND-01 / REND-02 保持 Pending(两条 FLAGGED ASSUMPTION),用户可据此继续裁决后续增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02、D-27 其余存量迁移、其余 ① / ③ 项)。
|
||||
|
||||
@ -488,9 +502,10 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|
||||
|
||||
| 类型 | 路径 | 内容 |
|
||||
|------|------|------|
|
||||
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/hero.ts` | `MapHeroRenderer` 持有注入的 `IFaceHandler<FaceDirection>` 并以 `degrade` / `movement` / `next` 取代三个弃用 helper(含 `:357` 的 Unknown→Down 显式回退);`MapHeroHook` 拆为 `MapHeroLocationHook` / `MapHeroMoverHook` 两个内部类分别注册;`onSetPos` 无条件且无 `moving` 守卫;`onStepEnd` 的 AnimDir 分支按 `step.dir` 设置动画方向;`mutate-animate` → `@motajs/animate` 的 `ExcitationCurve2D`;D-18 / D-22 / D-28 成员逐字保留 |
|
||||
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/manager.ts` | `addHero` 把 `state.mover.faceHandler` 作为第四个实参注入 `new MapHeroRenderer(...)`;其余一字不改 |
|
||||
| 摘要 | `.planning/phases/04-render-adaptation/04-04-SUMMARY.md` | 执行前核对七条(含注入处理器实测类名与装配链,带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段、D-24..D-28 处置对照与 7 处替换映射(含 `Dir8FaceHandler` 的 `degrade` / `next` 差异记录)、门禁输出摘要、人工复核九条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
|
||||
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/hero.ts` | `MapHeroRenderer` 持有注入的 `IFaceManager`(`faceManager` 字段)与派生的四向处理器字段(`faceManager.get<FaceDirection>(FaceGroup.Dir4)`),以 `dir4.degrade` / `dir4.next`(Dir4)与 `hero.mover.faceHandler.movement`(Dir8)取代三个弃用 helper(含 `:357` 的 Unknown→Down 显式回退);`MapHeroHook` 拆为 `MapHeroLocationHook` / `MapHeroMoverHook` 两个内部类分别注册;`onSetPos` 无条件且无 `moving` 守卫;`onStepEnd` 的 AnimDir 分支按 `step.dir` 设置动画方向;`mutate-animate` → `@motajs/animate` 的 `ExcitationCurve2D`;D-18 / D-22 / D-28 成员逐字保留 |
|
||||
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/types.ts` | `IMapExtensionManager.addHero` 新增 `faceManager: IFaceManager` 形参(D-29);成员集合无增删、`IMapHeroRenderer` 与其余成员逐字保留 |
|
||||
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/manager.ts` | `addHero(state, layer, faceManager)` 把 `faceManager` 形参转发给 `new MapHeroRenderer(this.renderer, layer, state, faceManager)`;`addHero` 之外的方法一字不改 |
|
||||
| 摘要 | `.planning/phases/04-render-adaptation/04-04-SUMMARY.md` | 执行前核对结论(含 `FaceGroup.Dir4` / `Dir8` 注册实测与注入链,带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段、D-24..D-29 处置对照与 7 处替换映射(含 `degrade`/`movement` 等价、`next` 仅正交等价与斜向分叉的记录)、门禁输出摘要、人工复核九条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/04-render-adaptation/04-04-SUMMARY.md` when done
|
||||
|
||||
Loading…
Reference in New Issue
Block a user