From f86f19ea894e5ab75c03be53675166b79da0e0f6 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 09:05:57 +0800
Subject: [PATCH 001/173] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E8=AE=A8?=
=?UTF-8?q?=E8=AE=BA=E8=A6=81=E6=B1=82=E6=8F=90=E7=A4=BA=E8=AF=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
AGENTS.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/AGENTS.md b/AGENTS.md
index 02c54bf..ad8aa65 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -13,3 +13,4 @@
- 执行任何计划前,必须先向我汇报计划内容,我确认没有问题后才可执行,不得擅自执行。汇报时说明计划准备解决什么问题,大致的解决方案是什么。如果是修复问题的计划,那么应该还需要汇报问题是什么。
- 除非我明确要求,否则任何修改必须经由 GSD 标准流程:Plan -> Execute -> Verify,不得自行修改。
- 任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈,我同意后才可以使用,不得擅自使用我未同意的命名。
+- 在 GSD 流程的 Discuss 讨论阶段中,全部由我主导,而不是由你主导。进入讨论阶段的第一件事不是开子代理探索这一阶段的需求,也不是自己分析需求并提问问题,而是让我先说这一阶段的计划。
From 01fe37fdb0769c6243e69a1cbf1133b03a5daea4 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 09:06:35 +0800
Subject: [PATCH 002/173] docs(04): capture phase context
---
.../phases/04-render-adaptation/04-CONTEXT.md | 119 ++++++++++++++++++
.../04-render-adaptation/04-DISCUSSION-LOG.md | 80 ++++++++++++
2 files changed, 199 insertions(+)
create mode 100644 .planning/phases/04-render-adaptation/04-CONTEXT.md
create mode 100644 .planning/phases/04-render-adaptation/04-DISCUSSION-LOG.md
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
new file mode 100644
index 0000000..265bd05
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -0,0 +1,119 @@
+# Phase 4: 渲染适配与双布局 - Context
+
+**Gathered:** 2026-09-18
+**Status:** Ready for planning (incremental — 本次仅锁定第一步)
+
+
+## Phase Boundary
+
+阶段 4 的最终目标(ROADMAP):渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局(REND-01、REND-02)。
+
+**重要:本阶段无法一次规划完毕。** 用户明确要求增量规划,本次讨论只规划**第一步**:
+
+> 收集当前所有与数据端接口不匹配的渲染端实现(含依赖数据端的渲染行为),形成对账文档,作为后续计划如何拆分实施的依据。数据端未提供而渲染端需要的接口、双方错配等,单独成节写入该文档,便于后续处理。
+
+第一步是**只读清点**,不修改任何代码。后续步骤待第一手对账结果出来后另行规划。
+
+
+
+
+## Implementation Decisions
+
+### 增量规划方式
+- **D-01:** 阶段 4 采用增量规划,不一次性产出完整计划;本 CONTEXT 仅锁定第一步(对账),后续步骤待对账结果出来后再规划。
+- **D-02:** 第一步交付物为对账文档 `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`;该步为只读清点,不改任何代码。
+
+### 对账范围与基准
+- **D-03:** 被查对象限定 `packages-user` 下的 `client-base` 与 `client-modules` 两个渲染端包。`packages` 偏向第三方库、本身不影响渲染端与数据端,不查;其余 packages-user 子包(`entry-client`、`legacy-plugin-client`、`legacy-plugin-data`、`data-fallback`)不纳入本次第一步。
+- **D-04:** 数据端 `data-common` / `data-base` / `data-system` / `data-state` 不作为被查对象,仅作为接口基准(契约源为各自 `types.ts`、`core.ts`、`ins.ts` 等)。
+- **D-05:** 用户会同步修改数据端**实现**,但接口签名不变;对账一律以「接口」为准,不受实现变动影响。
+
+### 问题分类与记录粒度
+- **D-06:** 问题按三类划分:① 错配(渲染端按旧形状/旧签名调用,数据端接口已变);② 数据端缺失(渲染端需要而数据端未提供的接口);③ 多余旧路径(渲染端仍在走已被新接口取代的 legacy/旧路径)。
+- **D-07:** 每条记录精确到接口名,并包含其所属文件、问题描述、影响等内容。
+- **D-08:** 采用单文档分节组织;「数据端缺失接口」独立成节,便于后续单独处理。
+
+### 执行方式
+- **D-09:** 允许派子代理进行只读扫描(不得修改任何文件)。
+
+### the agent's Discretion
+- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
+
+
+
+
+## Canonical References
+
+**Downstream agents MUST read these before planning or implementing.**
+
+### Project and phase requirements
+- `.planning/PROJECT.md` — 项目架构、双端分离、AI 实现边界与质量约束
+- `.planning/REQUIREMENTS.md` — REND-01 / REND-02 需求
+- `.planning/ROADMAP.md` — Phase 4 目标、成功标准与相邻阶段边界(Phase Details → Phase 4)
+- `.planning/STATE.md` — 当前仓库状态与既有决策
+- `dev.md` — 项目结构与开发原则;「双端分离」章节定义 `@user/client-base`(系统层)与 `@user/client-modules`(实现层)
+
+### Data-side interface baselines (基准,不是被查对象)
+- `packages-user/data-common/src/types.ts` — L0 `IDataCommon` 契约
+- `packages-user/data-base/src/types.ts` — L1 `IStateBase` 契约
+- `packages-user/data-system/src/types.ts` — L2 `IStateSystem` 契约
+- `packages-user/data-state/src/types.ts` — L3 `ICoreState` 契约
+- `packages-user/data-state/src/core.ts` — 顶层装配与 legacy 初始化路径
+- `packages-user/data-state/src/ins.ts` — 既有单例入口
+- `packages-user/data-common/src/replay/types.ts` — 录像命令/沙箱/状态契约
+- `packages-user/data-common/src/store/types.ts` — Tile raw-data 与事件访问契约
+- `packages-user/entry-data/src/mota.ts` — `Mota` 注册表与 `r()`/`rf()` 渲染调用门控
+- `packages-user/data-base/src/game.ts` — `hook` / `gameListener` 渲染通知机制
+
+### Render side under audit (被查对象)
+- `packages-user/client-base/src/index.ts` — 渲染系统层入口
+- `packages-user/client-modules/src/index.ts` — 渲染实现层入口
+
+### Prior phase context
+- `.planning/phases/03-data-completion/03-CONTEXT.md` — 数据端完成阶段的接口边界与已锁定决策(D-01..D-28、S-01..S-05)
+
+
+
+
+## Existing Code Insights
+
+> 本次未对渲染端代码做实际扫描(用户要求讨论阶段不由 AI 先行探索)。以下来自既有 `.planning/codebase/` 分析文档,仅作方向参考;对账执行时以实际代码为准。
+
+### Reusable Assets
+- `@user/client-base`(`packages-user/client-base/src/`)— 渲染端系统层,负责渲染核心系统(`load/`、`material/`)
+- `@user/client-modules`(`packages-user/client-modules/src/`)— 渲染端实现层,依赖系统层实现渲染与用户交互(`render/`、`action/`、`fallback/`)
+- `hook` / `gameListener`(`packages-user/data-base/src/game.ts`)— 数据端向渲染端发布通知的既有机制
+- `Mota.r()` / `Mota.rf()`(`packages-user/entry-data/src/mota.ts`)— 数据端调用渲染端代码的唯一受控入口
+
+### Established Patterns
+- 依赖方向单向:`src → packages-user → packages`;数据端分层 `data-common → data-base → data-system → data-state`
+- 双端分离:渲染端只向数据端发消息,不向数据端推送更新;数据端无 DOM
+- 公共契约以 `types.ts` + barrel 导出表达,接口设计归用户,AI 不得自行发明公共接口行为
+
+### Integration Points
+- 渲染端读取游戏状态的耦合点即对账重点:需判定其读取的是新数据层接口还是 legacy 全局
+- `packages-user/entry-client/src/create.ts` 为渲染端组合根(本次不在被查范围,但对账时可作为理解接线方式的参考)
+
+
+
+
+## Specific Ideas
+
+- 「收集当前所有与数据端接口不匹配的渲染端实现」——不止显式调用,依赖数据端的渲染**行为**也可能不匹配,需一并清点。
+- 数据端缺失接口与错配项要单独成节,便于用户后续处理(用户会自行决定其在数据端或渲染端解决)。
+- 对账以接口为准,不受用户并行修改数据端实现的影响。
+
+
+
+
+## Deferred Ideas
+
+- 阶段 4 的后续步骤(实际适配实施、移动端/桌面端双布局实现)——待第一步对账结果出来后另行规划。
+- 已确认不在本次第一步范围:`packages` 全部、`entry-client`、`legacy-plugin-client`、`legacy-plugin-data`、`data-fallback`。
+
+
+
+---
+
+*Phase: 4-渲染适配与双布局*
+*Context gathered: 2026-09-18*
diff --git a/.planning/phases/04-render-adaptation/04-DISCUSSION-LOG.md b/.planning/phases/04-render-adaptation/04-DISCUSSION-LOG.md
new file mode 100644
index 0000000..468b326
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-DISCUSSION-LOG.md
@@ -0,0 +1,80 @@
+# Phase 4: 渲染适配与双布局 - Discussion Log
+
+> **Audit trail only.** Do not use as input to planning, research, or execution agents.
+> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
+
+**Date:** 2026-09-18
+**Phase:** 4-渲染适配与双布局
+**Areas discussed:** 第一步范围(对账)、被查子包、文档组织、交付路径
+
+---
+
+## 讨论主导权
+
+用户明确要求:讨论阶段由用户主导,不由 AI 先行探索代码,也不由 AI 自行分析需求并提问。AI 在用户说明计划前不得开启子代理扫描项目。
+
+**Notes:** 用户指出「先别开子代理看项目,我还没说这阶段要干啥」。
+
+---
+
+## 第一步范围(对账)
+
+用户提出的第一步计划:收集当前所有与数据端接口不匹配的渲染端实现(含依赖数据端的渲染行为);数据端未提供而渲染端需要的接口、双方错配等单独写到文档中,便于后续处理。这一步只读清点、不改代码,作为后续计划拆分实施的依据。
+
+---
+
+## 被查子包
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| client-base | 渲染系统层 | ✓ |
+| client-modules | 渲染实现层 | ✓ |
+| entry-client | 渲染端组合根 | |
+| legacy-plugin-client / legacy-plugin-data | legacy 插件 | |
+| data-fallback | legacy 全局桥接兼容层 | |
+
+**User's choice:** 仅 client-base、client-modules。
+**Notes:** 用户另说明:范围「仅 packages-user」,`packages` 偏向第三方库、本身不影响渲染端与数据端,不查。
+
+---
+
+## 文档组织
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| 单文档分节 | 04-RENDER-INTERFACE-AUDIT.md 内分「错配 / 数据端缺失接口 / 多余旧路径」,缺失项独立小节 | ✓ |
+| 两份文档 | 对账清单 + 缺失接口登记各一份 | |
+
+**User's choice:** 单文档分节。
+
+---
+
+## 交付路径
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| 是,按 GSD 走 | discuss → /gsd-plan-phase 4(04-01 = 该对账)→ execute 产出文档,全程只读 | ✓ |
+| 否,先停在这 | 暂不进入 plan | |
+
+**User's choice:** 是,按 GSD 走。
+**Notes:** 用户强调「严格按 GSD 流程走」。
+
+---
+
+## 命名确认
+
+| 对象 | 命名 | 确认 |
+|------|------|------|
+| 阶段目录 | `04-render-adaptation` | ✓ |
+| 对账文档 | `04-RENDER-INTERFACE-AUDIT.md` | ✓ |
+
+---
+
+## the agent's Discretion
+
+- 「影响」字段的具体写法、「多余旧路径」是否进一步细分,由 AI 在执行对账时按实际情况把握,但不得据此扩大范围。
+
+## Deferred Ideas
+
+- 阶段 4 后续步骤(实际适配实施、双布局实现)——待对账结果出来后规划。
+- 明确不纳入本次第一步:`packages` 全部、`entry-client`、`legacy-plugin-*`、`data-fallback`。
From 8d6928294a72a8e373a14879b9ac741f0eca1d37 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 09:06:59 +0800
Subject: [PATCH 003/173] docs(state): record phase 4 context session
---
.planning/STATE.md | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 7b7daa6..8d622a5 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -4,16 +4,16 @@ milestone: v1.0
current_phase: 07
current_phase_name: 数据端缺陷修复
status: executing
-stopped_at: Phase 07 paused — 07-16 (post-refactor test alignment) DEFERRED until the user finishes the data-layer manual rework
-last_updated: "2026-09-17T12:45:00.000Z"
+stopped_at: Phase 4 context gathered
+last_updated: "2026-09-18T01:06:46.798Z"
last_activity: 2026-09-17
last_activity_desc: Phase 07 paused; plan 07-16 deferred pending the user's data-layer rework
-state_head: 19ad1ea91ab7a69b0b65020d49bbe65ab30ec301
+state_head: 01fe37fdb0769c6243e69a1cbf1133b03a5daea4
progress:
total_phases: 7
completed_phases: 0
- total_plans: 70
- completed_plans: 69
+ total_plans: 71
+ completed_plans: 70
milestone_name: milestone
---
@@ -249,6 +249,6 @@ None yet.
## Session Continuity
-Last session: 2026-09-17T12:45:00.000Z
-Stopped at: Phase 07 paused — 07-16 (post-refactor test alignment) deferred until the user's data-layer rework is complete
-Resume file: .planning/phases/07-data-fixes/.continue-here.md
+Last session: 2026-09-18T01:06:46.471Z
+Stopped at: Phase 4 context gathered
+Resume file: .planning/phases/04-render-adaptation/04-CONTEXT.md
From f480ec704cbb4da0a5ad805ded60699418dc5587 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 09:40:38 +0800
Subject: [PATCH 004/173] docs(04): create phase plan for read-only render/data
interface audit
---
.planning/ROADMAP.md | 9 +-
.../phases/04-render-adaptation/04-01-PLAN.md | 292 ++++++++++++++++++
2 files changed, 299 insertions(+), 2 deletions(-)
create mode 100644 .planning/phases/04-render-adaptation/04-01-PLAN.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index cd0fe82..245fa52 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,12 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: TBD
+**Plans**: 1 plan(第一步·只读对账;本阶段增量规划,后续适配实施与双布局待对账结果出来后另行规划 — 04-CONTEXT D-01/D-02)
+
+Plans:
+
+- [ ] 04-01-PLAN.md — 渲染端 ↔ 数据端接口对账(只读清点,产出 `04-RENDER-INTERFACE-AUDIT.md`;按 ① 错配 / ② 数据端缺失 / ③ 多余旧路径 三类登记)
+
**UI hint**: yes
### Phase 5: Legacy 移植
@@ -375,7 +380,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 0/TBD | Not started | - |
+| 4. 渲染适配与双布局 | 0/1 | In Progress| - |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/phases/04-render-adaptation/04-01-PLAN.md b/.planning/phases/04-render-adaptation/04-01-PLAN.md
new file mode 100644
index 0000000..e0c4785
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-01-PLAN.md
@@ -0,0 +1,292 @@
+---
+phase: 04-render-adaptation
+plan: 1
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+autonomous: true
+requirements:
+ - REND-01
+ - REND-02
+estimate:
+ tokens: 120000
+ raw_tokens: 120000
+ tasks: 3
+ confidence: low
+must_haves:
+ truths:
+ - "`.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` 存在,且含 `07-LOADSTATE-AUDIT.md` 式骨架:「背景 / 方法 / 发现 / 触发序列(举例) / 判定为「匹配」的同类边界 / 未能从阅读确定(未猜测) / 处置」七段,加上独立成节的「② 数据端缺失接口」,共 8 个二级标题"
+ - "发现表每条记录精确到「接口名 + 所属文件:行 + 分类 + 问题描述 + 影响 + 置信 + 依据」(D-07);分类只取 ① 错配(`#04-01-M-N`)或 ③ 多余旧路径(`#04-01-L-N`),② 数据端缺失另走独立节(D-08)"
+ - "「② 数据端缺失接口」为独立节,每条以 `### #04-01-G-N:标题(严重度)` 登记并含「现状 / 期望」两段;节末有固定结论行(确认 N 项,或明示本步未确认缺失接口)"
+ - "每条 `#04-01-M-*` / `#04-01-L-*` 记录的「所属文件:行」锚点指向 `packages-user/client-base` 或 `packages-user/client-modules` 的 tracked 源码行;每条 `#04-01-M-*` 在「依据」列引用至少一个数据端基准文件(`data-common` / `data-base` / `data-system` / `data-state` 的 `src/*.ts`),支持 D-05「以接口签名为准」"
+ - "已确认走新接口的同类读取(`core.material.*` 渲染侧素材族、`hook` / `loading` 订阅等)登记在「判定为『匹配』的同类边界」,未被误报为错配;静态阅读无法定论的调用链登记在「未能从阅读确定(未猜测)」而非写成缺陷"
+ - "被查两个渲染端包零改动:`git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空;除本审计文档与 `04-01-PLAN.md` 外不新增任何计划文件(后续适配实施与双布局未规划、未实施)"
+ - "`## 处置` 登记:本步为只读清点、无生产代码改动;后续步骤由用户在对账结果出来后决定如何拆分;双布局既有资产(`render/use.ts` 的 `Orientation`/`onOrientationChange`、`shared.ts` 布局常量)仅登记为后续起点,不展开实施"
+ artifacts:
+ - path: .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+ provides: "渲染端 ↔ 数据端接口对账账本:8 段骨架 + 三分类记录表 + 独立的数据端缺失接口节 + 匹配边界 + 未确定段 + 处置结论;供用户决定后续适配与双布局如何拆分"
+ key_links:
+ - "发现表「所属文件:行」列 ↔ `packages-user/client-{base,modules}/src/**` 的 tracked 源码行(经 `git ls-files` 验证,不得指向 gitignored 镜像路径)"
+ - "每条 `#04-01-M-*` 的「依据」列 ↔ 数据端 `types.ts` / `core.ts` / `ins.ts` 的现行接口签名(D-04/D-05);接口名必须能在基准文件中逐字命中"
+ - "「判定为『匹配』的同类边界」段 ↔ 04-PATTERNS.md §4 的 A3 组判据(`core.*` 读取先判「数据端状态」还是「渲染侧素材」),防止把素材读成数据端错配"
+ - "「未能从阅读确定(未猜测)」段 ↔ 07-LOADSTATE-AUDIT.md:48-53 的证据纪律(静态阅读不能定论的链路只登记、不判定)"
+ - "`## 处置` ↔ 后续步骤的用户决策(D-01):本 run 只交付清点,适配实施与双布局的拆分由用户在对账结果出来后决定"
+ assumptions:
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的边界/验收判据,探测结果为 `unclassified / unresolved`。本计划**不发明** REND-01 的验收阈值;它只交付供 REND-01 实施阶段消费的只读差异账本。用户须在规划适配工作前补齐 REND-01 的边界判据。"
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的边界/验收判据,探测结果为 `unclassified / unresolved`。本计划**不发明** REND-02 的验收阈值;仅在对账文档「处置」段登记既有双布局资产为后续起点,布局实现不在本 run。用户须在规划双布局前补齐 REND-02 的边界判据。"
+ - "数据端接口签名在本次 `04-PATTERNS.md` 之后可能仍被用户并行修改实现但不改签名(D-05);对账以基准文件的**现行签名**为准,若执行时发现签名已变,以执行当日基准文件为准并在「未能从阅读确定」记录该变更。"
+ - "审计文档为新增 untracked 文件,`04-PATTERNS.md` 亦为尚未提交的规划产物(`git status` 显示 `??`);本计划的门禁只断言「无新增计划文件」与「被查包零改动」,不对 `.planning` 下其它规划产物的提交状态做断言。"
+ prohibitions:
+ - "不得修改或新建 `packages-user/client-base` / `packages-user/client-modules` 下的任何文件——本步是只读清点(D-02/D-09)。"
+ - "不得规划、设计或实施渲染适配与双布局工作——本 run 只交付只读对账(D-01/D-02);不得新增任何后续步骤的计划文件。"
+ - "不得写入任何缺少 `file:line` 锚点的记录;静态阅读无法定论的调用链只能进「未能从阅读确定(未猜测)」段,不得写成缺陷或错配。"
+ - "不得把纯渲染侧读取(如 `core.material.*`、`core.icons.*` 等素材/全局)登记为数据端错配;它们属于「判定为『匹配』的同类边界」。"
+ - "不得把被查范围扩大到其它包(`packages/**`、`entry-client`、`legacy-plugin-client`、`legacy-plugin-data`、`data-fallback`,以及作为基准的 `data-*` 包本体)(D-03)。"
+---
+
+
+Phase 4 第一步(增量规划,D-01/D-02):对渲染端与数据端做**只读接口对账**,产出 `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`,清点所有「渲染端实现 ↔ 数据端现行接口」不匹配项,供用户后续决定剩余工作(适配实施 + 移动端/桌面端双布局)如何拆分。
+
+**本 run 只做清点,不改任何代码。** 被查对象限 `packages-user/client-base` 与 `packages-user/client-modules`(D-03);数据端 `data-common` / `data-base` / `data-system` / `data-state` 仅作接口基准(D-04),对账以接口签名为准、不受用户并行修改数据端**实现**的影响(D-05)。
+
+三类固定分类(D-06):① 错配(`#04-01-M-N`) ② 数据端缺失(`#04-01-G-N`,独立成节,D-08) ③ 多余旧路径(`#04-01-L-N`)。每条记录精确到接口名,含所属文件 `file:line`、问题描述、影响(D-07)。
+
+Purpose: 阶段 4 无法一次规划完毕。先拿到第一手对账账本,才能把「适配实施」与「双布局实施」拆成可执行、可验证的后续计划,避免在未核实接口差异的前提下直接改动渲染端。
+
+Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产代码改动)。
+
+**UI 说明**:本 run 为只读对账,不产出任何 UI/前端实现产物,不适用 UI-SPEC(`check ui-plan-gate` 因 ROADMAP Phase 4 含「移动端/桌面端布局」词汇而结构性命中,属预期)。Phase 4 的移动端/桌面端双布局 UI 工作属 `## Deferred Ideas`,其 UI-SPEC 应在规划双布局时单独产出;本 run 只在审计文档「处置」段登记既有双布局资产为其起点。
+
+**Artifacts this phase produces(本计划产出物)**:
+
+- **文件**:`.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`(新建,唯一写入目标)。
+- **章节**(8 个二级标题,下游验证按标题名核对):`## 背景` / `## 方法` / `## 发现` / `## ② 数据端缺失接口`(独立成节,D-08) / `## 触发序列(举例)` / `## 判定为「匹配」的同类边界` / `## 未能从阅读确定(未猜测)` / `## 处置`。
+- **元信息块**(标题下 4 行,对照 07-LOADSTATE-AUDIT.md:3-6):`**日期:**` / `**范围:**`(被查 client-base + client-modules;data-* 为接口基准) / `**性质:**`(只读清点,未修改任何代码) / `**接口基准:**`(各包 `types.ts` / `core.ts` / `ins.ts` 现行签名)。
+- **发现表列**(固定顺序):`| ID | 接口名 | 所属文件:行 | 分类 | 问题描述 | 影响 | 置信 | 依据 |`。
+- **记录 ID 前缀**(下游按前缀检索):
+ - `#04-01-M-N` —— ① 错配(N 从 1 递增)
+ - `#04-01-L-N` —— ③ 多余旧路径
+ - `#04-01-G-N` —— ② 数据端缺失,独立节内以 `### #04-01-G-N:标题(严重度)` + `- **现状**:` / `- **期望**:` 登记
+- **② 节结论行**(固定写法,必须出现其一):`**②节结论:** 本步确认 N 项数据端缺失接口(见上)` 或 `**②节结论:** 本步未确认数据端缺失接口;相关不确定项见「未能从阅读确定(未猜测)」`。
+- **`## 处置` 必备内容**:本步为只读清点、无生产代码改动;后续适配实施与移动端/桌面端双布局**未在本 run 规划**,由用户在对账结果出来后决定拆分;双布局既有资产(`render/use.ts` 的 `Orientation`/`onOrientationChange`、`shared.ts` 的 `MAP_BLOCK_WIDTH`/`STATUS_BAR_WIDTH`/`MAIN_WIDTH` 等布局常量)仅登记为后续起点,不展开。
+
+## Source Audit(多源覆盖审计)
+
+| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
+|--------|----|---------------------|------|--------|-------|
+| GOAL | — | Phase 4:「渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局」 | 04-01 | COVERED(仅第一步) | D-01 增量规划:本 run 只规划第一步(只读对账);适配实施与双布局属 CONTEXT.md `## Deferred Ideas`,待对账结果出来后另行规划 |
+| REQ | REND-01 | 渲染端适配新数据层接口 | 04-01 | COVERED(仅清点) | 本 run 交付 REND-01 的差异账本,不实施适配;REND-01 验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
+| REQ | REND-02 | 渲染端同时支持移动端与桌面端布局 | 04-01 | COVERED(仅登记资产) | 本 run 只登记双布局既有资产为后续起点(04-PATTERNS.md §7);布局实现属 Deferred |
+| CONTEXT | D-01 | 增量规划,本次只锁定第一步 | 04-01 | COVERED | 04-01 即第一步;不产出后续步骤计划 |
+| CONTEXT | D-02 | 交付物为审计文档;该步只读 | 04-01 | COVERED | `files_modified` 仅审计文档;只读门禁:`git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空 |
+| CONTEXT | D-03 | 被查对象限 client-base + client-modules | 04-01 | COVERED | 范围外路径不登记为被查项 |
+| CONTEXT | D-04 | data-* 仅作接口基准 | 04-01 | COVERED | 基准文件为各包 `types.ts` / `core.ts` / `ins.ts` 等 |
+| CONTEXT | D-05 | 以接口为准,不受实现变动影响 | 04-01 | COVERED | 每条 `#04-01-M-*` 的「依据」列须引用数据端基准文件 |
+| CONTEXT | D-06 | 三类划分 | 04-01 | COVERED | 三个 ID 前缀一一对应 |
+| CONTEXT | D-07 | 精确到接口名 + 文件 + 描述 + 影响 | 04-01 | COVERED | 发现表列固定;门禁要求每条含 `file:line` |
+| CONTEXT | D-08 | 单文档分节;缺失接口独立成节 | 04-01 | COVERED | `## ② 数据端缺失接口` 独立节 + 固定结论行 |
+| CONTEXT | D-09 | 允许只读子代理 | 04-01 | COVERED | Task 2/3 允许派只读子代理分派 A1..A7 扫描组,仅回传文本,不得写文件 |
+| RESEARCH | — | (本阶段无 RESEARCH.md) | — | N/A | Phase 4 未产出 RESEARCH.md,无研究项需覆盖 |
+
+
+
+@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
+@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/REQUIREMENTS.md
+@.planning/phases/04-render-adaptation/04-CONTEXT.md
+@.planning/phases/04-render-adaptation/04-PATTERNS.md
+@.planning/phases/03-data-completion/03-CONTEXT.md
+@.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md
+@.planning/phases/06-unit-tests/06-TEST-FINDINGS.md
+@.planning/phases/06-unit-tests/06-COVERAGE-GAPS.md
+@.planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md
+@AGENTS.md
+@dev.md
+
+
+
+
+
+ Task 1(tracer):审计文档骨架端到端贯通 —— 8 段结构 + 三分类各一条实测记录 + 结构/ID 门禁转绿
+ .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+
+ - .planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md(全文件:章节骨架、方法段、「触发序列」、「判定为安全/匹配的同类边界」、「未能从阅读确定(未猜测)」、处置段写法)
+ - .planning/phases/06-unit-tests/06-TEST-FINDINGS.md:7-18(字段定义表)与 :24-30(分节表格写法)
+ - .planning/phases/06-unit-tests/06-COVERAGE-GAPS.md:10-13(`### G-xx:标题(严重度)` + 现状 / 期望 骨架)
+ - .planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md:3-5(「接口事实源」声明块写法)
+ - .planning/phases/04-render-adaptation/04-PATTERNS.md(§1 章节骨架表与发现表字段、§4 A3 锚点表、§5 A4 新接口对照面)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-01..D-09、Deferred Ideas)
+ - packages-user/data-state/src/types.ts(ICoreState 基准,逐字比对用)
+ - packages-user/client-base/src/types.ts(`IClientBase extends ICoreState` 桥头)
+ - packages-user/client-modules/src/action/move.ts:1-6(`// @ts-expect-error 需要重构` + `import { HeroMover, IMoveController } from '@user/data-state'`)
+ - packages-user/client-modules/src/render/ui/main.tsx:28-30,99-128(新旧接口同文件并存)
+ - packages-user/client-modules/src/render/map/renderer.ts:33(`IGameMap` / `IGameMapHooks` / `IMapLayer` 直连)
+ - dev.md(「双端分离」:`@user/client-base` 为系统层、`@user/client-modules` 为实现层)
+
+
+ (1) 新建 `04-RENDER-INTERFACE-AUDIT.md`,先写标题与 4 行元信息块(`**日期:** 2026-09-18`;`**范围:**` 被查 `packages-user/client-base` + `packages-user/client-modules`、`data-*` 为接口基准;`**性质:** 只读清点,未修改任何代码`;`**接口基准:**` 各包 `types.ts` / `core.ts` / `ins.ts` 的现行签名,D-04/D-05)。
+ (2) 写 `## 背景`:REND-01 / REND-02、D-01 增量规划、本次只清点不实施、以及「对账以接口为准、不受实现变动影响」的理由。
+ (3) 写 `## 方法`:枚举面 = 04-PATTERNS.md 的 A1..A7 七组;判定口径 = 每条 `core.*` 读取先判「读的是数据端状态」还是「渲染侧素材/全局」;证据纪律 = 每条含 `file:line`、静态阅读不能定论者进「未能从阅读确定」;只读约束;并**实际执行** `git status --porcelain -- packages-user/client-base packages-user/client-modules`,把输出(预期为空)记入本节作为「扫描前基线」;写明允许只读子代理(D-09:仅回传文本、不得写文件)。
+ (4) 写 `## 发现` 表头(列序固定:ID / 接口名 / 所属文件:行 / 分类 / 问题描述 / 影响 / 置信 / 依据),并**实测**填入每类各一条(不得直接照抄 04-PATTERNS.md 的锚点定性,必须打开基准文件核对):
+ - ① 错配 `#04-01-M-01`:以 `packages-user/client-modules/src/action/move.ts:3`(`// @ts-expect-error 需要重构` + `import { HeroMover, IMoveController } from '@user/data-state'`)为起点,打开 `packages-user/data-state/src/types.ts`(必要时含该包 barrel)逐字确认这两个符号是否仍导出、形状/签名是否已变;「依据」列必须写出基准文件的 `file:line`。
+ - ③ 多余旧路径 `#04-01-L-01`:以 `packages-user/client-modules/src/render/ui/main.tsx:99-128` 的 legacy `core.status.*` / `core.isReplaying()` 读取为准,判定其应由 `ICoreState`(`packages-user/data-state/src/types.ts`)的哪个成员取代;「依据」列写出该成员的基准 `file:line`。
+ 若执行时锚点已漂移、或该读取已迁到新接口,改取同组其它锚点(同文件其它行或 04-PATTERNS.md §4/§5 的其它锚点),并在「未能从阅读确定」段记录替换原因;不得把未核实项写成缺陷。
+ (5) 写 `## ② 数据端缺失接口`:按 `### #04-01-G-N:标题(严重度)` + `- **现状**:` / `- **期望**:` 登记**经阅读确认**的「渲染端需要而数据端未提供」项;若阅读后确认不存在此类项,则本节不登记条目,改为写「本步未确认数据端缺失接口」的说明。节末**必须**写结论行,取以下之一:`**②节结论:** 本步确认 N 项数据端缺失接口(见上)` 或 `**②节结论:** 本步未确认数据端缺失接口;相关不确定项见「未能从阅读确定(未猜测)」`。不得为满足门禁而臆造缺失项。
+ (6) 写 `## 触发序列(举例)`(至少 1 条:渲染端调用 → 按旧形状解读 → 结果偏离的链路)、`## 判定为「匹配」的同类边界`(至少登记 `core.material.*` 素材族为何不算数据端错配,并给出判据)、`## 未能从阅读确定(未猜测)`(本 tracer 阶段至少登记「② 节是否有缺失项」的不确定性来源)、`## 处置`(含:本步只读;后续适配实施与双布局未在本 run 规划、由用户在对账结果出来后决定拆分;双布局既有资产 `packages-user/client-modules/src/render/use.ts` 的 `Orientation` / `onOrientationChange` 与 `packages-user/client-modules/src/shared.ts` 的布局常量仅登记为后续起点,不展开)。
+ (7) 全程不得修改任何被查源码;本任务只写审计文档一个文件。
+
+
+ node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u53d1\u73b0','## \u2461 \u6570\u636e\u7aef\u7f3a\u5931\u63a5\u53e3','## \u89e6\u53d1\u5e8f\u5217\uff08\u4e3e\u4f8b\uff09','## \u5224\u5b9a\u4e3a\u300c\u5339\u914d\u300d\u7684\u540c\u7c7b\u8fb9\u754c','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-01-[ML]-\d+\s*\|/.test(l));const bad=rows.filter(l=>!/:\d+/.test(l));if(rows.length<2||bad.length>0){console.error('BAD FINDING ROWS '+rows.length+'/'+bad.length);process.exit(1)}if(t.indexOf('#04-01-M-')<0||t.indexOf('#04-01-L-')<0){console.error('MISSING CATEGORY PREFIX');process.exit(1)}if(t.indexOf('\u2461\u8282\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK headings 8 findingRows '+rows.length)"
+ non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "BAD FINDING ROWS" / "MISSING CATEGORY PREFIX" / "GAP SECTION WITHOUT CONCLUSION"
+
+
+ - 审计文档存在于约定路径,且 8 个二级标题逐字存在(`## 背景` / `## 方法` / `## 发现` / `## ② 数据端缺失接口` / `## 触发序列(举例)` / `## 判定为「匹配」的同类边界` / `## 未能从阅读确定(未猜测)` / `## 处置`)
+ - 发现表列序为 `ID | 接口名 | 所属文件:行 | 分类 | 问题描述 | 影响 | 置信 | 依据`,且至少含 1 条 `#04-01-M-*` 与 1 条 `#04-01-L-*`,每条都带 `file:line` 锚点
+ - `#04-01-M-01` 的「依据」列引用 `packages-user/data-state/src/types.ts`(或该包基准)的具体行,且其定性来自实际阅读而非锚点照抄
+ - 「② 数据端缺失接口」节存在且含固定结论行(`②节结论`)
+ - `## 方法` 记录了 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 的扫描前基线输出
+ - 被查两个渲染端包零改动
+
+ 审计文档骨架与三分类各一条实测记录端到端贯通,结构/ID 门禁转绿;后续 Task 2/3 只需在同一模板上横向补齐,无需再改结构
+
+
+
+ Task 2:① 错配全量清点 —— 数据端 import / 契约桥 / 组合根继承 / 类型直连逐字比对 + 触发序列
+ .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md(Task 1 产出的现有内容,在同一模板上追加)
+ - .planning/phases/04-render-adaptation/04-PATTERNS.md(§2 A1 契约桥、§3 A2 组合根/单例、§5 A4 新接口对照面、§6 A5 类型直连;「既有重构标记的处置口径」)
+ - packages-user/data-common/src/types.ts(L0 `IDataCommon` 基准)
+ - packages-user/data-base/src/types.ts(L1 `IStateBase` 与 `IGameMap` / `IMapLayer` / `IGameMapHooks` 基准)
+ - packages-user/data-system/src/types.ts(L2 `IStateSystem` 基准)
+ - packages-user/data-state/src/types.ts(L3 `ICoreState` 基准)
+ - packages-user/data-common/src/replay/types.ts 与 packages-user/data-common/src/store/types.ts(录像/图块 raw-data 契约基准)
+ - packages-user/client-base/src/types.ts(`IClientBase extends ICoreState`)与 packages-user/client-modules/src/types.ts(实现层契约 extends 系统层契约)
+ - packages-user/client-modules/src/client.ts:46(`export class ClientCore extends CoreState implements IClientCore`)与 packages-user/client-modules/src/core.ts:1-6(单例 + 既有重构 TODO)
+ - packages-user/client-modules/src/render/map/{renderer,moving,element,status,vertex,types}.ts、packages-user/client-modules/src/render/elements/props.ts、packages-user/client-modules/src/render/map/extension/hero.ts(数据端类型/契约直连与 `@ts-expect-error 需要重构` 标记)
+
+
+ (1) 枚举被查两包内所有 `@user/data-common|data-base|data-system|data-state` 的 import(含 `import type`),逐处打开基准文件比对**接口名、成员、参数/返回签名**是否仍成立:A1 契约桥(`IClientBase extends ICoreState` 后 `ICoreState` 新增/改名/删成员会使桥断裂)、A2 组合根(`ClientCore extends CoreState` 的 override 签名、`loading.once('coreInit')` 生命周期、`core.firstData.name` 读取点)、A5 类型直连(`IGameMap` / `IMapLayer` / `IGameMapHooks` / `ISaveableContent` / `IHeroMoveController` 等)。
+ (2) 逐条登记为 `#04-01-M-N`(N 从 2 递增,`M-01` 已在 Task 1 占用):填入 接口名 / 所属文件:行 / 分类=`① 错配` / 问题描述(旧形状或旧签名具体差在哪)/ 影响 / 置信(高/中/低)/ 依据(基准文件 `file:line`)。**一条记录只归一类**;跨类则拆条。
+ (3) 带 `// @ts-expect-error 需要重构` 的 import(`action/move.ts:3`、`render/map/extension/hero.ts:5,7,9`、`render/ui/main.tsx:28`)是强线索但**不是结论**:必须与基准文件实签核对后才定性;核对后仍成立的保持「匹配」并写入边界段,核对后发现签名已变的登记为 `#04-01-M-*`。
+ (4) 把最高置信的 1–3 条错配的「调用 → 按旧形状解读 → 结果偏离」链路写进 `## 触发序列(举例)`(格式对照 07-LOADSTATE-AUDIT.md:29-35)。
+ (5) 若某 import 的定性无法从静态阅读确定(例如依赖运行时形状、或基准文件正在被用户并行改动),写入 `## 未能从阅读确定(未猜测)`,**不得**登记为错配。
+ (6) 允许派只读子代理分派 A1/A2/A4/A5 扫描组(D-09),子代理只回传文本,**不得写文件**;主执行者负责汇总与定性一致性。
+ (7) 只修改审计文档;不得改动任何生产源码,不得新增计划文件。
+
+
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-01-M-\d+\s*\|/.test(l));if(rows.length===0){console.error('NO MISMATCH ROWS');process.exit(1)}const noAnchor=rows.filter(l=>!/:\d+/.test(l));const noBase=rows.filter(l=>!/data-(common|base|system|state)\/src\//.test(l));if(noAnchor.length>0){console.error('MISMATCH ROW WITHOUT ANCHOR '+noAnchor.length);process.exit(1)}if(noBase.length>0){console.error('MISMATCH ROW WITHOUT BASELINE '+noBase.length);process.exit(1)}console.log('OK mismatch rows '+rows.length)"
+ non-zero exit, 或 stderr 含 "NO MISMATCH ROWS" / "MISMATCH ROW WITHOUT ANCHOR" / "MISMATCH ROW WITHOUT BASELINE"
+
+
+ - 每条 `#04-01-M-*` 记录都含 `file:line` 锚点(指向 `packages-user/client-base` 或 `packages-user/client-modules`)
+ - 每条 `#04-01-M-*` 的「依据」列引用至少一个 `data-common|data-base|data-system|data-state` 的 `src/*.ts` 基准文件
+ - A1 契约桥、A2 组合根、A5 类型直连三组均有实际比对结论(登记为错配或写明为匹配边界),A4 新接口调用作为「匹配」判据进入边界段
+ - `## 触发序列(举例)` 至少含 1 条错配链路,且与发现表中的 `#04-01-M-*` 对应
+ - 无法定论的 import 已进入「未能从阅读确定(未猜测)」,未被写成错配
+ - 被查两个渲染端包零改动;未新增任何计划文件
+
+ ① 错配在 A1/A2/A4/A5 四组上全量清点完毕,每条可追溯到接口名与基准 `file:line`;触发序列与边界段同步更新
+
+
+
+ Task 3:③ 多余旧路径全量清点 + ② 缺失节收口 + 匹配边界/未确定/处置定稿 + 只读与范围门禁
+ .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md(Task 1/2 产出,做定稿而非重写)
+ - .planning/phases/04-render-adaptation/04-PATTERNS.md(§4 A3 legacy `core.*` 读取锚点表与判定口径、§7 A6 双布局既有资产、Shared Patterns「分类口径」「证据纪律」「只读约束」)
+ - .planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md:37-46(「判定为安全/匹配的同类边界」写法)与 :48-53(「未能从阅读确定(未猜测)」写法)
+ - packages-user/data-base/src/game.ts:155,166,266-267(`hook`、`GameListener`、`@deprecated gameListener`)
+ - packages-user/data-state/src/core.ts(顶层装配与 legacy bridge 落点)、packages-user/data-state/src/ins.ts(`state` 单例)
+ - packages-user/entry-data/src/mota.ts:125-136(`r()` / `rf()` 渲染调用门控)
+ - packages-user/data-system/src/path/types.ts 与 packages-user/data-state/src/types.ts(判定 legacy 读取的替代接口目标)
+ - packages-user/client-modules/src/action/move.ts:66-67,97,123,156、packages-user/client-modules/src/render/ui/{main,statistics,viewmap,settings,save,toolbar,statusBar}.tsx、packages-user/client-modules/src/render/components/choices.tsx、packages-user/client-modules/src/fallback/load.ts、packages-user/client-modules/src/render/utils/saves.ts(A3 锚点面)
+ - packages-user/client-base/src/load/loader.ts:103,129,151,167,188,284,418 与 packages-user/client-modules/src/render/elements/{cache,misc}.ts、packages-user/client-modules/src/render/components/misc.tsx(渲染侧素材族,用于「匹配」判据)
+ - packages-user/client-modules/src/render/use.ts:22-52 与 packages-user/client-modules/src/shared.ts:33-97(双布局既有资产)
+
+
+ (1) 按 04-PATTERNS.md §4 的 A3 锚点表逐处核对 legacy `core.*` 读取,对每处先判定「读的是数据端状态」还是「渲染侧素材/全局」:
+ - 数据端状态类(如 `core.isReplaying()` / `core.isPlaying()` / `core.status.*` / `core.getNextLvUpNeed()` / `core.itemCount()` / `core.getLvName()` / `core.maps.*` / `core.flags.*` / `core.firstData.*` / `core.startGame()` / `core.status.replay.*`)→ 登记为 `#04-01-L-N`(N 从 2 递增,`L-01` 已在 Task 1 占用),分类=`③ 多余旧路径`,并在「依据」列写出应取代它的新接口/基准 `file:line`;
+ - 纯渲染侧素材/全局(`core.material.images.*`、`core.materials`、`core.icons.icons` 等)→ **不登记为缺陷**,写入 `## 判定为「匹配」的同类边界` 并给出判据(对照 04-PATTERNS.md 「不得因出现 `core.` 就一律登记」)。
+ 每条记录含 接口名 / 所属文件:行 / 分类 / 问题描述 / 影响 / 置信 / 依据。
+ (2) 若同一处 legacy 读取同时是「形状已变」而非单纯旧路径,按「一条只归一类」拆条(错配归 `#04-01-M-*`,旧路径归 `#04-01-L-*`),并在两条之间互相引用 ID。
+ (3) `## ② 数据端缺失接口` 定稿:逐条确认/补全 `### #04-01-G-N` 的「现状 / 期望」;最终结论行取固定两式之一。凡不能确认的候选一律不登记为缺失项,转入「未能从阅读确定(未猜测)」。
+ (4) 定稿 `## 判定为「匹配」的同类边界`(至少覆盖 `core.material.*` 素材族、`hook`/`loading` 订阅新接口用法),定稿 `## 未能从阅读确定(未猜测)`(逐条说明为何静态阅读不能定论),定稿 `## 处置`(本步只读、零生产改动;后续适配与双布局未在本 run 规划、由用户决定拆分;`render/use.ts` 的 `Orientation`/`onOrientationChange` 与 `shared.ts` 布局常量登记为后续双布局起点,不展开实施)。
+ (5) 全表 ID 唯一性自检:`#04-01-M-*` / `#04-01-L-*` / `#04-01-G-*` 各自序号连续且不重复;每条分类列与 ID 前缀一致(M=① 错配、L=③ 多余旧路径、G=② 数据端缺失)。
+ (6) 门禁与提交:运行只读门禁与范围门禁(见 ``);随后以 `docs(04-01): 渲染端 ↔ 数据端接口对账(只读清点,D-01 第一步)` 提交**仅**审计文档,不得提交任何被查源码改动,不得新增后续步骤的计划文件。
+
+
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-01-L-\d+\s*\|/.test(l));if(rows.length===0){console.error('NO LEGACY ROWS');process.exit(1)}const bad=rows.filter(l=>!/:\d+/.test(l));if(bad.length>0){console.error('LEGACY ROW WITHOUT ANCHOR '+bad.length);process.exit(1)}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l:l.trim(),i:i})).filter(o=>/^### #04-01-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+7).join('\n');if(body.indexOf('\u73b0\u72b6')<0||body.indexOf('\u671f\u671b')<0){console.error('GAP ENTRY MISSING FIELDS');process.exit(1)}}if(t.indexOf('\u2461\u8282\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK legacy rows '+rows.length+' gaps '+gh.length)"
+ non-zero exit, 或 stderr 含 "NO LEGACY ROWS" / "LEGACY ROW WITHOUT ANCHOR" / "GAP ENTRY MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION"
+ git status --porcelain -- packages-user/client-base packages-user/client-modules
+ stdout 非空(任一被查渲染端包出现改动/新增行)—— 违反只读约束
+ node -e "const cp=require('child_process');const out=cp.execSync('git status --porcelain -- .planning/phases/04-render-adaptation',{encoding:'utf8'});const extra=out.split(/\r?\n/).filter(l=>/\d{2}-\d{2}-PLAN\.md/.test(l)&&l.indexOf('04-01-PLAN.md')<0);if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.length);process.exit(1)}console.log('OK plan scope')"
+ non-zero exit, 或 stderr 含 "UNEXPECTED PLAN FILES"(出现了 04-01 以外的计划文件)
+
+
+ - A3 锚点表的每一处 `core.*` 读取都已判定并落位:数据端状态类登记为 `#04-01-L-*`、素材类进入「判定为『匹配』的同类边界」,无遗漏、无误报
+ - 每条 `#04-01-L-*` 含 `file:line` 锚点,并在「依据」列指出应取代它的新接口/基准位置
+ - 「② 数据端缺失接口」节定稿:条目(如有)均含「现状 / 期望」,且节末结论行取固定两式之一;未确认候选在「未能从阅读确定(未猜测)」中逐条列出
+ - `## 判定为「匹配」的同类边界` / `## 未能从阅读确定(未猜测)` / `## 处置` 三段均非空且符合 07-LOADSTATE-AUDIT 的写法;`## 处置` 含双布局资产登记与「后续步骤未在本 run 规划」的明确表述
+ - ID 唯一且前缀与分类列一一对应(M=① 错配、L=③ 多余旧路径、G=② 数据端缺失)
+ - `git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空;`.planning/phases/04-render-adaptation` 下无 04-01 以外的计划文件
+ - 审计文档已以 `docs(04-01)` 提交,提交内容仅含该文档
+
+ 三类记录全部落位且互斥、匹配边界与未确定段定稿、缺失节收口、只读与范围门禁全绿、审计文档已提交;用户可据此决定阶段 4 剩余工作的拆分
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| 数据端接口基准 → 审计文档 | 只读引用 `data-*` 的 `types.ts` / `core.ts` / `ins.ts`;基准不变,仅记录差异 |
+| 渲染端源码 → 审计文档 | 只读引用被查两包源码;不得产生任何写回 |
+| 执行者 → 仓库工作树 | 本 run 唯一允许写入的文件是审计文档;被查渲染端包必须保持零改动 |
+| 审计文档 → 后续适配计划 | 「处置」段的结论被下游用户/计划消费,错报会直接污染阶段 4 的拆分决策 |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-04-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1–3 均为只读;Task 3 门禁 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 必须为空,非空即失败 |
+| T-04-02 | Repudiation | 发现表记录(无锚点则不可追溯) | medium | mitigate | 每条记录强制含 `file:line`;`#04-01-M-*` 强制在「依据」列引用数据端基准文件;由 Task 2/3 的 `` 门禁机器判定 |
+| T-04-03 | Spoofing | 「判定为『匹配』的同类边界」段 | medium | mitigate | 该段必须给出判据(`core.material.*` 素材族 vs 数据端状态),且「未能从阅读确定」段为强制段——不得把未核实项伪装成匹配或缺陷 |
+| T-04-04 | Elevation of Privilege | 被查范围(`packages/**`、`entry-client`、`legacy-plugin-*`、`data-fallback`、`data-*` 本体) | medium | mitigate | 范围由 D-03/D-04 限定;范围外路径不登记为被查项,只能作为理解接线方式的参考 |
+| T-04-05 | Information Disclosure | 审计文档引用的源码行与接口名 | low | accept | 记录只含接口名、文件行号、问题描述与影响,不含凭据/密钥;如需提及敏感常量仅以标识符呈现 |
+| T-04-06 | Denial of Service | 只读子代理越权写入(D-09) | low | mitigate | Task 2/3 明确子代理仅回传文本、不得写文件;最终由主执行者的只读门禁统一验证 |
+| T-04-SC | Tampering | npm / pnpm 依赖安装 | high | mitigate | 本计划零依赖变更、不修改任何 `package.json`;出现安装需求即暂停并要求用户确认包合法性(blocking human checkpoint),不得自动继续 |
+
+
+
+1. 结构门禁(Task 1):审计文档 8 个二级标题逐字存在,`#04-01-M-*` / `#04-01-L-*` 各至少 1 条且每条含 `file:line`,「② 数据端缺失接口」节含固定结论行。
+2. 错配门禁(Task 2):每条 `#04-01-M-*` 含 `file:line` 且「依据」列引用 `data-common|data-base|data-system|data-state` 的 `src/*.ts`。
+3. 旧路径与缺失节门禁(Task 3):每条 `#04-01-L-*` 含 `file:line`;每个 `### #04-01-G-*` 之后紧跟的 6 行内含「现状」「期望」;② 节结论行存在。
+4. 只读门禁(Task 3):`git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出为空。
+5. 范围门禁(Task 3):`.planning/phases/04-render-adaptation` 下除 `04-01-PLAN.md` 外无其它 `*-PLAN.md`(后续步骤未被规划)。
+6. 人工核对:`## 判定为「匹配」的同类边界` 的素材族判据、`## 未能从阅读确定(未猜测)` 的逐条理由、`## 处置` 的双布局资产登记与「后续步骤未在本 run 规划」表述均齐备。
+7. 提交:`docs(04-01)` 仅含审计文档。
+
+
+
+- `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` 按 8 段骨架产出,三类记录(① 错配 `#04-01-M-*` / ② 数据端缺失 `#04-01-G-*`(独立节)/ ③ 多余旧路径 `#04-01-L-*`)互斥且完整,每条精确到接口名 + `file:line` + 描述 + 影响(D-06/D-07/D-08)。
+- 对账以数据端接口签名为基准(D-04/D-05),每条错配可追溯到基准文件行;未受用户并行修改实现的影响。
+- 已确认匹配的读取(`core.material.*` 素材族、`hook`/`loading` 订阅)进入边界段而非误报;无法定论的链路进入「未能从阅读确定(未猜测)」而非写成缺陷。
+- 被查两个渲染端包零改动;本 run 只新增审计文档一个文件,未规划、未实施后续适配与双布局(D-01/D-02/D-03)。
+- 双布局既有资产已在「处置」段登记为后续起点(REND-02 的已知落点),且明确「验收边界待用户补齐」(REND-01/REND-02 的 specless fallback flagged assumptions)。
+
+
+
+
From b0592d8ffe6d379c0511fb51d53f3ed8149b57cb Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 09:41:24 +0800
Subject: [PATCH 005/173] docs(04): mark the read-only audit plan as reversible
---
.planning/phases/04-render-adaptation/04-01-PLAN.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.planning/phases/04-render-adaptation/04-01-PLAN.md b/.planning/phases/04-render-adaptation/04-01-PLAN.md
index e0c4785..19ba6bb 100644
--- a/.planning/phases/04-render-adaptation/04-01-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-01-PLAN.md
@@ -143,6 +143,7 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
(6) 写 `## 触发序列(举例)`(至少 1 条:渲染端调用 → 按旧形状解读 → 结果偏离的链路)、`## 判定为「匹配」的同类边界`(至少登记 `core.material.*` 素材族为何不算数据端错配,并给出判据)、`## 未能从阅读确定(未猜测)`(本 tracer 阶段至少登记「② 节是否有缺失项」的不确定性来源)、`## 处置`(含:本步只读;后续适配实施与双布局未在本 run 规划、由用户在对账结果出来后决定拆分;双布局既有资产 `packages-user/client-modules/src/render/use.ts` 的 `Orientation` / `onOrientationChange` 与 `packages-user/client-modules/src/shared.ts` 的布局常量仅登记为后续起点,不展开)。
(7) 全程不得修改任何被查源码;本任务只写审计文档一个文件。
+ 审计文档是只读清点的记录,可随时修订或废弃;它不引入生产代码、公共接口或契约约束,因此这一步的任何取舍都不难回退。
node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u53d1\u73b0','## \u2461 \u6570\u636e\u7aef\u7f3a\u5931\u63a5\u53e3','## \u89e6\u53d1\u5e8f\u5217\uff08\u4e3e\u4f8b\uff09','## \u5224\u5b9a\u4e3a\u300c\u5339\u914d\u300d\u7684\u540c\u7c7b\u8fb9\u754c','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-01-[ML]-\d+\s*\|/.test(l));const bad=rows.filter(l=>!/:\d+/.test(l));if(rows.length<2||bad.length>0){console.error('BAD FINDING ROWS '+rows.length+'/'+bad.length);process.exit(1)}if(t.indexOf('#04-01-M-')<0||t.indexOf('#04-01-L-')<0){console.error('MISSING CATEGORY PREFIX');process.exit(1)}if(t.indexOf('\u2461\u8282\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK headings 8 findingRows '+rows.length)"
non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "BAD FINDING ROWS" / "MISSING CATEGORY PREFIX" / "GAP SECTION WITHOUT CONCLUSION"
From 6d8075166a0469783835272f2a4fa04c283ee71e Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 09:55:47 +0800
Subject: [PATCH 006/173] fix(04-render-adaptation): revise 04-01 plan per
checker feedback (re-measure estimate, CRLF + tracked-anchor gates)
---
.../phases/04-render-adaptation/04-01-PLAN.md | 64 ++++++++++++++-----
1 file changed, 48 insertions(+), 16 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-01-PLAN.md b/.planning/phases/04-render-adaptation/04-01-PLAN.md
index 19ba6bb..8f83006 100644
--- a/.planning/phases/04-render-adaptation/04-01-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-01-PLAN.md
@@ -11,13 +11,13 @@ requirements:
- REND-01
- REND-02
estimate:
- tokens: 120000
- raw_tokens: 120000
+ tokens: 62000
+ raw_tokens: 62000
tasks: 3
confidence: low
must_haves:
truths:
- - "`.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` 存在,且含 `07-LOADSTATE-AUDIT.md` 式骨架:「背景 / 方法 / 发现 / 触发序列(举例) / 判定为「匹配」的同类边界 / 未能从阅读确定(未猜测) / 处置」七段,加上独立成节的「② 数据端缺失接口」,共 8 个二级标题"
+ - "`.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` 存在,且含 `07-LOADSTATE-AUDIT.md` 式骨架:「背景 / 方法 / 发现 / 触发序列(举例) / 判定为「匹配」的同类边界 / 未能从阅读确定(未猜测) / 处置」七段,加上独立成节的「② 数据端缺失接口」,共 8 个二级标题;全文为 CRLF 换行(dev.md「其他要求」)"
- "发现表每条记录精确到「接口名 + 所属文件:行 + 分类 + 问题描述 + 影响 + 置信 + 依据」(D-07);分类只取 ① 错配(`#04-01-M-N`)或 ③ 多余旧路径(`#04-01-L-N`),② 数据端缺失另走独立节(D-08)"
- "「② 数据端缺失接口」为独立节,每条以 `### #04-01-G-N:标题(严重度)` 登记并含「现状 / 期望」两段;节末有固定结论行(确认 N 项,或明示本步未确认缺失接口)"
- "每条 `#04-01-M-*` / `#04-01-L-*` 记录的「所属文件:行」锚点指向 `packages-user/client-base` 或 `packages-user/client-modules` 的 tracked 源码行;每条 `#04-01-M-*` 在「依据」列引用至少一个数据端基准文件(`data-common` / `data-base` / `data-system` / `data-state` 的 `src/*.ts`),支持 D-05「以接口签名为准」"
@@ -28,7 +28,7 @@ must_haves:
- path: .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
provides: "渲染端 ↔ 数据端接口对账账本:8 段骨架 + 三分类记录表 + 独立的数据端缺失接口节 + 匹配边界 + 未确定段 + 处置结论;供用户决定后续适配与双布局如何拆分"
key_links:
- - "发现表「所属文件:行」列 ↔ `packages-user/client-{base,modules}/src/**` 的 tracked 源码行(经 `git ls-files` 验证,不得指向 gitignored 镜像路径)"
+ - "发现表「所属文件:行」列 ↔ `packages-user/client-{base,modules}/src/**` 的 tracked 源码行(不得指向 gitignored 镜像路径;由 Task 3 `` 的「锚点可追溯门禁」实际执行 `git ls-files packages-user/client-base packages-user/client-modules` 校验审计文档引用的全部 client 路径)"
- "每条 `#04-01-M-*` 的「依据」列 ↔ 数据端 `types.ts` / `core.ts` / `ins.ts` 的现行接口签名(D-04/D-05);接口名必须能在基准文件中逐字命中"
- "「判定为『匹配』的同类边界」段 ↔ 04-PATTERNS.md §4 的 A3 组判据(`core.*` 读取先判「数据端状态」还是「渲染侧素材」),防止把素材读成数据端错配"
- "「未能从阅读确定(未猜测)」段 ↔ 07-LOADSTATE-AUDIT.md:48-53 的证据纪律(静态阅读不能定论的链路只登记、不判定)"
@@ -89,6 +89,27 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
| CONTEXT | D-08 | 单文档分节;缺失接口独立成节 | 04-01 | COVERED | `## ② 数据端缺失接口` 独立节 + 固定结论行 |
| CONTEXT | D-09 | 允许只读子代理 | 04-01 | COVERED | Task 2/3 允许派只读子代理分派 A1..A7 扫描组,仅回传文本,不得写文件 |
| RESEARCH | — | (本阶段无 RESEARCH.md) | — | N/A | Phase 4 未产出 RESEARCH.md,无研究项需覆盖 |
+
+## Estimate Basis(估算依据 — revision 2026-09-18)
+
+`estimate-check` 初报 `tokens: 120000` 超出 `smart_zone_tokens` 100000 预算(ratio 1.20,`confidence: low`,`sample_count: 0`)。本计划按「执行者实际触及量」重测(chars/4;`estimate-calibration` factor 1.0,项目无历史样本,故 `confidence` 仍为 low),并在本次修订中把重型整文件读取改为**强制委派**,使单计划落在预算内:
+
+| 部分 | 口径 | chars | ≈tokens |
+|------|------|-------|---------|
+| Task 1 读 + 写 | 结构参考文件 + 基准 `types.ts` + 骨架与两条实测记录 | ~53k | ~13k |
+| Task 2 读 + 写 | 契约桥 / 组合根 / 类型直连基准 + 子代理事实表 | ~49k | ~12k |
+| Task 3 读 + 写 | legacy 锚点切片 + 素材匹配判据 + ②节与处置定稿 | ~61k | ~15k |
+| 三项任务固定开销 | 计划上下文 / 校验命令 / 编辑 | — | ~9k |
+| **合计** | | | **~49k–62k** |
+
+**为何 120000 不可直接采信(不可操作的原因):** 初报值来自「全量整文件读」的朴素加总(被查文件全量约 450k chars ≈ 126k tokens),它假设执行者亲自读完整文件;但本计划按 D-09 把重型整文件组**强制**交由只读子代理(详见 Task 2 action (6) 与 Task 3 action (1)):
+
+- Task 2 委派组:`render/map/{renderer,vertex,types,moving,element,status}.ts`、`render/elements/props.ts`、`render/map/extension/hero.ts`;
+- Task 3 委派组:`render/ui/{main,statistics,viewmap,settings,save,toolbar,statusBar}.tsx`、`render/components/{choices,misc}.tsx`、`render/utils/saves.ts`、`fallback/load.ts`、`render/elements/{cache,misc}.ts`、`client-base/src/load/loader.ts`。
+
+子代理只回传「接口名 + 所属 `file:line` + 数据端基准签名」或「legacy 调用 + 所属 `file:line` + 建议替代接口」事实表(不贴大段源码、不写文件),执行者据事实表定性并登记,因此执行者上下文是上表而非全量读之和。审计覆盖面(A1..A7)不变,仅读法改变。
+
+**为何不拆分为多计划:** D-01/D-02 锁定本次第一步为**单一**只读对账计划(04-01,交付物 `04-RENDER-INTERFACE-AUDIT.md`);拆分会违背用户决策。故对 `scope_sanity` 的非阻塞建议走「重测 + 强制委派(D-09)」路线而非 re-slice:`budget` 这一属性由**执行者上下文**满足,本计划的封面估算已按该口径给出。
@@ -132,7 +153,7 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
- dev.md(「双端分离」:`@user/client-base` 为系统层、`@user/client-modules` 为实现层)
- (1) 新建 `04-RENDER-INTERFACE-AUDIT.md`,先写标题与 4 行元信息块(`**日期:** 2026-09-18`;`**范围:**` 被查 `packages-user/client-base` + `packages-user/client-modules`、`data-*` 为接口基准;`**性质:** 只读清点,未修改任何代码`;`**接口基准:**` 各包 `types.ts` / `core.ts` / `ins.ts` 的现行签名,D-04/D-05)。
+ (1) 新建 `04-RENDER-INTERFACE-AUDIT.md`,先写标题与 4 行元信息块(`**日期:** 2026-09-18`;`**范围:**` 被查 `packages-user/client-base` + `packages-user/client-modules`、`data-*` 为接口基准;`**性质:** 只读清点,未修改任何代码`;`**接口基准:**` 各包 `types.ts` / `core.ts` / `ins.ts` 的现行签名,D-04/D-05)。**全文使用 CRLF 换行**(dev.md「其他要求」:换行使用 `CRLF` 格式);Task 2/3 追加内容同样保持 CRLF,不得出现孤立 `LF`。
(2) 写 `## 背景`:REND-01 / REND-02、D-01 增量规划、本次只清点不实施、以及「对账以接口为准、不受实现变动影响」的理由。
(3) 写 `## 方法`:枚举面 = 04-PATTERNS.md 的 A1..A7 七组;判定口径 = 每条 `core.*` 读取先判「读的是数据端状态」还是「渲染侧素材/全局」;证据纪律 = 每条含 `file:line`、静态阅读不能定论者进「未能从阅读确定」;只读约束;并**实际执行** `git status --porcelain -- packages-user/client-base packages-user/client-modules`,把输出(预期为空)记入本节作为「扫描前基线」;写明允许只读子代理(D-09:仅回传文本、不得写文件)。
(4) 写 `## 发现` 表头(列序固定:ID / 接口名 / 所属文件:行 / 分类 / 问题描述 / 影响 / 置信 / 依据),并**实测**填入每类各一条(不得直接照抄 04-PATTERNS.md 的锚点定性,必须打开基准文件核对):
@@ -147,6 +168,8 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u53d1\u73b0','## \u2461 \u6570\u636e\u7aef\u7f3a\u5931\u63a5\u53e3','## \u89e6\u53d1\u5e8f\u5217\uff08\u4e3e\u4f8b\uff09','## \u5224\u5b9a\u4e3a\u300c\u5339\u914d\u300d\u7684\u540c\u7c7b\u8fb9\u754c','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-01-[ML]-\d+\s*\|/.test(l));const bad=rows.filter(l=>!/:\d+/.test(l));if(rows.length<2||bad.length>0){console.error('BAD FINDING ROWS '+rows.length+'/'+bad.length);process.exit(1)}if(t.indexOf('#04-01-M-')<0||t.indexOf('#04-01-L-')<0){console.error('MISSING CATEGORY PREFIX');process.exit(1)}if(t.indexOf('\u2461\u8282\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK headings 8 findingRows '+rows.length)"
non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "BAD FINDING ROWS" / "MISSING CATEGORY PREFIX" / "GAP SECTION WITHOUT CONCLUSION"
+ node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md';const s=fs.readFileSync(p,'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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(审计文档出现非 CRLF 换行,违反 dev.md「换行使用 CRLF 格式」)
- 审计文档存在于约定路径,且 8 个二级标题逐字存在(`## 背景` / `## 方法` / `## 发现` / `## ② 数据端缺失接口` / `## 触发序列(举例)` / `## 判定为「匹配」的同类边界` / `## 未能从阅读确定(未猜测)` / `## 处置`)
@@ -154,6 +177,7 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
- `#04-01-M-01` 的「依据」列引用 `packages-user/data-state/src/types.ts`(或该包基准)的具体行,且其定性来自实际阅读而非锚点照抄
- 「② 数据端缺失接口」节存在且含固定结论行(`②节结论`)
- `## 方法` 记录了 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 的扫描前基线输出
+ - 审计文档全文为 CRLF 换行(dev.md「其他要求」),Task 1 的 CRLF 门禁转绿
- 被查两个渲染端包零改动
审计文档骨架与三分类各一条实测记录端到端贯通,结构/ID 门禁转绿;后续 Task 2/3 只需在同一模板上横向补齐,无需再改结构
@@ -164,7 +188,7 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
- .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md(Task 1 产出的现有内容,在同一模板上追加)
- - .planning/phases/04-render-adaptation/04-PATTERNS.md(§2 A1 契约桥、§3 A2 组合根/单例、§5 A4 新接口对照面、§6 A5 类型直连;「既有重构标记的处置口径」)
+ - .planning/phases/04-render-adaptation/04-PATTERNS.md(§2 A1 契约桥、§3 A2 组合根/单例、§5 A4 新接口对照面、§6 A5 类型直连;「既有重构标记的处置口径」;只读所列章节,不读全文件)
- packages-user/data-common/src/types.ts(L0 `IDataCommon` 基准)
- packages-user/data-base/src/types.ts(L1 `IStateBase` 与 `IGameMap` / `IMapLayer` / `IGameMapHooks` 基准)
- packages-user/data-system/src/types.ts(L2 `IStateSystem` 基准)
@@ -172,7 +196,7 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
- packages-user/data-common/src/replay/types.ts 与 packages-user/data-common/src/store/types.ts(录像/图块 raw-data 契约基准)
- packages-user/client-base/src/types.ts(`IClientBase extends ICoreState`)与 packages-user/client-modules/src/types.ts(实现层契约 extends 系统层契约)
- packages-user/client-modules/src/client.ts:46(`export class ClientCore extends CoreState implements IClientCore`)与 packages-user/client-modules/src/core.ts:1-6(单例 + 既有重构 TODO)
- - packages-user/client-modules/src/render/map/{renderer,moving,element,status,vertex,types}.ts、packages-user/client-modules/src/render/elements/props.ts、packages-user/client-modules/src/render/map/extension/hero.ts(数据端类型/契约直连与 `@ts-expect-error 需要重构` 标记)
+ - packages-user/client-modules/src/render/map/{renderer,moving,element,status,vertex,types}.ts、packages-user/client-modules/src/render/elements/props.ts、packages-user/client-modules/src/render/map/extension/hero.ts(数据端类型/契约直连与 `@ts-expect-error 需要重构` 标记;整文件扫描按 action (6) 委派给只读子代理,执行者只在存疑处按 `file:line` 切片复核)
(1) 枚举被查两包内所有 `@user/data-common|data-base|data-system|data-state` 的 import(含 `import type`),逐处打开基准文件比对**接口名、成员、参数/返回签名**是否仍成立:A1 契约桥(`IClientBase extends ICoreState` 后 `ICoreState` 新增/改名/删成员会使桥断裂)、A2 组合根(`ClientCore extends CoreState` 的 override 签名、`loading.once('coreInit')` 生命周期、`core.firstData.name` 读取点)、A5 类型直连(`IGameMap` / `IMapLayer` / `IGameMapHooks` / `ISaveableContent` / `IHeroMoveController` 等)。
@@ -180,7 +204,7 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
(3) 带 `// @ts-expect-error 需要重构` 的 import(`action/move.ts:3`、`render/map/extension/hero.ts:5,7,9`、`render/ui/main.tsx:28`)是强线索但**不是结论**:必须与基准文件实签核对后才定性;核对后仍成立的保持「匹配」并写入边界段,核对后发现签名已变的登记为 `#04-01-M-*`。
(4) 把最高置信的 1–3 条错配的「调用 → 按旧形状解读 → 结果偏离」链路写进 `## 触发序列(举例)`(格式对照 07-LOADSTATE-AUDIT.md:29-35)。
(5) 若某 import 的定性无法从静态阅读确定(例如依赖运行时形状、或基准文件正在被用户并行改动),写入 `## 未能从阅读确定(未猜测)`,**不得**登记为错配。
- (6) 允许派只读子代理分派 A1/A2/A4/A5 扫描组(D-09),子代理只回传文本,**不得写文件**;主执行者负责汇总与定性一致性。
+ (6) **必须**按 D-09 派只读子代理完成重型整文件扫描(对应 Estimate Basis 的重测口径):委派 `render/map/renderer.ts`、`render/map/vertex.ts`、`render/map/types.ts`、`render/map/moving.ts`、`render/map/element.ts`、`render/map/status.ts`、`render/elements/props.ts`、`render/map/extension/hero.ts`。子代理只回传事实表(每行:接口名 + 所属 `file:line` + 数据端基准签名或现行 import 原文),**不得写文件、不贴大段源码**;主执行者据事实表定性、登记并保证一致性,**不整文件读取上述八个文件**,只在事实表存疑时按 `file:line` 附近切片复核。
(7) 只修改审计文档;不得改动任何生产源码,不得新增计划文件。
@@ -203,18 +227,19 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
- .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md(Task 1/2 产出,做定稿而非重写)
- - .planning/phases/04-render-adaptation/04-PATTERNS.md(§4 A3 legacy `core.*` 读取锚点表与判定口径、§7 A6 双布局既有资产、Shared Patterns「分类口径」「证据纪律」「只读约束」)
+ - .planning/phases/04-render-adaptation/04-PATTERNS.md(§4 A3 legacy `core.*` 读取锚点表与判定口径、§7 A6 双布局既有资产、Shared Patterns「分类口径」「证据纪律」「只读约束」;只读所列章节,不读全文件)
- .planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md:37-46(「判定为安全/匹配的同类边界」写法)与 :48-53(「未能从阅读确定(未猜测)」写法)
- packages-user/data-base/src/game.ts:155,166,266-267(`hook`、`GameListener`、`@deprecated gameListener`)
- packages-user/data-state/src/core.ts(顶层装配与 legacy bridge 落点)、packages-user/data-state/src/ins.ts(`state` 单例)
- packages-user/entry-data/src/mota.ts:125-136(`r()` / `rf()` 渲染调用门控)
- packages-user/data-system/src/path/types.ts 与 packages-user/data-state/src/types.ts(判定 legacy 读取的替代接口目标)
- - packages-user/client-modules/src/action/move.ts:66-67,97,123,156、packages-user/client-modules/src/render/ui/{main,statistics,viewmap,settings,save,toolbar,statusBar}.tsx、packages-user/client-modules/src/render/components/choices.tsx、packages-user/client-modules/src/fallback/load.ts、packages-user/client-modules/src/render/utils/saves.ts(A3 锚点面)
- - packages-user/client-base/src/load/loader.ts:103,129,151,167,188,284,418 与 packages-user/client-modules/src/render/elements/{cache,misc}.ts、packages-user/client-modules/src/render/components/misc.tsx(渲染侧素材族,用于「匹配」判据)
+ - packages-user/client-modules/src/action/move.ts:66-67,97,123,156、packages-user/client-modules/src/render/ui/{main,statistics,viewmap,settings,save,toolbar,statusBar}.tsx、packages-user/client-modules/src/render/components/choices.tsx、packages-user/client-modules/src/fallback/load.ts、packages-user/client-modules/src/render/utils/saves.ts(A3 锚点面;整文件扫描按 action (1) 委派给只读子代理,执行者只按需切片)
+ - packages-user/client-base/src/load/loader.ts:103,129,151,167,188,284,418 与 packages-user/client-modules/src/render/elements/{cache,misc}.ts、packages-user/client-modules/src/render/components/misc.tsx(渲染侧素材族,用于「匹配」判据;整文件扫描按 action (1) 委派给只读子代理,执行者只按需切片)
- packages-user/client-modules/src/render/use.ts:22-52 与 packages-user/client-modules/src/shared.ts:33-97(双布局既有资产)
- (1) 按 04-PATTERNS.md §4 的 A3 锚点表逐处核对 legacy `core.*` 读取,对每处先判定「读的是数据端状态」还是「渲染侧素材/全局」:
+ (1) 先按 D-09 派只读子代理完成 Task 3 的重型整文件扫描(对应 Estimate Basis 的重测口径):委派 `render/ui/{main,statistics,viewmap,settings,save,toolbar,statusBar}.tsx`、`render/components/{choices,misc}.tsx`、`render/utils/saves.ts`、`fallback/load.ts`、`render/elements/{cache,misc}.ts`、`client-base/src/load/loader.ts`。子代理只回传事实表(每行:legacy 调用 + 所属 `file:line` + 建议替代接口/基准位置),**不得写文件、不贴大段源码**;执行者**不整文件读取上述文件**,只在事实表存疑时按 `file:line` 附近切片复核。
+ 然后按 04-PATTERNS.md §4 的 A3 锚点表逐处核对 legacy `core.*` 读取,对每处先判定「读的是数据端状态」还是「渲染侧素材/全局」:
- 数据端状态类(如 `core.isReplaying()` / `core.isPlaying()` / `core.status.*` / `core.getNextLvUpNeed()` / `core.itemCount()` / `core.getLvName()` / `core.maps.*` / `core.flags.*` / `core.firstData.*` / `core.startGame()` / `core.status.replay.*`)→ 登记为 `#04-01-L-N`(N 从 2 递增,`L-01` 已在 Task 1 占用),分类=`③ 多余旧路径`,并在「依据」列写出应取代它的新接口/基准 `file:line`;
- 纯渲染侧素材/全局(`core.material.images.*`、`core.materials`、`core.icons.icons` 等)→ **不登记为缺陷**,写入 `## 判定为「匹配」的同类边界` 并给出判据(对照 04-PATTERNS.md 「不得因出现 `core.` 就一律登记」)。
每条记录含 接口名 / 所属文件:行 / 分类 / 问题描述 / 影响 / 置信 / 依据。
@@ -222,11 +247,15 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
(3) `## ② 数据端缺失接口` 定稿:逐条确认/补全 `### #04-01-G-N` 的「现状 / 期望」;最终结论行取固定两式之一。凡不能确认的候选一律不登记为缺失项,转入「未能从阅读确定(未猜测)」。
(4) 定稿 `## 判定为「匹配」的同类边界`(至少覆盖 `core.material.*` 素材族、`hook`/`loading` 订阅新接口用法),定稿 `## 未能从阅读确定(未猜测)`(逐条说明为何静态阅读不能定论),定稿 `## 处置`(本步只读、零生产改动;后续适配与双布局未在本 run 规划、由用户决定拆分;`render/use.ts` 的 `Orientation`/`onOrientationChange` 与 `shared.ts` 布局常量登记为后续双布局起点,不展开实施)。
(5) 全表 ID 唯一性自检:`#04-01-M-*` / `#04-01-L-*` / `#04-01-G-*` 各自序号连续且不重复;每条分类列与 ID 前缀一致(M=① 错配、L=③ 多余旧路径、G=② 数据端缺失)。
- (6) 门禁与提交:运行只读门禁与范围门禁(见 ``);随后以 `docs(04-01): 渲染端 ↔ 数据端接口对账(只读清点,D-01 第一步)` 提交**仅**审计文档,不得提交任何被查源码改动,不得新增后续步骤的计划文件。
+ (6) 门禁与提交:运行只读门禁、**锚点可追溯门禁**(`git ls-files packages-user/client-base packages-user/client-modules`,确认审计文档引用的每个 `packages-user/client-{base,modules}/**` 路径都是 tracked 源码、未指向 gitignored 镜像路径)、CRLF 门禁与范围门禁(见 ``);追加内容保持 CRLF。随后以 `docs(04-01): 渲染端 ↔ 数据端接口对账(只读清点,D-01 第一步)` 提交**仅**审计文档,不得提交任何被查源码改动,不得新增后续步骤的计划文件。
node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-01-L-\d+\s*\|/.test(l));if(rows.length===0){console.error('NO LEGACY ROWS');process.exit(1)}const bad=rows.filter(l=>!/:\d+/.test(l));if(bad.length>0){console.error('LEGACY ROW WITHOUT ANCHOR '+bad.length);process.exit(1)}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l:l.trim(),i:i})).filter(o=>/^### #04-01-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+7).join('\n');if(body.indexOf('\u73b0\u72b6')<0||body.indexOf('\u671f\u671b')<0){console.error('GAP ENTRY MISSING FIELDS');process.exit(1)}}if(t.indexOf('\u2461\u8282\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK legacy rows '+rows.length+' gaps '+gh.length)"
non-zero exit, 或 stderr 含 "NO LEGACY ROWS" / "LEGACY ROW WITHOUT ANCHOR" / "GAP ENTRY MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION"
+ node -e "const fs=require('fs'),cp=require('child_process');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md','utf8');const paths=[...new Set(t.match(/packages-user\/(?:client-base|client-modules)\/[A-Za-z0-9._\/-]+\.tsx?/g)||[])];if(paths.length===0){console.error('NO CLIENT ANCHOR PATHS');process.exit(1)}const tracked=new Set(cp.execSync('git ls-files packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).split(/\r?\n/).filter(Boolean).map(s=>s.replace(/\\/g,'/')));const missing=paths.filter(p=>!tracked.has(p));if(missing.length>0){console.error('UNTRACKED ANCHOR PATHS '+missing.length+' '+missing.join(','));process.exit(1)}console.log('OK tracked anchor paths '+paths.length)"
+ non-zero exit, 或 stderr 含 "NO CLIENT ANCHOR PATHS" / "UNTRACKED ANCHOR PATHS"(审计文档引用了 `git ls-files` 不认识的路径,即 gitignored 镜像或被臆造的路径)
+ node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(审计文档出现非 CRLF 换行,违反 dev.md「换行使用 CRLF 格式」)
git status --porcelain -- packages-user/client-base packages-user/client-modules
stdout 非空(任一被查渲染端包出现改动/新增行)—— 违反只读约束
node -e "const cp=require('child_process');const out=cp.execSync('git status --porcelain -- .planning/phases/04-render-adaptation',{encoding:'utf8'});const extra=out.split(/\r?\n/).filter(l=>/\d{2}-\d{2}-PLAN\.md/.test(l)&&l.indexOf('04-01-PLAN.md')<0);if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.length);process.exit(1)}console.log('OK plan scope')"
@@ -238,6 +267,7 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
- 「② 数据端缺失接口」节定稿:条目(如有)均含「现状 / 期望」,且节末结论行取固定两式之一;未确认候选在「未能从阅读确定(未猜测)」中逐条列出
- `## 判定为「匹配」的同类边界` / `## 未能从阅读确定(未猜测)` / `## 处置` 三段均非空且符合 07-LOADSTATE-AUDIT 的写法;`## 处置` 含双布局资产登记与「后续步骤未在本 run 规划」的明确表述
- ID 唯一且前缀与分类列一一对应(M=① 错配、L=③ 多余旧路径、G=② 数据端缺失)
+ - 审计文档引用的每个 `packages-user/client-{base,modules}/**` 路径都能在 `git ls-files` 输出中命中(无 gitignored 镜像或被臆造路径);全文为 CRLF 换行
- `git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空;`.planning/phases/04-render-adaptation` 下无 04-01 以外的计划文件
- 审计文档已以 `docs(04-01)` 提交,提交内容仅含该文档
@@ -274,9 +304,11 @@ Output: `04-RENDER-INTERFACE-AUDIT.md`(本 run 唯一新增产物;零生产
2. 错配门禁(Task 2):每条 `#04-01-M-*` 含 `file:line` 且「依据」列引用 `data-common|data-base|data-system|data-state` 的 `src/*.ts`。
3. 旧路径与缺失节门禁(Task 3):每条 `#04-01-L-*` 含 `file:line`;每个 `### #04-01-G-*` 之后紧跟的 6 行内含「现状」「期望」;② 节结论行存在。
4. 只读门禁(Task 3):`git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出为空。
-5. 范围门禁(Task 3):`.planning/phases/04-render-adaptation` 下除 `04-01-PLAN.md` 外无其它 `*-PLAN.md`(后续步骤未被规划)。
-6. 人工核对:`## 判定为「匹配」的同类边界` 的素材族判据、`## 未能从阅读确定(未猜测)` 的逐条理由、`## 处置` 的双布局资产登记与「后续步骤未在本 run 规划」表述均齐备。
-7. 提交:`docs(04-01)` 仅含审计文档。
+5. 锚点可追溯门禁(Task 3):审计文档引用的每个 `packages-user/client-{base,modules}/**` 路径都能在 `git ls-files packages-user/client-base packages-user/client-modules` 输出中命中(落实 `must_haves.key_links[0]`)。
+6. CRLF 门禁(Task 1 与 Task 3):审计文档无孤立 `LF`,全文为 CRLF(dev.md「换行使用 CRLF 格式」)。
+7. 范围门禁(Task 3):`.planning/phases/04-render-adaptation` 下除 `04-01-PLAN.md` 外无其它 `*-PLAN.md`(后续步骤未被规划)。
+8. 人工核对:`## 判定为「匹配」的同类边界` 的素材族判据、`## 未能从阅读确定(未猜测)` 的逐条理由、`## 处置` 的双布局资产登记与「后续步骤未在本 run 规划」表述均齐备。
+9. 提交:`docs(04-01)` 仅含审计文档。
From 6754a9d4df2dfe521352289e020cc8908f386691 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 10:06:00 +0800
Subject: [PATCH 007/173] docs(04): create phase plan
---
.planning/STATE.md | 12 +-
.../04-render-adaptation/04-PATTERNS.md | 322 ++++++++++++++++++
.planning/state.json | 8 +-
3 files changed, 332 insertions(+), 10 deletions(-)
create mode 100644 .planning/phases/04-render-adaptation/04-PATTERNS.md
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 8d622a5..a1af049 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -1,18 +1,18 @@
---
gsd_state_version: "1.0"
milestone: v1.0
-current_phase: 07
-current_phase_name: 数据端缺陷修复
+current_phase: 04
+current_phase_name: 渲染适配与双布局
status: executing
stopped_at: Phase 4 context gathered
-last_updated: "2026-09-18T01:06:46.798Z"
+last_updated: "2026-09-18T02:05:26.602Z"
last_activity: 2026-09-17
last_activity_desc: Phase 07 paused; plan 07-16 deferred pending the user's data-layer rework
-state_head: 01fe37fdb0769c6243e69a1cbf1133b03a5daea4
+state_head: 6d8075166a0469783835272f2a4fa04c283ee71e
progress:
total_phases: 7
completed_phases: 0
- total_plans: 71
+ total_plans: 72
completed_plans: 70
milestone_name: milestone
---
@@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
-Phase: 07 (数据端缺陷修复) — PAUSED (plan 07-16 DEFERRED)
+Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
Plan: 15 of 16 complete (07-01..07-15); 07-16 planned, awaiting the user's data-layer rework
Status: Deferred — user is manually reworking the data layer; more tests will break until that finishes
Last activity: 2026-09-17 — Phase 07 paused; 07-16 deferred
diff --git a/.planning/phases/04-render-adaptation/04-PATTERNS.md b/.planning/phases/04-render-adaptation/04-PATTERNS.md
new file mode 100644
index 0000000..c98d846
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-PATTERNS.md
@@ -0,0 +1,322 @@
+# Phase 4: 渲染适配与双布局 - Pattern Map
+
+**Mapped:** 2026-09-18
+**本次规划范围:** 仅 plan 04-01(第一步·只读对账),不规划后续适配/双布局实施
+**Files analyzed:** 1 交付文档(新建)+ 50 个只读扫描目标(`client-base` 15 文件 / `client-modules` 81 文件,本次分类到组)
+**Analogs found:** 1 / 1 交付文档有同仓库结构范式;扫描目标均有既有耦合/契约范式可照抄
+
+> **性质声明(对齐 04-CONTEXT D-02 / D-09):** plan 04-01 是**只读清点**,不写任何生产代码。本文件
+> 中的「Analog」对交付文档是**结构范式**(从既有 `.planning` 审计/登记文档照抄章节骨架),
+> 对扫描目标是**耦合判定范式**(每一类耦合给出可照抄的 `file:line` 锚点与判定口径)。
+> 所有下列生产源码路径均已 `git ls-files` 验证为 **tracked source**(无 gitignored 镜像路径)。
+
+---
+
+## Scope Note(本 run 的边界)
+
+| 项 | 值 |
+|----|----|
+| 被查对象(audit targets) | `packages-user/client-base/src/**`、`packages-user/client-modules/src/**`(D-03) |
+| 接口基准(baseline,不作为被查对象) | `packages-user/data-common` / `data-base` / `data-system` / `data-state` 的 `types.ts`、`core.ts`、`ins.ts`(D-04) |
+| 明确排除 | `packages` 全部、`entry-client`、`legacy-plugin-client`、`legacy-plugin-data`、`data-fallback`(D-03 / Deferred) |
+| 交付物 | `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`(D-02) |
+| 不产出 | 任何生产/测试代码改动;后续适配与双布局的计划不在此 run |
+
+---
+
+## File Classification
+
+### A. 交付文档(新建,唯一写文件)
+
+| New File | Role | Data Flow | Closest Analog | Match Quality |
+|----------|------|-----------|----------------|---------------|
+| `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` | audit / registry document | read-only inventory(分类登记:错配 / 数据端缺失 / 多余旧路径) | `.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md`(只读审计骨架)+ `.planning/phases/06-unit-tests/06-TEST-FINDINGS.md`(字段定义表 + 分节登记) | exact(结构) |
+
+### B. 扫描目标组(只读,不修改)
+
+| 组 | 代表文件 | Role | Data Flow | Closest Analog(既有耦合范式) | Match Quality |
+|----|---------|------|-----------|-------------------------------|---------------|
+| A1 渲染契约桥 | `client-base/src/types.ts`、`client-modules/src/types.ts` | contract / types | request-response(类型面) | 同文件既有 `extends ICoreState` / `extends IClientBase` | exact |
+| A2 渲染组合根与单例 | `client-base/src/index.ts`、`client-modules/src/index.ts`、`client-modules/src/client.ts`、`client-modules/src/core.ts` | entry / composition-root | event-driven(`loading.once` 生命周期) | 同文件既有注册/继承/单例范式 | exact |
+| A3 legacy 全局 `core.*` 读取 | `action/move.ts`、`render/ui/{main,statistics,viewmap,settings,save,toolbar,statusBar}.tsx`、`render/elements/{cache,misc}.ts`、`render/components/{choices,misc}.tsx`、`render/weather/presets/*`、`fallback/load.ts`、`render/utils/saves.ts` | component / adapter | request-response(读全局) | 同组既有 `core.` 读取点(见下方锚点表) | exact(同类) |
+| A4 新数据层接口调用 | `render/map/extension/hero.ts`、`render/ui/main.tsx`、`render/ui/statistics.tsx`、`render/index.tsx`、`render/use.ts` | component / hook-consumer | event-driven(`hook` / `state` / `loading`) | 同文件既有 `state` / `hook` / `loading` 用法 | exact |
+| A5 数据端类型/契约直连 | `render/map/{renderer,moving,element,status,vertex,types}.ts`、`render/elements/props.ts`、`client-base/src/save/{system,types}.ts` | renderer / adapter | request-response(类型面) | 同组既有 `IGameMap` / `IMapLayer` / `ISaveableContent` import | role-match |
+| A6 双布局既有资产 | `render/use.ts`(`Orientation` / `onOrientationChange`)、`shared.ts`(`MAIN_WIDTH` 等) | utility / config | event-driven(resize) | 同文件既有横竖屏判定范式 | exact |
+| A7 加载/素材接线 | `client-base/src/load/{loader,data,types}.ts`、`client-base/src/material/**` | loader / manager | file-I/O(素材加载) | 同组既有 `core.material` / `IMotaDataLoader` 读取点 | role-match |
+
+### C. 接口基准(只读,不登记为被查对象;D-04)
+
+| File | Role | 用途 |
+|------|------|------|
+| `packages-user/data-common/src/types.ts` | contract L0 | `IDataCommon` 基准 |
+| `packages-user/data-base/src/types.ts` | contract L1 | `IStateBase` 基准 |
+| `packages-user/data-system/src/types.ts` | contract L2 | `IStateSystem` 基准 |
+| `packages-user/data-state/src/types.ts` | contract L3 | `ICoreState` 基准 |
+| `packages-user/data-state/src/core.ts` | assembly | 顶层装配 + `loadState` + legacy bridge 落点(`:54`,`:151`,`:292-307`) |
+| `packages-user/data-state/src/ins.ts` | singleton | `state` 单例入口 |
+| `packages-user/data-base/src/game.ts` | event bus | `hook`(`:155`)、`gameListener`(`:266-267`,`@deprecated`) |
+| `packages-user/entry-data/src/mota.ts` | gate | `r()` / `rf()` 渲染调用门控(`:125-136`) |
+
+---
+
+## Pattern Assignments
+
+### 1. `04-RENDER-INTERFACE-AUDIT.md`(交付文档,audit / read-only inventory)
+
+**结构主 Analog:** `.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md`
+**字段定义 Analog:** `.planning/phases/06-unit-tests/06-TEST-FINDINGS.md`
+**分类/排除口径 Analog:** `.planning/phases/06-unit-tests/06-SAVE-EXCLUSIONS.md`
+**接口契约引用 Analog:** `.planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md`
+
+**章节骨架(照抄 `07-LOADSTATE-AUDIT.md` 的 7 段式,逐段对应):**
+
+```markdown
+# 渲染端 ↔ 数据端接口对账审计 ← Analog: 07-LOADSTATE-AUDIT.md:1
+
+**日期:** 2026-09-18 ← Analog: :3
+**范围:** packages-user/client-base + client-modules(被查)
+ data-* 作为接口基准(D-04) ← Analog: :4
+**性质:** 只读清点,未修改任何代码 ← Analog: :5
+**接口基准:** 各包 types.ts / core.ts / ins.ts ← Analog: :6(种子缺陷位 → 基准源位)
+
+## 背景 ← Analog: :8-10
+## 方法 ← Analog: :12-14(枚举扫描面 + 判定口径)
+## 发现 ← Analog: :16-27(表格,见下方字段表)
+## 触发序列(举例) ← Analog: :29-35(错配的调用→旧形状解读链)
+## 判定为「匹配」的同类边界 ← Analog: :37-46(哪些读取点确认走的是新接口)
+## 未能从阅读确定(未猜测) ← Analog: :48-53(诚实边界,不臆断)
+## 处置 ← Analog: :55-58(后续处理归属)
+```
+
+**发现表字段(对齐 04-CONTEXT D-06 / D-07,锚定 `07-LOADSTATE-AUDIT.md:18-27` 与 `06-TEST-FINDINGS.md:9-18`):**
+
+```markdown
+| ID | 接口名 | 所属文件:行 | 分类 | 问题描述 | 影响 | 置信 | 依据 |
+|----|--------|-------------|------|----------|------|------|------|
+```
+
+- `分类` 取值固定三选一:**① 错配** / **② 数据端缺失** / **③ 多余旧路径**(D-06)。
+- **② 数据端缺失** 必须**独立成节**(D-08),不混入发现表主节;该节结构照抄
+ `06-COVERAGE-GAPS.md` 的逐条 `### G-xx:标题(严重度)→ 现状 / 期望` 写法(`:10-13`)。
+- **接口基准引用** 照抄 `02-INTERFACE-DRAFT.md` 顶部「接口事实源:… 为准」块(`:3-5`)与
+ `07-LOADSTATE-AUDIT.md:14` 的「枚举所有实现 → 判定持有者 → 检查覆盖」方法段。
+- 每条记录**必须**含 `file:line` 锚点(D-07);`判定为「匹配」的同类边界` 段防止把
+ 「已走新接口」误报为错配(照抄 `07-LOADSTATE-AUDIT.md:37-46` 的「安全边界」写法)。
+- `未能从阅读确定(未猜测)` 段为**强制段**:对无法从静态阅读下结论的调用链,只登记
+ 不确定项,不写成缺陷(照抄 `07-LOADSTATE-AUDIT.md:48-53`)。
+
+### 2. 组 A1 — 渲染契约桥(exact)
+
+**Analog:** 同文件既有继承链。
+
+`packages-user/client-base/src/types.ts:1-27` — 渲染系统层契约**直接 extends 数据端 L3 契约**(这是 REND-01 的桥头):
+```ts
+import { ICoreState } from '@user/data-state';
+// ...
+export interface IClientBase extends ICoreState {
+ /** 存档系统 */
+ readonly save: ISaveSystem;
+ // ...
+}
+export interface IClientBaseExtended {
+ /** 当前对象的渲染端基本层对象(Layer 4 对象) */
+ readonly state: IClientBase;
+}
+```
+
+`packages-user/client-modules/src/types.ts:1-20` — 实现层契约 extends 系统层契约;对账时逐字段比对 `ICoreState` 是否新增/改名。
+
+### 3. 组 A2 — 组合根 / 单例(exact)
+
+**Analog:** 同文件既有注册范式。
+
+`packages-user/client-modules/src/index.ts:1-11` — 生命周期入口,`loading.once('coreInit')` 是数据→渲染的既有启动门:
+```ts
+import { loading } from '@user/data-base';
+import { patchAll } from './fallback';
+import { createGameRenderer, createRender } from './render';
+
+export function create() {
+ patchAll();
+ createRender();
+ loading.once('coreInit', () => {
+ createGameRenderer();
+ });
+}
+```
+
+`packages-user/client-modules/src/client.ts:46` — `export class ClientCore extends CoreState implements IClientCore {`:
+渲染组合根**继承数据端 `CoreState`**,故所有 `this.` 读取都在对账面内;`:71` 的 `core.firstData.name` 是全局读取点。
+
+`packages-user/client-modules/src/core.ts:1-6` — 单例 + 既有重构 TODO(对账时记录为「已知待重构面」,不作为新缺陷):
+```ts
+// TODO: 逐渐弱化 ClientCore 的单例概念,每个接口都通过参数传入 IClientCore 对象
+
+/** 客户端实例 */
+export const client = new ClientCore();
+```
+
+`packages-user/client-base/src/index.ts:1-11` — 系统层 barrel;`create()` 仅 `createMaterial()`。
+
+### 4. 组 A3 — legacy 全局 `core.*` 读取(③ 多余旧路径的主证据面)
+
+**Analog:** 同组既有 `core.` 读取点。**扫描锚点(已核实行号):**
+
+| 文件:行 | 读取面 | 对应数据端新接口候选(基准) |
+|---------|--------|------------------------------|
+| `action/move.ts:66-67,97,123,156` | `core.isReplaying()` / `core.isPlaying()` / `core.waitHeroToStop()` / `core.status.lockControl` | `ICoreState` 状态查询 |
+| `render/ui/main.tsx:99,103,113-128,139,155,165` | `core.status.*` / `core.getNextLvUpNeed()` / `core.itemCount()` / `core.getLvName()` | `ICoreState` + `IStateSystem` |
+| `render/ui/statistics.tsx:144,158,174,215,217,225,240,248,250` | `core.status.hero` / `core.status.maps` / `window.flags` | `IStateBase` / `IHeroState` |
+| `render/ui/viewmap.tsx:54,74,361-362,566` | `core.status.floorId` / `core.status.maps` / `core.status.thisMap` | `IMapState` |
+| `render/ui/settings.tsx:165,168,224-230,299,317,504,522` | `core.status.route/hard` / `core.startGame()` / `core.firstData` / `core.formatDate2()` / `core.encodeRoute()` | replay / lifecycle |
+| `render/ui/save.tsx:111` | `core.maps.loadMap()` | `IMapState` 加载 |
+| `render/ui/toolbar.tsx:198` | `core.isPlaying()` / `core.isMoving()` / `core.status.lockControl` | `ICoreState` |
+| `render/ui/statusBar.tsx:158` | `core.flags.statusBarItems` | `IFlagSystem` |
+| `render/components/choices.tsx:675,715,718-719,733,770-774,789` | `core.status.replay.toReplay` / `core.status.route.push()` | replay 命令接口 |
+| `render/components/misc.tsx:312` | `core.material.images.images[...]` | 渲染侧素材(非数据端) |
+| `render/elements/cache.ts:11,90,92,160,197,250,442,518` | `core.material.images.*` | 渲染侧素材(非数据端) |
+| `render/elements/misc.ts:221,228` | `core.material.images.images[...]` | 渲染侧素材 |
+| `render/weather/presets/{cloud,fog,sun}.ts` | `core.material.images.images[...]` | 渲染侧素材 |
+| `fallback/load.ts:44,55-56,59` | `core.maps.blocksInfo` / `core.icons.icons` / `core.material.images` | `IMapState` / 素材 |
+| `render/utils/saves.ts:27-28,44-45,103` | `core.firstData.*` | 配置读取 |
+| `client-base/src/load/loader.ts:103,129,151,167,188,284,418` | `core.material.images.*` / `core.materials` | 渲染侧素材 |
+
+**判定口径(照抄 `07-LOADSTATE-AUDIT.md:14` 的方法段):** 每条 `core.*` 读取先判定其读的是
+**数据端状态**(→ 记为 ③ 多余旧路径或 ① 错配)还是**纯渲染侧素材/全局**(→ 判为「匹配」的
+同类边界,见 `core.material` 族)。**不得**因出现 `core.` 就一律登记(04-CONTEXT D-06 / the agent's Discretion)。
+
+**关键辅证:** `packages-user/client-modules/src/action/move.ts:1-6` — 数据端 import 已带既有重构标记:
+```ts
+import { KeyCode } from '@motajs/client-base';
+import { Hotkey, HotkeyData } from '@motajs/system';
+// @ts-expect-error 需要重构
+import { HeroMover, IMoveController } from '@user/data-state';
+```
+
+### 5. 组 A4 — 新数据层接口调用(对照面,用于判定「已走新接口」)
+
+**Analog:** 同文件既有 `state` / `hook` / `loading` 用法。
+
+`packages-user/client-modules/src/render/ui/main.tsx:28-30` — 新接口与 legacy 全局**同文件并存**(对账的典型错配/过渡样本):
+```tsx
+// @ts-expect-error 需要重构
+import { getHeroStatusOn, state } from '@user/data-state';
+import { hook } from '@user/data-base';
+```
+同文件 `:100-101` 已用新接口 `client.flags.getFieldValueDefaults(...)`,而 `:99-128` 仍读 legacy `core.status.*` / `core.isReplaying()`。
+
+`packages-user/client-modules/src/render/map/extension/hero.ts:1-21` — 数据端 import 多个 `@ts-expect-error 需要重构` + 新接口 `state`:
+```ts
+import {
+ degradeFace,
+ FaceDirection,
+ getFaceMovement,
+ // @ts-expect-error 需要重构
+ HeroAnimateDirection,
+ // @ts-expect-error 需要重构
+ IHeroMoveController,
+ // @ts-expect-error 需要重构
+ IHeroMoveControllerHooks,
+ nextFaceDirection
+} from '@user/data-common';
+import { IMapLayer } from '@user/data-base';
+// ...
+import { state } from '@user/data-state';
+```
+
+`packages-user/client-modules/src/render/index.tsx:4,37-40` — 数据端事件订阅范式:
+```ts
+import { hook } from '@user/data-base';
+// ...
+hook.on('restart', () => {
+ sceneController.closeAll();
+ sceneController.open(GameTitleUI, {});
+});
+```
+
+**数据端基准侧(只读,确认 hook/门控定义):**
+- `packages-user/data-base/src/game.ts:155` — `export const hook = new EventEmitter();`
+- `packages-user/data-base/src/game.ts:166` — `class GameListener extends EventEmitter`;`:266-267` `/** @deprecated */ export const gameListener = new GameListener();`
+- `packages-user/entry-data/src/mota.ts:125-136` — `rf()` 在 `main.replayChecking || main.mode === 'editor'` 时返回 `empty`(渲染端被调用门控)。
+- `packages-user/data-state/src/ins.ts:1-10` — `state` 单例,jsDoc 明确「仅服务于渲染…此对象是数据端状态」。
+
+### 6. 组 A5 — 数据端类型直连(role-match)
+
+**Analog:** 同组既有 import。
+
+`packages-user/client-modules/src/render/map/renderer.ts:33`:
+```ts
+import { IGameMap, IGameMapHooks, IMapLayer } from '@user/data-base';
+```
+同族:`render/map/types.ts:13`、`render/map/moving.ts:5`、`render/map/element.ts:2`、`render/map/status.ts:1`、`render/map/vertex.ts:1`、`render/elements/props.ts:3`。对账时比对这些 `IGameMap`/`IMapLayer` 成员是否与数据端 `types.ts` 现行签名一致。
+
+**数据端基准侧(只读):** `data-base/src/types.ts`(`IStateBase`)、`data-system/src/types.ts`、`data-state/src/types.ts`(`ICoreState`)。
+
+### 7. 组 A6 — 双布局既有资产(后续步骤参考,本 run 不实施)
+
+**Analog:** 同文件既有横竖屏判定。
+
+`packages-user/client-modules/src/render/use.ts:22-52` — 横竖屏枚举 + resize 监听已存在:
+```ts
+export const enum Orientation {
+ /** 横屏 */
+ Landscape,
+ /** 竖屏 */
+ Portrait
+}
+export type OrientationHook = (orientation: Orientation, width: number, height: number) => void;
+// ...
+window.addEventListener('resize', checkOrientation);
+export function onOrientationChange(hook: OrientationHook) { /* ... */ }
+```
+`packages-user/client-modules/src/shared.ts:33-97` — `MAP_BLOCK_WIDTH` / `STATUS_BAR_WIDTH` / `MAIN_WIDTH` 等布局常量(固定 13×13 + 单一状态栏,无双布局分支)。
+> **本 run 仅需在审计文档中登记该资产为「后续双布局起点」,不展开实施**(Deferred 明确)。
+
+---
+
+## Shared Patterns
+
+### 分类口径(① 错配 / ② 数据端缺失 / ③ 多余旧路径)
+**Source:** `07-LOADSTATE-AUDIT.md:18-27`(表格分类列)+ `04-CONTEXT D-06`
+**Apply to:** 发现表每一条记录。
+- 一条记录只归一类;跨类则拆条。
+- 「数据端缺失接口」独立成节(D-08),结构照抄 `06-COVERAGE-GAPS.md:10-13` 的 `现状 / 期望` 两段式。
+- 「影响」字段写法按 the agent's Discretion 把握,但**不得据此扩大范围**(D-03/D-04)。
+
+### 证据纪律(file:line,不臆断)
+**Source:** `07-LOADSTATE-AUDIT.md:12-14`(方法)与 `:48-53`(未能确定段)
+**Apply to:** 全部记录与「未确定」段。
+- 每条含 `接口名 + 所属文件:行 + 描述 + 影响`(D-07)。
+- 静态阅读无法定论的调用链一律进「未能从阅读确定(未猜测)」,**不得**写成缺陷。
+- 对账以**接口签名**为准,不受用户并行修改数据端实现影响(D-05);只引用 `types.ts`/`core.ts`/`ins.ts` 等契约面。
+
+### 只读约束
+**Source:** `04-CONTEXT D-02 / D-09`
+**Apply to:** 全部任务。
+- 允许派子代理只读扫描,**禁止修改任何文件**(D-09)。
+- 本 run 唯一允许写出的文件是 `04-RENDER-INTERFACE-AUDIT.md`。
+
+### 既有重构标记的处置口径
+**Source:** `action/move.ts:3`、`render/map/extension/hero.ts:5,7,9`、`render/ui/main.tsx:28`(`// @ts-expect-error 需要重构`)
+**Apply to:** 所有带该标记的 import。
+- 该标记是**既有已知待重构面**,应作为错配/旧路径的**强线索**列出,但需与数据端 `types.ts` 实签核对后定性;不得仅凭标记直接判为缺陷。
+
+---
+
+## No Analog Found
+
+| 对象 | Role | Data Flow | Reason |
+|------|------|-----------|--------|
+| 审计文档的「② 数据端缺失接口」**内容节** | registry | inventory | 仓内无「渲染端需要而数据端未提供」的既有登记文档;仅可借 `06-COVERAGE-GAPS.md` 的条目骨架,内容须由实际扫描产出 |
+| 审计文档的「双布局能力缺口」视角 | inventory | inventory | 无既有文档;本 run 不展开(Deferred) |
+
+> 其余 50 个扫描目标均有既有耦合/契约范式,无「无 analog」项。
+
+---
+
+## Metadata
+
+**Analog search scope:** `.planning/phases/*/`(审计/登记/契约文档)、`packages-user/client-base/src/`、`packages-user/client-modules/src/`、`packages-user/data-{common,base,system,state}/src/`、`packages-user/entry-data/src/`、`dev.md`、`.planning/codebase/`、`.planning/ROADMAP.md`
+**Files scanned:** 交付文档 4 个结构 analog + 扫描目标 96 文件(client-base 15 / client-modules 81)+ 数据端基准 8 文件
+**Tracked-source 校验:** 全部引用生产路径经 `git ls-files` 验证为 tracked(无 gitignored 镜像路径)
+**Pattern extraction date:** 2026-09-18
diff --git a/.planning/state.json b/.planning/state.json
index 1368771..6aa2087 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -21,7 +21,7 @@
{
"number": "4",
"name": "渲染适配与双布局",
- "status": "pending"
+ "status": "in_progress"
},
{
"number": "5",
@@ -36,13 +36,13 @@
{
"number": "7",
"name": "数据端缺陷修复",
- "status": "complete"
+ "status": "pending"
}
],
"next": {
"command": "/gsd:progress --next",
"label": "Advance to the next step",
- "reason": "Phase 07 of 7 · executing"
+ "reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-17T12:32:39.429Z"
+ "updated_at": "2026-09-18T02:05:26.702Z"
}
From 6f6e4b0dcf29cc738533cdacc50f9187d72ce2f5 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 10:10:36 +0800
Subject: [PATCH 008/173] =?UTF-8?q?docs:=20=E8=B0=83=E6=95=B4=E5=85=B3?=
=?UTF-8?q?=E4=BA=8E=E6=89=A7=E8=A1=8C=E8=AE=A1=E5=88=92=E7=9A=84=E6=8F=90?=
=?UTF-8?q?=E7=A4=BA=E8=AF=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
AGENTS.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/AGENTS.md b/AGENTS.md
index ad8aa65..a58abf2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -10,7 +10,7 @@
- dev.md 中所有关于建议做的事、最好怎么做的事,都视为必须这么做;而关于不建议怎么做的事,尽量不怎么做的事,都视为必须不能这么做。
- 如果发现任何问题,永远不要擅自将其视为问题并记录,必须经由我确认其为问题后才可记录。你认为是问题的地方很多时候是设计如此。
- 测试文件不必遵循上述规则,只需要遵循一个规则:每个测试前(it)都必须用一行注释说明这个测试是干什么的。测试文件全部由你维护,我一般不会动。
-- 执行任何计划前,必须先向我汇报计划内容,我确认没有问题后才可执行,不得擅自执行。汇报时说明计划准备解决什么问题,大致的解决方案是什么。如果是修复问题的计划,那么应该还需要汇报问题是什么。
+- 执行任何计划前,必须先向我汇报计划内容,我确认没有问题后才可执行,不得擅自执行,哪怕我是通过 `/gsd-execute-phase` 执行的,也必须先向我汇报并等我确认。汇报时说明计划准备解决什么问题,大致的解决方案是什么。如果是修复问题的计划,那么应该还需要汇报问题是什么。
- 除非我明确要求,否则任何修改必须经由 GSD 标准流程:Plan -> Execute -> Verify,不得自行修改。
- 任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈,我同意后才可以使用,不得擅自使用我未同意的命名。
- 在 GSD 流程的 Discuss 讨论阶段中,全部由我主导,而不是由你主导。进入讨论阶段的第一件事不是开子代理探索这一阶段的需求,也不是自己分析需求并提问问题,而是让我先说这一阶段的计划。
From 7225bdd4372c46ef97512e67c6758f12aec5a97c Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 10:37:19 +0800
Subject: [PATCH 009/173] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E8=AE=A1?=
=?UTF-8?q?=E5=88=92=E6=89=A7=E8=A1=8C=E8=A7=84=E5=88=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
AGENTS.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/AGENTS.md b/AGENTS.md
index a58abf2..662381f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -10,7 +10,7 @@
- dev.md 中所有关于建议做的事、最好怎么做的事,都视为必须这么做;而关于不建议怎么做的事,尽量不怎么做的事,都视为必须不能这么做。
- 如果发现任何问题,永远不要擅自将其视为问题并记录,必须经由我确认其为问题后才可记录。你认为是问题的地方很多时候是设计如此。
- 测试文件不必遵循上述规则,只需要遵循一个规则:每个测试前(it)都必须用一行注释说明这个测试是干什么的。测试文件全部由你维护,我一般不会动。
-- 执行任何计划前,必须先向我汇报计划内容,我确认没有问题后才可执行,不得擅自执行,哪怕我是通过 `/gsd-execute-phase` 执行的,也必须先向我汇报并等我确认。汇报时说明计划准备解决什么问题,大致的解决方案是什么。如果是修复问题的计划,那么应该还需要汇报问题是什么。
+- 任何计划的执行,其**第一个 block 点必须是向我汇报计划内容并等待我确认**;无论触发方式是什么(包括但不限于 `/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发),都不得例外,不得擅自执行。只有在我明确回复「可以执行」这四个字之后,才允许开始执行该计划;没有「可以执行」四个字,任何计划的任何一步都不得执行,也不得派发执行子代理。子代理/执行器同样受此约束,遇到该关卡必须停下并等待。汇报时说明计划准备解决什么问题,大致的解决方案是什么;如果是修复问题的计划,那么应该还需要汇报问题是什么。
- 除非我明确要求,否则任何修改必须经由 GSD 标准流程:Plan -> Execute -> Verify,不得自行修改。
- 任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈,我同意后才可以使用,不得擅自使用我未同意的命名。
- 在 GSD 流程的 Discuss 讨论阶段中,全部由我主导,而不是由你主导。进入讨论阶段的第一件事不是开子代理探索这一阶段的需求,也不是自己分析需求并提问问题,而是让我先说这一阶段的计划。
From 05b9af7077b5758892c4e595694d1168a081a341 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 10:45:50 +0800
Subject: [PATCH 010/173] =?UTF-8?q?docs(04-01):=20=E6=B8=B2=E6=9F=93?=
=?UTF-8?q?=E7=AB=AF=20=E2=86=94=20=E6=95=B0=E6=8D=AE=E7=AB=AF=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E5=AF=B9=E8=B4=A6=EF=BC=88=E9=AA=A8=E6=9E=B6=20+=20?=
=?UTF-8?q?=E5=88=86=E7=B1=BB=E5=90=84=E4=B8=80=E6=9D=A1=E5=AE=9E=E6=B5=8B?=
=?UTF-8?q?=E8=AE=B0=E5=BD=95=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../04-RENDER-INTERFACE-AUDIT.md | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
diff --git a/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md b/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
new file mode 100644
index 0000000..11c8a6f
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
@@ -0,0 +1,59 @@
+# 渲染端 ↔ 数据端接口对账审计
+
+**日期:** 2026-09-18
+**范围:** 被查对象 `packages-user/client-base` + `packages-user/client-modules`;`packages-user/data-{common,base,system,state}` 仅作接口基准(D-03/D-04)
+**性质:** 只读清点,未修改任何代码(D-02)
+**接口基准:** 各数据端包的 `types.ts` / `core.ts` / `ins.ts` 现行签名(D-04/D-05)
+
+## 背景
+
+本文件是 Phase 4 第一步(增量规划,D-01/D-02)的交付物:对渲染端与数据端做**只读接口对账**,清点所有「渲染端实现 ↔ 数据端现行接口」不匹配项,供用户决定阶段 4 剩余工作(适配实施 + 移动端/桌面端双布局,REND-01/REND-02)如何拆分。
+
+本步**只清点、不改代码**。被查对象限 `client-base` 与 `client-modules`(D-03);`data-common` / `data-base` / `data-system` / `data-state` 只作接口基准(D-04)。对账以**接口签名为准**,不受用户并行修改数据端**实现**的影响(D-05)。
+
+## 方法
+
+- **枚举面(A1..A7)**:A1 渲染契约桥、A2 组合根/单例、A3 legacy 全局 `core.*` 读取、A4 新数据层接口调用、A5 数据端类型直连、A6 双布局既有资产、A7 加载/素材接线。
+- **判定口径**:每条 `core.*` 读取先判「读的是数据端状态」还是「渲染侧素材/全局」;数据端状态 → ① 错配或 ③ 多余旧路径,纯渲染侧素材/全局 → 「判定为『匹配』的同类边界」。不因出现 `core.` 就一律登记。
+- **分类固定三选一**(D-06):① 错配 `#04-01-M-N`、② 数据端缺失 `#04-01-G-N`(独立成节,D-08)、③ 多余旧路径 `#04-01-L-N`。一条记录只归一类,跨类拆条。
+- **证据纪律**:每条含 `接口名 + 所属文件:行 + 分类 + 问题描述 + 影响 + 置信 + 依据`(D-07);`#04-01-M-*` 的「依据」列引用数据端基准文件;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺陷。
+- **只读约束**:本 run 唯一允许写出的文件是本审计文档;允许只读子代理扫描(D-09),但子代理仅回传文本、不得写文件。
+- **扫描前基线**:执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,输出为空(被查两包无任何工作树改动)。
+
+## 发现
+
+列序固定:`ID | 接口名 | 所属文件:行 | 分类 | 问题描述 | 影响 | 置信 | 依据`。本节只登记 ① 错配与 ③ 多余旧路径;② 数据端缺失见下一独立节。
+
+| ID | 接口名 | 所属文件:行 | 分类 | 问题描述 | 影响 | 置信 | 依据 |
+|----|--------|-------------|------|----------|------|------|------|
+| #04-01-M-01 | `HeroMover` / `IMoveController`(`@user/data-state`) | packages-user/client-modules/src/action/move.ts:4(使用 `:19,:29,:33`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`(`move.ts:3`)。`data-state` 未导出这两个符号:`HeroMover` 实际导出在 `@user/data-base`,`IMoveController` 数据端名为 `IMoverController`(拼写 Mover),属渲染端需局部重构采用新接口 | `HeroKeyMover` 全类无法编译;按键移动模块需改 import 源与类型名 | 高 | packages-user/data-base/src/hero/mover.ts:22;packages-user/data-common/src/common/mover.ts:142;packages-user/data-state/src/index.ts:1-9(无该导出) |
+| #04-01-L-01 | `core.status.*` / `core.itemCount()` / `core.status.replay` / `core.isReplaying()` | packages-user/client-modules/src/render/ui/main.tsx:99,103,114-120,124,127-128 | ③ 多余旧路径 | 同一文件 `:100-101,121-123,134` 已改用新接口 `client.flags.getFieldValueDefaults(...)`,但状态栏其余字段与回放态仍读 legacy 全局 `core.*`,新旧双路径并存 | 状态栏数值/回放状态读取仍走旧路径,未接新数据层接口 | 高 | packages-user/data-base/src/types.ts:21(IStateBase.hero);packages-user/data-base/src/hero/types.ts:310(floorId);packages-user/data-common/src/replay/types.ts:375(IReplaySystem.replaying) |
+
+## ② 数据端缺失接口
+
+本节登记「渲染端需要而数据端未提供」且**经阅读确认**的接口。凡不能确认的候选一律不登记为缺失项,转入「未能从阅读确定(未猜测)」。
+
+本步未确认数据端缺失接口。
+
+**②节结论:** 本步未确认数据端缺失接口;相关不确定项见「未能从阅读确定(未猜测)」
+
+## 触发序列(举例)
+
+**#04-01-M-01:** `HeroKeyMover` 构造期 import `{ HeroMover, IMoveController } from '@user/data-state'`(编译期即带 `// @ts-expect-error 需要重构`)→ 因其按旧概念「移动控制器」声明 `controller?: IMoveController`、`mover: HeroMover` → 数据端实际只在 `@user/data-base` 提供 `HeroMover`(`data-base/src/hero/mover.ts:22`)、把控制器命名为 `IMoverController`(`data-common/src/common/mover.ts:142`)→ 该模块无法按新接口接线,需局部重构为 `IHeroMover` / `IMoverController`。
+
+## 判定为「匹配」的同类边界
+
+- **渲染侧素材族(`core.material.*` / `core.materials` / `core.icons.*` / `core.tilesets`)**:这些读取的是渲染端自己的素材/纹理资产,不是数据端状态,因此不算数据端错配。判据:其值由渲染端 `client-base/src/material/**` 与加载器写入(如 `client-base/src/load/loader.ts:103,129,151,167` 写 `core.material.images.*`、`:284,418` 读 `core.materials`),数据端 `types.ts` 中并无对应状态字段。典型命中:`render/elements/cache.ts`、`render/elements/misc.ts`、`render/components/misc.tsx`、`render/weather/presets/{cloud,fog,sun}.ts`、`fallback/load.ts:55-56`。
+- **A4 新接口用法**:`hook` 订阅(`render/index.tsx:4,37-40`)、`loading` 生命周期(`client-modules/src/index.ts:1`、`client.ts:43`)、`state` 单例(`render/map/extension/hero.ts:21,225`)均命中数据端现行接口,属已走新接口。
+
+## 未能从阅读确定(未猜测)
+
+1. **② 节是否有缺失项**:本步未确认任何数据端缺失接口,但存在若干候选(如游戏工程元数据 `core.firstData.*`、勇士渲染粒度钩子),其「应由数据端补充」还是「由渲染端改接既有数据对象」无法仅凭静态阅读判定,故不登记为 ②,见 Task 3 定稿段。
+
+## 处置
+
+- **本步性质**:只读清点,**无任何生产代码改动**;被查两包 `client-base` / `client-modules` 保持零改动。
+- **后续拆分**:实际适配实施与移动端/桌面端双布局**未在本 run 规划**,由用户在对账结果出来后决定如何拆分(D-01/D-02)。REND-01(渲染端适配新数据层接口)与 REND-02(双布局)的验收边界尚未提供,需用户在规划实施前补齐。
+- **双布局既有资产(仅登记为后续起点,不展开实施)**:
+ - packages-user/client-modules/src/render/use.ts:22-66 —— `Orientation`(Landscape/Portrait)枚举、`OrientationHook`、`checkOrientation()`(`window.innerWidth >= window.innerHeight`)与 `onOrientationChange()`;当前在 `client-modules` 内无消费点。
+ - packages-user/client-modules/src/shared.ts:33-105 —— `MAP_BLOCK_WIDTH`/`MAP_BLOCK_HEIGHT`(13×13)、`MAP_WIDTH`/`MAP_HEIGHT`、`STATUS_BAR_WIDTH`、`MAIN_WIDTH`/`MAIN_HEIGHT` 等布局常量;现为「单一横屏 + 最多双状态栏」的静态常量,与 `Orientation` 判定尚未接线。
From c0b44c1b4572d72c850fae078e7b66efc6c5ab16 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 10:46:40 +0800
Subject: [PATCH 011/173] =?UTF-8?q?docs(04-01):=20=E2=91=A0=20=E9=94=99?=
=?UTF-8?q?=E9=85=8D=E5=85=A8=E9=87=8F=E6=B8=85=E7=82=B9=EF=BC=88=E5=A5=91?=
=?UTF-8?q?=E7=BA=A6=E6=A1=A5/=E7=BB=84=E5=90=88=E6=A0=B9/=E7=B1=BB?=
=?UTF-8?q?=E5=9E=8B=E7=9B=B4=E8=BF=9E=20+=20=E8=A7=A6=E5=8F=91=E5=BA=8F?=
=?UTF-8?q?=E5=88=97=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../04-RENDER-INTERFACE-AUDIT.md | 23 ++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md b/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
index 11c8a6f..fd6f271 100644
--- a/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+++ b/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
@@ -19,6 +19,7 @@
- **证据纪律**:每条含 `接口名 + 所属文件:行 + 分类 + 问题描述 + 影响 + 置信 + 依据`(D-07);`#04-01-M-*` 的「依据」列引用数据端基准文件;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺陷。
- **只读约束**:本 run 唯一允许写出的文件是本审计文档;允许只读子代理扫描(D-09),但子代理仅回传文本、不得写文件。
- **扫描前基线**:执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,输出为空(被查两包无任何工作树改动)。
+- **既有重构标记处置口径**:带 `// @ts-expect-error 需要重构` 的 import 是强线索但**不是结论**——按用户分类规则,此类「渲染端需局部重构以采用新接口」一律归 ① 错配(渲染侧适配项),不归 ②;每条仍须与数据端实签核对后定性。
## 发现
@@ -27,7 +28,14 @@
| ID | 接口名 | 所属文件:行 | 分类 | 问题描述 | 影响 | 置信 | 依据 |
|----|--------|-------------|------|----------|------|------|------|
| #04-01-M-01 | `HeroMover` / `IMoveController`(`@user/data-state`) | packages-user/client-modules/src/action/move.ts:4(使用 `:19,:29,:33`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`(`move.ts:3`)。`data-state` 未导出这两个符号:`HeroMover` 实际导出在 `@user/data-base`,`IMoveController` 数据端名为 `IMoverController`(拼写 Mover),属渲染端需局部重构采用新接口 | `HeroKeyMover` 全类无法编译;按键移动模块需改 import 源与类型名 | 高 | packages-user/data-base/src/hero/mover.ts:22;packages-user/data-common/src/common/mover.ts:142;packages-user/data-state/src/index.ts:1-9(无该导出) |
-| #04-01-L-01 | `core.status.*` / `core.itemCount()` / `core.status.replay` / `core.isReplaying()` | packages-user/client-modules/src/render/ui/main.tsx:99,103,114-120,124,127-128 | ③ 多余旧路径 | 同一文件 `:100-101,121-123,134` 已改用新接口 `client.flags.getFieldValueDefaults(...)`,但状态栏其余字段与回放态仍读 legacy 全局 `core.*`,新旧双路径并存 | 状态栏数值/回放状态读取仍走旧路径,未接新数据层接口 | 高 | packages-user/data-base/src/types.ts:21(IStateBase.hero);packages-user/data-base/src/hero/types.ts:310(floorId);packages-user/data-common/src/replay/types.ts:375(IReplaySystem.replaying) |
+| #04-01-M-02 | `HeroAnimateDirection`(`@user/data-common`) | packages-user/client-modules/src/render/map/extension/hero.ts:6(使用 `:53,:97,:170,:372,:437`);packages-user/client-modules/src/render/map/extension/types.ts:5(使用 `:147`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`。数据端该枚举已改名,`HeroAnimateDirection` 在全仓库 `data-*` 包中无定义;现行名为 `ObjectAnimDirection`,成员 Forward/Backward 一一对应 | 勇士/跟随者动画正向/反向播放方向类型无法解析(`hero.ts` 多处编译失败),需渲染端改用 `ObjectAnimDirection` | 高 | packages-user/data-common/src/common/mover.ts:40(ObjectAnimDirection) |
+| #04-01-M-03 | `IHeroMoveController`(`@user/data-common` / `@user/data-base`) | packages-user/client-modules/src/render/map/extension/hero.ts:8(使用 `:79`);packages-user/client-modules/src/render/map/extension/types.ts:7(使用 `:15,:27,:35`);packages-user/client-modules/src/render/map/extension/manager.ts:2(使用 `:18,:27,:39`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`。数据端无此名;旧「勇士移动控制器」被拆分为 `IObjectMover` / `IHeroMover` 与 `IMoverController`。渲染端 `heroMap: Map`、`addHero`/`removeHero`、`MapHeroRenderer` 构造参数均失配 | 整个 `extension/hero.ts` 勇士渲染拓展无法按新接口接线,需按 `IHeroState`/`IHeroMover` 局部重构 | 高 | packages-user/data-common/src/common/mover.ts:235,142;packages-user/data-base/src/hero/types.ts:399 |
+| #04-01-M-04 | `IHeroMoveControllerHooks`(`@user/data-common`) | packages-user/client-modules/src/render/map/extension/hero.ts:10(使用 `:61,:457`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`。数据端无此 hooks 契约;现行拆为 `IObjectMoverHooks` + `IHeroLocationHooks`,且钩子方法名整体不一致(渲染端 `onSetPosition`/`onStartMove`/`onEndMove` vs 数据端 `onSetPos`/`onMoveStart`/`onMoveEnd` 等) | 勇士贴图、移动、跳跃、透明度、跟随者的数据端通知全部断链,需改为订阅数据端现行 hooks | 高 | packages-user/data-common/src/common/mover.ts:166(IObjectMoverHooks);packages-user/data-base/src/hero/types.ts:277(IHeroLocationHooks)、:486(IHeroFollowersControllerHooks) |
+| #04-01-M-05 | `IHeroMoveController` 成员形状(`hero.x/y/direction/image/addHook`) | packages-user/client-modules/src/render/map/extension/hero.ts:79-89,115-119(另 `:143,:193`) | ① 错配 | 渲染端按旧 `HeroStatus` 形状读取 `hero.x`/`hero.y`/`hero.direction`/`hero.image`/`hero.addHook`。新 `IHeroMover` 经 `IObjectMover` 提供 x/y,但无 `direction`(朝向为 `faceDirection`/`moveDirection`)、无 `image`;`IHeroRendering` 仅暴露 `alpha`/`setAlpha` | `MapHeroRenderer` 构造、贴图选择、跟随者初始化全部错读旧成员 | 高 | packages-user/data-common/src/common/mover.ts:235-251;packages-user/data-base/src/hero/types.ts:436-449 |
+| #04-01-M-06 | `getHeroStatusOn`(`@user/data-state`) | packages-user/client-modules/src/render/ui/main.tsx:29(使用 `:104-112`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`(`main.tsx:28`)。`data-state` barrel(`index.ts:1-9`)无 `getHeroStatusOn` 导出,全仓库亦无定义(`data-state` 自身 `enemy/special.ts` 也是无 import 直接引用)。属性侧新等价为 `getFinalAttribute` | `main.tsx` 共 9 处属性读取无法解析,状态栏属性值读取断链 | 高 | packages-user/data-state/src/index.ts:1-9(无导出);packages-user/data-base/src/hero/types.ts:106(getFinalAttribute) |
+| #04-01-M-07 | `ItemState`(`@user/data-state`) | packages-user/client-modules/src/render/ui/statistics.tsx:11(使用 `:178`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`(`statistics.tsx:10`)。`data-state` 无此导出且全仓库无定义;新接口为 `state.itemStore`(`IItemStore.getData(num)`),且入参由字符串 id 变为图块数字 | `ItemState.items.get(...)` 无法解析;统计界面道具分类逻辑失效 | 高 | packages-user/data-common/src/store/types.ts:189(IItemStore);packages-user/data-state/src/core.ts:79(itemStore)、packages-user/data-state/src/ins.ts:10(state 单例) |
+| #04-01-M-08 | `state.maps` 作为 `IGameMap` 传入 `map-render` | packages-user/client-modules/src/render/ui/main.tsx:248 | ① 错配 | ``:`state.maps` 类型是 `IMapState`(多楼层管理对象),而 `layerState` 契约要求单楼层 `IGameMap`(`render/elements/props.ts:27`);`:247` 带 `// @ts-expect-error 需要重构`。正确取法应为 `state.maps.getActiveMap(floorId)` / `getMap(floorId)` | 主地图渲染入口状态对象类型/形状错配,地图图层读取链在根节点即失配 | 高 | packages-user/data-base/src/map/types.ts:617(IGameMap)、:744(IMapState)、:761(getMap)、:773(getActiveMap) |
+| #04-01-M-09 | `IGameMapHooks.onUpdateLayerArea` / `.onUpdateLayerBlock` | packages-user/client-modules/src/render/map/renderer.ts:1745,1755(注册 `:426`,类型 `:83`) | ① 错配 | 渲染把「区域更新/单点图块更新」钩子挂在 `IGameMapHooks` 上,但现行 `IGameMapHooks` 只有 `onChangeBackground?` / `onUpdateLayer?` / `onResizeLayer?`;图层级的 `onUpdateArea` / `onUpdateBlock` 已下移到 `IMapLayerHooks`,且 `GameMap` 的 `StateMapLayerHook` 只桥接 `onResize` → `onResizeLayer` | `MapRenderer.updateLayerArea` / `updateLayerBlock`(`renderer.ts:1527,:1545`)永不被触发,静态图块/区域变更后顶点数组不刷新 | 高 | packages-user/data-base/src/map/types.ts:588-608(IGameMapHooks)、:207-230(IMapLayerHooks);packages-user/data-base/src/map/gameMap.ts:222-232;packages-user/data-base/src/map/mapLayer.ts:243,300 |
## ② 数据端缺失接口
@@ -41,14 +49,23 @@
**#04-01-M-01:** `HeroKeyMover` 构造期 import `{ HeroMover, IMoveController } from '@user/data-state'`(编译期即带 `// @ts-expect-error 需要重构`)→ 因其按旧概念「移动控制器」声明 `controller?: IMoveController`、`mover: HeroMover` → 数据端实际只在 `@user/data-base` 提供 `HeroMover`(`data-base/src/hero/mover.ts:22`)、把控制器命名为 `IMoverController`(`data-common/src/common/mover.ts:142`)→ 该模块无法按新接口接线,需局部重构为 `IHeroMover` / `IMoverController`。
+**#04-01-M-06:** `updateStatus()` 调用 `getHeroStatusOn('atk')`(`main.tsx:104`)→ 该符号自 `@user/data-state` import(`main.tsx:28-29`,带 `// @ts-expect-error 需要重构`)→ `data-state` barrel 无此导出、全仓库无定义 → 按旧接口取属性失败;新路径应为 `state.hero.attribute.getFinalAttribute(...)`(`data-base/src/hero/types.ts:106`),需渲染端改读写法。
+
+**#04-01-M-09:** 地图上某点图块被改写 → 数据端 `MapLayer` 触发的是 `IMapLayerHooks.onUpdateBlock`(`data-base/src/map/mapLayer.ts:243`)→ 但渲染端把处理函数注册为 `IGameMapHooks.onUpdateLayerBlock`(`renderer.ts:426,1755`),而 `IGameMapHooks` 已无该方法、`StateMapLayerHook` 也只桥接 `onResize` → `MapRenderer.updateLayerBlock` 永不被调用 → 顶点数组不刷新,画面与数据不一致。
+
## 判定为「匹配」的同类边界
- **渲染侧素材族(`core.material.*` / `core.materials` / `core.icons.*` / `core.tilesets`)**:这些读取的是渲染端自己的素材/纹理资产,不是数据端状态,因此不算数据端错配。判据:其值由渲染端 `client-base/src/material/**` 与加载器写入(如 `client-base/src/load/loader.ts:103,129,151,167` 写 `core.material.images.*`、`:284,418` 读 `core.materials`),数据端 `types.ts` 中并无对应状态字段。典型命中:`render/elements/cache.ts`、`render/elements/misc.ts`、`render/components/misc.tsx`、`render/weather/presets/{cloud,fog,sun}.ts`、`fallback/load.ts:55-56`。
-- **A4 新接口用法**:`hook` 订阅(`render/index.tsx:4,37-40`)、`loading` 生命周期(`client-modules/src/index.ts:1`、`client.ts:43`)、`state` 单例(`render/map/extension/hero.ts:21,225`)均命中数据端现行接口,属已走新接口。
+- **A1 渲染契约桥(全链一致)**:`client-base/src/types.ts` 的 `IClientBase extends ICoreState` 继承成员全部核对一致;`client-modules/src/types.ts` 的 `IClientCore extends IClientBase` 新增成员无冲突 → 判为匹配。
+- **A2 组合根/单例**:`ClientCore extends CoreState implements IClientCore`(`client.ts:46`)接口要求成员逐一实现;`loading.once('coreInit')`(`index.ts:8`)、`loading.once('loaded')`/`emit('assetBuilt')`(`client.ts:97,99`)、`hook.on('restart')`(`render/index.tsx:37`)均命中数据端现行事件键;`core.ts:6` 单例与 `data-state/src/ins.ts:10` 一致 → 判为匹配。
+- **A4 新接口用法**:`hook` 订阅(`render/index.tsx:4,37-40`)、`loading` 生命周期(`client-modules/src/index.ts:1`、`client.ts:43`)、`state` 单例(`render/map/extension/hero.ts:21,225`)、`client.flags.getFieldValueDefaults`(`main.tsx:100-101,121-123,134`)均命中数据端现行接口,属已走新接口。
+- **A5 数据端类型直连(解析成功)**:`IGameMap` / `IMapLayer` / `IGameMapHooks` / `IMapState` / `IMapLayerHooks` / `IMapLayerHookController` 在 `render/map/{renderer,moving,element,status,vertex,types}.ts`、`render/elements/props.ts`、`render/map/extension/{manager,door}.ts` 的 import 均解析成功;`getMapRef()` 的 `IMapLayerData{expired,array}` 解构(`vertex.ts:620,820`)匹配;`extension/door.ts` 的 `MapDoorHook` 命中 `IMapLayerHooks` → 判为匹配。
+- **A7 加载器 import(解析成功)**:`client-base/src/load/loader.ts:16-28` 的处理器类型与 `loading`/`IMotaDataLoader` 均解析成功;`client-base/src/material/**` 无 `@user/data-*` import、无 `core.` 读取,为纯渲染素材实现。
+- **`gameListener`**:`data-base/src/game.ts:266-267` 的 `@deprecated gameListener` 在 `client-base`/`client-modules` 中无任何引用,渲染端已改走 `hook` → 判为匹配。
## 未能从阅读确定(未猜测)
-1. **② 节是否有缺失项**:本步未确认任何数据端缺失接口,但存在若干候选(如游戏工程元数据 `core.firstData.*`、勇士渲染粒度钩子),其「应由数据端补充」还是「由渲染端改接既有数据对象」无法仅凭静态阅读判定,故不登记为 ②,见 Task 3 定稿段。
+1. **② 节是否有缺失项**:本步未确认任何数据端缺失接口,但存在若干候选(如游戏工程元数据 `core.firstData.*`、勇士渲染粒度钩子),其「应由数据端补充」还是「由渲染端改接既有数据对象」无法仅凭静态阅读判定,故不登记为 ②,见下(Task 3 定稿段继续补全)。
## 处置
From 4327e6733b1e241c3a840a91c63c9c10e860624c Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 10:48:11 +0800
Subject: [PATCH 012/173] =?UTF-8?q?docs(04-01):=20=E6=B8=B2=E6=9F=93?=
=?UTF-8?q?=E7=AB=AF=20=E2=86=94=20=E6=95=B0=E6=8D=AE=E7=AB=AF=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E5=AF=B9=E8=B4=A6=EF=BC=88=E5=8F=AA=E8=AF=BB=E6=B8=85?=
=?UTF-8?q?=E7=82=B9=EF=BC=8CD-01=20=E7=AC=AC=E4=B8=80=E6=AD=A5=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../04-RENDER-INTERFACE-AUDIT.md | 67 +++++++++++++++++--
1 file changed, 63 insertions(+), 4 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md b/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
index fd6f271..acc4685 100644
--- a/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+++ b/.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
@@ -36,6 +36,48 @@
| #04-01-M-07 | `ItemState`(`@user/data-state`) | packages-user/client-modules/src/render/ui/statistics.tsx:11(使用 `:178`) | ① 错配 | import 带 `// @ts-expect-error 需要重构`(`statistics.tsx:10`)。`data-state` 无此导出且全仓库无定义;新接口为 `state.itemStore`(`IItemStore.getData(num)`),且入参由字符串 id 变为图块数字 | `ItemState.items.get(...)` 无法解析;统计界面道具分类逻辑失效 | 高 | packages-user/data-common/src/store/types.ts:189(IItemStore);packages-user/data-state/src/core.ts:79(itemStore)、packages-user/data-state/src/ins.ts:10(state 单例) |
| #04-01-M-08 | `state.maps` 作为 `IGameMap` 传入 `map-render` | packages-user/client-modules/src/render/ui/main.tsx:248 | ① 错配 | ``:`state.maps` 类型是 `IMapState`(多楼层管理对象),而 `layerState` 契约要求单楼层 `IGameMap`(`render/elements/props.ts:27`);`:247` 带 `// @ts-expect-error 需要重构`。正确取法应为 `state.maps.getActiveMap(floorId)` / `getMap(floorId)` | 主地图渲染入口状态对象类型/形状错配,地图图层读取链在根节点即失配 | 高 | packages-user/data-base/src/map/types.ts:617(IGameMap)、:744(IMapState)、:761(getMap)、:773(getActiveMap) |
| #04-01-M-09 | `IGameMapHooks.onUpdateLayerArea` / `.onUpdateLayerBlock` | packages-user/client-modules/src/render/map/renderer.ts:1745,1755(注册 `:426`,类型 `:83`) | ① 错配 | 渲染把「区域更新/单点图块更新」钩子挂在 `IGameMapHooks` 上,但现行 `IGameMapHooks` 只有 `onChangeBackground?` / `onUpdateLayer?` / `onResizeLayer?`;图层级的 `onUpdateArea` / `onUpdateBlock` 已下移到 `IMapLayerHooks`,且 `GameMap` 的 `StateMapLayerHook` 只桥接 `onResize` → `onResizeLayer` | `MapRenderer.updateLayerArea` / `updateLayerBlock`(`renderer.ts:1527,:1545`)永不被触发,静态图块/区域变更后顶点数组不刷新 | 高 | packages-user/data-base/src/map/types.ts:588-608(IGameMapHooks)、:207-230(IMapLayerHooks);packages-user/data-base/src/map/gameMap.ts:222-232;packages-user/data-base/src/map/mapLayer.ts:243,300 |
+| #04-01-L-01 | `core.status.*` / `core.itemCount()` / `core.status.replay` / `core.isReplaying()` | packages-user/client-modules/src/render/ui/main.tsx:99,103,114-120,124,127-128 | ③ 多余旧路径 | 同一文件 `:100-101,121-123,134` 已改用新接口 `client.flags.getFieldValueDefaults(...)`,但状态栏其余字段与回放态仍读 legacy 全局 `core.*`,新旧双路径并存 | 状态栏数值/回放状态读取仍走旧路径,未接新数据层接口 | 高 | packages-user/data-base/src/types.ts:21(IStateBase.hero);packages-user/data-base/src/hero/types.ts:310(floorId)、:636(itemCount);packages-user/data-common/src/replay/types.ts:375(IReplaySystem.replaying) |
+| #04-01-L-02 | `core.isReplaying()` | packages-user/client-modules/src/action/move.ts:66 | ③ 多余旧路径 | 经 legacy 全局查录像播放态,同一文件已 import `@user/data-state`(带重构标记) | 输入门控走旧路径,未接数据端录像系统 | 高 | packages-user/data-common/src/replay/types.ts:375 |
+| #04-01-L-03 | `core.isReplaying()` | packages-user/client-modules/src/action/hotkey.ts:478 | ③ 多余旧路径 | 按键门控读旧录像态 | 回放期按键门控走旧全局 | 高 | packages-user/data-common/src/replay/types.ts:375 |
+| #04-01-L-04 | `core.doSL()` | packages-user/client-modules/src/action/hotkey.ts:496,499 | ③ 多余旧路径 | 撤销/重做仍走 legacy 存读档命令 | 存读档链路未接新 `ISaveSystem` 与新存档契约 | 高 | packages-user/data-state/src/core.ts:283(saveState);packages-user/client-base/src/save/types.ts:42(ISaveSystem) |
+| #04-01-L-05 | `core.setFlag()` / `core.removeFlag()` | packages-user/client-modules/src/render/ui/statistics.tsx:140,219,224,251 | ③ 多余旧路径 | 统计期间用 legacy flag 全局开关 | 新 `IFlagSystem` 未用 | 高 | packages-user/data-base/src/flag/types.ts:47(IFlagSystem)、:123(getFieldValue) |
+| #04-01-L-06 | `core.status.hero`(读 + 赋 Proxy 写回) | packages-user/client-modules/src/render/ui/statistics.tsx:144,158,215,225,240 | ③ 多余旧路径 | 统计采样对旧 `core.status.hero` 读写并赋 Proxy;新 `state.hero` 为只读对象,无同名可写属性 | 统计代理手法无法直接平移,需改为对新 `IHeroState` 只读读取 | 中 | packages-user/data-base/src/types.ts:21;packages-user/data-base/src/hero/types.ts:889(IHeroState) |
+| #04-01-L-07 | `window.hero` / `window.flags` / `core.status.hero.flags`(写全局) | packages-user/client-modules/src/render/ui/statistics.tsx:216-217,248-250 | ③ 多余旧路径 | 把旧 `hero.flags` 灌到 `window` 全局 | flag 来源应为新 `IFlagSystem` | 中 | packages-user/data-base/src/types.ts:27(IStateBase.flags) |
+| #04-01-L-08 | `core.floorIds` | packages-user/client-modules/src/render/ui/statistics.tsx:243 | ③ 多余旧路径 | 遍历全楼层用旧 id 列表 | 楼层有序列表未接新数据端 | 高 | packages-user/data-base/src/map/types.ts:747(IMapState.maps) |
+| #04-01-L-09 | `core.status.maps[floorId].blocks` | packages-user/client-modules/src/render/ui/statistics.tsx:174 | ③ 多余旧路径 | 旧 `status.maps` 是 `Record`;新 `IMapState.getMap(id)` 返回 `IGameMap`,无 `.blocks` 字段(且该读取为 legacy 全局) | 全塔统计无法按旧字段取块,需改用新 `IGameMap` 取块接口 | 中 | packages-user/data-base/src/map/types.ts:761(getMap)、:617(IGameMap) |
+| #04-01-L-10 | `core.status.floorId` | packages-user/client-modules/src/render/ui/viewmap.tsx:54,566 | ③ 多余旧路径 | 浏览地图用旧楼层 id | 楼层态来源不统一 | 高 | packages-user/data-base/src/hero/types.ts:310(IHeroLocation.floorId) |
+| #04-01-L-11 | `core.floorIds` | packages-user/client-modules/src/render/ui/viewmap.tsx:71 | ③ 多余旧路径 | 同 L-08 | 同 L-08 | 高 | packages-user/data-base/src/map/types.ts:747 |
+| #04-01-L-12 | `core.status.hero.flags.__removed__` / `core.floors[v].cannotViewMap` | packages-user/client-modules/src/render/ui/viewmap.tsx:73-74,137 | ③ 多余旧路径 | 旧 `hero.flags` 判楼层移除;`cannotViewMap` 为旧全塔属性 | flag/可见性读取走旧形状 | 中 | packages-user/data-base/src/flag/types.ts:123;packages-user/data-base/src/map/types.ts:744 |
+| #04-01-L-13 | `core.hasItem()` | packages-user/client-modules/src/render/ui/viewmap.tsx:133 | ③ 多余旧路径 | 检查传送道具走旧全局 | 道具判定未接新物品栏 | 高 | packages-user/data-base/src/hero/types.ts:623(getItemState) |
+| #04-01-L-14 | `core.flyTo()` | packages-user/client-modules/src/render/ui/viewmap.tsx:135 | ③ 多余旧路径 | 楼层传送走旧命令 | 未接新寻路/瞬移接口 | 中 | packages-user/data-system/src/path/types.ts:193(teleportTo) |
+| #04-01-L-15 | `core.status.floorId` / `core.status.thisMap`(写回) | packages-user/client-modules/src/render/ui/viewmap.tsx:361-362 | ③ 多余旧路径 | 卸载时写旧 `status.floorId`/`thisMap`;新面为 `state.hero.location.setFloor(IGameMap)` | 浏览地图后状态恢复走旧形状 | 中 | packages-user/data-base/src/hero/types.ts:310(floorId)、:312(map) |
+| #04-01-L-16 | `core.status.route` | packages-user/client-modules/src/render/ui/settings.tsx:165,230 | ③ 多余旧路径 | 读取/序列化旧录像路由数组(`core.encodeRoute(core.status.route)`) | 录像数据走旧字符串数组 | 高 | packages-user/data-common/src/replay/types.ts:329(IReplaySandboxConfig.route) |
+| #04-01-L-17 | `core.status.hard` | packages-user/client-modules/src/render/ui/settings.tsx:168,228 | ③ 多余旧路径 | 难度读旧 status;同文件 `:166-167` 已用 `client.flags.getFieldValue` 取 seed,双路径 | 同 L-01 的双路径问题 | 中 | packages-user/data-base/src/flag/types.ts:123 |
+| #04-01-L-18 | `core.doSL()` | packages-user/client-modules/src/render/ui/settings.tsx:175,177,186,200,208,210 | ③ 多余旧路径 | 回放加载/接续走 legacy 存读档 | 存读档链路未接新契约 | 高 | packages-user/data-state/src/core.ts:283;packages-user/client-base/src/save/types.ts:42 |
+| #04-01-L-19 | `core.saves.saveIndex` | packages-user/client-modules/src/render/ui/settings.tsx:520 | ③ 多余旧路径 | 读旧存档索引 | 新 `ISaveSystem` 只有 `getLastSlot` | 高 | packages-user/client-base/src/save/types.ts:120(getLastSlot) |
+| #04-01-L-20 | `core.clearLocalForage()` | packages-user/client-modules/src/render/ui/settings.tsx:574 | ③ 多余旧路径 | 清空走旧 localForage 全局 | 新存档系统未用 | 高 | packages-user/client-base/src/save/types.ts:42,115 |
+| #04-01-L-21 | `core.saves.saveIndex`(写) | packages-user/client-modules/src/render/ui/settings.tsx:581,623 | ③ 多余旧路径 | 写旧存档索引 | 应为 `ISaveSystem.getLastSlot` 语义 | 中 | packages-user/client-base/src/save/types.ts:120 |
+| #04-01-L-22 | `core.removeLocalForage()` | packages-user/client-modules/src/render/ui/settings.tsx:615,617 | ③ 多余旧路径 | 删档走旧 localForage | 应为新 `deleteSave` | 高 | packages-user/client-base/src/save/types.ts:115(deleteSave) |
+| #04-01-L-23 | `core.maps.loadMap()` | packages-user/client-modules/src/render/ui/save.tsx:111 | ③ 多余旧路径 | 从存档原始数据重建地图走 legacy maps 模块 | 地图重建未接新数据端 | 中 | packages-user/data-base/src/map/types.ts:767(IMapState.fromRaw) |
+| #04-01-L-24 | `core.saves.saveIndex` | packages-user/client-modules/src/render/ui/save.tsx:243,246 | ③ 多余旧路径 | 存读档页定位用旧索引 | 同 L-19 | 高 | packages-user/client-base/src/save/types.ts:120 |
+| #04-01-L-25 | `core.removeSave()` | packages-user/client-modules/src/render/ui/save.tsx:531 | ③ 多余旧路径 | 删档走旧命令 | 应为 `ISaveSystem.deleteSave` | 高 | packages-user/client-base/src/save/types.ts:115 |
+| #04-01-L-26 | `core.doSL()` | packages-user/client-modules/src/render/ui/save.tsx:563,584,586 | ③ 多余旧路径 | 存档/读档走 legacy 命令 | 同 L-18 | 高 | packages-user/data-state/src/core.ts:283 |
+| #04-01-L-27 | `core.doSL()` | packages-user/client-modules/src/render/ui/toolbar.tsx:111,112 | ③ 多余旧路径 | 工具栏撤销/重做走 legacy 命令 | 同 L-18 | 高 | packages-user/data-state/src/core.ts:283 |
+| #04-01-L-28 | `core.resumeReplay()` / `pauseReplay()` / `stopReplay()` / `speedDownReplay()` / `speedUpReplay()` / `rewindReplay()` / `stepReplay()` | packages-user/client-modules/src/render/ui/toolbar.tsx:188-192,202,203 | ③ 多余旧路径 | 回放控制走 legacy 全局命令 | 未接新回放沙箱 | 中 | packages-user/data-common/src/replay/types.ts:108,113,123,129(IReplaySandbox) |
+| #04-01-L-29 | `core.isPlaying()` / `core.isMoving()` / `core.status.lockControl` | packages-user/client-modules/src/render/ui/toolbar.tsx:198 | ③ 多余旧路径 | 回放/移动门控读 legacy 全局;数据端无直接等价的播放态/移动态查询(见「未能从阅读确定」) | 门控走旧全局,无对位新接口 | 中 | packages-user/data-common/src/common/mover.ts:235(IObjectMover 移动态);packages-user/data-state/src/ins.ts:10(state 单例) |
+| #04-01-L-30 | `core.flags.statusBarItems` | packages-user/client-modules/src/render/ui/statusBar.tsx:158 | ③ 多余旧路径 | 状态栏显示项读旧 `core.flags`;同文件 `:119` 已用 `client.materials` | 状态项仍走旧全局 | 高 | packages-user/data-base/src/flag/types.ts:123;packages-user/data-base/src/types.ts:27 |
+| #04-01-L-31 | `core.floors[s.floor]?.title` | packages-user/client-modules/src/render/ui/statusBar.tsx:139 | ③ 多余旧路径 | 状态栏楼层名读旧全塔属性 `title`;新 `IMapState` 无对应 title 字段(见「未能从阅读确定」) | 楼层名元数据来源未接新数据端 | 中 | packages-user/data-base/src/map/types.ts:744 |
+| #04-01-L-32 | `core.status.replay.toReplay` / `core.status.route.push()` | packages-user/client-modules/src/render/components/choices.tsx:675,718-719,733,773-774,789 | ③ 多余旧路径 | 录像步消费/偏移与录像路由写回走旧结构 | 应为 `IReplayArray.add` 等新录像接口 | 高 | packages-user/data-common/src/replay/types.ts:329(route)、:195(IReplayArray.add) |
+| #04-01-L-33 | `core.isReplaying()` | packages-user/client-modules/src/render/components/choices.tsx:715,770 | ③ 多余旧路径 | 选择框回放分支读旧录像态 | 同 L-02 | 高 | packages-user/data-common/src/replay/types.ts:375 |
+| #04-01-L-34 | `core.maps.blocksInfo` | packages-user/client-modules/src/fallback/load.ts:44,59 | ③ 多余旧路径 | 兼容层按旧图块定义表建素材 | 图块定义应走新 `tileStore` | 中 | packages-user/data-common/src/store/types.ts:65(ITileStore) |
+| #04-01-L-35 | `core.floorIds` | packages-user/client-modules/src/fallback/load.ts:111 | ③ 多余旧路径 | 遍历全楼层用旧 id 列表 | 同 L-08 | 中 | packages-user/data-base/src/map/types.ts:747 |
+| #04-01-L-36 | `core.floors[v]`(原始楼层数据) | packages-user/client-modules/src/fallback/load.ts:112 | ③ 多余旧路径 | 读旧全塔楼层原始数据(bgmap/fgmap 等),新 `IMapStore` 数据形状不同 | 需按新 `IMapRawData`(`floorId`/`width`/`map`/`layerAlias`/`events`)适配 | 低 | packages-user/data-common/src/store/types.ts:254(IMapRawData)、:267(IMapStore) |
+| #04-01-L-37 | `core.getSave()` | packages-user/client-modules/src/render/utils/saves.ts:21 | ③ 多余旧路径 | 读单档走 legacy 回调 API | 新异步 `ISaveSystem.load` 未用 | 高 | packages-user/client-base/src/save/types.ts:109(load) |
+| #04-01-L-38 | `core.setLocalForage()` | packages-user/client-modules/src/render/utils/saves.ts:172,187 | ③ 多余旧路径 | 写档走旧 localForage | 应为新 `setGlobal` | 高 | packages-user/client-base/src/save/types.ts:133(setGlobal) |
+| #04-01-L-39 | `core.removeLocalForage()` | packages-user/client-modules/src/render/utils/saves.ts:174 | ③ 多余旧路径 | 删档走旧 localForage | 应为新 `deleteSave` | 高 | packages-user/client-base/src/save/types.ts:115 |
+| #04-01-L-40 | `core.saves.saveIndex` | packages-user/client-modules/src/render/utils/saves.ts:185 | ③ 多余旧路径 | 读旧存档索引 | 应为 `getLastSlot` | 高 | packages-user/client-base/src/save/types.ts:120 |
+| #04-01-L-41 | `core.saves.saveIndex` / `core.saves.ids` | packages-user/client-modules/src/render/utils/saves.ts:173 | ③ 多余旧路径 | 判断存档存在走旧 `saves.ids` | 新保存系统无 ids 映射,语义需迁移 | 中 | packages-user/client-base/src/save/types.ts:42,120 |
+| #04-01-L-42 | `core.floors[].title` | packages-user/client-modules/src/render/components/floorSelect.tsx:62,181 | ③ 多余旧路径 | 楼层选择器读旧 `core.floors[].title` | 楼层标题元数据未接新数据端(同 L-31) | 中 | packages-user/data-base/src/map/types.ts:744 |
## ② 数据端缺失接口
@@ -55,21 +97,38 @@
## 判定为「匹配」的同类边界
-- **渲染侧素材族(`core.material.*` / `core.materials` / `core.icons.*` / `core.tilesets`)**:这些读取的是渲染端自己的素材/纹理资产,不是数据端状态,因此不算数据端错配。判据:其值由渲染端 `client-base/src/material/**` 与加载器写入(如 `client-base/src/load/loader.ts:103,129,151,167` 写 `core.material.images.*`、`:284,418` 读 `core.materials`),数据端 `types.ts` 中并无对应状态字段。典型命中:`render/elements/cache.ts`、`render/elements/misc.ts`、`render/components/misc.tsx`、`render/weather/presets/{cloud,fog,sun}.ts`、`fallback/load.ts:55-56`。
+- **渲染侧素材族(`core.material.*` / `core.materials` / `core.icons.*` / `core.tilesets`)**:这些读取的是渲染端自己的素材/纹理资产,不是数据端状态,因此不算数据端错配。判据:其值由渲染端 `client-base/src/material/**` 与加载器写入(如 `client-base/src/load/loader.ts:103,129,151,167` 写 `core.material.images.*`、`:284,418` 读 `core.materials`),数据端 `types.ts` 中并无对应状态字段。典型命中:`render/elements/cache.ts`、`render/elements/misc.ts`、`render/components/misc.tsx`、`render/weather/presets/cloud.ts`、`render/weather/presets/fog.ts`、`render/weather/presets/sun.ts`、`fallback/load.ts:55-56`。
+- **渲染工具/全局(非数据端状态)**:`core.formatBigNumber`(显示格式化)、`core.arrayToRGBA`(`render/ui/title.tsx:126`)、`core.drawThumbnail`(`render/components/thumbnail.tsx:75`)、`core.drawTip`(`render/ui/save.tsx:524,533`)、`core.platform.isPC`(`render/ui/settings.tsx:284`)、`core.download`(`render/ui/settings.tsx:223`)、`core.playSound`(`render/ui/settings.tsx:432`)、`core.formatDate2`(`render/ui/settings.tsx:224`)、`core.getTilesetOffset` / `core.getAnimateFrames`(`render/elements/cache.ts:135,254`)、`core.getMappedName`(`fallback/audio.ts:49`)——均为渲染/浏览器/工具侧能力。
+- **legacy 行为命令 / UI 控制(非数据端状态读取)**:`core.openBook`/`openToolbox`/`openEquipbox`/`useFly`/`openQuickShop`/`turnHero`/`getNextItem`/`confirmRestart`/`debug`(`action/hotkey.ts:481-514`、`render/ui/toolbar.tsx`、`render/ui/viewmap.tsx:130`)、`core.ui.closePanel`(`render/ui/settings.tsx:164`)、`core.ui._drawViewMaps`(`render/ui/toolbar.tsx:199`)、`core.doRegisteredAction`(`render/ui/main.tsx:198,207,216`)、`core.chooseReplayFile`(`render/ui/title.tsx:214`、`render/ui/settings.tsx:217`)、`core.startGame`(`render/ui/title.tsx:227`、`render/ui/settings.tsx:168`)、`core.restart`(`render/ui/settings.tsx:112`)——为生命周期/交互命令,非数据端状态读取。
- **A1 渲染契约桥(全链一致)**:`client-base/src/types.ts` 的 `IClientBase extends ICoreState` 继承成员全部核对一致;`client-modules/src/types.ts` 的 `IClientCore extends IClientBase` 新增成员无冲突 → 判为匹配。
- **A2 组合根/单例**:`ClientCore extends CoreState implements IClientCore`(`client.ts:46`)接口要求成员逐一实现;`loading.once('coreInit')`(`index.ts:8`)、`loading.once('loaded')`/`emit('assetBuilt')`(`client.ts:97,99`)、`hook.on('restart')`(`render/index.tsx:37`)均命中数据端现行事件键;`core.ts:6` 单例与 `data-state/src/ins.ts:10` 一致 → 判为匹配。
- **A4 新接口用法**:`hook` 订阅(`render/index.tsx:4,37-40`)、`loading` 生命周期(`client-modules/src/index.ts:1`、`client.ts:43`)、`state` 单例(`render/map/extension/hero.ts:21,225`)、`client.flags.getFieldValueDefaults`(`main.tsx:100-101,121-123,134`)均命中数据端现行接口,属已走新接口。
-- **A5 数据端类型直连(解析成功)**:`IGameMap` / `IMapLayer` / `IGameMapHooks` / `IMapState` / `IMapLayerHooks` / `IMapLayerHookController` 在 `render/map/{renderer,moving,element,status,vertex,types}.ts`、`render/elements/props.ts`、`render/map/extension/{manager,door}.ts` 的 import 均解析成功;`getMapRef()` 的 `IMapLayerData{expired,array}` 解构(`vertex.ts:620,820`)匹配;`extension/door.ts` 的 `MapDoorHook` 命中 `IMapLayerHooks` → 判为匹配。
+- **A5 数据端类型直连(解析成功)**:`IGameMap` / `IMapLayer` / `IGameMapHooks` / `IMapState` / `IMapLayerHooks` / `IMapLayerHookController` 在 `render/map/renderer.ts`、`render/map/moving.ts`、`render/map/element.ts`、`render/map/status.ts`、`render/map/vertex.ts`、`render/map/types.ts`、`render/elements/props.ts`、`render/map/extension/manager.ts`、`render/map/extension/door.ts` 的 import 均解析成功;`getMapRef()` 的 `IMapLayerData{expired,array}` 解构(`vertex.ts:620,820`)匹配;`extension/door.ts` 的 `MapDoorHook` 命中 `IMapLayerHooks` → 判为匹配。
- **A7 加载器 import(解析成功)**:`client-base/src/load/loader.ts:16-28` 的处理器类型与 `loading`/`IMotaDataLoader` 均解析成功;`client-base/src/material/**` 无 `@user/data-*` import、无 `core.` 读取,为纯渲染素材实现。
- **`gameListener`**:`data-base/src/game.ts:266-267` 的 `@deprecated gameListener` 在 `client-base`/`client-modules` 中无任何引用,渲染端已改走 `hook` → 判为匹配。
## 未能从阅读确定(未猜测)
-1. **② 节是否有缺失项**:本步未确认任何数据端缺失接口,但存在若干候选(如游戏工程元数据 `core.firstData.*`、勇士渲染粒度钩子),其「应由数据端补充」还是「由渲染端改接既有数据对象」无法仅凭静态阅读判定,故不登记为 ②,见下(Task 3 定稿段继续补全)。
+1. **② 候选:游戏工程元数据(name/version/title)** —— 渲染端经 legacy `core.firstData.*` 读取(`client.ts:71`、`render/utils/saves.ts:27,28,44,45,103`、`render/ui/settings.tsx:224,227,299,317,504,522`、`render/ui/title.tsx:368`);`@user/data-*` 无任何工程元数据接口(`IMotaDataLoader` 仅 progress/jsonProcessor/initSystemLoadTask/addCustomLoadTask/load,`data-base/src/load/types.ts:10-38`)。「应由数据端新增接口,还是继续由 legacy bridge 供给」无法判定,故不登记为 ②。
+2. **② 候选:勇士/跟随者渲染粒度钩子** —— 渲染端需要 `onTurnHero`/`onMoveHero`/`onJumpHero`/`onSetFollowerAlpha`/`onAddFollower(number,string)`/`onRemoveFollower(string,animate)`(`render/map/extension/hero.ts:473,481,489,502-516`);数据端 `IObjectMoverHooks`/`IHeroFollowersControllerHooks` 提供的是面向数据对象的事件(`data-common/src/common/mover.ts:166`、`data-base/src/hero/types.ts:486-506`)。「数据端补渲染语义钩子」还是「渲染端改接数据对象钩子」无法判定,可能与 M-04 的重构方案重叠。
+3. **勇士贴图别名 `hero.image`** —— `render/map/extension/hero.ts:115,119,143,193`;`IHeroState` 暴露 location/attribute/followers/rendering/items/equip,`IHeroRendering` 仅 `alpha`(`data-base/src/hero/types.ts:436-449`)。按用户分类规则,该缺口随 `IHeroMoveController` 整体归 ①(M-05),不重复登记为 ②;「数据端是否确应提供贴图别名」未定。
+4. **无对位的旧状态读**(数据端无直接等价查询,故不登记为 ③,也不登记为 ②):`core.status.lockControl`(`action/move.ts:97,123,156`、`action/hotkey.ts:478`、`render/ui/main.tsx:139`、`render/ui/toolbar.tsx:198`)、`core.status.stepPostfix`(`render/ui/main.tsx:155,165`)、`core.getNextLvUpNeed()`(`render/ui/main.tsx:113`)、`core.getLvName()`(`render/ui/main.tsx:125`)、`core.isPlaying()`/`core.isMoving()`(`action/move.ts:66`、`render/ui/toolbar.tsx:198`)、`core.waitHeroToStop()`(`action/move.ts:67`)。
+5. **旧存档内部态**(新 `ISaveSystem` 无对应成员):`core.saves.ids`/`autosave`/`favorite`/`favoriteName`/`cache`(`render/ui/settings.tsx:575-583,618-625`、`render/utils/saves.ts:173`)、`core.control._updateFavoriteSaves()`(`render/ui/settings.tsx:585,627`)、`core.removeLocalStorage()`(`render/ui/settings.tsx:586,628`)、`core.syncSave()`(`render/ui/settings.tsx:440,453`)、`core.getAllSaves()`(`render/utils/saves.ts:38`)。
+6. **`core.control.__replay_getTimeout()`** —— `render/components/choices.tsx:717,728,772,783`;数据端无对应接口。
+7. **`core.extractBlocks(floorId)` / `core.extractBlocksForUI(map, flags)`** —— `render/ui/statistics.tsx:160`、`render/ui/save.tsx:112`。
+8. **`core.calValue` / `core.replaceText`** —— `render/components/textboxTyper.ts:906`、`render/components/tip.tsx:103`。
+9. **`fallback/load.ts:112` 的 `core.floors[v]` 形状** —— 读出 bgmap/bg2map/map/fgmap/fg2map 五层数组,而 `IMapRawData` 为 `map` + `layerAlias` + `events` 结构(`data-common/src/store/types.ts:254`);是否可等价映射需人工确认。
+10. **`getHeroStatusOn` / `ItemState` 的归属** —— 是否将作为 `data-state` 导出补齐,还是由 `state.hero.attribute.getFinalAttribute`(`data-base/src/hero/types.ts:106`)/ `state.itemStore`(`data-common/src/store/types.ts:189`)取代:全仓库无定义。
+11. **`state.maps` 在 `render/ui/main.tsx:248` 的正确替代** —— `getActiveMap`/`getMap`/新 selector 无法从静态契约确定。
+12. **`core.materials` / `core.loader._loadAnimate` 是否会由 `MaterialManager`/新加载器取代** —— `client-base/src/load/loader.ts:188,284,418` 暂留在「匹配」边界,未登记为 ③。
+13. **A6 双布局资产是否已规划接线** —— `render/use.ts` 的 `Orientation`/`onOrientationChange` 是否应接到 `shared.ts` 布局常量,无法判定(当前无消费点)。
+14. **模式实现未验证** —— `state` 单例(`data-state/src/ins.ts:10`)与 `client`(`client-modules/src/core.ts:6`)运行时是否已完全覆盖上述 legacy 字段,仅静态阅读、未做运行验证。
+15. **`ISaveSystem`(`client-base/src/save/types.ts:42`)与数据端存档方案的最终关系** —— 渲染侧除 `client.ts:70-71` 初始化外无任何消费者;其 `autosave` / `save(state: Map>)` 与数据端 `ICoreState.saveState(): ReadonlyMap`(`data-state/src/types.ts:15-16`)是两套不同形态。
## 处置
-- **本步性质**:只读清点,**无任何生产代码改动**;被查两包 `client-base` / `client-modules` 保持零改动。
+- **本步性质**:只读清点,**无任何生产代码改动**;被查两包 `client-base` / `client-modules` 保持零改动(`git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空)。
+- **分类分工**:① 错配(M)为「已引向新面但形状/名称不兼容、需渲染端局部重构」;③ 多余旧路径(L)为「仍走已被新接口取代的 legacy 全局 `core.*`」;② 独立节本步未确认缺失项,候选见「未能从阅读确定」。
- **后续拆分**:实际适配实施与移动端/桌面端双布局**未在本 run 规划**,由用户在对账结果出来后决定如何拆分(D-01/D-02)。REND-01(渲染端适配新数据层接口)与 REND-02(双布局)的验收边界尚未提供,需用户在规划实施前补齐。
- **双布局既有资产(仅登记为后续起点,不展开实施)**:
- packages-user/client-modules/src/render/use.ts:22-66 —— `Orientation`(Landscape/Portrait)枚举、`OrientationHook`、`checkOrientation()`(`window.innerWidth >= window.innerHeight`)与 `onOrientationChange()`;当前在 `client-modules` 内无消费点。
From 41616833b2ef88bdcd22fb486bc0a716faf9b99c Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 10:53:59 +0800
Subject: [PATCH 013/173] =?UTF-8?q?docs(04-01):=20complete=20=E6=B8=B2?=
=?UTF-8?q?=E6=9F=93=E9=80=82=E9=85=8D=E4=B8=8E=E5=8F=8C=E5=B8=83=E5=B1=80?=
=?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AD=A5=EF=BC=88=E5=8F=AA=E8=AF=BB=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E5=AF=B9=E8=B4=A6=EF=BC=89plan?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.planning/ROADMAP.md | 6 +-
.planning/STATE.md | 35 +++--
.../04-render-adaptation/04-01-SUMMARY.md | 135 ++++++++++++++++++
.planning/state.json | 6 +-
4 files changed, 161 insertions(+), 21 deletions(-)
create mode 100644 .planning/phases/04-render-adaptation/04-01-SUMMARY.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 245fa52..33c1500 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,11 +181,11 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 1 plan(第一步·只读对账;本阶段增量规划,后续适配实施与双布局待对账结果出来后另行规划 — 04-CONTEXT D-01/D-02)
+**Plans**: 1/1 plans executed(第一步·只读对账;本阶段增量规划,后续适配实施与双布局待对账结果出来后另行规划 — 04-CONTEXT D-01/D-02)
Plans:
-- [ ] 04-01-PLAN.md — 渲染端 ↔ 数据端接口对账(只读清点,产出 `04-RENDER-INTERFACE-AUDIT.md`;按 ① 错配 / ② 数据端缺失 / ③ 多余旧路径 三类登记)
+- [x] 04-01-PLAN.md — 渲染端 ↔ 数据端接口对账(只读清点,产出 `04-RENDER-INTERFACE-AUDIT.md`;按 ① 错配 / ② 数据端缺失 / ③ 多余旧路径 三类登记)
**UI hint**: yes
@@ -380,7 +380,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 0/1 | In Progress| - |
+| 4. 渲染适配与双布局 | 1/1 | In Progress| |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index a1af049..8f5040e 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -3,17 +3,17 @@ gsd_state_version: "1.0"
milestone: v1.0
current_phase: 04
current_phase_name: 渲染适配与双布局
-status: executing
-stopped_at: Phase 4 context gathered
-last_updated: "2026-09-18T02:05:26.602Z"
-last_activity: 2026-09-17
-last_activity_desc: Phase 07 paused; plan 07-16 deferred pending the user's data-layer rework
-state_head: 6d8075166a0469783835272f2a4fa04c283ee71e
+status: verifying
+stopped_at: Completed 04-01-PLAN.md(只读对账账本已交付;阶段 4 剩余工作待用户拆分)
+last_updated: "2026-09-18T02:52:58.322Z"
+last_activity: 2026-09-18
+last_activity_desc: Phase 04 execution started
+state_head: 4327e6733b1e241c3a840a91c63c9c10e860624c
progress:
total_phases: 7
completed_phases: 0
total_plans: 72
- completed_plans: 70
+ completed_plans: 71
milestone_name: milestone
---
@@ -24,14 +24,14 @@ milestone_name: milestone
See: .planning/PROJECT.md (updated 2026-09-17)
**Core value:** 引擎能完整跑通一部魔塔——开局到结局,存档、战斗、地图、事件、剧情全链路可玩。
-**Current focus:** Phase 07 — 数据端缺陷修复
+**Current focus:** Phase 04 — 渲染适配与双布局
## Current Position
-Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
-Plan: 15 of 16 complete (07-01..07-15); 07-16 planned, awaiting the user's data-layer rework
-Status: Deferred — user is manually reworking the data layer; more tests will break until that finishes
-Last activity: 2026-09-17 — Phase 07 paused; 07-16 deferred
+Phase: 04 (渲染适配与双布局) — EXECUTING
+Plan: 1 of 1
+Status: Phase complete — ready for verification
+Last activity: 2026-09-18 — Phase 04 execution started
Progress: [█████░░░░░] 50%
@@ -96,6 +96,7 @@ Progress: [█████░░░░░] 50%
| Phase 06 P15 | 12min | 2 tasks | 3 files |
| Phase 07-data-fixes P13 | 56min | 5 tasks | 5 files |
| Phase 07 P14 | 3min | 3 tasks | 1 files |
+| Phase 04 P01 | 11min | 3 tasks | 1 files |
## Accumulated Context
@@ -212,6 +213,10 @@ Recent decisions affecting current work:
- [Phase 07]: [Phase 07] 07-14 Q1..Q4 = 不改动(用户,2026-09-17,原话「关于旧引擎的兼容部分不动,很快就要删除了,没必要改。」):`packages-user/data-fallback` 旧引擎兼容层即将删除,代理不修、`get` 口径不变、不建 `hero.test.ts`、`patchFlags` 不处理
- [Phase 07]: [Phase 07] 07-14 `#06-17-7`(审计 C)判为 WONTFIX/经用户裁定不修复,属本阶段成功标准第 1 条「修复或经用户裁定改契约/不修复」的合法闭合;该计划零代码、零测试、零依赖,`WINDOWS.md` 不新建条目(D-14)
- [Phase 07]: [Phase 07] 07-14 收口基线维持 66 文件 / 737 通过 / 0 失败 / 1 跳过(无新增测试文件,计划原预期的 66→67 被裁决取代);`07-VERIFICATION.md` 已失效,需重跑 `/gsd-verify-work 7`
+- [Phase 04]: 04-01:只读接口对账交付 04-RENDER-INTERFACE-AUDIT.md(8 段骨架 + ① 9 条 / ③ 42 条 / ② 本步未确认),被查两包零改动。
+- [Phase 04]: 04-01:带 // @ts-expect-error 需要重构 的 import(HeroMover/IMoveController、HeroAnimateDirection、IHeroMoveController(Hooks)、getHeroStatusOn、ItemState、state.maps layerState)一律归 ① 错配(渲染侧适配项),不归 ②。
+- [Phase 04]: 04-01:② 数据端缺失节零确认——候选(core.firstData 工程元数据、勇士渲染粒度钩子)转入「未能从阅读确定(未猜测)」,不臆造缺失接口。
+- [Phase 04]: 04-01:REND-01/REND-02 保持 Pending,本 run 只交付差异账本,适配实施与双布局未规划/未实施。
### Roadmap Evolution
@@ -249,6 +254,6 @@ None yet.
## Session Continuity
-Last session: 2026-09-18T01:06:46.471Z
-Stopped at: Phase 4 context gathered
-Resume file: .planning/phases/04-render-adaptation/04-CONTEXT.md
+Last session: 2026-09-18T02:52:58.072Z
+Stopped at: Completed 04-01-PLAN.md(只读对账账本已交付;阶段 4 剩余工作待用户拆分)
+Resume file: None
diff --git a/.planning/phases/04-render-adaptation/04-01-SUMMARY.md b/.planning/phases/04-render-adaptation/04-01-SUMMARY.md
new file mode 100644
index 0000000..8f0e0a2
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-01-SUMMARY.md
@@ -0,0 +1,135 @@
+---
+phase: 04-render-adaptation
+plan: 01
+subsystem: render-adaptation / audit
+tags: [audit, render, data-layer, interface-mismatch, legacy-path, readonly, crlf, d-01]
+
+requires:
+ - phase: 03-data-completion
+ provides: 完成的数据端 L0–L3 接口契约与 CoreState 单例(本次对账的接口基准)
+provides:
+ - 渲染端 ↔ 数据端接口对账账本(8 段骨架 + ①/②/③ 三分类互斥登记)
+ - 9 条 ① 错配(#04-01-M-01..09)与 42 条 ③ 多余旧路径(#04-01-L-01..42),每条含 `file:line` 锚点且 ① 条引用数据端基准
+ - 数据端缺失候选与「未能从阅读确定」清单(供用户裁决 REND-01/REND-02 后续拆分)
+ - 双布局既有资产登记(`render/use.ts` 的 Orientation/onOrientationChange、`shared.ts` 布局常量)
+affects: [REND-01, REND-02, phase-04 remaining plans, Phase 5 legacy 移植]
+
+actuals:
+ tokens: 6568
+ tasks: 3
+ commits: 3
+plan_head_before: 7225bdd4372c46ef97512e67c6758f12aec5a97c
+
+tech-stack:
+ added: []
+ patterns:
+ - 只读对账:以数据端接口签名为基准(D-04/D-05),不改任何生产代码
+ - 三分类互斥登记(① 错配 / ② 数据端缺失独立成节 / ③ 多余旧路径),每条精确到接口名 + `file:line`
+
+key-files:
+ created:
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+ modified: []
+
+key-decisions:
+ - "按用户分类规则,带 `// @ts-expect-error 需要重构` 的 import(HeroMover/IMoveController、HeroAnimateDirection、IHeroMoveController(Hooks)、getHeroStatusOn、ItemState、state.maps layerState)一律归 ① 错配(渲染侧适配项),不归 ②。"
+ - "② 数据端缺失节本步未确认任何缺失项:候选(core.firstData 工程元数据、勇士渲染粒度钩子)无法仅凭静态阅读定性,按要求转入「未能从阅读确定(未猜测)」而非臆造缺失接口。"
+ - "素材/工具/行为命令类 core.* 读取(core.material.*/core.icons.*/formatBigNumber 等)归「判定为匹配的同类边界」,不误报为数据端错配。"
+ - "REND-01/REND-02 不标记完成:本 run 只交付差异账本,适配实施与双布局未规划、未实施,需求保持 Pending。"
+ - "被查两包零改动;审计文档全文 CRLF(dev.md「换行使用 CRLF 格式」)。"
+
+patterns-established:
+ - "渲染接口对账以「接口签名」为准,不受用户并行修改数据端实现的影响(D-04/D-05)"
+ - "区分「数据端状态读取」与「渲染侧素材/工具/行为命令」的判定口径(A3 判据)"
+
+requirements-completed: []
+
+coverage:
+ - id: D1
+ description: "只读接口对账账本 04-RENDER-INTERFACE-AUDIT.md:8 段骨架、三分类记录、匹配边界、未确定段、处置(零生产代码改动)"
+ requirement: REND-01
+ verification:
+ - kind: other
+ ref: "node gates: 8 headings / 51 finding rows (9 M + 42 L) each with file:line / CRLF / `git status --porcelain -- packages-user/client-base packages-user/client-modules` empty"
+ status: pass
+ human_judgment: true
+ rationale: "对账账本的完整性/定性正确性(是否漏报、错报、候选定性)须由用户审阅裁决,非机器门禁可证"
+
+duration: 11min
+completed: 2026-09-18
+status: complete
+---
+
+# Phase 4 Plan 01: 渲染适配与双布局(第一步·只读接口对账)Summary
+
+**纯只读的渲染端 ↔ 数据端接口对账账本:9 条 ① 错配 + 42 条 ③ 多余旧路径 + 独立 ② 缺失节(本步未确认缺失),每条带 `file:line` 锚点,被查两包零改动**
+
+## Performance
+
+- **Duration:** 11 min
+- **Started:** 2026-09-18T10:40:00+08:00(approx)
+- **Completed:** 2026-09-18T10:51:00+08:00(approx)
+- **Tasks:** 3
+- **Files modified:** 1(新建审计文档;零生产代码改动)
+
+## Accomplishments
+
+- 新建 `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`,含 8 个二级标题(背景 / 方法 / 发现 / ② 数据端缺失接口 / 触发序列(举例) / 判定为「匹配」的同类边界 / 未能从阅读确定(未猜测) / 处置),全文 CRLF。
+- ① 错配全量清点 9 条(`#04-01-M-01..09`):HeroMover/IMoveController、HeroAnimateDirection、IHeroMoveController(Hooks)、成员形状、getHeroStatusOn、ItemState、`state.maps` layerState、IGameMapHooks.onUpdateLayerArea/onUpdateLayerBlock;每条引用数据端基准 `file:line`(D-05)。
+- ③ 多余旧路径全量清点 42 条(`#04-01-L-01..42`),覆盖 main/statistics/viewmap/settings/save/toolbar/statusBar/choices/floorSelect/saves/fallback 等 legacy `core.*` 数据端状态读取,按 A3 判据排除 `core.material.*` 素材族等误报。
+- 「匹配边界」段给出素材族判据与 A1/A2/A4/A5/A7 解析一致结论;「未能从阅读确定」段逐条登记 15 类无法静态定论项(含 `core.firstData` 元数据、无对位旧状态读、旧存档内部态等)。
+- 门禁全绿:8 标题 / 51 行记录(9 M + 42 L)均含 `file:line` / CRLF / 锚点可追溯(20 条 client 路径全部 tracked)/ `git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空 / 无 04-01 以外计划文件。
+
+## Task Commits
+
+Each task was committed atomically:
+
+1. **Task 1: 审计文档骨架端到端贯通** — `05b9af7` (docs)
+2. **Task 2: ① 错配全量清点** — `c0b44c1` (docs)
+3. **Task 3: ③ 多余旧路径全量清点 + ② 收口 + 门禁** — `4327e67` (docs)
+
+**Plan metadata:** pending final metadata commit
+
+## Files Created/Modified
+
+- `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` — 只读接口对账账本(本 run 唯一新增文件)
+
+## Decisions Made
+
+- **既有重构标记的归类**:按用户规则,带 `// @ts-expect-error 需要重构` 的 import 属渲染端需局部重构,归 ① 错配,不归 ②。
+- **② 节零确认**:不臆造数据端缺失项;候选(`core.firstData` 工程元数据、勇士渲染粒度钩子)转入「未能从阅读确定(未猜测)」。
+- **需求状态**:REND-01/REND-02 保持 Pending —— 本 run 只交付清点账本,未实施适配/双布局。
+- **零生产改动**:只写审计文档,被查两包保持零改动。
+
+## Deviations from Plan
+
+None - plan executed exactly as written.
+
+> 说明:计划 Task 2/3 要求派 D-09 只读子代理完成重型整文件扫描;本 run 由编排方预先完成该委派并交付三份只读事实表(`gsd-04-scan-1..3.md`),执行者据事实表定性并逐条复核锚点,未额外派子代理、未整文件读取被委派文件。审计覆盖面(A1..A7)与门禁不变,属执行手段的等价替换,非计划偏离。
+
+## Issues Encountered
+
+- 无。三份事实表与实测锚点逐条核对一致;`data-state`/`data-base` 的 `HeroMover`/`IMoveController`/`HeroAnimateDirection` 确无导出(对账定性为 ① 的依据)。
+
+## User Setup Required
+
+None - no external service configuration required.
+
+## Next Phase Readiness
+
+- 对账账本已就绪,供用户决定阶段 4 剩余工作(适配实施 + 移动端/桌面端双布局)如何拆分。
+- **待用户补齐**:REND-01、REND-02 的验收边界(当前为 specless fallback flagged assumption,见 04-01-PLAN)。
+- **已知后续起点**:双布局既有资产 `render/use.ts`(Orientation/onOrientationChange)与 `shared.ts` 布局常量仅在「处置」段登记,未展开实施。
+- **风险提示**:`#04-01-M-04`(IHeroMoveControllerHooks)与「② 候选·勇士渲染粒度钩子」可能重叠,拆分实施时需一并裁定。
+
+---
+*Phase: 04-render-adaptation*
+*Completed: 2026-09-18*
+
+## Self-Check: PASSED
+
+- FOUND: `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`
+- FOUND: `.planning/phases/04-render-adaptation/04-01-SUMMARY.md`
+- FOUND commit: `05b9af7` (Task 1)
+- FOUND commit: `c0b44c1` (Task 2)
+- FOUND commit: `4327e67` (Task 3)
diff --git a/.planning/state.json b/.planning/state.json
index 6aa2087..12f5719 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -41,8 +41,8 @@
],
"next": {
"command": "/gsd:progress --next",
- "label": "Advance to the next step",
- "reason": "Phase 04 of 7 · executing"
+ "label": "Advance to the next step (verify)",
+ "reason": "Phase 04 of 7 · ready to verify"
},
- "updated_at": "2026-09-18T02:05:26.702Z"
+ "updated_at": "2026-09-18T02:52:40.728Z"
}
From 4a1e9b36f82f5d88d9d67583f726d23eb5888898 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 11:20:07 +0800
Subject: [PATCH 014/173] =?UTF-8?q?docs(04):=20create=2004-02=20=E5=8B=87?=
=?UTF-8?q?=E5=A3=AB=E7=A7=BB=E5=8A=A8=E6=8E=A5=E5=8F=A3=E6=8E=A2=E7=B4=A2?=
=?UTF-8?q?=20plan=20(=E5=8F=AA=E8=AF=BB,=20D-17)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.planning/ROADMAP.md | 5 +-
.../phases/04-render-adaptation/04-02-PLAN.md | 295 ++++++++++++++++++
2 files changed, 298 insertions(+), 2 deletions(-)
create mode 100644 .planning/phases/04-render-adaptation/04-02-PLAN.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 33c1500..83d71ba 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,11 +181,12 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 1/1 plans executed(第一步·只读对账;本阶段增量规划,后续适配实施与双布局待对账结果出来后另行规划 — 04-CONTEXT D-01/D-02)
+**Plans**: 1/2 plans executed(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已规划 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18;后续适配实施与双布局待探索结果与缺失候选裁决后另行规划)
Plans:
- [x] 04-01-PLAN.md — 渲染端 ↔ 数据端接口对账(只读清点,产出 `04-RENDER-INTERFACE-AUDIT.md`;按 ① 错配 / ② 数据端缺失 / ③ 多余旧路径 三类登记)
+- [ ] 04-02-PLAN.md — 勇士移动接口探索(只读,D-17):产出 `04-HERO-MOVER-INTERFACE.md`(数据端现行接口 / 渲染端现状 / 逐成员三态对账 / 缺失接口候选 / 未确定 / 处置;排除 `HeroRendering`(D-18)与 `core.*`(D-12);不改任何代码,适配(04-03+)未规划)
**UI hint**: yes
@@ -380,7 +381,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 1/1 | In Progress| |
+| 4. 渲染适配与双布局 | 1/2 | In Progress| |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/phases/04-render-adaptation/04-02-PLAN.md b/.planning/phases/04-render-adaptation/04-02-PLAN.md
new file mode 100644
index 0000000..f5ae431
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-02-PLAN.md
@@ -0,0 +1,295 @@
+---
+phase: 04-render-adaptation
+plan: 2
+type: execute
+wave: 1
+depends_on:
+ - 04-01
+files_modified:
+ - .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+autonomous: true
+requirements:
+ - REND-01
+ - REND-02
+estimate:
+ tokens: 58000
+ raw_tokens: 58000
+ tasks: 3
+ confidence: low
+must_haves:
+ truths:
+ - "`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 存在,含 7 个二级标题:`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`;全文为 CRLF 换行(dev.md「换行使用 CRLF 格式」)"
+ - "逐成员对账表每条 `#04-02-R-N` 精确到接口/成员名,同行同时含渲染端 `client-base|client-modules` 与数据端 `data-common|data-base|data-state` 两侧 `file:line` 锚点(数据端无对位时写「无对位,最接近:」),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一"
+ - "带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」(用户规则,见 04-RENDER-INTERFACE-AUDIT.md 处置口径),不归「数据端缺失」"
+ - "「缺失接口候选」独立成节,每条 `### #04-02-G-N:标题` 含 `**渲染端需求**` / `**数据端现状**` / `**待确认**` 三段,数据端现状引用最接近的现行成员 `file:line`;候选只记录、不设计、不修复(D-13),未经用户裁决不视为最终缺失清单"
+ - "`HeroRendering` 相关(alpha / 贴图 / 渲染粒度钩子)与 legacy `core.*` 为显式排除项(D-18 / D-12):文中不出现针对它们的对账行或缺失候选,仅在「处置」与范围排除说明中登记排除理由"
+ - "全部生产代码零改动:`git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空;本次只新增交付文档与 `04-02-PLAN.md`,不新增任何适配计划(04-03+ 未规划)"
+ - "无法从静态阅读定论的链路进入「未能从阅读确定(未猜测)」而非写成缺失或错配;文档引用的生产路径均为 tracked 源码路径(可被 `git ls-files` 命中)"
+ artifacts:
+ - path: .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+ provides: "勇士移动子系统的只读接口探索账本:数据端现行接口清单 + 渲染端现状清单 + 逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失) + 缺失接口候选 + 未确定项 + 处置;供用户裁决缺失候选并据此规划后续适配(D-17/D-13)"
+ key_links:
+ - "渲染端 `render/map/extension/hero.ts:81` 的 `hero.addHook(new MapHeroHook(this))` ↔ 数据端 `IHookable`(经 `data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接)与 `IHeroLocationHooks.onSetPos`(`data-base/src/hero/types.ts:283`)/ `IObjectMoverHooks.onSetPos`(`data-common/src/common/mover.ts:216`)"
+ - "渲染端 `action/move.ts:4` 的 `{ HeroMover, IMoveController } from '@user/data-state'` ↔ 数据端 `data-base/src/hero/mover.ts:22` 的 `HeroMover` 与 `data-common/src/common/mover.ts:142` 的 `IMoverController`(`data-state` barrel 无该导出)"
+ - "渲染端 `render/map/extension/manager.ts:18` 的 `heroMap: Map` ↔ 数据端 `IHeroLocation`/`IHeroMover`(`data-base/src/hero/types.ts:303,399`)与 `IHeroState.location`(`:894`)"
+ - "「缺失接口候选」段 ↔ 用户增量裁决(D-13):本步只登记候选与数据端现状,是否新增接口由用户在适配开始时逐条确认"
+ - "「处置」/范围排除说明 ↔ D-12(`core.*` 属第五阶段)与 D-18(`HeroRendering` 先改数据端后处理)"
+ assumptions:
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付勇士移动子系统的只读接口探索账本,供其适配阶段消费。用户须在规划适配工作前补齐边界判据。"
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局实现,也不发明其验收阈值。"
+ - "数据端接口签名在规划后可能仍被用户并行修改**实现**但不改签名(D-05);本计划以执行当日基准文件的现行签名为准,若发现签名已变,以当日文件为准并在「未能从阅读确定(未猜测)」记录该变化。"
+ - "数据端包(`data-common` / `data-base` / `data-state`)工作树可能因用户并发重构而非空;只读门禁只对渲染端两包(`client-base` / `client-modules`)与计划文件范围做机器断言,不对数据端工作树状态做断言(否则会与用户的进行中工作冲突)。"
+ prohibitions:
+ - "不得修改或新建任何生产代码(`packages-user/client-base`、`packages-user/client-modules`、`packages-user/data-common`、`packages-user/data-base`、`packages-user/data-state`)——本步是只读接口探索(D-17)。"
+ - "不得规划、设计或实施勇士移动适配工作(04-03+ 适配计划不在本 run)。"
+ - "不得把 `HeroRendering` 相关(alpha / 勇士贴图 image / 渲染粒度钩子)纳入对账行或缺失候选(D-18)。"
+ - "不得把 legacy `core.*`(含 `client.*` 以外的旧全局)纳入对账行或缺失候选(D-12,属第五阶段)。"
+ - "不得为缺失接口候选设计解决方案,也不得新增或改写数据端接口(D-13)。"
+ - "不得写入缺少两侧 `file:line` 锚点的对账行;静态阅读无法定论的链路只能进「未能从阅读确定(未猜测)」。"
+ - "不得把带 `// @ts-expect-error 需要重构` 的 import 归为「数据端缺失」——它们一律是「渲染端需改」。"
+---
+
+
+Phase 4 第二个增量(D-17):对**勇士移动子系统**做**只读接口探索**,产出 `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`,逐成员对账「渲染端现行期望 ↔ 数据端现行接口」,并把渲染端需要而数据端未提供的接口登记为**缺失接口候选**(D-13:只记录、不设计、不修复)。
+
+**本 run 只做探索,不改任何代码。** 探索面 = 勇士移动子系统的数据端现行接口(`data-common/src/common/mover.ts` 的 `IObjectMover` / `IObjectMovable` / `IMoverController` / `IObjectMoverHooks` / `ObjectAnimDirection` 与 `data-base/src/hero/types.ts` 的 `IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,以及各包 barrel 的实际导出,D-05)与渲染端使用面(`render/map/extension/{hero,types,manager}.ts`、`action/move.ts`,及相关移动器契约参考 `render/map/{moving,status,renderer}.ts`)。
+
+对账结论固定三态:**匹配 / 渲染端需改 / 数据端缺失**。带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」(不归数据端缺失)。
+
+**显式排除:** `HeroRendering` 相关(alpha / 贴图 / 渲染粒度钩子)按 D-18 排除(用户先改数据端,之后再处理);legacy `core.*` 按 D-12 排除(属第五阶段)。二者不产生任何对账行或候选。
+
+**后续适配(04-03+)在本 run 不规划**:适配依赖本探索输出,且需用户先裁决缺失候选(D-13/D-17)。
+
+Purpose: 勇士移动是本次对账暴露的主要失配点(D-17)。在动适配代码前先把「数据端到底提供什么、渲染端到底需要什么、差在哪」钉死到接口成员级,避免按旧形状直接改渲染端。
+
+Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产代码改动)。
+
+**Artifacts this phase produces(本计划产出物)**:
+
+- **文件**:`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`(新建,唯一写入目标)。
+- **章节**(7 个二级标题,下游验证按标题名核对):`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。
+- **元信息块**(标题下 5 行,对照 04-RENDER-INTERFACE-AUDIT.md:3-6):`**日期:**` / `**范围:**`(勇士移动子系统;排除 HeroRendering 与 core.*) / `**性质:**`(只读接口探索,未修改任何生产代码) / `**接口基准:**`(`data-common/src/common/mover.ts`、`data-base/src/hero/{types,mover}.ts` 及 barrel 现行导出) / `**输入账本:**`(`04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`)。
+- **逐成员对账表列**(固定顺序):`| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |`。
+- **记录 ID 前缀**(下游按前缀检索):`#04-02-R-N` —— 逐成员对账行(结论 ∈ 匹配 / 渲染端需改 / 数据端缺失);`#04-02-G-N` —— 缺失接口候选(独立节,`### #04-02-G-N:标题` + `**渲染端需求**` / `**数据端现状**` / `**待确认**`)。
+- **缺失候选节结论行**(固定写法,必须出现):`**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
+- **`## 处置` 必备内容**:本步只读、零生产代码改动;适配工作(04-03+)未在本 run 规划、由用户在本探索结果与缺失候选裁决后另行规划;`HeroRendering`(D-18)与 `core.*`(D-12)为显式排除项;缺失接口按 D-13 增量确认。
+
+## Source Audit(多源覆盖审计)
+
+| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
+|--------|----|---------------------|------|--------|-------|
+| GOAL | — | Phase 4:「渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局」 | 04-02 | COVERED(增量·探索) | D-17 增量:本 run 只做勇士移动的接口探索;适配实施与双布局属后续增量,未在本 run 规划 |
+| REQ | REND-01 | 渲染端适配新数据层接口 | 04-02 | COVERED(仅探索) | 本 run 交付 REND-01 勇士移动子集的差异账本,不实施适配;验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
+| REQ | REND-02 | 渲染端同时支持移动端与桌面端布局 | 04-02 | COVERED(不涉及) | 本 run 范围不含双布局;REND-02 验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
+| CONTEXT | D-05 | 以接口为准,不受数据端实现变动影响 | 04-02 | COVERED | 数据端锚点引用现行签名与 barrel 导出;签名变动记入「未能从阅读确定」 |
+| CONTEXT | D-09 | 允许只读子代理扫描 | 04-02 | COVERED | Task 2 派只读子代理;子代理仅回传文本,不得写文件 |
+| CONTEXT | D-11 | `getHeroStatusOn` 等裸函数多为待删除 | 04-02 | COVERED(边界) | 该裸函数属 UI 属性读取,非勇士移动接口;只在「处置」说明其不属本增量范围,不登记为缺失 |
+| CONTEXT | D-12 | `core.*` 属旧引擎适配,归第五阶段 | 04-02 | COVERED | `core.*` 为显式排除项,不产生对账行/候选 |
+| CONTEXT | D-13 | 缺失接口待改时逐步确认,不一次定死 | 04-02 | COVERED | 「缺失接口候选」节只登记候选 + 数据端现状,不设计、不修复、不视为最终清单 |
+| CONTEXT | D-17 | 下一步=勇士移动适配;先接口探索再适配 | 04-02 | COVERED | 本计划即「先接口探索」的一半;适配半未规划 |
+| CONTEXT | D-18 | `HeroRendering` 暂不处理,用户先改数据端 | 04-02 | COVERED | `HeroRendering`(alpha/贴图/渲染粒度钩子)为显式排除项 |
+| RESEARCH | — | (本阶段无 RESEARCH.md) | — | N/A | Phase 4 未产出 RESEARCH.md,无研究项需覆盖 |
+
+## Estimate Basis(估算依据)
+
+`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。本计划按「执行者实际触及量」估算:Task 1 读交付文档结构范式 + 两个基准切开面 + 写骨架(约 14k);Task 2 数据端/渲染端两侧清单(约 22k,重型整文件读取按 D-09 委派只读子代理);Task 3 对账、缺失候选、未确定、处置与门禁(约 18k);固定开销约 4k;合计约 58k,落在 smart-zone 预算内。
+
+
+
+
+@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
+@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/REQUIREMENTS.md
+@.planning/phases/04-render-adaptation/04-CONTEXT.md
+@.planning/phases/04-render-adaptation/04-01-SUMMARY.md
+@.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+@.planning/phases/04-render-adaptation/04-PATTERNS.md
+@.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md
+@.planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md
+@AGENTS.md
+@dev.md
+
+
+
+
+
+ Task 1(tracer):交付文档骨架 + 勇士移动一条端到端对账切片 —— 7 段结构 + `#04-02-R-01` 双锚点实测 + 结构/CRLF 门禁转绿
+ .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+
+ - .planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md:1-14(元信息块 + 背景/方法骨架)
+ - .planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md:1-10(「接口事实源」声明块写法)
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:1-23(元信息块/方法段写法)与 :30-34(`#04-01-M-01..05` 勇士移动既有登记,作为本计划的输入账本)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-11 / D-12 / D-13 / D-17 / D-18)
+ - packages-user/client-modules/src/render/map/extension/hero.ts:76-132(构造器、`hero.addHook`、`hero.x`/`hero.y`/`hero.direction`/`hero.image` 使用点)与 :452-517(`MapHeroHook` 全部钩子方法名)
+ - packages-user/client-modules/src/render/map/extension/types.ts:13-40,63-159(`IMapExtensionManager` / `IMapHeroRenderer` 契约)
+ - packages-user/client-modules/src/action/move.ts:1-40(`// @ts-expect-error 需要重构` 与 `HeroMover` / `IMoveController` import)
+ - packages-user/data-common/src/common/mover.ts:40-64,100-164,166-233,235-351(`ObjectAnimDirection` / `IMoverController` / `IObjectMoverHooks` / `IObjectMover`)
+ - packages-user/data-base/src/hero/types.ts:275-322,399-417(`IHeroLocationHooks` / `IHeroLocation` / `IHeroMover`)
+ - packages-user/data-base/src/hero/mover.ts:22-76(`HeroMover` 类公开面)
+ - dev.md(「双端分离」「命名规则」「注释规范」;交付文档换行 CRLF)
+
+
+ (1) 新建 `04-HERO-MOVER-INTERFACE.md`,先写标题与 5 行元信息块:`**日期:** 2026-09-18`;`**范围:**` 勇士移动子系统(渲染端使用点 → 数据端现行接口),排除 `HeroRendering` 与 `core.*`(D-18/D-12);`**性质:**` 只读接口探索,未修改任何生产代码(D-17);`**接口基准:**` `packages-user/data-common/src/common/mover.ts`、`packages-user/data-base/src/hero/{types,mover}.ts` 及各自 barrel 现行导出(D-05);`**输入账本:**` `04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`。**全文使用 CRLF 换行**(dev.md「换行使用 CRLF 格式」);Task 2/3 追加内容同样保持 CRLF,不得出现孤立 `LF`。
+ (2) 写 `## 背景`:D-17(对账暴露的主要失配点在勇士移动;下一步先接口探索并找出缺失接口,再适配)、本步只读、明确本步不实施适配、不裁决缺失接口(D-13)、不涉 `HeroRendering`(D-18)与 `core.*`(D-12)。
+ (3) 写 `## 方法`:枚举两个面 ——(a)**数据端现行接口面** = `data-common` 移动器族(`IObjectMovable` / `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `ObjectMoveStep` 各族 / `ObjectAnimDirection`)+ `data-base/hero` 位置移动族(`IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `HeroMover`)+ 各 `index.ts` barrel 实际导出;(b)**渲染端勇士移动使用面** = `render/map/extension/{hero,types,manager}.ts` + `action/move.ts` + `client.ts` 接线点,`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考(不登记为独立对账对象)。判定三态固定为 **匹配 / 渲染端需改 / 数据端缺失**;带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」,不归「数据端缺失」。证据纪律:每条含 `接口/成员名 + 两侧 file:line`;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺失或错配。允许按 D-09 派只读子代理(仅回传文本、不得写文件)。
+ (4) 写 7 个二级标题占位:`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`(Task 2/3 在同一骨架上横向补齐,不再改结构)。
+ (5) **端到端切片(本任务唯一实测对账行)**:以 `MapHeroRenderer` 构造期的钩子注册为对象,从渲染端 `packages-user/client-modules/src/render/map/extension/hero.ts:81`(`hero.addHook(new MapHeroHook(this))`)与 :457-516(`MapHeroHook implements Partial`:`onSetImage` / `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onSetAlpha` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` / `onSetFollowerAlpha`)出发,打开数据端基准核对:`addHook` 来自 `IHookable`(经 `packages-user/data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接),`onSetPos` 在 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:283`)与 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:216`)各有对位,而 `onSetPosition`/`onStartMove`/`onEndMove`/`onMoveHero`/`onJumpHero`/`onTurnHero`/`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`/`onSetFollowerAlpha` 在数据端无同名对位。据此在 `## 逐成员对账(...)` 下写表头 `| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |` 与 `#04-02-R-01` 一行:成员=`IHeroMoveControllerHooks` 钩子族;渲染端锚点=hero.ts:81,457-516;数据端锚点=`data-common/src/common/mover.ts:166,216` 与 `data-base/src/hero/types.ts:277,283`(逐项写「有对位」或「无对位,最接近:」);结论=`渲染端需改`(随 `IHeroMoveControllerHooks` 整体重构)。同时在 `## 数据端现行接口` 与 `## 渲染端现状` 各写至少 1 条带 `file:line` 的最小条目,证明三段相互可达。
+ (6) 写 `## 处置` 初稿:本步只读、未改任何生产代码;适配工作(04-03+)不在本 run 规划;明确 `HeroRendering`(D-18)与 `core.*`(D-12)为排除项;缺失接口候选须经用户逐步裁决(D-13);`getHeroStatusOn` 类裸函数(D-11)不属本增量范围。
+ (7) 全程不得修改任何被查源码;本任务只写该交付文档一个文件。
+
+ 交付文档是只读探索的记录,可随时修订或废弃;它不引入生产代码、公共接口或契约约束,因此这一步的任何取舍都不难回退。
+
+ node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3','## \u6e32\u67d3\u7aef\u73b0\u72b6','## \u9010\u6210\u5458\u5bf9\u8d26\uff08\u5339\u914d / \u6e32\u67d3\u7aef\u9700\u6539 / \u6570\u636e\u7aef\u7f3a\u5931\uff09','## \u7f3a\u5931\u63a5\u53e3\u5019\u9009','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO RECON ROWS');process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}if(t.indexOf('HeroRendering')<0||t.indexOf('core.*')<0){console.error('MISSING SCOPE EXCLUSION');process.exit(1)}console.log('OK headings 7 recRows '+rows.length)"
+ non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "NO RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING SCOPE EXCLUSION"
+ node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行,违反 dev.md「换行使用 CRLF 格式」)
+
+
+ - 交付文档存在于约定路径,7 个二级标题逐字存在(`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`)
+ - 元信息块 5 行齐备(日期 / 范围 / 性质 / 接口基准 / 输入账本),且「范围」明写排除 `HeroRendering` 与 `core.*`
+ - `## 逐成员对账(...)` 表列序为 `ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明`,至少含 1 条 `#04-02-R-*`,同行两侧均有 `file:line` 锚点,结论取自三态
+ - `#04-02-R-01` 的钩子族判定来自实际打开基准文件核对(hero.ts 的钩子方法名与 `data-common`/`data-base` 现行 hooks 逐项比对),而非照抄 04-RENDER-INTERFACE-AUDIT.md
+ - `## 数据端现行接口` 与 `## 渲染端现状` 各至少 1 条带 `file:line` 条目,三段(数据端 / 渲染端 / 对账)相互可达
+ - `## 处置` 含 D-12 / D-13 / D-17 / D-18 的明确表述;全文 CRLF;无生产代码改动
+
+ 交付文档骨架与一条勇士移动端到端对账切片贯通,7 段结构 + 双锚点对账行 + CRLF 门禁转绿;Task 2/3 只需在同一模板上横向补齐
+
+
+
+ Task 2:数据端现行接口 + 渲染端现状两侧清单 —— 成员级 `file:line` 清点 + barrel 实际导出核对 + `@ts-expect-error` 标记清单
+ .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+
+ - .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md(Task 1 产出的现有内容,在同一模板上追加)
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:30-34,82-88,110-127(`#04-01-M-01..05` 与「未能从阅读确定」中与勇士移动相关条目,作为输入与对照)
+ - packages-user/data-common/src/common/mover.ts:12-45,47-140,142-164,166-233,235-351,357-731(移动器族接口与基类)
+ - packages-user/data-base/src/hero/types.ts:275-322,327-349,351-388,390-417,455-506,508-571,845-956(位置/移动/跟随者/顶层状态)
+ - packages-user/data-base/src/hero/mover.ts:1-100(`HeroMover` 公开面与 `IHeroMoveTopImpl` 用法)
+ - packages-user/data-common/src/index.ts、packages-user/data-common/src/common/index.ts、packages-user/data-base/src/index.ts、packages-user/data-base/src/hero/index.ts、packages-user/data-state/src/index.ts、packages-user/data-state/src/hero/index.ts、packages-user/data-state/src/ins.ts(barrel 实际导出核对)
+ - packages-user/client-modules/src/render/map/extension/hero.ts(整文件;构造器 / `addHeroMoving` / `updateHeroTexture` / `moveEntity` / `jumpEntity` / `move` / `jumpTo` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setFollowerAlpha` / `setHeroAnimateDirection` / `turn` / `MapHeroHook`)
+ - packages-user/client-modules/src/render/map/extension/types.ts:13-40,63-159 与 packages-user/client-modules/src/render/map/extension/manager.ts:1-44
+ - packages-user/client-modules/src/action/move.ts:1-183(`HeroKeyMover` 全类)
+ - packages-user/client-modules/src/client.ts:143-154(注释掉的 `addHero` 接线)与 packages-user/client-modules/src/render/map/{moving,status,renderer}.ts(仅移动器契约参考锚点,如 `addMovingBlock` / `IMovingBlock` / 图层更新钩子)
+ - dev.md(「双端分离」「不转发导出」「无类型导入」)
+
+
+ (1) **数据端现行接口段**:逐成员登记并给 `file:line` —— `data-common/src/common/mover.ts` 的 `ObjectMoveType` / `ObjectSpecialStep` / `ObjectAnimDirection` / `IObjectMovable` / 各 `IObjectMoveStep*` / `ObjectMoveStep` / `IMoverController` / `IObjectMoverHooks` / `IObjectMover`(含每个方法名与签名,如 `start(): Readonly | null`、`step/stepFace/forward/backward/speed/face/animDir/tp/jump/push/clear/setPos/setFaceDir/setMoveDir`),以及 `data-base/src/hero/types.ts` 的 `HeroMoveCode` / `IHeroMoveTopHandler` / `IHeroMoveTopImpl` / `IHeroMoverConfig` / `IHeroMover` / `IHeroLocationHooks` / `IHeroLocation` / `IHeroFollower` / `IHeroFollowersControllerHooks` / `IHeroFollowersController` / `IHeroStateHooks` / `IHeroState` 与 `data-base/src/hero/mover.ts` 的 `HeroMover` 公开面(`config` / `getConfig` / `useTopImplementation`)。另单列一条 **barrel 实际导出核对**:写明 `IHeroMover` / `IHeroLocation` / `HeroMover` 等符号实际可经哪个包取得(例如 `HeroMover` 在 `@user/data-base`,`IMoverController` 在 `@user/data-common`,`@user/data-state` barrel 未导出 `HeroMover`/`IMoveController`),每条给 barrel `file:line`。
+ (2) **渲染端现状段**:逐使用点登记并给 `file:line` —— `extension/hero.ts`(构造器 `:76-102` 的 `hero.addHook`/`hero.x`/`hero.y`/`hero.direction`/`hero.image`、`addHeroMoving :110-132`、`updateHeroTexture :138-163`、`tick :165-189`、`setImage :191-196`、`setAlpha :198-200`、`setPosition :202-207`、`moveEntity :215-238`、`jumpEntity :264-280`、`startMove :282-286`、`waitMoveEnd :295-298`、`stopMove :300-305`、`move :307-314`、`jumpTo :316-339`、`addFollower :341-376`、`removeFollower :378-422`、`removeAllFollowers :424-429`、`setFollowerAlpha :431-435`、`setHeroAnimateDirection :437-439`、`turn :441-450`、`MapHeroHook :457-517`);`extension/types.ts`(`heroMap` 键类型 `:15`、`addHero`/`removeHero` `:26-35`、`IMapHeroRenderer` `:63-159`);`extension/manager.ts`(`:2` import 源、`:18` heroMap、`:26-44` addHero/removeHero);`action/move.ts`(`:3-4` 重构标记与 import、`:11` 类、`:19` `IMoveController`、`:29,:33` `HeroMover` 类型、`:66-67` 全局门控、`:97,:123,:156` `core.status.lockControl`、`:125-127` `oneStep`+`startMove(...)`、`:130-134` `controller.onEnd`、`:137` `on('stepEnd')`、`:169-171` `controller.queue`+`push({type:'dir',value})`、`:180` `off('stepEnd')`);`client.ts:148-153`(被注释的 `addHero(this.hero.mover, layer)` 接线)。段末单列 **`// @ts-expect-error 需要重构` 清单**(文件 + 行号)。`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考,写明其与勇士移动的触点即可,不逐成员展开。
+ (3) **重型整文件读取按 D-09 委派只读子代理**(`hero.ts`、`extension/types.ts`、`action/move.ts`、`data-common/src/common/mover.ts`、`data-base/src/hero/types.ts`、`render/map/moving.ts`/`status.ts`/`renderer.ts`)。子代理只回传事实表(每行:成员名 + 所属 `file:line` + 签名或使用形态),**不得写文件、不贴大段源码**;主执行者据事实表登记并在存疑处按 `file:line` 切片复核。
+ (4) 只修改交付文档;不得改动任何生产源码,不得新增计划文件。
+
+
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const d=seg('## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3');const r=seg('## \u6e32\u67d3\u7aef\u73b0\u72b6');const cnt=(s,re)=>(s.match(re)||[]).length;const dA=cnt(d,/data-(common|base|state)\/[^\s|)]*:\d+/g);const rA=cnt(r,/client-(base|modules)\/[^\s|)]*:\d+/g);if(dA<8){console.error('DATA SECTION ANCHORS '+dA);process.exit(1)}if(rA<12){console.error('RENDER SECTION ANCHORS '+rA);process.exit(1)}if(t.indexOf('@ts-expect-error')<0){console.error('NO TSEXEPECT MARKER SURVEY');process.exit(1)}if(!/index\.ts:\d+/.test(t)){console.error('NO BARREL EXPORT CHECK');process.exit(1)}console.log('OK dataAnchors '+dA+' renderAnchors '+rA)"
+ non-zero exit, 或 stderr 含 "DATA SECTION ANCHORS" / "RENDER SECTION ANCHORS" / "NO TSEXEPECT MARKER SURVEY" / "NO BARREL EXPORT CHECK"
+ node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(追加内容未保持 CRLF)
+
+
+ - `## 数据端现行接口` 覆盖 `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `IObjectMovable` / `ObjectAnimDirection` / `IHeroMover` / `IHeroLocation` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `IHeroState` / `HeroMover`,每条带 `data-*` `file:line`
+ - 该段含 barrel 实际导出核对(明确 `HeroMover` / `IMoveController` / `IMoverController` 等符号的实际来源包),并带 `index.ts:line` 锚点
+ - `## 渲染端现状` 覆盖 `extension/hero.ts`(构造器 + 移动/跳跃/跟随者/朝向/动画方向/钩子注册与回调)、`extension/types.ts`(heroMap 与 `IMapHeroRenderer`)、`extension/manager.ts`(addHero/removeHero/heroMap)、`action/move.ts`(`HeroKeyMover` 全类)、`client.ts` 接线点,每条带 `client-*` `file:line`
+ - 段末列出全部 `// @ts-expect-error 需要重构` 出现位置(文件 + 行号)
+ - 相关渲染文件 `render/map/{moving,status,renderer}.ts` 仅作为移动器契约参考登记,未被当作独立对账对象
+ - 被查两渲染端包零改动;未新增任何计划文件;全文 CRLF
+
+ 数据端现行接口与渲染端现状两侧清单成员级落位,每条可追溯到 `file:line`,barrel 导出与 `@ts-expect-error` 标记清单齐备
+
+
+
+ Task 3:逐成员三态对账 + 缺失接口候选 + 未确定/处置定稿 + 只读与范围门禁
+ .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+
+ - .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md(Task 1/2 产出,做定稿而非重写)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-11 / D-12 / D-13 / D-17 / D-18;Deferred Ideas)
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:30-34(`M-01..05` 归类口径)与 :110-127(「未能从阅读确定」第 2、3、4 条:渲染粒度钩子、`hero.image`、无对位旧状态读)
+ - packages-user/data-common/src/common/mover.ts:142-233,235-351(`IMoverController` / `IObjectMoverHooks` / `IObjectMover` 逐方法签名,用于三态判定)
+ - packages-user/data-base/src/hero/types.ts:277-322,399-417,486-506,858-872,889-954(hooks 与 `IHeroState` 成员,用于三态判定)
+ - packages-user/data-base/src/hero/mover.ts:22-76(`HeroMover` 与 `config`/`getConfig`/`useTopImplementation`)
+ - packages-user/client-modules/src/action/move.ts:118-182(`HeroKeyMover.tryStartMove`/`onStepEnd`/`endMove` 的移动器期望:`oneStep` / `startMove(false,false,false,true)` / `controller.onEnd` / `controller.queue` / `controller.push` / `stepEnd` 事件)
+ - packages-user/data-state/src/ins.ts(`state` 单例)与 packages-user/data-base/src/hero/types.ts:889-904(`IHeroState.location` 可达性参考)
+ - dev.md(「双端分离」「其他要求」)
+
+
+ (1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.image`(hero.ts:115,119,121,143)→ 按既有口径随 `IHeroMoveController` 归「渲染端需改」,并在「未能从阅读确定」交叉引用其与 D-18 的边界;`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。
+ (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的渲染粒度移动钩子 `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
+ (3) **未能从阅读确定段**:逐条登记无法静态定论项,例如 `hero.image` 贴图别名归属与 D-18 边界、`controller.queue`(`action/move.ts:169`)的运行时形状与 `IMoverController` 的可达性、`state.hero.location` 是否为当前渲染端可取的入口、渲染粒度钩子候选与 04-RENDER-INTERFACE-AUDIT.md 第 2 条候选的重叠关系。不得写成缺失或错配。
+ (4) **处置段定稿**:本步只读、零生产代码改动;适配工作(04-03+)由用户在本探索结果与候选裁决后另行规划(D-17/D-13);显式排除 `HeroRendering`(D-18)与 `core.*`(D-12);`getHeroStatusOn` 类裸函数(D-11)不属本增量。
+ (5) **门禁与提交**:运行只读门禁、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动。
+
+
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}}if(t.indexOf('\u5019\u9009\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)"
+ non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION"
+ node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行)
+ git status --porcelain -- packages-user/client-base packages-user/client-modules
+ stdout 非空(任一被查渲染端包出现改动/新增行)—— 违反只读约束
+ node -e "const fs=require('fs');const dir='.planning/phases/04-render-adaptation';const plans=fs.readdirSync(dir).filter(f=>/-PLAN\.md$/.test(f)).sort();const allowed=['04-01-PLAN.md','04-02-PLAN.md'];const extra=plans.filter(p=>allowed.indexOf(p)<0);if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.join(','));process.exit(1)}console.log('OK plan scope '+plans.join(','))"
+ non-zero exit, 或 stderr 含 "UNEXPECTED PLAN FILES"(出现了 04-03+ 适配计划文件,即提前规划了未授权的适配工作)
+
+
+ - 逐成员对账行 ≥ 10 条,每条含两侧 `file:line`;三态词表(匹配 / 渲染端需改 / 数据端缺失)在文档中出现
+ - `HeroMover`/`IMoveController`、`HeroAnimateDirection`、`IHeroMoveController`、`IHeroMoveControllerHooks`、`hero.x/y/direction/image/addHook`、`HeroKeyMover` 的移动器期望、`manager.ts` heroMap 键类型均已逐成员落位
+ - 「缺失接口候选」独立成节,每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状带 `file:line`;节末含固定结论行;候选未设计/未修复(D-13)
+ - 「未能从阅读确定(未猜测)」非空,且 `hero.image`、`controller.queue` 等不确定项只登记不判定
+ - `## 处置` 含 D-11 / D-12 / D-13 / D-17 / D-18 的明确表述,并明写适配工作(04-03+)未规划
+ - `git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空;`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`;全文 CRLF
+ - 交付文档已以 `docs(04-02)` 提交,提交内容仅含该文档与 `04-02-PLAN.md`
+
+ 逐成员三态对账、缺失接口候选、未确定与处置全部定稿;只读/CRLF/范围门禁全绿;交付文档已提交,用户可据此裁决缺失候选并规划后续勇士移动适配
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| 数据端接口基准 → 探索文档 | 只读引用 `data-common` / `data-base` / `data-state` 的接口与 barrel;基准不变,仅记录差异 |
+| 渲染端源码 → 探索文档 | 只读引用 `client-base` / `client-modules`;不得产生任何写回 |
+| 执行者 → 仓库工作树 | 本 run 唯一允许写入的交付文件是探索文档;被查渲染端包必须保持零改动 |
+| 缺失接口候选 → 用户裁决/后续适配 | 候选被下游用户与适配计划消费;把「渲染端需改」误报为「数据端缺失」会诱导错误的数据端改动(D-13) |
+| 探索范围 → Phase 5 / 后续增量 | 把 `HeroRendering`(D-18)或 `core.*`(D-12)混入本增量会扩大范围并污染阶段边界 |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1–3 均为只读;Task 3 门禁 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 必须为空,非空即失败 |
+| T-04-02-02 | Repudiation | 对账行(无锚点则不可追溯) | medium | mitigate | 每条 `#04-02-R-*` 强制含两侧 `file:line`;由 Task 1/3 的 `` 门禁机器判定(两侧锚点正则) |
+| T-04-02-03 | Spoofing | 「缺失接口候选」段 | high | mitigate | 每条候选必须含 `渲染端需求` / `数据端现状` / `待确认` 三段且数据端现状带 `file:line`;节末固定结论行;门禁机器判定;明写「未经用户裁决不视为最终清单」(D-13) |
+| T-04-02-04 | Elevation of Privilege | 探索范围(`HeroRendering` / `core.*` / 双布局 / legacy 移植) | medium | mitigate | D-12/D-18 为显式排除项;文中排除说明与「处置」必须出现 `HeroRendering` 与 `core.*`;范围门禁禁止产生 04-03+ 计划文件 |
+| T-04-02-05 | Information Disclosure | 探索文档引用的源码行与接口名 | low | accept | 记录只含接口名、文件行号与成员签名,不含凭据/密钥 |
+| T-04-02-06 | Denial of Service | 只读子代理越权写入(D-09) | low | mitigate | Task 2 明确子代理仅回传事实表、不写文件、不贴大段源码;最终由主执行者只读门禁统一验证 |
+| T-04-02-SC | Tampering | npm / pnpm 依赖安装 | high | mitigate | 本计划零依赖变更、不修改任何 `package.json`;出现安装需求即暂停并要求用户确认包合法性(blocking human checkpoint),不得自动继续 |
+
+
+
+
+1. 结构门禁(Task 1):探索文档 7 个二级标题逐字存在;至少 1 条 `#04-02-R-*` 双锚点对账行;文档含 `HeroRendering` 与 `core.*` 排除说明。
+2. CRLF 门禁(Task 1/2/3):文档无孤立 `LF`,全文 CRLF(dev.md「换行使用 CRLF 格式」)。
+3. 两侧清单门禁(Task 2):数据端段 ≥ 8 个 `data-*` `file:line` 锚点;渲染端段 ≥ 12 个 `client-*` `file:line` 锚点;含 `@ts-expect-error` 标记清单与 `index.ts:line` barrel 导出核对。
+4. 对账完整性门禁(Task 3):`#04-02-R-*` ≥ 10 条且每条两侧锚点;三态词表齐备;每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`;`候选结论` 行存在。
+5. 只读门禁(Task 3):`git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出为空。
+6. 范围门禁(Task 3):`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`(未规划 04-03+ 适配)。
+7. 人工核对:「未能从阅读确定(未猜测)」的逐条理由;`## 处置` 对 D-11/D-12/D-13/D-17/D-18 的表述;缺失候选是否确实只记录未设计。
+8. 提交:`docs(04-02)` 仅含探索文档与 `04-02-PLAN.md`。
+
+
+
+- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 按 7 段骨架产出,成员级覆盖数据端现行接口与渲染端现状,逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失)每条两侧 `file:line` 可追溯(D-05/D-17)。
+- 缺失接口候选独立成节、含「渲染端需求 / 数据端现状 / 待确认」,且只记录不设计不修复、未经用户裁决不视为最终清单(D-13)。
+- `HeroRendering`(D-18)与 `core.*`(D-12)被显式排除,未产生对账行或候选;`getHeroStatusOn` 类裸函数(D-11)不属本增量。
+- 被查两个渲染端包零改动;本 run 只新增探索文档一个交付文件,未规划、未实施任何适配工作(04-03+)。
+- 静态阅读无法定论的项进入「未能从阅读确定(未猜测)」而非写成缺失/错配;全文 CRLF。
+- 用户可据此裁决缺失候选并规划后续勇士移动适配。
+
+
+
From e326f4b9c8441708ac59edf00dc86eac296e8e35 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 11:33:03 +0800
Subject: [PATCH 015/173] fix(04): revise 04-02 plan based on checker feedback
---
.../phases/04-render-adaptation/04-02-PLAN.md | 112 +++++++++---------
1 file changed, 58 insertions(+), 54 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-02-PLAN.md b/.planning/phases/04-render-adaptation/04-02-PLAN.md
index f5ae431..805a9c6 100644
--- a/.planning/phases/04-render-adaptation/04-02-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-02-PLAN.md
@@ -2,7 +2,7 @@
phase: 04-render-adaptation
plan: 2
type: execute
-wave: 1
+wave: 2
depends_on:
- 04-01
files_modified:
@@ -18,12 +18,12 @@ estimate:
confidence: low
must_haves:
truths:
- - "`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 存在,含 7 个二级标题:`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`;全文为 CRLF 换行(dev.md「换行使用 CRLF 格式」)"
+ - "`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 存在,含 8 个二级标题:`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`;`## 方法` 含「扫描前基线」条目(原样记录起始 `git status --porcelain` 输出);全文为 CRLF 换行(dev.md「换行使用 CRLF 格式」)"
- "逐成员对账表每条 `#04-02-R-N` 精确到接口/成员名,同行同时含渲染端 `client-base|client-modules` 与数据端 `data-common|data-base|data-state` 两侧 `file:line` 锚点(数据端无对位时写「无对位,最接近:」),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一"
- "带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」(用户规则,见 04-RENDER-INTERFACE-AUDIT.md 处置口径),不归「数据端缺失」"
- "「缺失接口候选」独立成节,每条 `### #04-02-G-N:标题` 含 `**渲染端需求**` / `**数据端现状**` / `**待确认**` 三段,数据端现状引用最接近的现行成员 `file:line`;候选只记录、不设计、不修复(D-13),未经用户裁决不视为最终缺失清单"
- - "`HeroRendering` 相关(alpha / 贴图 / 渲染粒度钩子)与 legacy `core.*` 为显式排除项(D-18 / D-12):文中不出现针对它们的对账行或缺失候选,仅在「处置」与范围排除说明中登记排除理由"
- - "全部生产代码零改动:`git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空;本次只新增交付文档与 `04-02-PLAN.md`,不新增任何适配计划(04-03+ 未规划)"
+ - "`HeroRendering` **渲染状态**相关成员(`IHeroRendering`、`hero.image` / 贴图别名、`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 legacy `core.*` 为显式排除项(D-18 / D-12):文中不出现针对它们的对账行或缺失候选,仅在「处置」与范围排除说明中登记排除理由。边界另一侧——勇士移动契约与移动钩子(`IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,以及 `onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 与 step 钩子)——**仍在范围内**,照常登记对账与候选"
+ - "生产代码相对规划时基线零**新增**改动:`git status --porcelain -- packages-user/client-base packages-user/client-modules` 只含规划时既有的、与勇士移动无关的用户未提交改动(`packages-user/client-base/src/load/**`,D-17 范围外,**不得回滚 / 暂存 / 提交**),不出现任何新增条目;本次只新增交付文档与 `04-02-PLAN.md`,不新增任何适配计划(04-03+ 未规划)"
- "无法从静态阅读定论的链路进入「未能从阅读确定(未猜测)」而非写成缺失或错配;文档引用的生产路径均为 tracked 源码路径(可被 `git ls-files` 命中)"
artifacts:
- path: .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
@@ -38,11 +38,12 @@ must_haves:
- "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付勇士移动子系统的只读接口探索账本,供其适配阶段消费。用户须在规划适配工作前补齐边界判据。"
- "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局实现,也不发明其验收阈值。"
- "数据端接口签名在规划后可能仍被用户并行修改**实现**但不改签名(D-05);本计划以执行当日基准文件的现行签名为准,若发现签名已变,以当日文件为准并在「未能从阅读确定(未猜测)」记录该变化。"
- - "数据端包(`data-common` / `data-base` / `data-state`)工作树可能因用户并发重构而非空;只读门禁只对渲染端两包(`client-base` / `client-modules`)与计划文件范围做机器断言,不对数据端工作树状态做断言(否则会与用户的进行中工作冲突)。"
+ - "数据端包(`data-common` / `data-base` / `data-state`)工作树可能因用户并发重构而非空;只读门禁不对数据端工作树状态做机器断言(否则会与用户的进行中工作冲突)。"
+ - "被查渲染端两包(`client-base` / `client-modules`)在规划时已带**与勇士移动无关**的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`(`git status --porcelain` 基线两条)。本计划**不回滚、不暂存、不提交**它们;只读门禁以「相对该基线无**新增**条目」为准(仅允许 `packages-user/client-base/src/load/` 下条目,其余任何条目即失败)。"
prohibitions:
- "不得修改或新建任何生产代码(`packages-user/client-base`、`packages-user/client-modules`、`packages-user/data-common`、`packages-user/data-base`、`packages-user/data-state`)——本步是只读接口探索(D-17)。"
- "不得规划、设计或实施勇士移动适配工作(04-03+ 适配计划不在本 run)。"
- - "不得把 `HeroRendering` 相关(alpha / 勇士贴图 image / 渲染粒度钩子)纳入对账行或缺失候选(D-18)。"
+ - "不得把 `HeroRendering` **渲染状态**相关成员(`IHeroRendering`、`hero.image` / 勇士贴图别名、`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)纳入对账行或缺失候选(D-18)——它们属 D-10 待删除的数据端渲染状态子系统。勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)**在范围内,不适用本条**。"
- "不得把 legacy `core.*`(含 `client.*` 以外的旧全局)纳入对账行或缺失候选(D-12,属第五阶段)。"
- "不得为缺失接口候选设计解决方案,也不得新增或改写数据端接口(D-13)。"
- "不得写入缺少两侧 `file:line` 锚点的对账行;静态阅读无法定论的链路只能进「未能从阅读确定(未猜测)」。"
@@ -56,7 +57,7 @@ Phase 4 第二个增量(D-17):对**勇士移动子系统**做**只读接
对账结论固定三态:**匹配 / 渲染端需改 / 数据端缺失**。带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」(不归数据端缺失)。
-**显式排除:** `HeroRendering` 相关(alpha / 贴图 / 渲染粒度钩子)按 D-18 排除(用户先改数据端,之后再处理);legacy `core.*` 按 D-12 排除(属第五阶段)。二者不产生任何对账行或候选。
+**显式排除:** `HeroRendering` **渲染状态**相关成员(`IHeroRendering`、`hero.image` / 贴图别名、`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)按 D-18 排除(用户先改数据端,之后再处理);legacy `core.*` 按 D-12 排除(属第五阶段)。二者不产生任何对账行或候选。**边界另一侧**——勇士移动契约与移动钩子(`IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 与 step 钩子)——**不属排除范围**,照常登记。
**后续适配(04-03+)在本 run 不规划**:适配依赖本探索输出,且需用户先裁决缺失候选(D-13/D-17)。
@@ -67,8 +68,8 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
**Artifacts this phase produces(本计划产出物)**:
- **文件**:`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`(新建,唯一写入目标)。
-- **章节**(7 个二级标题,下游验证按标题名核对):`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。
-- **元信息块**(标题下 5 行,对照 04-RENDER-INTERFACE-AUDIT.md:3-6):`**日期:**` / `**范围:**`(勇士移动子系统;排除 HeroRendering 与 core.*) / `**性质:**`(只读接口探索,未修改任何生产代码) / `**接口基准:**`(`data-common/src/common/mover.ts`、`data-base/src/hero/{types,mover}.ts` 及 barrel 现行导出) / `**输入账本:**`(`04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`)。
+- **章节**(8 个二级标题,下游验证按标题名核对):`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。
+- **元信息块**(标题下 5 行,对照 04-RENDER-INTERFACE-AUDIT.md:3-6):`**日期:**` / `**范围:**`(勇士移动子系统;排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子)与 `core.*`) / `**性质:**`(只读接口探索,未修改任何生产代码;相对规划时基线零新增改动) / `**接口基准:**`(`data-common/src/common/mover.ts`、`data-base/src/hero/{types,mover}.ts` 及 barrel 现行导出) / `**输入账本:**`(`04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`)。
- **逐成员对账表列**(固定顺序):`| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |`。
- **记录 ID 前缀**(下游按前缀检索):`#04-02-R-N` —— 逐成员对账行(结论 ∈ 匹配 / 渲染端需改 / 数据端缺失);`#04-02-G-N` —— 缺失接口候选(独立节,`### #04-02-G-N:标题` + `**渲染端需求**` / `**数据端现状**` / `**待确认**`)。
- **缺失候选节结论行**(固定写法,必须出现):`**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
@@ -87,7 +88,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
| CONTEXT | D-12 | `core.*` 属旧引擎适配,归第五阶段 | 04-02 | COVERED | `core.*` 为显式排除项,不产生对账行/候选 |
| CONTEXT | D-13 | 缺失接口待改时逐步确认,不一次定死 | 04-02 | COVERED | 「缺失接口候选」节只登记候选 + 数据端现状,不设计、不修复、不视为最终清单 |
| CONTEXT | D-17 | 下一步=勇士移动适配;先接口探索再适配 | 04-02 | COVERED | 本计划即「先接口探索」的一半;适配半未规划 |
-| CONTEXT | D-18 | `HeroRendering` 暂不处理,用户先改数据端 | 04-02 | COVERED | `HeroRendering`(alpha/贴图/渲染粒度钩子)为显式排除项 |
+| CONTEXT | D-18 | `HeroRendering` 暂不处理,用户先改数据端 | 04-02 | COVERED | `HeroRendering` **渲染状态**(`IHeroRendering` / 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)为显式排除项;勇士移动契约与移动钩子在范围内照常登记 |
| RESEARCH | — | (本阶段无 RESEARCH.md) | — | N/A | Phase 4 未产出 RESEARCH.md,无研究项需覆盖 |
## Estimate Basis(估算依据)
@@ -119,14 +120,14 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- Task 1(tracer):交付文档骨架 + 勇士移动一条端到端对账切片 —— 7 段结构 + `#04-02-R-01` 双锚点实测 + 结构/CRLF 门禁转绿
+ Task 1(tracer):交付文档骨架 + 勇士移动一条端到端对账切片 —— 8 段结构(含 `## 方法`)+ `#04-02-R-01` 双锚点实测 + 结构/CRLF 门禁转绿
.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
- .planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md:1-14(元信息块 + 背景/方法骨架)
- .planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md:1-10(「接口事实源」声明块写法)
- .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:1-23(元信息块/方法段写法)与 :30-34(`#04-01-M-01..05` 勇士移动既有登记,作为本计划的输入账本)
- .planning/phases/04-render-adaptation/04-CONTEXT.md(D-11 / D-12 / D-13 / D-17 / D-18)
- - packages-user/client-modules/src/render/map/extension/hero.ts:76-132(构造器、`hero.addHook`、`hero.x`/`hero.y`/`hero.direction`/`hero.image` 使用点)与 :452-517(`MapHeroHook` 全部钩子方法名)
+ - packages-user/client-modules/src/render/map/extension/hero.ts:76-132(构造器、`hero.addHook`、`hero.x`/`hero.y`/`hero.direction` 使用点;`hero.image` 仅作 D-18 排除边界的识别点,不登记对账行或候选)与 :452-517(`MapHeroHook` 钩子方法名;贴图/alpha 钩子仅作排除边界识别)
- packages-user/client-modules/src/render/map/extension/types.ts:13-40,63-159(`IMapExtensionManager` / `IMapHeroRenderer` 契约)
- packages-user/client-modules/src/action/move.ts:1-40(`// @ts-expect-error 需要重构` 与 `HeroMover` / `IMoveController` import)
- packages-user/data-common/src/common/mover.ts:40-64,100-164,166-233,235-351(`ObjectAnimDirection` / `IMoverController` / `IObjectMoverHooks` / `IObjectMover`)
@@ -135,30 +136,32 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- dev.md(「双端分离」「命名规则」「注释规范」;交付文档换行 CRLF)
- (1) 新建 `04-HERO-MOVER-INTERFACE.md`,先写标题与 5 行元信息块:`**日期:** 2026-09-18`;`**范围:**` 勇士移动子系统(渲染端使用点 → 数据端现行接口),排除 `HeroRendering` 与 `core.*`(D-18/D-12);`**性质:**` 只读接口探索,未修改任何生产代码(D-17);`**接口基准:**` `packages-user/data-common/src/common/mover.ts`、`packages-user/data-base/src/hero/{types,mover}.ts` 及各自 barrel 现行导出(D-05);`**输入账本:**` `04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`。**全文使用 CRLF 换行**(dev.md「换行使用 CRLF 格式」);Task 2/3 追加内容同样保持 CRLF,不得出现孤立 `LF`。
- (2) 写 `## 背景`:D-17(对账暴露的主要失配点在勇士移动;下一步先接口探索并找出缺失接口,再适配)、本步只读、明确本步不实施适配、不裁决缺失接口(D-13)、不涉 `HeroRendering`(D-18)与 `core.*`(D-12)。
- (3) 写 `## 方法`:枚举两个面 ——(a)**数据端现行接口面** = `data-common` 移动器族(`IObjectMovable` / `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `ObjectMoveStep` 各族 / `ObjectAnimDirection`)+ `data-base/hero` 位置移动族(`IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `HeroMover`)+ 各 `index.ts` barrel 实际导出;(b)**渲染端勇士移动使用面** = `render/map/extension/{hero,types,manager}.ts` + `action/move.ts` + `client.ts` 接线点,`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考(不登记为独立对账对象)。判定三态固定为 **匹配 / 渲染端需改 / 数据端缺失**;带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」,不归「数据端缺失」。证据纪律:每条含 `接口/成员名 + 两侧 file:line`;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺失或错配。允许按 D-09 派只读子代理(仅回传文本、不得写文件)。
- (4) 写 7 个二级标题占位:`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`(Task 2/3 在同一骨架上横向补齐,不再改结构)。
- (5) **端到端切片(本任务唯一实测对账行)**:以 `MapHeroRenderer` 构造期的钩子注册为对象,从渲染端 `packages-user/client-modules/src/render/map/extension/hero.ts:81`(`hero.addHook(new MapHeroHook(this))`)与 :457-516(`MapHeroHook implements Partial`:`onSetImage` / `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onSetAlpha` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` / `onSetFollowerAlpha`)出发,打开数据端基准核对:`addHook` 来自 `IHookable`(经 `packages-user/data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接),`onSetPos` 在 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:283`)与 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:216`)各有对位,而 `onSetPosition`/`onStartMove`/`onEndMove`/`onMoveHero`/`onJumpHero`/`onTurnHero`/`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`/`onSetFollowerAlpha` 在数据端无同名对位。据此在 `## 逐成员对账(...)` 下写表头 `| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |` 与 `#04-02-R-01` 一行:成员=`IHeroMoveControllerHooks` 钩子族;渲染端锚点=hero.ts:81,457-516;数据端锚点=`data-common/src/common/mover.ts:166,216` 与 `data-base/src/hero/types.ts:277,283`(逐项写「有对位」或「无对位,最接近:」);结论=`渲染端需改`(随 `IHeroMoveControllerHooks` 整体重构)。同时在 `## 数据端现行接口` 与 `## 渲染端现状` 各写至少 1 条带 `file:line` 的最小条目,证明三段相互可达。
- (6) 写 `## 处置` 初稿:本步只读、未改任何生产代码;适配工作(04-03+)不在本 run 规划;明确 `HeroRendering`(D-18)与 `core.*`(D-12)为排除项;缺失接口候选须经用户逐步裁决(D-13);`getHeroStatusOn` 类裸函数(D-11)不属本增量范围。
- (7) 全程不得修改任何被查源码;本任务只写该交付文档一个文件。
+ (0) **起始基线快照(只读前置)**:先执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,把输出原样记为「扫描前基线」并写入 `## 方法` 的一条 bullet(写法对照 04-RENDER-INTERFACE-AUDIT.md:21)。规划时该输出为两条与勇士移动无关的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`。**不得回滚、暂存、提交或修改这些既有改动**;若基线中出现 `packages-user/client-base/src/load/` 之外的条目,停下向用户报告,不得继续(后续 Task 3 的只读门禁以该基线为基准,只断言无**新增**条目)。
+ (1) 新建 `04-HERO-MOVER-INTERFACE.md`,先写标题与 5 行元信息块:`**日期:** 2026-09-18`;`**范围:**` 勇士移动子系统(渲染端使用点 → 数据端现行接口),排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子,D-18)与 `core.*`(D-12);`**性质:**` 只读接口探索,未修改任何生产代码,相对规划时基线零新增改动(D-17);`**接口基准:**` `packages-user/data-common/src/common/mover.ts`、`packages-user/data-base/src/hero/{types,mover}.ts` 及各自 barrel 现行导出(D-05);`**输入账本:**` `04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`。**全文使用 CRLF 换行**(dev.md「换行使用 CRLF 格式」);Task 2/3 追加内容同样保持 CRLF,不得出现孤立 `LF`。
+ (2) 写 `## 背景`:D-17(对账暴露的主要失配点在勇士移动;下一步先接口探索并找出缺失接口,再适配)、本步只读、明确本步不实施适配、不裁决缺失接口(D-13)、不涉 `HeroRendering` 渲染状态(D-18)与 `core.*`(D-12)。同时写明范围的**另一侧**:勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在本步范围内;`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 不在范围内(D-18)。
+ (3) 写 `## 方法`(步骤 (0) 记录的「扫描前基线」bullet 也在本节):枚举两个面 ——(a)**数据端现行接口面** = `data-common` 移动器族(`IObjectMovable` / `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `ObjectMoveStep` 各族 / `ObjectAnimDirection`)+ `data-base/hero` 位置移动族(`IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `HeroMover`)+ 各 `index.ts` barrel 实际导出;(b)**渲染端勇士移动使用面** = `render/map/extension/{hero,types,manager}.ts` + `action/move.ts` + `client.ts` 接线点,`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考(不登记为独立对账对象)。判定三态固定为 **匹配 / 渲染端需改 / 数据端缺失**;带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」,不归「数据端缺失」。证据纪律:每条含 `接口/成员名 + 两侧 file:line`;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺失或错配。允许按 D-09 派只读子代理(仅回传文本、不得写文件)。
+ (4) 补齐 8 个二级标题(`## 背景` 与 `## 方法` 已在前两步写入,此处补齐其余 6 个占位,其后 Task 2/3 在同一骨架上横向补齐,不再改结构):`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。
+ (5) **端到端切片(本任务唯一实测对账行)**:以 `MapHeroRenderer` 构造期的钩子注册为对象,从渲染端 `packages-user/client-modules/src/render/map/extension/hero.ts:81`(`hero.addHook(new MapHeroHook(this))`)与 :457-516(`MapHeroHook implements Partial`)出发,打开数据端基准核对。**成员范围**:只登记移动/位置/跟随者钩子 —— `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`;`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 贴图/alpha 子系统,按 D-18 **排除**(不写对账行,仅在范围排除说明中提及)。数据端核对:`addHook` 来自 `IHookable`(经 `packages-user/data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接),`onSetPos` 在 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:283`)与 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:216`)各有对位,而 `onSetPosition`/`onStartMove`/`onEndMove`/`onMoveHero`/`onJumpHero`/`onTurnHero`/`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers` 在数据端无同名对位。据此在 `## 逐成员对账(...)` 下写表头 `| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |` 与 `#04-02-R-01` 一行:成员=`IHeroMoveControllerHooks` 钩子族(移动/位置/跟随者子集);渲染端锚点=hero.ts:81,457-516;数据端锚点=`data-common/src/common/mover.ts:166,216` 与 `data-base/src/hero/types.ts:277,283`(逐项写「有对位」或「无对位,最接近:」);结论=`渲染端需改`(随 `IHeroMoveControllerHooks` 整体重构);说明中注明贴图/alpha 钩子按 D-18 不在本行范围。同时在 `## 数据端现行接口` 与 `## 渲染端现状` 各写至少 1 条带 `file:line` 的最小条目,证明三段相互可达。
+ (6) 写 `## 处置` 初稿:本步只读、未改任何生产代码;被查渲染端包相对「扫描前基线」零新增改动(用户既有 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交);适配工作(04-03+)不在本 run 规划;明确 `HeroRendering` 渲染状态(D-18:`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 `core.*`(D-12)为排除项,并说明勇士移动契约与移动钩子在范围内;缺失接口候选须经用户逐步裁决(D-13);`getHeroStatusOn` 类裸函数(D-11)不属本增量范围。
+ (7) 全程不得修改任何被查源码(含用户既有的 `packages-user/client-base/src/load/**` 未提交改动);本任务只写该交付文档一个文件。
交付文档是只读探索的记录,可随时修订或废弃;它不引入生产代码、公共接口或契约约束,因此这一步的任何取舍都不难回退。
- node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3','## \u6e32\u67d3\u7aef\u73b0\u72b6','## \u9010\u6210\u5458\u5bf9\u8d26\uff08\u5339\u914d / \u6e32\u67d3\u7aef\u9700\u6539 / \u6570\u636e\u7aef\u7f3a\u5931\uff09','## \u7f3a\u5931\u63a5\u53e3\u5019\u9009','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO RECON ROWS');process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}if(t.indexOf('HeroRendering')<0||t.indexOf('core.*')<0){console.error('MISSING SCOPE EXCLUSION');process.exit(1)}console.log('OK headings 7 recRows '+rows.length)"
- non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "NO RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING SCOPE EXCLUSION"
+ node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3','## \u6e32\u67d3\u7aef\u73b0\u72b6','## \u9010\u6210\u5458\u5bf9\u8d26\uff08\u5339\u914d / \u6e32\u67d3\u7aef\u9700\u6539 / \u6570\u636e\u7aef\u7f3a\u5931\uff09','## \u7f3a\u5931\u63a5\u53e3\u5019\u9009','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}if(t.indexOf('\u626b\u63cf\u524d\u57fa\u7ebf')<0){console.error('MISSING BASELINE SNAPSHOT');process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO RECON ROWS');process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}if(t.indexOf('HeroRendering')<0||t.indexOf('core.*')<0){console.error('MISSING SCOPE EXCLUSION');process.exit(1)}console.log('OK headings 8 recRows '+rows.length)"
+ non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "MISSING BASELINE SNAPSHOT" / "NO RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING SCOPE EXCLUSION"
node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行,违反 dev.md「换行使用 CRLF 格式」)
- - 交付文档存在于约定路径,7 个二级标题逐字存在(`## 背景` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`)
- - 元信息块 5 行齐备(日期 / 范围 / 性质 / 接口基准 / 输入账本),且「范围」明写排除 `HeroRendering` 与 `core.*`
+ - 交付文档存在于约定路径,8 个二级标题逐字存在(`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`)
+ - `## 方法` 含「扫描前基线」条目,原样记录起始 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出(规划时为 `client-base/src/load/**` 两条无关用户改动),并明写不破坏该基线
+ - 元信息块 5 行齐备(日期 / 范围 / 性质 / 接口基准 / 输入账本),且「范围」明写排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子)与 `core.*`
- `## 逐成员对账(...)` 表列序为 `ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明`,至少含 1 条 `#04-02-R-*`,同行两侧均有 `file:line` 锚点,结论取自三态
- - `#04-02-R-01` 的钩子族判定来自实际打开基准文件核对(hero.ts 的钩子方法名与 `data-common`/`data-base` 现行 hooks 逐项比对),而非照抄 04-RENDER-INTERFACE-AUDIT.md
+ - `#04-02-R-01` 的钩子族判定来自实际打开基准文件核对(hero.ts 的钩子方法名与 `data-common`/`data-base` 现行 hooks 逐项比对),而非照抄 04-RENDER-INTERFACE-AUDIT.md;且该行**不含** `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(按 D-18 排除)
- `## 数据端现行接口` 与 `## 渲染端现状` 各至少 1 条带 `file:line` 条目,三段(数据端 / 渲染端 / 对账)相互可达
- - `## 处置` 含 D-12 / D-13 / D-17 / D-18 的明确表述;全文 CRLF;无生产代码改动
+ - `## 处置` 含 D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界;全文 CRLF;相对「扫描前基线」无新增生产代码改动(不回滚用户既有 `client-base/src/load/**` 改动)
- 交付文档骨架与一条勇士移动端到端对账切片贯通,7 段结构 + 双锚点对账行 + CRLF 门禁转绿;Task 2/3 只需在同一模板上横向补齐
+ 交付文档骨架与一条勇士移动端到端对账切片贯通,8 段结构(含 `## 方法`)+ 双锚点对账行 + CRLF 门禁转绿;Task 2/3 只需在同一模板上横向补齐
@@ -179,7 +182,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
(1) **数据端现行接口段**:逐成员登记并给 `file:line` —— `data-common/src/common/mover.ts` 的 `ObjectMoveType` / `ObjectSpecialStep` / `ObjectAnimDirection` / `IObjectMovable` / 各 `IObjectMoveStep*` / `ObjectMoveStep` / `IMoverController` / `IObjectMoverHooks` / `IObjectMover`(含每个方法名与签名,如 `start(): Readonly | null`、`step/stepFace/forward/backward/speed/face/animDir/tp/jump/push/clear/setPos/setFaceDir/setMoveDir`),以及 `data-base/src/hero/types.ts` 的 `HeroMoveCode` / `IHeroMoveTopHandler` / `IHeroMoveTopImpl` / `IHeroMoverConfig` / `IHeroMover` / `IHeroLocationHooks` / `IHeroLocation` / `IHeroFollower` / `IHeroFollowersControllerHooks` / `IHeroFollowersController` / `IHeroStateHooks` / `IHeroState` 与 `data-base/src/hero/mover.ts` 的 `HeroMover` 公开面(`config` / `getConfig` / `useTopImplementation`)。另单列一条 **barrel 实际导出核对**:写明 `IHeroMover` / `IHeroLocation` / `HeroMover` 等符号实际可经哪个包取得(例如 `HeroMover` 在 `@user/data-base`,`IMoverController` 在 `@user/data-common`,`@user/data-state` barrel 未导出 `HeroMover`/`IMoveController`),每条给 barrel `file:line`。
- (2) **渲染端现状段**:逐使用点登记并给 `file:line` —— `extension/hero.ts`(构造器 `:76-102` 的 `hero.addHook`/`hero.x`/`hero.y`/`hero.direction`/`hero.image`、`addHeroMoving :110-132`、`updateHeroTexture :138-163`、`tick :165-189`、`setImage :191-196`、`setAlpha :198-200`、`setPosition :202-207`、`moveEntity :215-238`、`jumpEntity :264-280`、`startMove :282-286`、`waitMoveEnd :295-298`、`stopMove :300-305`、`move :307-314`、`jumpTo :316-339`、`addFollower :341-376`、`removeFollower :378-422`、`removeAllFollowers :424-429`、`setFollowerAlpha :431-435`、`setHeroAnimateDirection :437-439`、`turn :441-450`、`MapHeroHook :457-517`);`extension/types.ts`(`heroMap` 键类型 `:15`、`addHero`/`removeHero` `:26-35`、`IMapHeroRenderer` `:63-159`);`extension/manager.ts`(`:2` import 源、`:18` heroMap、`:26-44` addHero/removeHero);`action/move.ts`(`:3-4` 重构标记与 import、`:11` 类、`:19` `IMoveController`、`:29,:33` `HeroMover` 类型、`:66-67` 全局门控、`:97,:123,:156` `core.status.lockControl`、`:125-127` `oneStep`+`startMove(...)`、`:130-134` `controller.onEnd`、`:137` `on('stepEnd')`、`:169-171` `controller.queue`+`push({type:'dir',value})`、`:180` `off('stepEnd')`);`client.ts:148-153`(被注释的 `addHero(this.hero.mover, layer)` 接线)。段末单列 **`// @ts-expect-error 需要重构` 清单**(文件 + 行号)。`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考,写明其与勇士移动的触点即可,不逐成员展开。
+ (2) **渲染端现状段**:逐使用点登记并给 `file:line` —— `extension/hero.ts`(构造器 `:76-102` 的 `hero.addHook`/`hero.x`/`hero.y`/`hero.direction`/`hero.image`、`addHeroMoving :110-132`、`updateHeroTexture :138-163`、`tick :165-189`、`setImage :191-196`、`setAlpha :198-200`、`setPosition :202-207`、`moveEntity :215-238`、`jumpEntity :264-280`、`startMove :282-286`、`waitMoveEnd :295-298`、`stopMove :300-305`、`move :307-314`、`jumpTo :316-339`、`addFollower :341-376`、`removeFollower :378-422`、`removeAllFollowers :424-429`、`setFollowerAlpha :431-435`、`setHeroAnimateDirection :437-439`、`turn :441-450`、`MapHeroHook :457-517`);`extension/types.ts`(`heroMap` 键类型 `:15`、`addHero`/`removeHero` `:26-35`、`IMapHeroRenderer` `:63-159`);`extension/manager.ts`(`:2` import 源、`:18` heroMap、`:26-44` addHero/removeHero);`action/move.ts`(`:3-4` 重构标记与 import、`:11` 类、`:19` `IMoveController`、`:29,:33` `HeroMover` 类型、`:66-67` 全局门控、`:97,:123,:156` `core.status.lockControl`、`:125-127` `oneStep`+`startMove(...)`、`:130-134` `controller.onEnd`、`:137` `on('stepEnd')`、`:169-171` `controller.queue`+`push({type:'dir',value})`、`:180` `off('stepEnd')`);`client.ts:148-153`(被注释的 `addHero(this.hero.mover, layer)` 接线)。段末单列 **`// @ts-expect-error 需要重构` 清单**(文件 + 行号)。其中 `setImage` / `setAlpha` / `setFollowerAlpha` / `hero.image` 属 `HeroRendering` 贴图/alpha 子系统,按 D-18 只作**渲染端现状的排除标记**(就地写明「按 D-18 排除,不进入对账与候选」),不得进入 `## 逐成员对账` 或 `## 缺失接口候选`。`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考,写明其与勇士移动的触点即可,不逐成员展开。
(3) **重型整文件读取按 D-09 委派只读子代理**(`hero.ts`、`extension/types.ts`、`action/move.ts`、`data-common/src/common/mover.ts`、`data-base/src/hero/types.ts`、`render/map/moving.ts`/`status.ts`/`renderer.ts`)。子代理只回传事实表(每行:成员名 + 所属 `file:line` + 签名或使用形态),**不得写文件、不贴大段源码**;主执行者据事实表登记并在存疑处按 `file:line` 切片复核。
(4) 只修改交付文档;不得改动任何生产源码,不得新增计划文件。
@@ -194,8 +197,9 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- 该段含 barrel 实际导出核对(明确 `HeroMover` / `IMoveController` / `IMoverController` 等符号的实际来源包),并带 `index.ts:line` 锚点
- `## 渲染端现状` 覆盖 `extension/hero.ts`(构造器 + 移动/跳跃/跟随者/朝向/动画方向/钩子注册与回调)、`extension/types.ts`(heroMap 与 `IMapHeroRenderer`)、`extension/manager.ts`(addHero/removeHero/heroMap)、`action/move.ts`(`HeroKeyMover` 全类)、`client.ts` 接线点,每条带 `client-*` `file:line`
- 段末列出全部 `// @ts-expect-error 需要重构` 出现位置(文件 + 行号)
+ - `HeroRendering` 贴图/alpha 使用点(`setImage` / `setAlpha` / `setFollowerAlpha` / `hero.image`)只作现状段的排除标记,未产生任何对账行或候选(D-18)
- 相关渲染文件 `render/map/{moving,status,renderer}.ts` 仅作为移动器契约参考登记,未被当作独立对账对象
- - 被查两渲染端包零改动;未新增任何计划文件;全文 CRLF
+ - 被查两渲染端包相对「扫描前基线」零新增改动;未新增任何计划文件;全文 CRLF
数据端现行接口与渲染端现状两侧清单成员级落位,每条可追溯到 `file:line`,barrel 导出与 `@ts-expect-error` 标记清单齐备
@@ -206,7 +210,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md(Task 1/2 产出,做定稿而非重写)
- .planning/phases/04-render-adaptation/04-CONTEXT.md(D-11 / D-12 / D-13 / D-17 / D-18;Deferred Ideas)
- - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:30-34(`M-01..05` 归类口径)与 :110-127(「未能从阅读确定」第 2、3、4 条:渲染粒度钩子、`hero.image`、无对位旧状态读)
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:30-34(`M-01..05` 归类口径)与 :110-127(「未能从阅读确定」第 2、3、4 条:移动钩子候选(其贴图/alpha 子项按 D-18 排除)、`hero.image` 贴图别名边界(D-18 排除,本步不重复登记)、无对位旧状态读)
- packages-user/data-common/src/common/mover.ts:142-233,235-351(`IMoverController` / `IObjectMoverHooks` / `IObjectMover` 逐方法签名,用于三态判定)
- packages-user/data-base/src/hero/types.ts:277-322,399-417,486-506,858-872,889-954(hooks 与 `IHeroState` 成员,用于三态判定)
- packages-user/data-base/src/hero/mover.ts:22-76(`HeroMover` 与 `config`/`getConfig`/`useTopImplementation`)
@@ -215,29 +219,29 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- dev.md(「双端分离」「其他要求」)
- (1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.image`(hero.ts:115,119,121,143)→ 按既有口径随 `IHeroMoveController` 归「渲染端需改」,并在「未能从阅读确定」交叉引用其与 D-18 的边界;`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。
- (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的渲染粒度移动钩子 `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
- (3) **未能从阅读确定段**:逐条登记无法静态定论项,例如 `hero.image` 贴图别名归属与 D-18 边界、`controller.queue`(`action/move.ts:169`)的运行时形状与 `IMoverController` 的可达性、`state.hero.location` 是否为当前渲染端可取的入口、渲染粒度钩子候选与 04-RENDER-INTERFACE-AUDIT.md 第 2 条候选的重叠关系。不得写成缺失或错配。
- (4) **处置段定稿**:本步只读、零生产代码改动;适配工作(04-03+)由用户在本探索结果与候选裁决后另行规划(D-17/D-13);显式排除 `HeroRendering`(D-18)与 `core.*`(D-12);`getHeroStatusOn` 类裸函数(D-11)不属本增量。
- (5) **门禁与提交**:运行只读门禁、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动。
+ (1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);跟随者增删钩子(`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`,hero.ts:341-429,473-500)→ 渲染端需改(对到 `IHeroFollowersControllerHooks` `data-base/src/hero/types.ts:486-506`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。**范围外(D-18)**:`hero.image` 贴图别名(hero.ts:115,119,121,143)与 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 渲染状态,**不写对账行**(04-01 的 `#04-01-M-05` 已登记该错配,本步不重复),只在「处置」排除说明中提及。
+ (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的**移动语义钩子** `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。上述移动语义钩子属勇士移动契约,**在范围内**——D-18 的排除只覆盖 `IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,不得把贴图/alpha 项登记为候选。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
+ (3) **未能从阅读确定段**:逐条登记无法静态定论项,例如 `controller.queue`(`action/move.ts:169`)的运行时形状与 `IMoverController` 的可达性、`state.hero.location` 是否为当前渲染端可取的入口、移动语义钩子候选与 04-RENDER-INTERFACE-AUDIT.md「未能从阅读确定」第 2 条候选的重叠关系(该条中贴图/alpha 子项按 D-18 排除,只保留移动子项在本步登记)。不得写成缺失或错配;**不得把 `hero.image` / 贴图 / alpha 钩子写进本节**(按 D-18 只在「处置」排除说明中提及)。
+ (4) **处置段定稿**:本步只读、相对「扫描前基线」零新增生产代码改动(用户既有 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交);适配工作(04-03+)由用户在本探索结果与候选裁决后另行规划(D-17/D-13);显式排除 `HeroRendering` 渲染状态(D-18:`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 `core.*`(D-12),并写明范围的另一侧(勇士移动契约与移动钩子在范围内);`getHeroStatusOn` 类裸函数(D-11)不属本增量。
+ (5) **门禁与提交**:运行只读门禁(相对「扫描前基线」无**新增**条目)、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动(尤其不得把用户既有的 `client-base/src/load/**` 改动纳入本次提交)。
node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}}if(t.indexOf('\u5019\u9009\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)"
non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION"
node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行)
- git status --porcelain -- packages-user/client-base packages-user/client-modules
- stdout 非空(任一被查渲染端包出现改动/新增行)—— 违反只读约束
+ node -e "const{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const lines=out?out.split(/\r?\n/):[];const allowed=/^.. packages-user\/client-base\/src\/load\//;const extra=lines.filter(l=>!allowed.test(l));if(extra.length>0){console.error('NEW RENDER PKG CHANGES '+extra.length+' '+extra.join(' ; '));process.exit(1)}console.log('OK no new render-pkg changes; pre-existing baseline entries '+lines.length)"
+ non-zero exit, 或 stderr 含 "NEW RENDER PKG CHANGES"(相对规划时基线出现了新增的渲染端包改动/新增文件 —— 违反只读约束;规划时既有的 `packages-user/client-base/src/load/**` 用户改动不算,且不得回滚它们)
node -e "const fs=require('fs');const dir='.planning/phases/04-render-adaptation';const plans=fs.readdirSync(dir).filter(f=>/-PLAN\.md$/.test(f)).sort();const allowed=['04-01-PLAN.md','04-02-PLAN.md'];const extra=plans.filter(p=>allowed.indexOf(p)<0);if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.join(','));process.exit(1)}console.log('OK plan scope '+plans.join(','))"
non-zero exit, 或 stderr 含 "UNEXPECTED PLAN FILES"(出现了 04-03+ 适配计划文件,即提前规划了未授权的适配工作)
- 逐成员对账行 ≥ 10 条,每条含两侧 `file:line`;三态词表(匹配 / 渲染端需改 / 数据端缺失)在文档中出现
- - `HeroMover`/`IMoveController`、`HeroAnimateDirection`、`IHeroMoveController`、`IHeroMoveControllerHooks`、`hero.x/y/direction/image/addHook`、`HeroKeyMover` 的移动器期望、`manager.ts` heroMap 键类型均已逐成员落位
- - 「缺失接口候选」独立成节,每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状带 `file:line`;节末含固定结论行;候选未设计/未修复(D-13)
- - 「未能从阅读确定(未猜测)」非空,且 `hero.image`、`controller.queue` 等不确定项只登记不判定
- - `## 处置` 含 D-11 / D-12 / D-13 / D-17 / D-18 的明确表述,并明写适配工作(04-03+)未规划
- - `git status --porcelain -- packages-user/client-base packages-user/client-modules` 为空;`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`;全文 CRLF
+ - `HeroMover`/`IMoveController`、`HeroAnimateDirection`、`IHeroMoveController`、`IHeroMoveControllerHooks`、`hero.x/y/direction/addHook`、跟随者增删钩子、`HeroKeyMover` 的移动器期望、`manager.ts` heroMap 键类型均已逐成员落位
+ - 「缺失接口候选」独立成节,每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状带 `file:line`;节末含固定结论行;候选未设计/未修复(D-13);候选只含移动语义 / 移动器成员,不含 `IHeroRendering` / 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)
+ - 「未能从阅读确定(未猜测)」非空,且 `controller.queue` 等不确定项只登记不判定;不出现 `hero.image` / 贴图 / alpha 钩子条目(按 D-18 只在「处置」排除说明中提及)
+ - `## 处置` 含 D-11 / D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界,并明写适配工作(04-03+)未规划
+ - 相对规划时基线无新增渲染端包改动(`git status --porcelain -- packages-user/client-base packages-user/client-modules` 只含既有 `packages-user/client-base/src/load/**` 条目,且未被回滚/暂存/提交);`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`;全文 CRLF
- 交付文档已以 `docs(04-02)` 提交,提交内容仅含该文档与 `04-02-PLAN.md`
逐成员三态对账、缺失接口候选、未确定与处置全部定稿;只读/CRLF/范围门禁全绿;交付文档已提交,用户可据此裁决缺失候选并规划后续勇士移动适配
@@ -252,18 +256,18 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
|----------|-------------|
| 数据端接口基准 → 探索文档 | 只读引用 `data-common` / `data-base` / `data-state` 的接口与 barrel;基准不变,仅记录差异 |
| 渲染端源码 → 探索文档 | 只读引用 `client-base` / `client-modules`;不得产生任何写回 |
-| 执行者 → 仓库工作树 | 本 run 唯一允许写入的交付文件是探索文档;被查渲染端包必须保持零改动 |
+| 执行者 → 仓库工作树 | 本 run 唯一允许写入的交付文件是探索文档;被查渲染端包相对规划时基线必须零**新增**改动(规划时既有的 `client-base/src/load/**` 用户改动原样保留、不回滚) |
| 缺失接口候选 → 用户裁决/后续适配 | 候选被下游用户与适配计划消费;把「渲染端需改」误报为「数据端缺失」会诱导错误的数据端改动(D-13) |
-| 探索范围 → Phase 5 / 后续增量 | 把 `HeroRendering`(D-18)或 `core.*`(D-12)混入本增量会扩大范围并污染阶段边界 |
+| 探索范围 → Phase 5 / 后续增量 | 把 `HeroRendering` 渲染状态(D-18)或 `core.*`(D-12)混入本增量会扩大范围并污染阶段边界;反之把勇士移动契约/移动钩子误排除会漏掉本次主要失配点 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1–3 均为只读;Task 3 门禁 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 必须为空,非空即失败 |
+| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1 先记录「扫描前基线」(规划时既有、与勇士移动无关的用户改动 `client-base/src/load/**`);Task 1–3 均为只读;Task 3 门禁断言相对基线**无新增条目**(仅放行 `packages-user/client-base/src/load/` 下条目),出现任何其它条目即失败;不得回滚/暂存/提交用户既有改动 |
| T-04-02-02 | Repudiation | 对账行(无锚点则不可追溯) | medium | mitigate | 每条 `#04-02-R-*` 强制含两侧 `file:line`;由 Task 1/3 的 `` 门禁机器判定(两侧锚点正则) |
| T-04-02-03 | Spoofing | 「缺失接口候选」段 | high | mitigate | 每条候选必须含 `渲染端需求` / `数据端现状` / `待确认` 三段且数据端现状带 `file:line`;节末固定结论行;门禁机器判定;明写「未经用户裁决不视为最终清单」(D-13) |
-| T-04-02-04 | Elevation of Privilege | 探索范围(`HeroRendering` / `core.*` / 双布局 / legacy 移植) | medium | mitigate | D-12/D-18 为显式排除项;文中排除说明与「处置」必须出现 `HeroRendering` 与 `core.*`;范围门禁禁止产生 04-03+ 计划文件 |
+| T-04-02-04 | Elevation of Privilege | 探索范围(`HeroRendering` 渲染状态 / `core.*` / 双布局 / legacy 移植) | medium | mitigate | D-12/D-18 为显式排除项且边界写明(排除 `IHeroRendering`/贴图别名/alpha 钩子,保留移动契约与移动钩子);文中排除说明与「处置」必须出现 `HeroRendering` 与 `core.*`;范围门禁禁止产生 04-03+ 计划文件 |
| T-04-02-05 | Information Disclosure | 探索文档引用的源码行与接口名 | low | accept | 记录只含接口名、文件行号与成员签名,不含凭据/密钥 |
| T-04-02-06 | Denial of Service | 只读子代理越权写入(D-09) | low | mitigate | Task 2 明确子代理仅回传事实表、不写文件、不贴大段源码;最终由主执行者只读门禁统一验证 |
| T-04-02-SC | Tampering | npm / pnpm 依赖安装 | high | mitigate | 本计划零依赖变更、不修改任何 `package.json`;出现安装需求即暂停并要求用户确认包合法性(blocking human checkpoint),不得自动继续 |
@@ -271,21 +275,21 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
-1. 结构门禁(Task 1):探索文档 7 个二级标题逐字存在;至少 1 条 `#04-02-R-*` 双锚点对账行;文档含 `HeroRendering` 与 `core.*` 排除说明。
+1. 结构门禁(Task 1):探索文档 8 个二级标题(含 `## 方法`)逐字存在;`## 方法` 含「扫描前基线」条目;至少 1 条 `#04-02-R-*` 双锚点对账行;文档含 `HeroRendering` 与 `core.*` 排除说明。
2. CRLF 门禁(Task 1/2/3):文档无孤立 `LF`,全文 CRLF(dev.md「换行使用 CRLF 格式」)。
-3. 两侧清单门禁(Task 2):数据端段 ≥ 8 个 `data-*` `file:line` 锚点;渲染端段 ≥ 12 个 `client-*` `file:line` 锚点;含 `@ts-expect-error` 标记清单与 `index.ts:line` barrel 导出核对。
-4. 对账完整性门禁(Task 3):`#04-02-R-*` ≥ 10 条且每条两侧锚点;三态词表齐备;每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`;`候选结论` 行存在。
-5. 只读门禁(Task 3):`git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出为空。
+3. 两侧清单门禁(Task 2):数据端段 ≥ 8 个 `data-*` `file:line` 锚点;渲染端段 ≥ 12 个 `client-*` `file:line` 锚点;含 `@ts-expect-error` 标记清单与 `index.ts:line` barrel 导出核对;贴图/alpha 使用点只作 D-18 排除标记。
+4. 对账完整性门禁(Task 3):`#04-02-R-*` ≥ 10 条且每条两侧锚点;三态词表齐备;每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`;`候选结论` 行存在;对账行与候选均不含 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)。
+5. 只读门禁(Task 3):`git status --porcelain -- packages-user/client-base packages-user/client-modules` 相对规划时基线**无新增条目**(仅既有 `packages-user/client-base/src/load/**` 条目被放行;不回滚它们)。
6. 范围门禁(Task 3):`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`(未规划 04-03+ 适配)。
-7. 人工核对:「未能从阅读确定(未猜测)」的逐条理由;`## 处置` 对 D-11/D-12/D-13/D-17/D-18 的表述;缺失候选是否确实只记录未设计。
-8. 提交:`docs(04-02)` 仅含探索文档与 `04-02-PLAN.md`。
+7. 人工核对:「未能从阅读确定(未猜测)」的逐条理由;`## 处置` 对 D-11/D-12/D-13/D-17/D-18 的表述与范围两侧边界;缺失候选是否确实只记录未设计。
+8. 提交:`docs(04-02)` 仅含探索文档与 `04-02-PLAN.md`(不含用户既有的 `client-base/src/load/**` 改动)。
-- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 按 7 段骨架产出,成员级覆盖数据端现行接口与渲染端现状,逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失)每条两侧 `file:line` 可追溯(D-05/D-17)。
+- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 按 8 段骨架(含 `## 方法`,其中含「扫描前基线」)产出,成员级覆盖数据端现行接口与渲染端现状,逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失)每条两侧 `file:line` 可追溯(D-05/D-17)。
- 缺失接口候选独立成节、含「渲染端需求 / 数据端现状 / 待确认」,且只记录不设计不修复、未经用户裁决不视为最终清单(D-13)。
-- `HeroRendering`(D-18)与 `core.*`(D-12)被显式排除,未产生对账行或候选;`getHeroStatusOn` 类裸函数(D-11)不属本增量。
-- 被查两个渲染端包零改动;本 run 只新增探索文档一个交付文件,未规划、未实施任何适配工作(04-03+)。
+- `HeroRendering` **渲染状态**(`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,D-18)与 `core.*`(D-12)被显式排除,未产生对账行或候选;勇士移动契约与移动钩子(`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在范围内照常登记;`getHeroStatusOn` 类裸函数(D-11)不属本增量。
+- 被查两个渲染端包相对规划时基线零**新增**改动(用户既有 `packages-user/client-base/src/load/**` 改动未被回滚/暂存/提交);本 run 只新增探索文档一个交付文件,未规划、未实施任何适配工作(04-03+)。
- 静态阅读无法定论的项进入「未能从阅读确定(未猜测)」而非写成缺失/错配;全文 CRLF。
- 用户可据此裁决缺失候选并规划后续勇士移动适配。
From 208c22ce0c2716f9e2da3eebbd5f09e4d06f9499 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 11:34:37 +0800
Subject: [PATCH 016/173] fix(04): enforce D-18 boundary and baseline-relative
read-only gate in 04-02
---
.planning/phases/04-render-adaptation/04-02-PLAN.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-02-PLAN.md b/.planning/phases/04-render-adaptation/04-02-PLAN.md
index 805a9c6..1edff14 100644
--- a/.planning/phases/04-render-adaptation/04-02-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-02-PLAN.md
@@ -141,7 +141,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
(2) 写 `## 背景`:D-17(对账暴露的主要失配点在勇士移动;下一步先接口探索并找出缺失接口,再适配)、本步只读、明确本步不实施适配、不裁决缺失接口(D-13)、不涉 `HeroRendering` 渲染状态(D-18)与 `core.*`(D-12)。同时写明范围的**另一侧**:勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在本步范围内;`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 不在范围内(D-18)。
(3) 写 `## 方法`(步骤 (0) 记录的「扫描前基线」bullet 也在本节):枚举两个面 ——(a)**数据端现行接口面** = `data-common` 移动器族(`IObjectMovable` / `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `ObjectMoveStep` 各族 / `ObjectAnimDirection`)+ `data-base/hero` 位置移动族(`IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `HeroMover`)+ 各 `index.ts` barrel 实际导出;(b)**渲染端勇士移动使用面** = `render/map/extension/{hero,types,manager}.ts` + `action/move.ts` + `client.ts` 接线点,`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考(不登记为独立对账对象)。判定三态固定为 **匹配 / 渲染端需改 / 数据端缺失**;带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」,不归「数据端缺失」。证据纪律:每条含 `接口/成员名 + 两侧 file:line`;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺失或错配。允许按 D-09 派只读子代理(仅回传文本、不得写文件)。
(4) 补齐 8 个二级标题(`## 背景` 与 `## 方法` 已在前两步写入,此处补齐其余 6 个占位,其后 Task 2/3 在同一骨架上横向补齐,不再改结构):`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。
- (5) **端到端切片(本任务唯一实测对账行)**:以 `MapHeroRenderer` 构造期的钩子注册为对象,从渲染端 `packages-user/client-modules/src/render/map/extension/hero.ts:81`(`hero.addHook(new MapHeroHook(this))`)与 :457-516(`MapHeroHook implements Partial`)出发,打开数据端基准核对。**成员范围**:只登记移动/位置/跟随者钩子 —— `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`;`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 贴图/alpha 子系统,按 D-18 **排除**(不写对账行,仅在范围排除说明中提及)。数据端核对:`addHook` 来自 `IHookable`(经 `packages-user/data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接),`onSetPos` 在 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:283`)与 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:216`)各有对位,而 `onSetPosition`/`onStartMove`/`onEndMove`/`onMoveHero`/`onJumpHero`/`onTurnHero`/`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers` 在数据端无同名对位。据此在 `## 逐成员对账(...)` 下写表头 `| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |` 与 `#04-02-R-01` 一行:成员=`IHeroMoveControllerHooks` 钩子族(移动/位置/跟随者子集);渲染端锚点=hero.ts:81,457-516;数据端锚点=`data-common/src/common/mover.ts:166,216` 与 `data-base/src/hero/types.ts:277,283`(逐项写「有对位」或「无对位,最接近:」);结论=`渲染端需改`(随 `IHeroMoveControllerHooks` 整体重构);说明中注明贴图/alpha 钩子按 D-18 不在本行范围。同时在 `## 数据端现行接口` 与 `## 渲染端现状` 各写至少 1 条带 `file:line` 的最小条目,证明三段相互可达。
+ (5) **端到端切片(本任务唯一实测对账行)**:以 `MapHeroRenderer` 构造期的钩子注册为对象,从渲染端 `packages-user/client-modules/src/render/map/extension/hero.ts:81`(`hero.addHook(new MapHeroHook(this))`)与 :457-516(`MapHeroHook implements Partial`)出发,打开数据端基准核对。**成员范围**:只登记移动/位置/跟随者钩子 —— `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`;`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 贴图/alpha 子系统,按 D-18 **排除**(不写对账行,仅在范围排除说明中提及)。数据端核对:`addHook` 来自 `IHookable`(经 `packages-user/data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接),`onSetPos` 在 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:283`)与 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:216`)各有对位,而 `onSetPosition`/`onStartMove`/`onEndMove`/`onMoveHero`/`onJumpHero`/`onTurnHero`/`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers` 在数据端无同名对位。据此在 `## 逐成员对账(...)` 下写表头 `| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |` 与 `#04-02-R-01` 一行:成员=`IHeroMoveControllerHooks` 钩子族(移动/位置/跟随者子集);渲染端锚点=hero.ts:81,457-516;数据端锚点=`data-common/src/common/mover.ts:166,216` 与 `data-base/src/hero/types.ts:277,283`(逐项写「有对位」或「无对位,最接近:」);结论=`渲染端需改`(随 `IHeroMoveControllerHooks` 整体重构);说明中只写「贴图/alpha 钩子按 D-18 排除,不在本行范围」这类表述,**不得在本行内出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(这些字样只允许出现在「处置」的排除说明中,由 Task 3 门禁核对)。同时在 `## 数据端现行接口` 与 `## 渲染端现状` 各写至少 1 条带 `file:line` 的最小条目,证明三段相互可达。
(6) 写 `## 处置` 初稿:本步只读、未改任何生产代码;被查渲染端包相对「扫描前基线」零新增改动(用户既有 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交);适配工作(04-03+)不在本 run 规划;明确 `HeroRendering` 渲染状态(D-18:`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 `core.*`(D-12)为排除项,并说明勇士移动契约与移动钩子在范围内;缺失接口候选须经用户逐步裁决(D-13);`getHeroStatusOn` 类裸函数(D-11)不属本增量范围。
(7) 全程不得修改任何被查源码(含用户既有的 `packages-user/client-base/src/load/**` 未提交改动);本任务只写该交付文档一个文件。
@@ -219,15 +219,15 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- dev.md(「双端分离」「其他要求」)
- (1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);跟随者增删钩子(`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`,hero.ts:341-429,473-500)→ 渲染端需改(对到 `IHeroFollowersControllerHooks` `data-base/src/hero/types.ts:486-506`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。**范围外(D-18)**:`hero.image` 贴图别名(hero.ts:115,119,121,143)与 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 渲染状态,**不写对账行**(04-01 的 `#04-01-M-05` 已登记该错配,本步不重复),只在「处置」排除说明中提及。
- (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的**移动语义钩子** `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。上述移动语义钩子属勇士移动契约,**在范围内**——D-18 的排除只覆盖 `IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,不得把贴图/alpha 项登记为候选。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
+ (1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);跟随者增删钩子(`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`,hero.ts:341-429,473-500)→ 渲染端需改(对到 `IHeroFollowersControllerHooks` `data-base/src/hero/types.ts:486-506`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。**范围外(D-18)**:`hero.image` 贴图别名(hero.ts:115,119,121,143)与 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 渲染状态,**不写对账行**(04-01 的 `#04-01-M-05` 已登记该错配,本步不重复),只在「处置」排除说明中提及。**对账行内不得出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(只写「贴图/alpha 钩子」这类通用指代)。
+ (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的**移动语义钩子** `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。上述移动语义钩子属勇士移动契约,**在范围内**——D-18 的排除只覆盖 `IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,不得把贴图/alpha 项登记为候选。**候选的标题与三段正文均不得出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(避免把排除项误登记为候选)。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
(3) **未能从阅读确定段**:逐条登记无法静态定论项,例如 `controller.queue`(`action/move.ts:169`)的运行时形状与 `IMoverController` 的可达性、`state.hero.location` 是否为当前渲染端可取的入口、移动语义钩子候选与 04-RENDER-INTERFACE-AUDIT.md「未能从阅读确定」第 2 条候选的重叠关系(该条中贴图/alpha 子项按 D-18 排除,只保留移动子项在本步登记)。不得写成缺失或错配;**不得把 `hero.image` / 贴图 / alpha 钩子写进本节**(按 D-18 只在「处置」排除说明中提及)。
(4) **处置段定稿**:本步只读、相对「扫描前基线」零新增生产代码改动(用户既有 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交);适配工作(04-03+)由用户在本探索结果与候选裁决后另行规划(D-17/D-13);显式排除 `HeroRendering` 渲染状态(D-18:`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 `core.*`(D-12),并写明范围的另一侧(勇士移动契约与移动钩子在范围内);`getHeroStatusOn` 类裸函数(D-11)不属本增量。
(5) **门禁与提交**:运行只读门禁(相对「扫描前基线」无**新增**条目)、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动(尤其不得把用户既有的 `client-base/src/load/**` 改动纳入本次提交)。
- node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}}if(t.indexOf('\u5019\u9009\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)"
- non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION"
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}const banned=/IHeroRendering|hero\.image|onSetImage|onSetAlpha|onSetFollowerAlpha/;const rowBad=rows.filter(l=>banned.test(l));if(rowBad.length>0){console.error('D18 TOKEN IN RECON ROW '+rowBad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}if(banned.test(g.l)||banned.test(body)){console.error('D18 TOKEN IN GAP CANDIDATE');process.exit(1)}}if(t.indexOf('\u5019\u9009\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)"
+ non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION" / "D18 TOKEN IN RECON ROW" / "D18 TOKEN IN GAP CANDIDATE"
node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行)
node -e "const{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const lines=out?out.split(/\r?\n/):[];const allowed=/^.. packages-user\/client-base\/src\/load\//;const extra=lines.filter(l=>!allowed.test(l));if(extra.length>0){console.error('NEW RENDER PKG CHANGES '+extra.length+' '+extra.join(' ; '));process.exit(1)}console.log('OK no new render-pkg changes; pre-existing baseline entries '+lines.length)"
From a65d93221f4279131b01daf8bcac75c04688f692 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 11:54:35 +0800
Subject: [PATCH 017/173] fix(04): revise plan 04-02 per checker feedback
(AGENTS.md gate + verification Derivation)
---
.../phases/04-render-adaptation/04-02-PLAN.md | 136 +++++++++++++-----
.../04-render-adaptation/04-PATTERNS.md | 1 +
2 files changed, 100 insertions(+), 37 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-02-PLAN.md b/.planning/phases/04-render-adaptation/04-02-PLAN.md
index 1edff14..db0e210 100644
--- a/.planning/phases/04-render-adaptation/04-02-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-02-PLAN.md
@@ -7,24 +7,25 @@ depends_on:
- 04-01
files_modified:
- .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
-autonomous: true
+autonomous: false
requirements:
- REND-01
- REND-02
estimate:
- tokens: 58000
- raw_tokens: 58000
- tasks: 3
+ tokens: 60000
+ raw_tokens: 60000
+ tasks: 4
confidence: low
must_haves:
truths:
- - "`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 存在,含 8 个二级标题:`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`;`## 方法` 含「扫描前基线」条目(原样记录起始 `git status --porcelain` 输出);全文为 CRLF 换行(dev.md「换行使用 CRLF 格式」)"
+ - "本计划的**第一个 block 点**是 Task 0 的 `blocking-human` 汇报关卡:先向用户汇报本计划要解决的问题、解决方案与范围边界,然后必须**停下等待**用户明确回复「可以执行」四个字,收到该回复前不得执行任何任务、不得派发任何执行子代理;无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
+ - "`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 存在,含 8 个二级标题:`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`;`## 方法` 含「扫描前基线」条目(原样逐行记录起始 `git status --porcelain` 输出条目)与 `基线摘要` sha256 行(Task 3 只读门禁据此判定相对基线零变化);全文为 CRLF 换行(dev.md「换行使用 CRLF 格式」)"
- "逐成员对账表每条 `#04-02-R-N` 精确到接口/成员名,同行同时含渲染端 `client-base|client-modules` 与数据端 `data-common|data-base|data-state` 两侧 `file:line` 锚点(数据端无对位时写「无对位,最接近:」),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一"
- "带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」(用户规则,见 04-RENDER-INTERFACE-AUDIT.md 处置口径),不归「数据端缺失」"
- - "「缺失接口候选」独立成节,每条 `### #04-02-G-N:标题` 含 `**渲染端需求**` / `**数据端现状**` / `**待确认**` 三段,数据端现状引用最接近的现行成员 `file:line`;候选只记录、不设计、不修复(D-13),未经用户裁决不视为最终缺失清单"
+ - "「缺失接口候选」独立成节,**至少登记 1 条**(本节确有「渲染端需要而数据端无对位」的移动语义钩子,空节即失败),每条 `### #04-02-G-N:标题` 含 `**渲染端需求**` / `**数据端现状**` / `**待确认**` 三段,数据端现状引用最接近的现行成员 `file:line`;节末固定结论行的 N 必须等于实际候选条数且 ≥ 1;候选只记录、不设计、不修复(D-13),未经用户裁决不视为最终缺失清单"
- "`HeroRendering` **渲染状态**相关成员(`IHeroRendering`、`hero.image` / 贴图别名、`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 legacy `core.*` 为显式排除项(D-18 / D-12):文中不出现针对它们的对账行或缺失候选,仅在「处置」与范围排除说明中登记排除理由。边界另一侧——勇士移动契约与移动钩子(`IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,以及 `onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 与 step 钩子)——**仍在范围内**,照常登记对账与候选"
- - "生产代码相对规划时基线零**新增**改动:`git status --porcelain -- packages-user/client-base packages-user/client-modules` 只含规划时既有的、与勇士移动无关的用户未提交改动(`packages-user/client-base/src/load/**`,D-17 范围外,**不得回滚 / 暂存 / 提交**),不出现任何新增条目;本次只新增交付文档与 `04-02-PLAN.md`,不新增任何适配计划(04-03+ 未规划)"
- - "无法从静态阅读定论的链路进入「未能从阅读确定(未猜测)」而非写成缺失或错配;文档引用的生产路径均为 tracked 源码路径(可被 `git ls-files` 命中)"
+ - "生产代码相对**记录在 `## 方法` 的扫描前基线**零变化:当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 的条目集合与基线逐条一致(不新增、不消失、状态码不变),且 `基线摘要` 的 `sha256`(对 status 条目 + 各基线条目文件内容取摘要,能发现基线文件内容被改动)未变;规划时既有、与勇士移动无关的用户未提交改动(`packages-user/client-base/src/load/**`,D-17 范围外)**不得回滚 / 暂存 / 提交**;本次只新增交付文档与 `04-02-PLAN.md`,不新增任何适配计划(04-03+ 未规划)"
+ - "无法从静态阅读定论的链路进入「未能从阅读确定(未猜测)」而非写成缺失或错配,且该节不出现 D-18 排除项字样(`IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,由 Task 3 门禁机器判定);文档引用的生产路径均为 tracked 源码路径(可被 `git ls-files` 命中)"
artifacts:
- path: .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
provides: "勇士移动子系统的只读接口探索账本:数据端现行接口清单 + 渲染端现状清单 + 逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失) + 缺失接口候选 + 未确定项 + 处置;供用户裁决缺失候选并据此规划后续适配(D-17/D-13)"
@@ -39,8 +40,9 @@ must_haves:
- "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局实现,也不发明其验收阈值。"
- "数据端接口签名在规划后可能仍被用户并行修改**实现**但不改签名(D-05);本计划以执行当日基准文件的现行签名为准,若发现签名已变,以当日文件为准并在「未能从阅读确定(未猜测)」记录该变化。"
- "数据端包(`data-common` / `data-base` / `data-state`)工作树可能因用户并发重构而非空;只读门禁不对数据端工作树状态做机器断言(否则会与用户的进行中工作冲突)。"
- - "被查渲染端两包(`client-base` / `client-modules`)在规划时已带**与勇士移动无关**的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`(`git status --porcelain` 基线两条)。本计划**不回滚、不暂存、不提交**它们;只读门禁以「相对该基线无**新增**条目」为准(仅允许 `packages-user/client-base/src/load/` 下条目,其余任何条目即失败)。"
+ - "被查渲染端两包(`client-base` / `client-modules`)在规划时已带**与勇士移动无关**的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`(`git status --porcelain` 基线两条)。本计划**不回滚、不暂存、不提交**它们;只读门禁以「相对**记录在 `## 方法` 的扫描前基线**零变化」为准——当前 porcelain 条目集合须与基线记录**逐条一致**(不新增、不消失、状态码不变),且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变;该机制不再按 `packages-user/client-base/src/load/` 路径前缀放行,因此基线目录内的新增/消失条目或基线文件内容被改动都会失败。"
prohibitions:
+ - "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理(AGENTS.md 的第一个 block 点约束;该约束不因触发方式或 `yolo`/`auto_advance` 模式而放宽)。"
- "不得修改或新建任何生产代码(`packages-user/client-base`、`packages-user/client-modules`、`packages-user/data-common`、`packages-user/data-base`、`packages-user/data-state`)——本步是只读接口探索(D-17)。"
- "不得规划、设计或实施勇士移动适配工作(04-03+ 适配计划不在本 run)。"
- "不得把 `HeroRendering` **渲染状态**相关成员(`IHeroRendering`、`hero.image` / 勇士贴图别名、`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)纳入对账行或缺失候选(D-18)——它们属 D-10 待删除的数据端渲染状态子系统。勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)**在范围内,不适用本条**。"
@@ -61,15 +63,18 @@ Phase 4 第二个增量(D-17):对**勇士移动子系统**做**只读接
**后续适配(04-03+)在本 run 不规划**:适配依赖本探索输出,且需用户先裁决缺失候选(D-13/D-17)。
+**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「本计划要解决的问题 / 大致解决方案 / 范围边界与禁令」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,均不得自动放行。故本计划 `autonomous: false`。
+
Purpose: 勇士移动是本次对账暴露的主要失配点(D-17)。在动适配代码前先把「数据端到底提供什么、渲染端到底需要什么、差在哪」钉死到接口成员级,避免按旧形状直接改渲染端。
-Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产代码改动)。
+Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产代码改动)。Task 0 为只读汇报关卡,不写任何文件。
**Artifacts this phase produces(本计划产出物)**:
- **文件**:`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`(新建,唯一写入目标)。
- **章节**(8 个二级标题,下游验证按标题名核对):`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。
- **元信息块**(标题下 5 行,对照 04-RENDER-INTERFACE-AUDIT.md:3-6):`**日期:**` / `**范围:**`(勇士移动子系统;排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子)与 `core.*`) / `**性质:**`(只读接口探索,未修改任何生产代码;相对规划时基线零新增改动) / `**接口基准:**`(`data-common/src/common/mover.ts`、`data-base/src/hero/{types,mover}.ts` 及 barrel 现行导出) / `**输入账本:**`(`04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`)。
+- **基线记录格式**(写在 `## 方法`,Task 3 只读门禁按此读取):`- **扫描前基线**:` bullet + 原样逐行抄录的 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出条目(围栏 `text` 代码块内,每条一行、行首为两字符状态码;输出为空则写 `(无)`)+ `- **基线摘要**:sha256=<64hex>` bullet(由固定命令对 status 条目 + 各基线条目**文件内容的 sha256** 取摘要,能发现基线文件内容被改动)。只读门禁要求「当前 porcelain 条目集合 == 基线记录条目集合」且该 `sha256` 未变。
- **逐成员对账表列**(固定顺序):`| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |`。
- **记录 ID 前缀**(下游按前缀检索):`#04-02-R-N` —— 逐成员对账行(结论 ∈ 匹配 / 渲染端需改 / 数据端缺失);`#04-02-G-N` —— 缺失接口候选(独立节,`### #04-02-G-N:标题` + `**渲染端需求**` / `**数据端现状**` / `**待确认**`)。
- **缺失候选节结论行**(固定写法,必须出现):`**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
@@ -93,7 +98,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
## Estimate Basis(估算依据)
-`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。本计划按「执行者实际触及量」估算:Task 1 读交付文档结构范式 + 两个基准切开面 + 写骨架(约 14k);Task 2 数据端/渲染端两侧清单(约 22k,重型整文件读取按 D-09 委派只读子代理);Task 3 对账、缺失候选、未确定、处置与门禁(约 18k);固定开销约 4k;合计约 58k,落在 smart-zone 预算内。
+`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。本计划按「执行者实际触及量」估算(`estimate.tasks: 4` = Task 0 汇报关卡 + 原三个任务):Task 0 只读汇报并等待用户确认(约 2k,不写文件);Task 1 读交付文档结构范式 + 两个基准切开面 + 写骨架(约 14k);Task 2 数据端/渲染端两侧清单(约 22k,重型整文件读取按 D-09 委派只读子代理);Task 3 对账、缺失候选、未确定、处置与门禁(约 18k);固定开销约 4k;合计约 60k,落在 smart-zone 预算内。
@@ -119,6 +124,57 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
+
+ Task 0: 预执行汇报关卡 —— 汇报本计划要解决的问题与解决方案,等待用户回复「可以执行」
+ (只读汇报,不修改任何文件)
+
+ - AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行)
+ - .planning/phases/04-render-adaptation/04-02-PLAN.md(本计划全文:objective / tasks / 禁令 / 门禁)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-11 / D-12 / D-13 / D-17 / D-18)
+ - .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:30-35,110-127(本计划的输入:`#04-01-M-01..05` 勇士移动既有错配登记与相关「未能从阅读确定」条目)
+
+
+ **停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
+
+ ① **要解决的问题**:Phase 4 第二步(D-17)要在「适配勇士移动」之前先把差异钉死到接口成员级 —— 04-01 的对账显示主要失配点在勇士移动(`#04-01-M-01..05`),但渲染端到底需要哪些数据端接口、哪些是渲染端需改、哪些是数据端缺失,尚无成员级结论;不先探索就直接改渲染端会按旧形状改错。
+
+ ② **大致解决方案**:本 run 只做**只读接口探索**,产出唯一交付物 `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`:8 段骨架(背景 / 方法 / 数据端现行接口 / 渲染端现状 / 逐成员对账 / 缺失接口候选 / 未能从阅读确定 / 处置);逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失),每条两侧 `file:line`;缺失接口候选独立成节、只登记不设计不修复(D-13);重型整文件读取按 D-09 委派只读子代理(仅回传事实表、不写文件)。
+
+ ③ **边界与禁令**:零生产代码改动(D-17);`HeroRendering` 渲染状态(`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)按 D-18 显式排除,legacy `core.*` 按 D-12 排除(属第五阶段),但**勇士移动契约与移动钩子仍在范围内**;不规划、不设计、不实施适配工作(04-03+ 不属本 run);被查渲染端包相对「扫描前基线」(`packages-user/client-base/src/load/**` 的用户既有未提交改动)必须零变化(不新增、不消失、不改内容),且**不得回滚 / 暂存 / 提交**这些既有改动。
+
+ ④ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
+
+ ⑤ **若用户给出修订意见**:退出本计划、修订 `04-02-PLAN.md` 后重新汇报,不得自行改道执行。
+
+ 是否按本计划(只读勇士移动接口探索,产出 `04-HERO-MOVER-INTERFACE.md`,零生产代码改动,不规划 04-03+ 适配)开始执行
+
+ AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认;本计划虽是只读探索,仍不得例外。`.planning/config.json` 为 `mode: yolo`(execute-plan 默认自动推进),因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`,否则计划层面不提供任何 block 点。
+
+
+
+
+
+ 回复「可以执行」四个字,或给出修订意见
+
+ 用户在对话中明确回复「可以执行」四个字
+ 用户未回复「可以执行」即继续,或回复的是其它内容(含 yolo/auto 模式的自动推进)—— 必须停止执行,不派发任何执行子代理
+
+
+ - 用户回复中明确包含「可以执行」四个字
+ - 汇报内容涵盖:本计划要解决的问题(勇士移动成员级差异未钉死)、大致解决方案(只读三态对账 + 缺失接口候选)、只读边界与唯一写入目标、排除项(D-12/D-18)与「04-03+ 适配未规划」
+ - 汇报中已原样告知关卡性质:执行者/子代理必须停下等待,不因触发方式或 yolo/auto 模式自动放行
+
+ 用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
+
+
Task 1(tracer):交付文档骨架 + 勇士移动一条端到端对账切片 —— 8 段结构(含 `## 方法`)+ `#04-02-R-01` 双锚点实测 + 结构/CRLF 门禁转绿
.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
@@ -136,7 +192,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- dev.md(「双端分离」「命名规则」「注释规范」;交付文档换行 CRLF)
- (0) **起始基线快照(只读前置)**:先执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,把输出原样记为「扫描前基线」并写入 `## 方法` 的一条 bullet(写法对照 04-RENDER-INTERFACE-AUDIT.md:21)。规划时该输出为两条与勇士移动无关的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`。**不得回滚、暂存、提交或修改这些既有改动**;若基线中出现 `packages-user/client-base/src/load/` 之外的条目,停下向用户报告,不得继续(后续 Task 3 的只读门禁以该基线为基准,只断言无**新增**条目)。
+ (0) **起始基线快照(只读前置,先做这一步)**:执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,把输出**原样逐行**记为「扫描前基线」写入 `## 方法`:先写 bullet `- **扫描前基线**:`,其后以围栏 `text` 代码块逐行抄录输出(每条一行,行首为两字符状态码;输出为空则写 `(无)`),再写一条 `- **基线摘要**:sha256=` bullet,hex 由这条固定命令产出:node -e "const fs=require('fs'),c=require('crypto'),{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const cur=out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const parts=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});console.log(c.createHash('sha256').update(parts.join('\n')).digest('hex'))"(对照 04-RENDER-INTERFACE-AUDIT.md:21 的既有基线写法)。规划时该输出为两条与勇士移动无关的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`。**不得回滚、暂存、提交或修改这些既有改动**;若基线中出现 `packages-user/client-base/src/load/` 之外的条目,停下向用户报告,不得继续。Task 3 的只读门禁以「当前 porcelain 条目集合 == 基线记录条目集合」且「基线摘要 `sha256` 未变(status 条目 + 各基线条目文件内容的 sha256,能发现基线文件内容被改动)」判定相对基线零变化。
(1) 新建 `04-HERO-MOVER-INTERFACE.md`,先写标题与 5 行元信息块:`**日期:** 2026-09-18`;`**范围:**` 勇士移动子系统(渲染端使用点 → 数据端现行接口),排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子,D-18)与 `core.*`(D-12);`**性质:**` 只读接口探索,未修改任何生产代码,相对规划时基线零新增改动(D-17);`**接口基准:**` `packages-user/data-common/src/common/mover.ts`、`packages-user/data-base/src/hero/{types,mover}.ts` 及各自 barrel 现行导出(D-05);`**输入账本:**` `04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`。**全文使用 CRLF 换行**(dev.md「换行使用 CRLF 格式」);Task 2/3 追加内容同样保持 CRLF,不得出现孤立 `LF`。
(2) 写 `## 背景`:D-17(对账暴露的主要失配点在勇士移动;下一步先接口探索并找出缺失接口,再适配)、本步只读、明确本步不实施适配、不裁决缺失接口(D-13)、不涉 `HeroRendering` 渲染状态(D-18)与 `core.*`(D-12)。同时写明范围的**另一侧**:勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在本步范围内;`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 不在范围内(D-18)。
(3) 写 `## 方法`(步骤 (0) 记录的「扫描前基线」bullet 也在本节):枚举两个面 ——(a)**数据端现行接口面** = `data-common` 移动器族(`IObjectMovable` / `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `ObjectMoveStep` 各族 / `ObjectAnimDirection`)+ `data-base/hero` 位置移动族(`IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `HeroMover`)+ 各 `index.ts` barrel 实际导出;(b)**渲染端勇士移动使用面** = `render/map/extension/{hero,types,manager}.ts` + `action/move.ts` + `client.ts` 接线点,`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考(不登记为独立对账对象)。判定三态固定为 **匹配 / 渲染端需改 / 数据端缺失**;带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」,不归「数据端缺失」。证据纪律:每条含 `接口/成员名 + 两侧 file:line`;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺失或错配。允许按 D-09 派只读子代理(仅回传文本、不得写文件)。
@@ -147,19 +203,21 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
交付文档是只读探索的记录,可随时修订或废弃;它不引入生产代码、公共接口或契约约束,因此这一步的任何取舍都不难回退。
- node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3','## \u6e32\u67d3\u7aef\u73b0\u72b6','## \u9010\u6210\u5458\u5bf9\u8d26\uff08\u5339\u914d / \u6e32\u67d3\u7aef\u9700\u6539 / \u6570\u636e\u7aef\u7f3a\u5931\uff09','## \u7f3a\u5931\u63a5\u53e3\u5019\u9009','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}if(t.indexOf('\u626b\u63cf\u524d\u57fa\u7ebf')<0){console.error('MISSING BASELINE SNAPSHOT');process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO RECON ROWS');process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}if(t.indexOf('HeroRendering')<0||t.indexOf('core.*')<0){console.error('MISSING SCOPE EXCLUSION');process.exit(1)}console.log('OK headings 8 recRows '+rows.length)"
- non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "MISSING BASELINE SNAPSHOT" / "NO RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING SCOPE EXCLUSION"
+ node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3','## \u6e32\u67d3\u7aef\u73b0\u72b6','## \u9010\u6210\u5458\u5bf9\u8d26\uff08\u5339\u914d / \u6e32\u67d3\u7aef\u9700\u6539 / \u6570\u636e\u7aef\u7f3a\u5931\uff09','## \u7f3a\u5931\u63a5\u53e3\u5019\u9009','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}if(t.indexOf('\u626b\u63cf\u524d\u57fa\u7ebf')<0){console.error('MISSING BASELINE SNAPSHOT');process.exit(1)}const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');if(!/\u57fa\u7ebf\u6458\u8981[^\r\n]*?sha256=[0-9a-f]{64}/.test(ms)){console.error('MISSING BASELINE DIGEST');process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO RECON ROWS');process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}if(t.indexOf('HeroRendering')<0||t.indexOf('core.*')<0){console.error('MISSING SCOPE EXCLUSION');process.exit(1)}console.log('OK headings 8 recRows '+rows.length)"
+ non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "MISSING BASELINE SNAPSHOT" / "MISSING BASELINE DIGEST" / "BASELINE NOT RECORDED VERBATIM" / "NO RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING SCOPE EXCLUSION"(基线未逐条原样记录或基线摘要缺失/与当前状态不符)
node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行,违反 dev.md「换行使用 CRLF 格式」)
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');const rm=ms.match(/^[ MARCUD?!]{2} packages-user\/[^\r\n]*/gm);const rec=(rm||[]).map(s=>s.replace(/\s+$/,''));const{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const cur=out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('BASELINE NOT RECORDED VERBATIM extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}console.log('OK baseline verbatim entries '+cur.length)"
+ non-zero exit, 或 stderr 含 "BASELINE NOT RECORDED VERBATIM"(`## 方法` 的「扫描前基线」没有把起始 `git status --porcelain` 输出条目逐行原样记录,或记录内容与记录时的实际状态不一致)
- 交付文档存在于约定路径,8 个二级标题逐字存在(`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`)
- - `## 方法` 含「扫描前基线」条目,原样记录起始 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出(规划时为 `client-base/src/load/**` 两条无关用户改动),并明写不破坏该基线
+ - `## 方法` 含「扫描前基线」条目,**逐行原样**记录起始 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出条目(规划时为 `client-base/src/load/**` 两条无关用户改动,逐条一行;为空写「(无)」),并含 `- **基线摘要**:sha256=<64hex>` bullet(由固定命令对 status 条目 + 各基线条目文件内容的 sha256 取摘要);门禁断言记录条目与当前状态逐条一致(Task 3 据此判定相对基线零变化),并明写不破坏该基线
- 元信息块 5 行齐备(日期 / 范围 / 性质 / 接口基准 / 输入账本),且「范围」明写排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子)与 `core.*`
- `## 逐成员对账(...)` 表列序为 `ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明`,至少含 1 条 `#04-02-R-*`,同行两侧均有 `file:line` 锚点,结论取自三态
- `#04-02-R-01` 的钩子族判定来自实际打开基准文件核对(hero.ts 的钩子方法名与 `data-common`/`data-base` 现行 hooks 逐项比对),而非照抄 04-RENDER-INTERFACE-AUDIT.md;且该行**不含** `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(按 D-18 排除)
- `## 数据端现行接口` 与 `## 渲染端现状` 各至少 1 条带 `file:line` 条目,三段(数据端 / 渲染端 / 对账)相互可达
- - `## 处置` 含 D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界;全文 CRLF;相对「扫描前基线」无新增生产代码改动(不回滚用户既有 `client-base/src/load/**` 改动)
+ - `## 处置` 含 D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界;全文 CRLF;相对记录在 `## 方法` 的扫描前基线(条目逐条一致 + `基线摘要 sha256` 未变)零变化,不回滚用户既有 `client-base/src/load/**` 改动
交付文档骨架与一条勇士移动端到端对账切片贯通,8 段结构(含 `## 方法`)+ 双锚点对账行 + CRLF 门禁转绿;Task 2/3 只需在同一模板上横向补齐
@@ -199,7 +257,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- 段末列出全部 `// @ts-expect-error 需要重构` 出现位置(文件 + 行号)
- `HeroRendering` 贴图/alpha 使用点(`setImage` / `setAlpha` / `setFollowerAlpha` / `hero.image`)只作现状段的排除标记,未产生任何对账行或候选(D-18)
- 相关渲染文件 `render/map/{moving,status,renderer}.ts` 仅作为移动器契约参考登记,未被当作独立对账对象
- - 被查两渲染端包相对「扫描前基线」零新增改动;未新增任何计划文件;全文 CRLF
+ - 被查两渲染端包相对记录在 `## 方法` 的扫描前基线零变化(porcelain 条目逐条一致 + `基线摘要 sha256` 未变);未新增任何计划文件;全文 CRLF
数据端现行接口与渲染端现状两侧清单成员级落位,每条可追溯到 `file:line`,barrel 导出与 `@ts-expect-error` 标记清单齐备
@@ -220,28 +278,28 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
(1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);跟随者增删钩子(`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`,hero.ts:341-429,473-500)→ 渲染端需改(对到 `IHeroFollowersControllerHooks` `data-base/src/hero/types.ts:486-506`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。**范围外(D-18)**:`hero.image` 贴图别名(hero.ts:115,119,121,143)与 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 渲染状态,**不写对账行**(04-01 的 `#04-01-M-05` 已登记该错配,本步不重复),只在「处置」排除说明中提及。**对账行内不得出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(只写「贴图/alpha 钩子」这类通用指代)。
- (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的**移动语义钩子** `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。上述移动语义钩子属勇士移动契约,**在范围内**——D-18 的排除只覆盖 `IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,不得把贴图/alpha 项登记为候选。**候选的标题与三段正文均不得出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(避免把排除项误登记为候选)。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
- (3) **未能从阅读确定段**:逐条登记无法静态定论项,例如 `controller.queue`(`action/move.ts:169`)的运行时形状与 `IMoverController` 的可达性、`state.hero.location` 是否为当前渲染端可取的入口、移动语义钩子候选与 04-RENDER-INTERFACE-AUDIT.md「未能从阅读确定」第 2 条候选的重叠关系(该条中贴图/alpha 子项按 D-18 排除,只保留移动子项在本步登记)。不得写成缺失或错配;**不得把 `hero.image` / 贴图 / alpha 钩子写进本节**(按 D-18 只在「处置」排除说明中提及)。
+ (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的**移动语义钩子** `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。上述移动语义钩子属勇士移动契约,**在范围内**——D-18 的排除只覆盖 `IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,不得把贴图/alpha 项登记为候选。**候选的标题与三段正文均不得出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(避免把排除项误登记为候选)。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。**本节至少登记 1 条候选**(移动语义钩子族确无对位,空节即失败),且结论行的 N 必须等于实际候选条数且 ≥ 1(由门禁机器判定,不得为了过门禁而臆造或漏算)。
+ (3) **未能从阅读确定段**:逐条登记无法静态定论项,例如 `controller.queue`(`action/move.ts:169`)的运行时形状与 `IMoverController` 的可达性、`state.hero.location` 是否为当前渲染端可取的入口、移动语义钩子候选与 04-RENDER-INTERFACE-AUDIT.md「未能从阅读确定」第 2 条候选的重叠关系(该条中贴图/alpha 子项按 D-18 排除,只保留移动子项在本步登记)。不得写成缺失或错配;**不得把 `hero.image` / 贴图 / alpha 钩子写进本节**(按 D-18 只在「处置」排除说明中提及)——门禁对本节做 D-18 禁用词机器判定。
(4) **处置段定稿**:本步只读、相对「扫描前基线」零新增生产代码改动(用户既有 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交);适配工作(04-03+)由用户在本探索结果与候选裁决后另行规划(D-17/D-13);显式排除 `HeroRendering` 渲染状态(D-18:`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 `core.*`(D-12),并写明范围的另一侧(勇士移动契约与移动钩子在范围内);`getHeroStatusOn` 类裸函数(D-11)不属本增量。
- (5) **门禁与提交**:运行只读门禁(相对「扫描前基线」无**新增**条目)、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动(尤其不得把用户既有的 `client-base/src/load/**` 改动纳入本次提交)。
+ (5) **门禁与提交**:运行只读门禁(当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 条目集合必须与 `## 方法` 记录的扫描前基线条目**逐条一致**,且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变 —— 能发现基线目录内的新增/消失条目与基线文件内容被改动)、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动(尤其不得把用户既有的 `client-base/src/load/**` 改动纳入本次提交)。
- node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}const banned=/IHeroRendering|hero\.image|onSetImage|onSetAlpha|onSetFollowerAlpha/;const rowBad=rows.filter(l=>banned.test(l));if(rowBad.length>0){console.error('D18 TOKEN IN RECON ROW '+rowBad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}if(banned.test(g.l)||banned.test(body)){console.error('D18 TOKEN IN GAP CANDIDATE');process.exit(1)}}if(t.indexOf('\u5019\u9009\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)"
- non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION" / "D18 TOKEN IN RECON ROW" / "D18 TOKEN IN GAP CANDIDATE"
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}const banned=/IHeroRendering|hero\.image|onSetImage|onSetAlpha|onSetFollowerAlpha/;const rowBad=rows.filter(l=>banned.test(l));if(rowBad.length>0){console.error('D18 TOKEN IN RECON ROW '+rowBad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));if(gh.length<1){console.error('NO GAP CANDIDATES');process.exit(1)}for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}if(banned.test(g.l)||banned.test(body)){console.error('D18 TOKEN IN GAP CANDIDATE');process.exit(1)}}const cm=t.match(/\*\*\u5019\u9009\u7ed3\u8bba\uff1a\*\*\s*\u672c\u6b65\u767b\u8bb0\s*(\d+)\s*\u9879/);if(!cm){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}if(Number(cm[1])<1||Number(cm[1])!==gh.length){console.error('GAP CONCLUSION COUNT MISMATCH '+cm[1]+' vs '+gh.length);process.exit(1)}const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const und=seg('## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09');if(und===''){console.error('MISSING UNDETERMINED SECTION');process.exit(1)}if(banned.test(und)){console.error('D18 TOKEN IN UNDETERMINED SECTION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)"
+ non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "NO GAP CANDIDATES" / "GAP SECTION WITHOUT CONCLUSION" / "GAP CONCLUSION COUNT MISMATCH" / "MISSING UNDETERMINED SECTION" / "D18 TOKEN IN RECON ROW" / "D18 TOKEN IN GAP CANDIDATE" / "D18 TOKEN IN UNDETERMINED SECTION"
node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行)
- node -e "const{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const lines=out?out.split(/\r?\n/):[];const allowed=/^.. packages-user\/client-base\/src\/load\//;const extra=lines.filter(l=>!allowed.test(l));if(extra.length>0){console.error('NEW RENDER PKG CHANGES '+extra.length+' '+extra.join(' ; '));process.exit(1)}console.log('OK no new render-pkg changes; pre-existing baseline entries '+lines.length)"
- non-zero exit, 或 stderr 含 "NEW RENDER PKG CHANGES"(相对规划时基线出现了新增的渲染端包改动/新增文件 —— 违反只读约束;规划时既有的 `packages-user/client-base/src/load/**` 用户改动不算,且不得回滚它们)
+ node -e "const fs=require('fs'),{execSync}=require('child_process'),c=require('crypto');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');const rm=ms.match(/^[ MARCUD?!]{2} packages-user\/[^\r\n]*/gm);const rec=(rm||[]).map(s=>s.replace(/\s+$/,''));const digest=(lines)=>{const parts=lines.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});return c.createHash('sha256').update(parts.join('\n')).digest('hex')};const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const cur=out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('BASELINE ENTRY SET CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}const dm=ms.match(/\u57fa\u7ebf\u6458\u8981[^\r\n]*?sha256=([0-9a-f]{64})/);if(!dm){console.error('NO BASELINE DIGEST');process.exit(1)}const curDigest=digest(cur);if(dm[1]!==curDigest){console.error('BASELINE DIGEST CHANGED '+dm[1]+' vs '+curDigest);process.exit(1)}console.log('OK baseline unchanged entries '+cur.length+' digest '+curDigest.slice(0,12))"
+ non-zero exit, 或 stderr 含 "BASELINE ENTRY SET CHANGED" / "NO BASELINE DIGEST" / "BASELINE DIGEST CHANGED"(相对记录在 `## 方法` 的扫描前基线出现了条目新增/消失/状态码变化,或基线文件内容被改动 —— 违反只读约束;规划时既有的 `packages-user/client-base/src/load/**` 用户改动必须原样保留,不得回滚/暂存/提交,也不得在其中追加改动)
node -e "const fs=require('fs');const dir='.planning/phases/04-render-adaptation';const plans=fs.readdirSync(dir).filter(f=>/-PLAN\.md$/.test(f)).sort();const allowed=['04-01-PLAN.md','04-02-PLAN.md'];const extra=plans.filter(p=>allowed.indexOf(p)<0);if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.join(','));process.exit(1)}console.log('OK plan scope '+plans.join(','))"
non-zero exit, 或 stderr 含 "UNEXPECTED PLAN FILES"(出现了 04-03+ 适配计划文件,即提前规划了未授权的适配工作)
- 逐成员对账行 ≥ 10 条,每条含两侧 `file:line`;三态词表(匹配 / 渲染端需改 / 数据端缺失)在文档中出现
- `HeroMover`/`IMoveController`、`HeroAnimateDirection`、`IHeroMoveController`、`IHeroMoveControllerHooks`、`hero.x/y/direction/addHook`、跟随者增删钩子、`HeroKeyMover` 的移动器期望、`manager.ts` heroMap 键类型均已逐成员落位
- - 「缺失接口候选」独立成节,每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状带 `file:line`;节末含固定结论行;候选未设计/未修复(D-13);候选只含移动语义 / 移动器成员,不含 `IHeroRendering` / 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)
- - 「未能从阅读确定(未猜测)」非空,且 `controller.queue` 等不确定项只登记不判定;不出现 `hero.image` / 贴图 / alpha 钩子条目(按 D-18 只在「处置」排除说明中提及)
+ - 「缺失接口候选」独立成节且**至少 1 条**(空节即失败),每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状带 `file:line`;节末含固定结论行且其 N 等于实际候选条数、≥ 1;候选未设计/未修复(D-13);候选只含移动语义 / 移动器成员,不含 `IHeroRendering` / 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)
+ - 「未能从阅读确定(未猜测)」非空,且 `controller.queue` 等不确定项只登记不判定;该节**不含** `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样(门禁机器判定;按 D-18 只在「处置」排除说明中以通用措辞提及)
- `## 处置` 含 D-11 / D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界,并明写适配工作(04-03+)未规划
- - 相对规划时基线无新增渲染端包改动(`git status --porcelain -- packages-user/client-base packages-user/client-modules` 只含既有 `packages-user/client-base/src/load/**` 条目,且未被回滚/暂存/提交);`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`;全文 CRLF
+ - 相对记录在 `## 方法` 的扫描前基线**零变化**:当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 条目集合与基线记录逐条一致(不新增、不消失、状态码不变),且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变;既有 `packages-user/client-base/src/load/**` 用户改动未被回滚/暂存/提交、内容未被改动;`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`;全文 CRLF
- 交付文档已以 `docs(04-02)` 提交,提交内容仅含该文档与 `04-02-PLAN.md`
逐成员三态对账、缺失接口候选、未确定与处置全部定稿;只读/CRLF/范围门禁全绿;交付文档已提交,用户可据此裁决缺失候选并规划后续勇士移动适配
@@ -256,7 +314,8 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
|----------|-------------|
| 数据端接口基准 → 探索文档 | 只读引用 `data-common` / `data-base` / `data-state` 的接口与 barrel;基准不变,仅记录差异 |
| 渲染端源码 → 探索文档 | 只读引用 `client-base` / `client-modules`;不得产生任何写回 |
-| 执行者 → 仓库工作树 | 本 run 唯一允许写入的交付文件是探索文档;被查渲染端包相对规划时基线必须零**新增**改动(规划时既有的 `client-base/src/load/**` 用户改动原样保留、不回滚) |
+| 执行者/子代理 → 执行前置关卡 | AGENTS.md 要求第一个 block 点先汇报并等待用户回复「可以执行」;`yolo`/`auto_advance` 或子代理派发都不是放行理由(Task 0 为 `blocking-human`、`autonomous: false`) |
+| 执行者 → 仓库工作树 | 本 run 唯一允许写入的交付文件是探索文档;被查渲染端包相对记录在 `## 方法` 的扫描前基线必须零变化(条目逐条一致 + `基线摘要 sha256` 未变;规划时既有的 `client-base/src/load/**` 用户改动原样保留、不回滚、内容不得改动) |
| 缺失接口候选 → 用户裁决/后续适配 | 候选被下游用户与适配计划消费;把「渲染端需改」误报为「数据端缺失」会诱导错误的数据端改动(D-13) |
| 探索范围 → Phase 5 / 后续增量 | 把 `HeroRendering` 渲染状态(D-18)或 `core.*`(D-12)混入本增量会扩大范围并污染阶段边界;反之把勇士移动契约/移动钩子误排除会漏掉本次主要失配点 |
@@ -264,9 +323,10 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1 先记录「扫描前基线」(规划时既有、与勇士移动无关的用户改动 `client-base/src/load/**`);Task 1–3 均为只读;Task 3 门禁断言相对基线**无新增条目**(仅放行 `packages-user/client-base/src/load/` 下条目),出现任何其它条目即失败;不得回滚/暂存/提交用户既有改动 |
+| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1 先记录「扫描前基线」(逐行 porcelain 条目 + `sha256` 摘要;规划时既有、与勇士移动无关的用户改动 `client-base/src/load/**`);Task 1–3 均为只读;Task 3 门禁断言「当前 porcelain 条目集合与基线记录逐条一致」且「基线摘要 `sha256`(status 条目 + 各基线条目文件内容的 sha256)未变」——基线目录内的新增/消失条目、状态码变化或基线文件内容被改动均失败;不得回滚/暂存/提交用户既有改动 |
| T-04-02-02 | Repudiation | 对账行(无锚点则不可追溯) | medium | mitigate | 每条 `#04-02-R-*` 强制含两侧 `file:line`;由 Task 1/3 的 `` 门禁机器判定(两侧锚点正则) |
-| T-04-02-03 | Spoofing | 「缺失接口候选」段 | high | mitigate | 每条候选必须含 `渲染端需求` / `数据端现状` / `待确认` 三段且数据端现状带 `file:line`;节末固定结论行;门禁机器判定;明写「未经用户裁决不视为最终清单」(D-13) |
+| T-04-02-03 | Spoofing | 「缺失接口候选」段 | high | mitigate | 候选**至少 1 条**且每条必须含 `渲染端需求` / `数据端现状` / `待确认` 三段、数据端现状带 `file:line`;节末固定结论行的 N 必须等于实际候选条数且 ≥ 1;门禁机器判定;明写「未经用户裁决不视为最终清单」(D-13) |
+| T-04-02-07 | Elevation of Privilege | 执行前置关卡(AGENTS.md 要求的第一处 block 点) | high | mitigate | Task 0 为 `checkpoint:decision` + `gate="blocking-human"` 且本计划 `autonomous: false`;`` 明示未收到用户「可以执行」四个字即必须停止、不派发执行子代理;`yolo` / `workflow.auto_advance` 等模式不得自动放行 |
| T-04-02-04 | Elevation of Privilege | 探索范围(`HeroRendering` 渲染状态 / `core.*` / 双布局 / legacy 移植) | medium | mitigate | D-12/D-18 为显式排除项且边界写明(排除 `IHeroRendering`/贴图别名/alpha 钩子,保留移动契约与移动钩子);文中排除说明与「处置」必须出现 `HeroRendering` 与 `core.*`;范围门禁禁止产生 04-03+ 计划文件 |
| T-04-02-05 | Information Disclosure | 探索文档引用的源码行与接口名 | low | accept | 记录只含接口名、文件行号与成员签名,不含凭据/密钥 |
| T-04-02-06 | Denial of Service | 只读子代理越权写入(D-09) | low | mitigate | Task 2 明确子代理仅回传事实表、不写文件、不贴大段源码;最终由主执行者只读门禁统一验证 |
@@ -275,21 +335,23 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
-1. 结构门禁(Task 1):探索文档 8 个二级标题(含 `## 方法`)逐字存在;`## 方法` 含「扫描前基线」条目;至少 1 条 `#04-02-R-*` 双锚点对账行;文档含 `HeroRendering` 与 `core.*` 排除说明。
+0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题 / 大致解决方案 / 范围边界」后**停下等待**用户明确回复「可以执行」四个字;未收到该回复前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo`/`auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
+1. 结构门禁(Task 1):探索文档 8 个二级标题(含 `## 方法`)逐字存在;`## 方法` 含「扫描前基线」条目(逐行原样记录 porcelain 条目)与 `基线摘要 sha256=<64hex>` 行,且记录条目与当前 `git status` 逐条一致;至少 1 条 `#04-02-R-*` 双锚点对账行;文档含 `HeroRendering` 与 `core.*` 排除说明。
2. CRLF 门禁(Task 1/2/3):文档无孤立 `LF`,全文 CRLF(dev.md「换行使用 CRLF 格式」)。
3. 两侧清单门禁(Task 2):数据端段 ≥ 8 个 `data-*` `file:line` 锚点;渲染端段 ≥ 12 个 `client-*` `file:line` 锚点;含 `@ts-expect-error` 标记清单与 `index.ts:line` barrel 导出核对;贴图/alpha 使用点只作 D-18 排除标记。
-4. 对账完整性门禁(Task 3):`#04-02-R-*` ≥ 10 条且每条两侧锚点;三态词表齐备;每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`;`候选结论` 行存在;对账行与候选均不含 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)。
-5. 只读门禁(Task 3):`git status --porcelain -- packages-user/client-base packages-user/client-modules` 相对规划时基线**无新增条目**(仅既有 `packages-user/client-base/src/load/**` 条目被放行;不回滚它们)。
+4. 对账完整性门禁(Task 3):`#04-02-R-*` ≥ 10 条且每条两侧锚点;三态词表齐备;`### #04-02-G-*` **≥ 1 条**且每条含 `渲染端需求` / `数据端现状` / `待确认`,`候选结论` 行的 N 等于实际候选条数且 ≥ 1;对账行、候选与「未能从阅读确定(未猜测)」节均不含 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)。
+5. 只读门禁(Task 3):当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 条目集合与记录在 `## 方法` 的扫描前基线**逐条一致**(不新增、不消失、状态码不变),且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变 —— 基线目录内的新增/消失条目或基线文件内容被改动都会失败;既有 `packages-user/client-base/src/load/**` 改动原样保留(不回滚/暂存/提交)。
6. 范围门禁(Task 3):`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`(未规划 04-03+ 适配)。
7. 人工核对:「未能从阅读确定(未猜测)」的逐条理由;`## 处置` 对 D-11/D-12/D-13/D-17/D-18 的表述与范围两侧边界;缺失候选是否确实只记录未设计。
8. 提交:`docs(04-02)` 仅含探索文档与 `04-02-PLAN.md`(不含用户既有的 `client-base/src/load/**` 改动)。
-- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 按 8 段骨架(含 `## 方法`,其中含「扫描前基线」)产出,成员级覆盖数据端现行接口与渲染端现状,逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失)每条两侧 `file:line` 可追溯(D-05/D-17)。
-- 缺失接口候选独立成节、含「渲染端需求 / 数据端现状 / 待确认」,且只记录不设计不修复、未经用户裁决不视为最终清单(D-13)。
-- `HeroRendering` **渲染状态**(`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,D-18)与 `core.*`(D-12)被显式排除,未产生对账行或候选;勇士移动契约与移动钩子(`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在范围内照常登记;`getHeroStatusOn` 类裸函数(D-11)不属本增量。
-- 被查两个渲染端包相对规划时基线零**新增**改动(用户既有 `packages-user/client-base/src/load/**` 改动未被回滚/暂存/提交);本 run 只新增探索文档一个交付文件,未规划、未实施任何适配工作(04-03+)。
+- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报「要解决的问题 / 大致解决方案 / 范围边界」后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式(含子代理派发)与 `yolo`/`auto_advance` 模式均不得自动放行。
+- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 按 8 段骨架(含 `## 方法`,其中含「扫描前基线」与 `基线摘要 sha256`)产出,成员级覆盖数据端现行接口与渲染端现状,逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失)每条两侧 `file:line` 可追溯(D-05/D-17)。
+- 缺失接口候选独立成节且**至少 1 条**,含「渲染端需求 / 数据端现状 / 待确认」,结论行 N 等于候选条数;只记录不设计不修复、未经用户裁决不视为最终清单(D-13)。
+- `HeroRendering` **渲染状态**(`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,D-18)与 `core.*`(D-12)被显式排除,未产生对账行或候选,且「未能从阅读确定(未猜测)」节不含这些字样;勇士移动契约与移动钩子(`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在范围内照常登记;`getHeroStatusOn` 类裸函数(D-11)不属本增量。
+- 被查两个渲染端包相对记录在 `## 方法` 的扫描前基线**零变化**(porcelain 条目集合逐条一致 + `基线摘要 sha256` 未变);用户既有 `packages-user/client-base/src/load/**` 改动未被回滚/暂存/提交、内容未被改动;本 run 只新增探索文档一个交付文件,未规划、未实施任何适配工作(04-03+)。
- 静态阅读无法定论的项进入「未能从阅读确定(未猜测)」而非写成缺失/错配;全文 CRLF。
- 用户可据此裁决缺失候选并规划后续勇士移动适配。
diff --git a/.planning/phases/04-render-adaptation/04-PATTERNS.md b/.planning/phases/04-render-adaptation/04-PATTERNS.md
index c98d846..708fc51 100644
--- a/.planning/phases/04-render-adaptation/04-PATTERNS.md
+++ b/.planning/phases/04-render-adaptation/04-PATTERNS.md
@@ -31,6 +31,7 @@
| New File | Role | Data Flow | Closest Analog | Match Quality |
|----------|------|-----------|----------------|---------------|
| `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` | audit / registry document | read-only inventory(分类登记:错配 / 数据端缺失 / 多余旧路径) | `.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md`(只读审计骨架)+ `.planning/phases/06-unit-tests/06-TEST-FINDINGS.md`(字段定义表 + 分节登记) | exact(结构) |
+| `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` | audit / interface-reconciliation ledger | read-only interface exploration(逐成员三态对账:匹配 / 渲染端需改 / 数据端缺失 + 缺失接口候选登记) | `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`(同阶段只读对账骨架、元信息块与「方法」段写法)+ `.planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md`(「接口事实源」声明块)+ `.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md`(8 段审计骨架) | exact(结构) |
### B. 扫描目标组(只读,不修改)
From 908fbf385b8517429de5c3095e0b79b46ecd1df6 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 12:13:39 +0800
Subject: [PATCH 018/173] fix(04): revise plans based on checker feedback
---
.planning/phases/04-render-adaptation/04-02-PLAN.md | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-02-PLAN.md b/.planning/phases/04-render-adaptation/04-02-PLAN.md
index db0e210..a5d5d28 100644
--- a/.planning/phases/04-render-adaptation/04-02-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-02-PLAN.md
@@ -144,7 +144,9 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
④ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
- ⑤ **若用户给出修订意见**:退出本计划、修订 `04-02-PLAN.md` 后重新汇报,不得自行改道执行。
+ ⑤ **失败语义(等同硬门禁,非自动命令)**:用户未回复「可以执行」即继续,或回复的是其它任何内容(含修订意见、沉默、`yolo`/`auto_advance` 模式下的自动推进、或子代理/执行器的自行放行),一律视为**未通过本关卡**:必须立即停止执行,不派发任何执行子代理,保持只读、不写任何文件,直到用户在对话中明确回复「可以执行」四个字后方可继续。本条失败语义只以本 `` 与 `` 的形式表达,不写成 XML 失败声明元素(人工关卡没有可运行的自动命令可与之绑定)。
+
+ ⑥ **若用户给出修订意见**:退出本计划、修订 `04-02-PLAN.md` 后重新汇报,不得自行改道执行。
是否按本计划(只读勇士移动接口探索,产出 `04-HERO-MOVER-INTERFACE.md`,零生产代码改动,不规划 04-03+ 适配)开始执行
@@ -164,8 +166,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
回复「可以执行」四个字,或给出修订意见
- 用户在对话中明确回复「可以执行」四个字
- 用户未回复「可以执行」即继续,或回复的是其它内容(含 yolo/auto 模式的自动推进)—— 必须停止执行,不派发任何执行子代理
+ 用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无自动命令,失败语义写在 Task 0 的 action ④/⑤ 与 acceptance_criteria 中)
- 用户回复中明确包含「可以执行」四个字
@@ -326,7 +327,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1 先记录「扫描前基线」(逐行 porcelain 条目 + `sha256` 摘要;规划时既有、与勇士移动无关的用户改动 `client-base/src/load/**`);Task 1–3 均为只读;Task 3 门禁断言「当前 porcelain 条目集合与基线记录逐条一致」且「基线摘要 `sha256`(status 条目 + 各基线条目文件内容的 sha256)未变」——基线目录内的新增/消失条目、状态码变化或基线文件内容被改动均失败;不得回滚/暂存/提交用户既有改动 |
| T-04-02-02 | Repudiation | 对账行(无锚点则不可追溯) | medium | mitigate | 每条 `#04-02-R-*` 强制含两侧 `file:line`;由 Task 1/3 的 `` 门禁机器判定(两侧锚点正则) |
| T-04-02-03 | Spoofing | 「缺失接口候选」段 | high | mitigate | 候选**至少 1 条**且每条必须含 `渲染端需求` / `数据端现状` / `待确认` 三段、数据端现状带 `file:line`;节末固定结论行的 N 必须等于实际候选条数且 ≥ 1;门禁机器判定;明写「未经用户裁决不视为最终清单」(D-13) |
-| T-04-02-07 | Elevation of Privilege | 执行前置关卡(AGENTS.md 要求的第一处 block 点) | high | mitigate | Task 0 为 `checkpoint:decision` + `gate="blocking-human"` 且本计划 `autonomous: false`;`` 明示未收到用户「可以执行」四个字即必须停止、不派发执行子代理;`yolo` / `workflow.auto_advance` 等模式不得自动放行 |
+| T-04-02-07 | Elevation of Privilege | 执行前置关卡(AGENTS.md 要求的第一处 block 点) | high | mitigate | Task 0 为 `checkpoint:decision` + `gate="blocking-human"` 且本计划 `autonomous: false`;Task 0 的 `` ④/⑤ 与 `` 明示未收到用户「可以执行」四个字即必须停止、不派发执行子代理;`yolo` / `workflow.auto_advance` 等模式不得自动放行 |
| T-04-02-04 | Elevation of Privilege | 探索范围(`HeroRendering` 渲染状态 / `core.*` / 双布局 / legacy 移植) | medium | mitigate | D-12/D-18 为显式排除项且边界写明(排除 `IHeroRendering`/贴图别名/alpha 钩子,保留移动契约与移动钩子);文中排除说明与「处置」必须出现 `HeroRendering` 与 `core.*`;范围门禁禁止产生 04-03+ 计划文件 |
| T-04-02-05 | Information Disclosure | 探索文档引用的源码行与接口名 | low | accept | 记录只含接口名、文件行号与成员签名,不含凭据/密钥 |
| T-04-02-06 | Denial of Service | 只读子代理越权写入(D-09) | low | mitigate | Task 2 明确子代理仅回传事实表、不写文件、不贴大段源码;最终由主执行者只读门禁统一验证 |
From 35a5973cbeada7e464d56f17340d4c7bbf43d1bb Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 12:24:27 +0800
Subject: [PATCH 019/173] docs(04): plan hero-mover interface exploration
(04-02)
---
.planning/ROADMAP.md | 4 ++++
.planning/STATE.md | 12 ++++++------
.planning/phases/04-render-adaptation/04-02-PLAN.md | 2 ++
.planning/phases/04-render-adaptation/04-CONTEXT.md | 13 +++++++++++++
.planning/state.json | 6 +++---
5 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 83d71ba..b4adeea 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -184,8 +184,12 @@ Plans:
**Plans**: 1/2 plans executed(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已规划 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18;后续适配实施与双布局待探索结果与缺失候选裁决后另行规划)
Plans:
+**Wave 1**
- [x] 04-01-PLAN.md — 渲染端 ↔ 数据端接口对账(只读清点,产出 `04-RENDER-INTERFACE-AUDIT.md`;按 ① 错配 / ② 数据端缺失 / ③ 多余旧路径 三类登记)
+
+**Wave 2** *(blocked on Wave 1 completion)*
+
- [ ] 04-02-PLAN.md — 勇士移动接口探索(只读,D-17):产出 `04-HERO-MOVER-INTERFACE.md`(数据端现行接口 / 渲染端现状 / 逐成员三态对账 / 缺失接口候选 / 未确定 / 处置;排除 `HeroRendering`(D-18)与 `core.*`(D-12);不改任何代码,适配(04-03+)未规划)
**UI hint**: yes
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 8f5040e..08eb587 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -3,16 +3,16 @@ gsd_state_version: "1.0"
milestone: v1.0
current_phase: 04
current_phase_name: 渲染适配与双布局
-status: verifying
+status: executing
stopped_at: Completed 04-01-PLAN.md(只读对账账本已交付;阶段 4 剩余工作待用户拆分)
-last_updated: "2026-09-18T02:52:58.322Z"
+last_updated: "2026-09-18T04:24:14.367Z"
last_activity: 2026-09-18
last_activity_desc: Phase 04 execution started
-state_head: 4327e6733b1e241c3a840a91c63c9c10e860624c
+state_head: 908fbf385b8517429de5c3095e0b79b46ecd1df6
progress:
total_phases: 7
completed_phases: 0
- total_plans: 72
+ total_plans: 73
completed_plans: 71
milestone_name: milestone
---
@@ -28,9 +28,9 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
-Phase: 04 (渲染适配与双布局) — EXECUTING
+Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
Plan: 1 of 1
-Status: Phase complete — ready for verification
+Status: Ready to execute
Last activity: 2026-09-18 — Phase 04 execution started
Progress: [█████░░░░░] 50%
diff --git a/.planning/phases/04-render-adaptation/04-02-PLAN.md b/.planning/phases/04-render-adaptation/04-02-PLAN.md
index a5d5d28..798dba8 100644
--- a/.planning/phases/04-render-adaptation/04-02-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-02-PLAN.md
@@ -63,6 +63,8 @@ Phase 4 第二个增量(D-17):对**勇士移动子系统**做**只读接
**后续适配(04-03+)在本 run 不规划**:适配依赖本探索输出,且需用户先裁决缺失候选(D-13/D-17)。
+**后续增量决策(显式延后,本计划不涉及、不实施):** CONTEXT.md 的三项设计补充裁定属于**后续增量**,与本 run 的只读勇士移动接口探索无关,故本计划不规划、不设计、不实施、不裁决它们,留待相应增量规划时另行覆盖:D-14(全局 `hook` 和 `loading` 也是 legacy 内容,需删除)、D-15(存档系统 UI 尚未接入新的存档系统)、D-16(渲染端结构化重构:拆为 `base-system-client` 三层,不再需要 common)。三者均不在本计划的交付物、对账范围或范围门禁内。
+
**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「本计划要解决的问题 / 大致解决方案 / 范围边界与禁令」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,均不得自动放行。故本计划 `autonomous: false`。
Purpose: 勇士移动是本次对账暴露的主要失配点(D-17)。在动适配代码前先把「数据端到底提供什么、渲染端到底需要什么、差在哪」钉死到接口成员级,避免按旧形状直接改渲染端。
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
index 265bd05..b8c2772 100644
--- a/.planning/phases/04-render-adaptation/04-CONTEXT.md
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -36,6 +36,19 @@
### 执行方式
- **D-09:** 允许派子代理进行只读扫描(不得修改任何文件)。
+### 设计补充(用户裁定,2026-09-18;仅记录,暂不执行)
+- **D-10:** `HeroRendering` 删除;渲染端相关的数据存储放到渲染端,渲染端自身也可以继承 `ISaveableContent`。
+- **D-11:** 诸如 `getHeroStatusOn` 这种裸函数,除非它是功能性函数,或者属于某些已明确的函数(如 replay 的那些修饰器裸函数),否则大多是将要删除的。
+- **D-12:** 所有调用 `core.*` 或 `client.*` 的地方大多需要删除。现行接口设计倾向**无全局单例**:所有内容全部挂到主类上,主类允许多例。其中 `core.*` 属旧引擎适配,应属于**第五阶段**,暂时可忽略;`client.*` 属于**本阶段**需要处理的内容。
+- **D-13:** 数据端缺失接口可能较多,等到开始改的时候再逐步确认(本阶段不从对账清单一次性定死缺失接口)。
+- **D-14:** 全局 `hook` 和 `loading` 也是 legacy 内容,需要删除。
+- **D-15:** 存档系统的 UI 目前还未接入新的存档系统。
+- **D-16:** 渲染端还需要进行结构化重构:将渲染端拆为类似数据端的 common-base-system-state 结构,但只需拆为三层——`base-system-client`(不再需要 common)。
+
+### 下一步聚焦(用户裁定,2026-09-18;待规划)
+- **D-17:** 由对账结果看,主要失配点在**勇士移动**部分。下一步规划:把**勇士移动适配到新接口**——先进行**接口探索**(并找出缺失的接口),再进行**适配工作**。
+- **D-18:** `HeroRendering` 相关暂不处理:用户先修改数据端,之后再处理。
+
### the agent's Discretion
- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
diff --git a/.planning/state.json b/.planning/state.json
index 12f5719..8592719 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -41,8 +41,8 @@
],
"next": {
"command": "/gsd:progress --next",
- "label": "Advance to the next step (verify)",
- "reason": "Phase 04 of 7 · ready to verify"
+ "label": "Advance to the next step",
+ "reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-18T02:52:40.728Z"
+ "updated_at": "2026-09-18T04:24:14.460Z"
}
From 5f2743624a8565488419ad6bc8291f45e827f670 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 12:42:48 +0800
Subject: [PATCH 020/173] =?UTF-8?q?docs(04-02):=20=E5=8B=87=E5=A3=AB?=
=?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=8E=A5=E5=8F=A3=E6=8E=A2=E7=B4=A2=E9=AA=A8?=
=?UTF-8?q?=E6=9E=B6=E4=B8=8E=20R-01=20=E5=88=87=E7=89=87=EF=BC=88?=
=?UTF-8?q?=E5=8F=AA=E8=AF=BB=EF=BC=8CD-17=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新建 04-HERO-MOVER-INTERFACE.md:8 段骨架 + 扫描前基线与 sha256 摘要
- R-01 钩子族端到端对账切片(两侧 file:line 锚点)
- 全文 CRLF;零生产代码改动
---
.../04-HERO-MOVER-INTERFACE.md | 83 +++++++++++++++++++
1 file changed, 83 insertions(+)
create mode 100644 .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
diff --git a/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md b/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
new file mode 100644
index 0000000..cb458f4
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
@@ -0,0 +1,83 @@
+# 勇士移动子系统:渲染端 ↔ 数据端接口探索
+
+**日期:** 2026-09-18
+**范围:** 勇士移动子系统(渲染端使用点 → 数据端现行接口);排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子,D-18)与 legacy `core.*`(D-12,属第五阶段)
+**性质:** 只读接口探索,未修改任何生产代码;相对规划时基线零新增改动(D-17)
+**接口基准:** `packages-user/data-common/src/common/mover.ts`、`packages-user/data-base/src/hero/{types,mover}.ts` 及各自 barrel 现行导出(D-05)
+**输入账本:** `04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`
+
+## 背景
+
+Phase 4 第二步(D-17):04-01 的只读对账把主要失配点定位在**勇士移动**(`#04-01-M-01..05`),但「数据端到底提供什么、渲染端到底需要什么、差在哪」尚缺成员级结论。不先把差异钉到接口成员级就直接改渲染端,极易按旧形状改错,因此本步先做**只读接口探索**。
+
+本步**只探索、不改任何代码**:不实施适配,不规划 04-03+,**不裁决缺失接口**(D-13:缺失接口等到开始改的时候再逐步确认)。
+
+**范围的两侧边界:**
+
+- **在范围内**:勇士移动契约与移动钩子 —— `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,以及 `onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等移动语义钩子。
+- **不在范围内(显式排除)**:`HeroRendering` **渲染状态**相关成员(`IHeroRendering`、勇士贴图别名、alpha 钩子,D-18 —— 用户先改数据端,之后再处理);legacy `core.*`(D-12 —— 属第五阶段)。二者不产生任何对账行或缺失候选。
+
+## 方法
+
+**枚举面:**
+
+- **(a)数据端现行接口面**:`data-common` 移动器族(`IObjectMovable` / `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `ObjectMoveStep` 各族 / `ObjectAnimDirection`)+ `data-base/hero` 位置移动族(`IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `HeroMover`)+ 各 `index.ts` barrel 实际导出。
+- **(b)渲染端勇士移动使用面**:`render/map/extension/{hero,types,manager}.ts` + `action/move.ts` + `client.ts` 接线点;`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考,不登记为独立对账对象。
+
+**判定三态(固定):** **匹配** / **渲染端需改** / **数据端缺失**。带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」,不归「数据端缺失」。
+
+**证据纪律:** 每条含 `接口/成员名 + 两侧 file:line`;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺失或错配。允许只读子代理扫描(D-09),但子代理仅回传事实表、不得写文件。
+
+**只读约束:** 本 run 仅新增本交付文档一个文件;被查渲染端两包相对下述扫描前基线零变化。
+
+- **扫描前基线**:执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,输出**逐行原样**记录如下(规划时为两条与勇士移动无关的用户未提交改动;**不回滚 / 不暂存 / 不提交 / 不修改**):
+
+```text
+ M packages-user/client-base/src/load/loader.ts
+ M packages-user/client-base/src/load/types.ts
+```
+
+- **基线摘要**:sha256=7497384469364c38952b3f144f256f29058a79ce820cbc4bcd9e141704f33eae
+
+(摘要由固定命令对 status 条目 + 各基线条目**文件内容的 sha256** 取摘要;只读门禁要求「当前 porcelain 条目集合 == 基线记录条目集合」且该 `sha256` 未变,能发现基线文件内容被改动。)
+
+## 数据端现行接口
+
+(Task 2 横向补齐成员级清单)
+
+- **`IObjectMovable`**(`x` / `y` / `setPos` / `getCurrentFaceDirection`):`packages-user/data-common/src/common/mover.ts:47`
+- **`IHeroMover`**(`config` / `getConfig` / `useTopImplementation`,extends `IObjectMover`):`packages-user/data-base/src/hero/types.ts:399`
+- **barrel 实际导出核对**:`packages-user/data-common/src/index.ts:1` → `packages-user/data-common/src/common/index.ts:4`;`packages-user/data-base/src/index.ts:3` → `packages-user/data-base/src/hero/index.ts:8`
+
+## 渲染端现状
+
+(Task 2 横向补齐使用点清单)
+
+- **`MapHeroRenderer` 构造器**(`hero.addHook` / `hero.x` / `hero.y` / `hero.direction`):`packages-user/client-modules/src/render/map/extension/hero.ts:76`
+- **`hero.addHook(new MapHeroHook(this))`**:`packages-user/client-modules/src/render/map/extension/hero.ts:81`
+- **`HeroKeyMover`**(移动器使用面):`packages-user/client-modules/src/action/move.ts:11`
+
+## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)
+
+结论取 **匹配** / **渲染端需改** / **数据端缺失** 三态之一;每条两侧均给 `file:line`,数据端无对位时写「无对位,最接近:」。
+
+| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |
+|----|------|------------|------------|----------|------|
+| #04-02-R-01 | `IHeroMoveControllerHooks` 钩子族(移动 / 位置 / 跟随者子集) | `packages-user/client-modules/src/render/map/extension/hero.ts:81` `packages-user/client-modules/src/render/map/extension/hero.ts:457` | 有对位:`packages-user/data-common/src/common/mover.ts:166` `packages-user/data-common/src/common/mover.ts:216`;有对位:`packages-user/data-base/src/hero/types.ts:277` `packages-user/data-base/src/hero/types.ts:283`;`onSetPosition` / `onStartMove` / `onEndMove` / `onMoveHero` / `onJumpHero` / `onTurnHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` 无同名对位,最接近:`packages-user/data-common/src/common/mover.ts:166` | 渲染端需改 | `addHook` 注册的移动 / 位置 / 跟随者钩子方法名与数据端现行 hooks 整体不一致,需随 `IHeroMoveControllerHooks` 整体重构;贴图 / alpha 钩子按 D-18 排除,不在本行范围 |
+
+## 缺失接口候选
+
+(Task 3 补齐:候选登记与结论行)
+
+## 未能从阅读确定(未猜测)
+
+(Task 3 补齐:无法静态定论项)
+
+## 处置
+
+- **本步性质**:只读接口探索,**未修改任何生产代码**;被查渲染端两包相对「扫描前基线」零新增改动(用户既有的 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交)。
+- **适配工作**:勇士移动适配实施(04-03+)**未在本 run 规划**,由用户在本探索结果与缺失候选裁决后另行规划(D-17 / D-13)。
+- **显式排除项**:`HeroRendering` 渲染状态(D-18:`IHeroRendering` / 勇士贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` —— 用户先改数据端,之后再处理)与 legacy `core.*`(D-12 —— 属第五阶段)。二者不产生任何对账行或缺失候选。
+- **范围的另一侧**:勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)**在范围内**,照常登记。
+- **缺失接口裁决**:缺失接口候选须经用户逐步裁决(D-13),本步只记录、不设计、不修复,未经裁决不视为最终缺失清单。
+- **`getHeroStatusOn` 类裸函数**(D-11)属 UI 属性读取,不属本增量范围,不登记为缺失。
From 77e2ad5d45faffedf1d7e2d19bf805aebfe9d319 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 12:44:10 +0800
Subject: [PATCH 021/173] =?UTF-8?q?docs(04-02):=20=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E7=AB=AF=E7=8E=B0=E8=A1=8C=E6=8E=A5=E5=8F=A3=E4=B8=8E=E6=B8=B2?=
=?UTF-8?q?=E6=9F=93=E7=AB=AF=E7=8E=B0=E7=8A=B6=E4=B8=A4=E4=BE=A7=E6=B8=85?=
=?UTF-8?q?=E5=8D=95=EF=BC=88=E5=8F=AA=E8=AF=BB=EF=BC=8CD-17=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 数据端成员级清单:data-common 移动器族 + data-base/hero 位置移动族 + barrel 实际导出核对
- 渲染端使用点清单:hero/types/manager/move/client + 移动器契约参考
- @ts-expect-error 需要重构 全量清单位置;全文 CRLF
---
.../04-HERO-MOVER-INTERFACE.md | 104 +++++++++++++++++-
1 file changed, 98 insertions(+), 6 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md b/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
index cb458f4..0e9fa9f 100644
--- a/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+++ b/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
@@ -43,19 +43,111 @@ Phase 4 第二步(D-17):04-01 的只读对账把主要失配点定位在**
## 数据端现行接口
-(Task 2 横向补齐成员级清单)
+**`data-common` 移动器族(`packages-user/data-common/src/common/mover.ts`):**
+- **`ObjectMoveType`**(步骤类型枚举:`Dir` / `DirFace` / `Speed` / `Face` / `Special` / `AnimDir` / `Teleport` / `Jump`):`packages-user/data-common/src/common/mover.ts:14`
+- **`ObjectSpecialStep`**(`Forward` / `Backward`):`packages-user/data-common/src/common/mover.ts:33`
+- **`ObjectAnimDirection`**(`Forward` / `Backward`;替代渲染端 `HeroAnimateDirection`):`packages-user/data-common/src/common/mover.ts:40`
- **`IObjectMovable`**(`x` / `y` / `setPos` / `getCurrentFaceDirection`):`packages-user/data-common/src/common/mover.ts:47`
+- **`IObjectMoveStepDir` / `IObjectMoveStepDirFace` / `IObjectMoveStepSpeed` / `IObjectMoveStepFace` / `IObjectMoveStepSpecial` / `IObjectMoveAnimDir` / `IObjectMoveTP` / `IObjectMoveJump`**:`packages-user/data-common/src/common/mover.ts:66` 起
+- **`ObjectMoveStep`**(上述各步骤的联合类型):`packages-user/data-common/src/common/mover.ts:132`
+- **`IMoverController`**(`done` / `onEnd` / `push(...steps)` / `insert(...steps)` / `stop()`):`packages-user/data-common/src/common/mover.ts:142`
+- **`IObjectMoverHooks`**(`onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onSetPos` / `onSetFaceDir` / `onSetMoveDir`):`packages-user/data-common/src/common/mover.ts:166`
+- **`IObjectMover`**(`moving` / `tile` / `faceDirection` / `moveDirection` / `currAnimDir` / `currentSpeed` / `faceHandler` / `setPos` / `setFaceDir` / `setMoveDir` / `tp` / `jump` / `step` / `stepFace` / `forward` / `backward` / `speed` / `face` / `animDir` / `push` / `clear` / `start`,extends `IHookable>`):`packages-user/data-common/src/common/mover.ts:235`
+- **`ObjectMover` 基类**(`moveQueue` / `start()` 实现):`packages-user/data-common/src/common/mover.ts:357`(队列 `:364`、`start` `:689`)
+
+**`data-base/hero` 位置移动族:**
+
+- **`IHeroLocationHooks`**(`onSetPos` / `onSetFloor`):`packages-user/data-base/src/hero/types.ts:277`
+- **`IHeroLocation`**(`floorId` / `map` / `mover`,extends `IObjectMovable`):`packages-user/data-base/src/hero/types.ts:303`
+- **`HeroMoveCode`** / **`IHeroMoveTopHandler`** / **`IHeroMoveTopImpl`** / **`IHeroMoverConfig`**:`packages-user/data-base/src/hero/types.ts:327` / `:338` / `:351` / `:390`
- **`IHeroMover`**(`config` / `getConfig` / `useTopImplementation`,extends `IObjectMover`):`packages-user/data-base/src/hero/types.ts:399`
-- **barrel 实际导出核对**:`packages-user/data-common/src/index.ts:1` → `packages-user/data-common/src/common/index.ts:4`;`packages-user/data-base/src/index.ts:3` → `packages-user/data-base/src/hero/index.ts:8`
+- **`IHeroFollower`**:`packages-user/data-base/src/hero/types.ts:464`
+- **`IHeroFollowersControllerHooks`**(`onAddFollower` / `onRemoveFollower` / `onGatherFollowers`):`packages-user/data-base/src/hero/types.ts:486`
+- **`IHeroFollowersController`**(`addFollower` / `getFollower` / `getFollowersById` / `getAllFollowers` / `removeFollower` / `removeAllFollowers` / `gatherFollowers` / `gatherFollowersSync`):`packages-user/data-base/src/hero/types.ts:508`
+- **`IHeroStateHooks`**(`onBeforeChangeFloor` / `onAfterChangeFloor`):`packages-user/data-base/src/hero/types.ts:858`
+- **`IHeroState`**(`location` → `IHeroLocation`、`followers` → `IHeroFollowersController`):`packages-user/data-base/src/hero/types.ts:889`
+- **`HeroMover`**(`config` / `getConfig` / `useTopImplementation`,extends `ObjectMover`):`packages-user/data-base/src/hero/mover.ts:22`(配置 `:46` / `:59` / `:67`)
+
+**barrel 实际导出核对(D-05):**
+
+- **`@user/data-common`**:`packages-user/data-common/src/index.ts:1` 转发 `./common` → `packages-user/data-common/src/common/index.ts:4` 转发 `./mover`;故 `IObjectMover` / `IMoverController` / `IObjectMoverHooks` / `IObjectMovable` / `ObjectAnimDirection` 可经 `@user/data-common` 取得。
+- **`@user/data-base`**:`packages-user/data-base/src/index.ts:3` 转发 `./hero` → `packages-user/data-base/src/hero/index.ts:8` 转发 `./mover`、`:10` 转发 `./types`;故 `HeroMover` / `IHeroMover` / `IHeroLocation` / `IHeroState` 可经 `@user/data-base` 取得。
+- **`@user/data-state`**:`packages-user/data-state/src/index.ts:1` 转发 `./hero`,但 `packages-user/data-state/src/hero/index.ts:1` 仅转发 `./moverImpl` / `:2` `./predicate` / `:3` `./types`,**未导出** `HeroMover` / `IMoveController`;渲染端 `packages-user/client-modules/src/action/move.ts:4` 从 `@user/data-state` import 二者因此失配(见 `#04-02-R-02`)。
+- **`@motajs/common`**:`IHookable.addHook` / `IHookController.load/unload` 契约位于 `packages/common/src/types.ts:81` / `:52`,由 `packages/common/src/hook.ts:24` 的 `Hookable.addHook` 实现(`hero.addHook` 的落点)。
## 渲染端现状
-(Task 2 横向补齐使用点清单)
+**`render/map/extension/hero.ts`(`MapHeroRenderer` / `MapHeroHook`):**
-- **`MapHeroRenderer` 构造器**(`hero.addHook` / `hero.x` / `hero.y` / `hero.direction`):`packages-user/client-modules/src/render/map/extension/hero.ts:76`
-- **`hero.addHook(new MapHeroHook(this))`**:`packages-user/client-modules/src/render/map/extension/hero.ts:81`
-- **`HeroKeyMover`**(移动器使用面):`packages-user/client-modules/src/action/move.ts:11`
+- 构造器(`hero.addHook(new MapHeroHook(this))` / `hero.x` / `hero.y` / `hero.direction`):`packages-user/client-modules/src/render/map/extension/hero.ts:76-102`
+- `addHeroMoving`(`hero.image` 贴图别名读取 `:115` / `:119` / `:121` —— 属贴图子系统,按 D-18 排除,不进入对账与候选):`packages-user/client-modules/src/render/map/extension/hero.ts:110`
+- `updateHeroTexture`(贴图,按 D-18 排除):`packages-user/client-modules/src/render/map/extension/hero.ts:138`
+- `tick`(`HeroAnimateDirection.Forward` 使用点 `:170`):`packages-user/client-modules/src/render/map/extension/hero.ts:165`
+- `setImage`(贴图,按 D-18 排除):`packages-user/client-modules/src/render/map/extension/hero.ts:191`
+- `setAlpha`(alpha,按 D-18 排除):`packages-user/client-modules/src/render/map/extension/hero.ts:198`
+- `setPosition`:`packages-user/client-modules/src/render/map/extension/hero.ts:202`
+- `moveEntity`:`packages-user/client-modules/src/render/map/extension/hero.ts:215`
+- `jumpEntity`:`packages-user/client-modules/src/render/map/extension/hero.ts:264`
+- `startMove`:`packages-user/client-modules/src/render/map/extension/hero.ts:282`
+- `waitMoveEnd`:`packages-user/client-modules/src/render/map/extension/hero.ts:295`
+- `stopMove`:`packages-user/client-modules/src/render/map/extension/hero.ts:300`
+- `move`:`packages-user/client-modules/src/render/map/extension/hero.ts:307`
+- `jumpTo`:`packages-user/client-modules/src/render/map/extension/hero.ts:316`
+- `addFollower`:`packages-user/client-modules/src/render/map/extension/hero.ts:341`
+- `removeFollower`:`packages-user/client-modules/src/render/map/extension/hero.ts:378`
+- `removeAllFollowers`:`packages-user/client-modules/src/render/map/extension/hero.ts:424`
+- `setFollowerAlpha`(alpha,按 D-18 排除):`packages-user/client-modules/src/render/map/extension/hero.ts:431`
+- `setHeroAnimateDirection`:`packages-user/client-modules/src/render/map/extension/hero.ts:437`
+- `turn`:`packages-user/client-modules/src/render/map/extension/hero.ts:441`
+- `MapHeroHook`(`Partial`;`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属贴图 / alpha 子系统按 D-18 排除;移动 / 位置 / 跟随者钩子 `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` 见 `:469-512`):`packages-user/client-modules/src/render/map/extension/hero.ts:457`
+
+**`render/map/extension/types.ts`:**
+
+- `IMapExtensionManager.heroMap`(键类型 `IHeroMoveController`):`packages-user/client-modules/src/render/map/extension/types.ts:15`
+- `addHero` / `removeHero`(参数 `IHeroMoveController`):`packages-user/client-modules/src/render/map/extension/types.ts:26` / `:35`
+- `IMapHeroRenderer` 契约(`setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setAlpha` / `setFollowerAlpha` / `setHeroAnimateDirection` / `turn`):`packages-user/client-modules/src/render/map/extension/types.ts:63`
+- `setHeroAnimateDirection(direction: HeroAnimateDirection)`:`packages-user/client-modules/src/render/map/extension/types.ts:147`
+
+**`render/map/extension/manager.ts`:**
+
+- `// @ts-expect-error 需要重构` + `import { IHeroMoveController } from '@user/data-base'`:`packages-user/client-modules/src/render/map/extension/manager.ts:1-2`
+- `heroMap` / `addHero` / `removeHero`:`packages-user/client-modules/src/render/map/extension/manager.ts:18` / `:26` / `:39`
+
+**`action/move.ts`(`HeroKeyMover` 全类):**
+
+- `// @ts-expect-error 需要重构` + `import { HeroMover, IMoveController } from '@user/data-state'`:`packages-user/client-modules/src/action/move.ts:3-4`
+- `class HeroKeyMover` / `controller?: IMoveController` / `mover: HeroMover`:`packages-user/client-modules/src/action/move.ts:11` / `:19` / `:29`
+- legacy 门控 `core.isReplaying()` / `core.isPlaying()` / `core.waitHeroToStop()` / `core.status.lockControl`:`packages-user/client-modules/src/action/move.ts:66` / `:97` / `:123` / `:156`
+- `this.mover.oneStep(this.moveDir)` / `this.mover.startMove(false, false, false, true)`:`packages-user/client-modules/src/action/move.ts:125`
+- `controller.onEnd.then(...)`:`packages-user/client-modules/src/action/move.ts:130`
+- `this.mover.on('stepEnd', this.onStepEnd)`:`packages-user/client-modules/src/action/move.ts:137`
+- `con.queue.length` / `con.push({ type: 'dir', value: this.moveDir })`:`packages-user/client-modules/src/action/move.ts:169`
+- `this.controller?.stop()` / `this.mover.off('stepEnd', this.onStepEnd)`:`packages-user/client-modules/src/action/move.ts:145` / `:180`
+
+**`client.ts` 接线点:**
+
+- 被注释的 `this.mainMapExtension.addHero(this.hero.mover, layer)`:`packages-user/client-modules/src/client.ts:148`
+
+**`render/map/{moving,status,renderer}.ts`(仅移动器契约参考,不登记为独立对账对象):**
+
+- `IMovingBlock` / `addMovingBlock` / `getMovingBlock` / `updateMoving`:`packages-user/client-modules/src/render/map/types.ts:194` / `:534` / `:544` / `:1101`
+- `IMovingRenderer` / `MovingBlock`:`packages-user/client-modules/src/render/map/moving.ts:8` / `:26`
+- `MapRenderer.addMovingBlock` / `requestTicker`:`packages-user/client-modules/src/render/map/renderer.ts:1616` / `:1698`
+- `StaticBlockStatus` / `DynamicBlockStatus`:`packages-user/client-modules/src/render/map/status.ts:4` / `:39`
+
+**`// @ts-expect-error 需要重构` 清单位置(被查两包全部出现点):**
+
+- `packages-user/client-modules/src/action/move.ts:3`
+- `packages-user/client-modules/src/render/map/extension/hero.ts:5`
+- `packages-user/client-modules/src/render/map/extension/hero.ts:7`
+- `packages-user/client-modules/src/render/map/extension/hero.ts:9`
+- `packages-user/client-modules/src/render/map/extension/manager.ts:1`
+- `packages-user/client-modules/src/render/map/extension/types.ts:4`
+- `packages-user/client-modules/src/render/map/extension/types.ts:6`
+- `packages-user/client-modules/src/render/ui/main.tsx:28`(非勇士移动,属 D-11 属性读取)
+- `packages-user/client-modules/src/render/ui/main.tsx:247`(非勇士移动,属 `state.maps` 类型错配)
+- `packages-user/client-modules/src/render/ui/statistics.tsx:10`(非勇士移动,属 D-11)
## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)
From d4fe24c9dd1cd272d054c872e5c15f36777b105b Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 12:46:33 +0800
Subject: [PATCH 022/173] =?UTF-8?q?docs(04-02):=20=E5=8B=87=E5=A3=AB?=
=?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=8E=A5=E5=8F=A3=E6=8E=A2=E7=B4=A2=EF=BC=88?=
=?UTF-8?q?=E5=8F=AA=E8=AF=BB=EF=BC=8CD-17=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 逐成员三态对账 12 条(#04-02-R-01..12),每条两侧 file:line 锚点
- 缺失接口候选 2 条(#04-02-G-01..02)+ 固定结论行
- 未能从阅读确定 7 条;处置定稿(D-11/D-12/D-13/D-17/D-18)
- 只读 / CRLF / 基线摘要未变 / 范围门禁全绿;零生产代码改动
---
.../04-HERO-MOVER-INTERFACE.md | 36 +++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md b/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
index 0e9fa9f..dfbc71d 100644
--- a/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+++ b/.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
@@ -156,14 +156,45 @@ Phase 4 第二步(D-17):04-01 的只读对账把主要失配点定位在**
| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |
|----|------|------------|------------|----------|------|
| #04-02-R-01 | `IHeroMoveControllerHooks` 钩子族(移动 / 位置 / 跟随者子集) | `packages-user/client-modules/src/render/map/extension/hero.ts:81` `packages-user/client-modules/src/render/map/extension/hero.ts:457` | 有对位:`packages-user/data-common/src/common/mover.ts:166` `packages-user/data-common/src/common/mover.ts:216`;有对位:`packages-user/data-base/src/hero/types.ts:277` `packages-user/data-base/src/hero/types.ts:283`;`onSetPosition` / `onStartMove` / `onEndMove` / `onMoveHero` / `onJumpHero` / `onTurnHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` 无同名对位,最接近:`packages-user/data-common/src/common/mover.ts:166` | 渲染端需改 | `addHook` 注册的移动 / 位置 / 跟随者钩子方法名与数据端现行 hooks 整体不一致,需随 `IHeroMoveControllerHooks` 整体重构;贴图 / alpha 钩子按 D-18 排除,不在本行范围 |
+| #04-02-R-02 | `HeroMover` / `IMoveController`(import 源与类型名) | `packages-user/client-modules/src/action/move.ts:4` `packages-user/client-modules/src/action/move.ts:19` `packages-user/client-modules/src/action/move.ts:29` `packages-user/client-modules/src/action/move.ts:33` | `packages-user/data-base/src/hero/mover.ts:22` `packages-user/data-common/src/common/mover.ts:142` | 渲染端需改 | 带 `// @ts-expect-error 需要重构`(`move.ts:3`);`IMoveController` 数据端现名为 `IMoverController`,`HeroMover` 实际在 `@user/data-base`,`@user/data-state` barrel 未导出二者 |
+| #04-02-R-03 | `HeroAnimateDirection` | `packages-user/client-modules/src/render/map/extension/hero.ts:6` `packages-user/client-modules/src/render/map/extension/hero.ts:53` `packages-user/client-modules/src/render/map/extension/hero.ts:97` `packages-user/client-modules/src/render/map/extension/types.ts:5` `packages-user/client-modules/src/render/map/extension/types.ts:147` | `packages-user/data-common/src/common/mover.ts:40` | 渲染端需改 | 带 `// @ts-expect-error 需要重构`;数据端现行为 `ObjectAnimDirection`,成员 `Forward` / `Backward` 一一对应 |
+| #04-02-R-04 | `IHeroMoveController` | `packages-user/client-modules/src/render/map/extension/hero.ts:8` `packages-user/client-modules/src/render/map/extension/hero.ts:79` `packages-user/client-modules/src/render/map/extension/types.ts:7` `packages-user/client-modules/src/render/map/extension/types.ts:15` `packages-user/client-modules/src/render/map/extension/manager.ts:2` `packages-user/client-modules/src/render/map/extension/manager.ts:18` | `packages-user/data-base/src/hero/types.ts:303` `packages-user/data-base/src/hero/types.ts:399` | 渲染端需改 | 带 `// @ts-expect-error 需要重构`;数据端无此名,已拆为 `IHeroLocation` + `IHeroMover`,`IHeroLocation.mover` 提供 `IHeroMover` |
+| #04-02-R-05 | `hero.x` / `hero.y` | `packages-user/client-modules/src/render/map/extension/hero.ts:87` `packages-user/client-modules/src/render/map/extension/hero.ts:88` `packages-user/client-modules/src/render/map/extension/hero.ts:117` `packages-user/client-modules/src/render/map/extension/hero.ts:122` | `packages-user/data-common/src/common/mover.ts:48` `packages-user/data-common/src/common/mover.ts:49` `packages-user/data-common/src/common/mover.ts:58` | 匹配 | `IHeroLocation extends IObjectMovable`(`data-base/src/hero/types.ts:303`),`x` / `y` / `setPos` 均有对位 |
+| #04-02-R-06 | `hero.direction` | `packages-user/client-modules/src/render/map/extension/hero.ts:89` `packages-user/client-modules/src/render/map/extension/hero.ts:125` `packages-user/client-modules/src/render/map/extension/hero.ts:193` | `packages-user/data-common/src/common/mover.ts:243` `packages-user/data-common/src/common/mover.ts:245` `packages-user/data-common/src/common/mover.ts:63` | 渲染端需改 | 现行无单成员 `direction`:朝向为 `IObjectMover.faceDirection` / `moveDirection`,只读朝向可经 `IObjectMovable.getCurrentFaceDirection()` |
+| #04-02-R-07 | `hero.addHook` | `packages-user/client-modules/src/render/map/extension/hero.ts:81` | `packages-user/data-common/src/common/mover.ts:235` `packages-user/data-common/src/common/mover.ts:216` | 匹配 | `IObjectMover extends IHookable>`;`addHook` 来自 `IHookable`(`packages/common/src/types.ts:81`),返回 `IHookController`,`load` / `unload` 对位 `hero.ts:82` / `:453` |
+| #04-02-R-08 | 跟随者增删钩子(`onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`) | `packages-user/client-modules/src/render/map/extension/hero.ts:502` `packages-user/client-modules/src/render/map/extension/hero.ts:506` `packages-user/client-modules/src/render/map/extension/hero.ts:510` `packages-user/client-modules/src/render/map/extension/hero.ts:341` | `packages-user/data-base/src/hero/types.ts:486` `packages-user/data-base/src/hero/types.ts:492` `packages-user/data-base/src/hero/types.ts:499` | 渲染端需改 | 现行 `IHeroFollowersControllerHooks` 参数为 `(follower: IHeroFollower, index)`,渲染端按 `(number, string)` / `(string, boolean)`;`onRemoveAllFollowers` 无同名钩子,最接近 `IHeroFollowersController.removeAllFollowers()`(`types.ts:556`) |
+| #04-02-R-09 | `HeroKeyMover` 单步启动期望(`oneStep` / `startMove(false, false, false, true)`) | `packages-user/client-modules/src/action/move.ts:125` `packages-user/client-modules/src/action/move.ts:126` | `packages-user/data-common/src/common/mover.ts:293` `packages-user/data-common/src/common/mover.ts:350`;无对位,最接近:`packages-user/data-base/src/hero/mover.ts:22` | 渲染端需改 | `oneStep` / `startMove(...)` 在 `IObjectMover` 无同名方法;对应能力为 `step(dir)` + `start(): Readonly | null`,需改为「追加步 + 启动」组合 |
+| #04-02-R-10 | `controller.queue` / `controller.push({ type: 'dir', value })` | `packages-user/client-modules/src/action/move.ts:169` `packages-user/client-modules/src/action/move.ts:170` | 无对位,最接近:`packages-user/data-common/src/common/mover.ts:152` `packages-user/data-common/src/common/mover.ts:132` `packages-user/data-common/src/common/mover.ts:364` | 渲染端需改 | `IMoverController` 无 `queue` 成员(队列私有于 `ObjectMover.moveQueue`);`push` 参数为 `ObjectMoveStep` 而非 `{ type: 'dir', value }`,渲染端需改构造 `IObjectMoveStepDir` |
+| #04-02-R-11 | `controller.onEnd` / `controller.stop()` / `mover.on('stepEnd')` | `packages-user/client-modules/src/action/move.ts:130` `packages-user/client-modules/src/action/move.ts:137` `packages-user/client-modules/src/action/move.ts:145` `packages-user/client-modules/src/action/move.ts:180` | `packages-user/data-common/src/common/mover.ts:146` `packages-user/data-common/src/common/mover.ts:163` `packages-user/data-common/src/common/mover.ts:202` | 渲染端需改 | `onEnd` / `stop()` 有对位;`mover.on('stepEnd', ...)` 事件名订阅与 `IObjectMoverHooks.onStepEnd` 回调契约不同,需改订阅方式 |
+| #04-02-R-12 | `IMapExtensionManager.heroMap` 键类型 / `addHero` / `removeHero` | `packages-user/client-modules/src/render/map/extension/types.ts:15` `packages-user/client-modules/src/render/map/extension/types.ts:27` `packages-user/client-modules/src/render/map/extension/types.ts:35` `packages-user/client-modules/src/render/map/extension/manager.ts:18` | `packages-user/data-base/src/hero/types.ts:303` `packages-user/data-base/src/hero/types.ts:399` | 渲染端需改 | 键类型 `IHeroMoveController` 需改为 `IHeroLocation` / `IHeroMover`;被注释接线 `client.ts:150` 以 `this.hero.mover`(`IHeroMover`)为参,与 `addHero(state: IHeroMoveController, ...)` 形状不一致 |
## 缺失接口候选
-(Task 3 补齐:候选登记与结论行)
+本节登记「渲染端需要而数据端无对位」的**候选**;只记录、不设计、不修复(D-13),未经用户裁决不视为最终缺失清单。
+
+### #04-02-G-01:勇士移动语义钩子族(onMoveHero / onJumpHero / onTurnHero / onStartMove / onEndMove)
+
+- **渲染端需求**:`MapHeroHook` 需要按方向 / 时长的移动语义钩子(`onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero(direction)` / `onStartMove()` / `onEndMove()`)来驱动 `MapHeroRenderer.move` / `jumpTo` / `turn` / `startMove` / `waitMoveEnd`(`packages-user/client-modules/src/render/map/extension/hero.ts:469` 起)。
+- **数据端现状**:无同名对位;最接近的是面向数据对象事件的 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:166`,含 `onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onSetPos`)与 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:277`,含 `onSetPos`),其参数为 `(tile, mover)` 数据对象而非渲染语义。
+- **待确认**:应由数据端补齐渲染语义钩子,还是渲染端改接数据对象钩子(可能与本表 `#04-02-R-01` 及 `#04-02-R-04` 的整体重构方案重叠)?留待用户裁决(D-13)。
+
+### #04-02-G-02:HeroKeyMover 依赖的 oneStep 与控制器 queue
+
+- **渲染端需求**:`HeroKeyMover.tryStartMove` / `onStepEnd` 依赖 `mover.oneStep(dir)` 与 `controller.queue` / `controller.push({ type: 'dir', value })`(`packages-user/client-modules/src/action/move.ts:125`、`:169`、`:170`)。
+- **数据端现状**:无同名对位;最接近的是 `IObjectMover.step(dir, count?)`(`packages-user/data-common/src/common/mover.ts:293`)、`IMoverController.push(...steps)`(`packages-user/data-common/src/common/mover.ts:152`)与私有队列 `ObjectMover.moveQueue`(`packages-user/data-common/src/common/mover.ts:364`,`IMoverController` 不暴露队列)。
+- **待确认**:`oneStep` 的单步语义与队列可读性应由数据端补齐,还是渲染端改为只用 `step` + `push`(不读取队列)?留待用户裁决(D-13)。
+
+**候选结论:** 本步登记 2 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)
## 未能从阅读确定(未猜测)
-(Task 3 补齐:无法静态定论项)
+1. **`controller.queue` 的运行时形状与 `IMoverController` 可达性**:渲染端 `action/move.ts:169` 读取 `con.queue.length`,但 `IMoverController`(`data-common/src/common/mover.ts:142`)无队列成员;队列在 `ObjectMover` 内为受保护成员(`mover.ts:364`)。渲染端是否曾依赖某个已删除的控制器扩展面,无法静态定论。
+2. **`state.hero.location` 是否为当前渲染端可取的入口**:`IHeroState.location` → `IHeroLocation`(`data-base/src/hero/types.ts:889`、`:303`)在静态契约上可达;但 `state` 单例(`data-state/src/ins.ts:10`)运行时是否已暴露与渲染端期望一致的取法,无法仅凭静态阅读确认。
+3. **钩子合并关系**:渲染端 `MapHeroHook`(`hero.ts:457`)与数据端 `IObjectMoverHooks` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` 的合并方式无法静态定论(见 `#04-02-R-01` / `#04-02-G-01`)。
+4. **移动语义钩子候选与 04-01「未能从阅读确定」第 2 条的重叠关系**:04-01 已登记「勇士 / 跟随者渲染粒度钩子」候选;该条中的贴图 / alpha 子项按 D-18 排除,本步只保留移动子项(`#04-02-G-01`),二者是否应合并为单一缺失项无法静态定论。
+5. **`state.roleFace` 的可达性**:渲染端在 `hero.ts:225` / `:348` / `:407` 读取 `state.roleFace.getFaceOf(...)`,该成员属于本增量对账面还是渲染侧局部能力,无法从勇士移动契约静态定论。
+6. **`IMapHeroRenderer.waitMoveEnd` 签名落差**:接口声明带参(`packages-user/client-modules/src/render/map/extension/types.ts:103`),实现无参(`packages-user/client-modules/src/render/map/extension/hero.ts:295`);属渲染端内部签名落差还是与数据端钩子契约相关,无法静态定论。
+7. **`stepEnd` 事件来源**:渲染端订阅 `mover.on('stepEnd', ...)`(`action/move.ts:137`),数据端现行钩子为 `IObjectMoverHooks.onStepEnd`(`mover.ts:202`)回调而非事件发射器事件;事件名与回调的桥接方式无法静态定论。
## 处置
@@ -173,3 +204,4 @@ Phase 4 第二步(D-17):04-01 的只读对账把主要失配点定位在**
- **范围的另一侧**:勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)**在范围内**,照常登记。
- **缺失接口裁决**:缺失接口候选须经用户逐步裁决(D-13),本步只记录、不设计、不修复,未经裁决不视为最终缺失清单。
- **`getHeroStatusOn` 类裸函数**(D-11)属 UI 属性读取,不属本增量范围,不登记为缺失。
+- **只读门禁**:相对基线的零变化(porcelain 条目集合逐条一致 + `基线摘要 sha256` 未变)由机器门禁判定;本 run 只新增本交付文档一个文件,未规划 04-03+ 适配。
From 847df00d2d95aca66b7531dc1e80f13e070d87a7 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 12:52:51 +0800
Subject: [PATCH 023/173] =?UTF-8?q?docs(04-02):=20complete=20=E5=8B=87?=
=?UTF-8?q?=E5=A3=AB=E7=A7=BB=E5=8A=A8=E6=8E=A5=E5=8F=A3=E6=8E=A2=E7=B4=A2?=
=?UTF-8?q?=20plan?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- SUMMARY:8 段账本 + 12 条三态对账 + 2 条缺失候选 + 7 条未确定
- STATE:Plan 2/2、decisions、session;ROADMAP:04-02 计划完成
- 零生产代码改动;被查两包相对基线零变化
---
.planning/ROADMAP.md | 6 +-
.planning/STATE.md | 20 +--
.../04-render-adaptation/04-02-SUMMARY.md | 136 ++++++++++++++++++
3 files changed, 151 insertions(+), 11 deletions(-)
create mode 100644 .planning/phases/04-render-adaptation/04-02-SUMMARY.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index b4adeea..848d965 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 1/2 plans executed(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已规划 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18;后续适配实施与双布局待探索结果与缺失候选裁决后另行规划)
+**Plans**: 2/2 plans executed(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已规划 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18;后续适配实施与双布局待探索结果与缺失候选裁决后另行规划)
Plans:
**Wave 1**
@@ -190,7 +190,7 @@ Plans:
**Wave 2** *(blocked on Wave 1 completion)*
-- [ ] 04-02-PLAN.md — 勇士移动接口探索(只读,D-17):产出 `04-HERO-MOVER-INTERFACE.md`(数据端现行接口 / 渲染端现状 / 逐成员三态对账 / 缺失接口候选 / 未确定 / 处置;排除 `HeroRendering`(D-18)与 `core.*`(D-12);不改任何代码,适配(04-03+)未规划)
+- [x] 04-02-PLAN.md — 勇士移动接口探索(只读,D-17):产出 `04-HERO-MOVER-INTERFACE.md`(数据端现行接口 / 渲染端现状 / 逐成员三态对账 / 缺失接口候选 / 未确定 / 处置;排除 `HeroRendering`(D-18)与 `core.*`(D-12);不改任何代码,适配(04-03+)未规划)
**UI hint**: yes
@@ -385,7 +385,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 1/2 | In Progress| |
+| 4. 渲染适配与双布局 | 2/2 | In Progress| |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 08eb587..bf96453 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -4,16 +4,16 @@ milestone: v1.0
current_phase: 04
current_phase_name: 渲染适配与双布局
status: executing
-stopped_at: Completed 04-01-PLAN.md(只读对账账本已交付;阶段 4 剩余工作待用户拆分)
-last_updated: "2026-09-18T04:24:14.367Z"
+stopped_at: Completed 04-02-PLAN.md(勇士移动只读接口探索账本已交付;缺失候选待用户裁决)
+last_updated: "2026-09-18T04:51:30.189Z"
last_activity: 2026-09-18
last_activity_desc: Phase 04 execution started
-state_head: 908fbf385b8517429de5c3095e0b79b46ecd1df6
+state_head: d4fe24c9dd1cd272d054c872e5c15f36777b105b
progress:
total_phases: 7
completed_phases: 0
total_plans: 73
- completed_plans: 71
+ completed_plans: 72
milestone_name: milestone
---
@@ -28,8 +28,8 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
-Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
-Plan: 1 of 1
+Phase: 04 (渲染适配与双布局) — EXECUTING
+Plan: 2 of 2
Status: Ready to execute
Last activity: 2026-09-18 — Phase 04 execution started
@@ -97,6 +97,7 @@ Progress: [█████░░░░░] 50%
| Phase 07-data-fixes P13 | 56min | 5 tasks | 5 files |
| Phase 07 P14 | 3min | 3 tasks | 1 files |
| Phase 04 P01 | 11min | 3 tasks | 1 files |
+| Phase 04 P2 | 23min | 4 tasks | 1 files |
## Accumulated Context
@@ -217,6 +218,9 @@ Recent decisions affecting current work:
- [Phase 04]: 04-01:带 // @ts-expect-error 需要重构 的 import(HeroMover/IMoveController、HeroAnimateDirection、IHeroMoveController(Hooks)、getHeroStatusOn、ItemState、state.maps layerState)一律归 ① 错配(渲染侧适配项),不归 ②。
- [Phase 04]: 04-01:② 数据端缺失节零确认——候选(core.firstData 工程元数据、勇士渲染粒度钩子)转入「未能从阅读确定(未猜测)」,不臆造缺失接口。
- [Phase 04]: 04-01:REND-01/REND-02 保持 Pending,本 run 只交付差异账本,适配实施与双布局未规划/未实施。
+- [Phase 04]: 04-02:勇士移动只读接口探索交付 04-HERO-MOVER-INTERFACE.md(8 段骨架 + 12 条三态对账 #04-02-R-01..12 + 2 条缺失候选 + 7 条未确定),被查两包相对基线零变化、零生产代码改动。
+- [Phase 04]: 04-02:缺失接口候选 2 条——移动语义钩子族(#04-02-G-01)与 HeroKeyMover 依赖的 oneStep/控制器 queue(#04-02-G-02)——只记录不设计不修复,待用户裁决(D-13)。
+- [Phase 04]: 04-02:HeroRendering 渲染状态(D-18)与 legacy core.*(D-12)为显式排除项;REND-01/REND-02 保持 Pending(本 run 只探索未实施)。
### Roadmap Evolution
@@ -254,6 +258,6 @@ None yet.
## Session Continuity
-Last session: 2026-09-18T02:52:58.072Z
-Stopped at: Completed 04-01-PLAN.md(只读对账账本已交付;阶段 4 剩余工作待用户拆分)
+Last session: 2026-09-18T04:50:15.723Z
+Stopped at: Completed 04-02-PLAN.md(勇士移动只读接口探索账本已交付;缺失候选待用户裁决)
Resume file: None
diff --git a/.planning/phases/04-render-adaptation/04-02-SUMMARY.md b/.planning/phases/04-render-adaptation/04-02-SUMMARY.md
new file mode 100644
index 0000000..9da31d7
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-02-SUMMARY.md
@@ -0,0 +1,136 @@
+---
+phase: 04-render-adaptation
+plan: 02
+subsystem: render-adaptation / interface-exploration
+tags: [audit, hero-mover, render, data-layer, interface-reconciliation, readonly, crlf, d-17]
+
+requires:
+ - phase: 04-render-adaptation
+ provides: 04-RENDER-INTERFACE-AUDIT.md 的勇士移动失配登记(#04-01-M-01..05)与「未能从阅读确定」中的移动钩子候选
+provides:
+ - 勇士移动子系统只读接口探索账本(8 段骨架:背景 / 方法 / 数据端现行接口 / 渲染端现状 / 逐成员对账 / 缺失接口候选 / 未能从阅读确定 / 处置)
+ - 逐成员三态对账 12 条(#04-02-R-01..12),每条同行含渲染端与数据端两侧 file:line 锚点
+ - 缺失接口候选 2 条(#04-02-G-01..02,移动语义钩子族 / HeroKeyMover 的 oneStep 与控制器队列)+ 固定结论行
+ - 数据端现行接口面 + 渲染端现状面成员级清单(含 barrel 实际导出核对、@ts-expect-error 全量清单)
+affects: [REND-01, REND-02, phase-04 remaining plans, 勇士移动适配 04-03+]
+
+actuals:
+ tokens: 7400
+ tasks: 4
+ commits: 3
+plan_head_before: 35a5973cbeada7e464d56f17340d4c7bbf43d1bb
+
+tech-stack:
+ added: []
+ patterns:
+ - 只读接口探索:逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失),每条两侧 file:line,静态不可定论者进「未能从阅读确定」
+ - 缺失接口候选独立成节、只记录不设计(D-13);节末结论行 N 等于候选条数
+
+key-files:
+ created:
+ - .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+ modified: []
+
+key-decisions:
+ - "带 `// @ts-expect-error 需要重构` 的 import(HeroMover/IMoveController、HeroAnimateDirection、IHeroMoveController(Hooks))一律归「渲染端需改」,不归「数据端缺失」。"
+ - "勇士移动语义钩子族(onMoveHero / onJumpHero / onTurnHero / onStartMove / onEndMove)与 HeroKeyMover 依赖的 oneStep / 控制器 queue 登记为 2 条缺失接口候选;只记录、不设计、不修复,未经用户裁决不视为最终缺失清单(D-13)。"
+ - "HeroRendering 渲染状态(IHeroRendering / 贴图别名 / alpha 钩子,D-18)与 legacy core.*(D-12)为显式排除项:不产生对账行或候选,仅在「处置」与范围排除说明中登记;勇士移动契约与移动钩子仍在范围内照常登记。"
+ - "REND-01/REND-02 保持 Pending:本 run 只交付只读探索账本,未实施适配、未实施双布局。"
+ - "被查渲染端两包相对记录在「## 方法」的扫描前基线零变化(porcelain 条目逐条一致 + 基线摘要 sha256 未变),用户既有 client-base/src/load/** 改动未被回滚/暂存/提交;交付文档全文 CRLF。"
+
+patterns-established:
+ - "勇士移动接口差异钉到成员级:三态对账 + 双侧 file:line,作为后续适配计划的输入"
+ - "排除边界的机器可判定化:对账行 / 缺失候选 / 未确定节不得出现 D-18 禁用词"
+
+requirements-completed: []
+
+coverage:
+ - id: D1
+ description: "只读接口探索账本 04-HERO-MOVER-INTERFACE.md:8 段骨架、12 条三态对账(两侧 file:line)、2 条缺失接口候选、7 条未确定项、处置(零生产代码改动)"
+ requirement: REND-01
+ verification:
+ - kind: other
+ ref: "node gates: 8 headings / recRows 12 each two-sided / gapCandidates 2 with 渲染端需求+数据端现状+待确认 + conclusion N=2 / undetermined non-empty / CRLF 207 lines / baseline sha256 unchanged / plan scope 04-01,04-02"
+ status: pass
+ human_judgment: true
+ rationale: "对账定性(是否漏报/错报、缺失候选是否成立、排除边界是否得当)须由用户审阅裁决,非机器门禁可证"
+
+duration: 23min
+completed: 2026-09-18
+status: complete
+---
+
+# Phase 4 Plan 02: 勇士移动子系统只读接口探索 Summary
+
+**勇士移动子系统的只读接口探索账本:12 条逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失,每条两侧 `file:line`)+ 2 条缺失接口候选(移动语义钩子族、oneStep/控制器队列)+ 7 条未确定项,零生产代码改动、全文 CRLF**
+
+## Performance
+
+- **Duration:** 23 min
+- **Started:** 2026-09-18T12:24:00+08:00(approx)
+- **Completed:** 2026-09-18T12:47:00+08:00(approx)
+- **Tasks:** 4(Task 0 汇报关卡已由用户「可以执行」放行;Task 1–3 执行并各自提交)
+- **Files modified:** 1(新建探索文档;零生产代码改动)
+
+## Accomplishments
+
+- 新建 `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`,含 8 个二级标题(背景 / 方法 / 数据端现行接口 / 渲染端现状 / 逐成员对账(匹配 / 渲染端需改 / 数据端缺失) / 缺失接口候选 / 未能从阅读确定(未猜测) / 处置),全文 CRLF。
+- `## 方法` 记录**扫描前基线**(逐行原样抄录 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 的两条用户既有改动:`client-base/src/load/loader.ts`、`types.ts`)与 `基线摘要 sha256=749738446936…`;只读门禁断言相对基线零变化。
+- **逐成员三态对账 12 条**(`#04-02-R-01..12`):钩子族、`HeroMover`/`IMoveController` import 源与类型名、`HeroAnimateDirection`、`IHeroMoveController`、`hero.x/y`、`hero.direction`、`hero.addHook`、跟随者增删钩子、`HeroKeyMover` 单步启动期望、`controller.queue`、`controller.onEnd/stop/stepEnd`、`heroMap` 键类型;其中 2 条判为**匹配**(`hero.x/y`、`hero.addHook`),其余判为**渲染端需改**,每条同行含两侧 `file:line`。
+- **缺失接口候选 2 条**(`#04-02-G-01` 移动语义钩子族、`#04-02-G-02` oneStep 与控制器队列),每条含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状引用最接近的现行成员 `file:line`;节末固定结论行 N=2;只记录、不设计、不修复(D-13)。
+- 数据端现行接口面与渲染端现状面成员级落位(数据端 26 个 `data-*` 锚点、渲染端 50 个 `client-*` 锚点),含各包 `index.ts` barrel 实际导出核对与 `// @ts-expect-error 需要重构` 全量清单。
+- 门禁全绿:8 标题 / recRows 12 / gapCandidates 2(结论 N=2)/ undetermined 7 条 / CRLF 207 行 / 基线条目与摘要未变 / 计划文件仅 04-01、04-02。
+
+## Task Commits
+
+Each task was committed atomically:
+
+1. **Task 1(tracer): 骨架 + R-01 端到端切片** - `5f27436` (docs)
+2. **Task 2: 数据端现行接口 + 渲染端现状两侧清单** - `77e2ad5` (docs)
+3. **Task 3: 逐成员三态对账 + 缺失候选 + 未确定/处置定稿 + 门禁** - `d4fe24c` (docs)
+
+**Plan metadata:** pending final metadata commit (SUMMARY / STATE / ROADMAP)
+
+## Files Created/Modified
+
+- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` — 勇士移动子系统只读接口探索账本(本 run 唯一新增文件)
+
+## Decisions Made
+
+- 既有重构标记的归类:带 `// @ts-expect-error 需要重构` 的 import 属渲染端需局部重构,归「渲染端需改」,不归「数据端缺失」。
+- 缺失候选只登记不设计:移动语义钩子族与 `oneStep`/控制器 `queue` 各一条候选,数据端现状引用最接近成员,待用户裁决(D-13)。
+- 显式排除:`HeroRendering` 渲染状态(D-18)与 legacy `core.*`(D-12)不产生对账行/候选;勇士移动契约与移动钩子仍在范围内。
+- 需求状态:REND-01/REND-02 保持 Pending —— 本 run 只交付探索账本,未实施适配/双布局。
+
+## Deviations from Plan
+
+None - plan executed exactly as written.
+
+> 说明:计划 Task 1/2 提及按 D-09 委派只读子代理做重型整文件读取;本执行环境不提供子代理派发能力,执行者改为**直接只读**(Read / Grep / 目标切片)完成同等工作,未修改任何生产文件。覆盖面与门禁不变,属执行手段的等价替换,非计划偏离。
+
+## Issues Encountered
+
+- 无。数据端 `HeroAnimateDirection` / `IHeroMoveController` / `IHeroMoveControllerHooks` 经跨包检索确认无定义(对账定性为「渲染端需改」的依据);基线两条用户改动在执行期间内容未变,只读门禁通过。
+
+## User Setup Required
+
+None - no external service configuration required.
+
+## Next Phase Readiness
+
+- 探索账本已就绪,供用户裁决 2 条缺失接口候选并据此规划**勇士移动适配(04-03+)**。
+- **待用户裁决**:`#04-02-G-01`(移动语义钩子由数据端补齐 vs 渲染端改接数据对象钩子)、`#04-02-G-02`(`oneStep`/`queue` 由数据端补齐 vs 渲染端改用 `step`+`push`)。
+- **待用户补齐**:REND-01、REND-02 的验收边界(沿 04-01 的 specless fallback flagged assumption)。
+- **排除项提醒**:`HeroRendering` 渲染状态(D-18)待用户先改数据端后再处理;legacy `core.*`(D-12)属第五阶段。
+
+---
+*Phase: 04-render-adaptation*
+*Completed: 2026-09-18*
+
+## Self-Check: PASSED
+
+- FOUND: `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`
+- FOUND: `.planning/phases/04-render-adaptation/04-02-SUMMARY.md`
+- FOUND commit: `5f27436` (Task 1)
+- FOUND commit: `77e2ad5` (Task 2)
+- FOUND commit: `d4fe24c` (Task 3)
From 90d14ef1c18fcefe7f19fc6f227da1ae9087202f Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 13:01:33 +0800
Subject: [PATCH 024/173] docs(04-02): record execution state
---
.planning/state.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.planning/state.json b/.planning/state.json
index 8592719..28e2b0f 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -44,5 +44,5 @@
"label": "Advance to the next step",
"reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-18T04:24:14.460Z"
+ "updated_at": "2026-09-18T04:49:55.928Z"
}
From d7f2e277d97d495296ea66e70d4496c7c179bfa0 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 14:14:12 +0800
Subject: [PATCH 025/173] docs(04-03): plan hero body render adaptation
---
.planning/ROADMAP.md | 6 +-
.../phases/04-render-adaptation/04-03-PLAN.md | 423 ++++++++++++++++++
2 files changed, 428 insertions(+), 1 deletion(-)
create mode 100644 .planning/phases/04-render-adaptation/04-03-PLAN.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 848d965..213fe24 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 2/2 plans executed(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已规划 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18;后续适配实施与双布局待探索结果与缺失候选裁决后另行规划)
+**Plans**: 3 plans(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已完成;第三步·勇士本体适配(渲染端被动重接)已规划,待执行 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18/D-19/D-20/D-21/D-22;其余渲染适配与移动端/桌面端双布局待后续增量另行规划)
Plans:
**Wave 1**
@@ -192,6 +192,10 @@ Plans:
- [x] 04-02-PLAN.md — 勇士移动接口探索(只读,D-17):产出 `04-HERO-MOVER-INTERFACE.md`(数据端现行接口 / 渲染端现状 / 逐成员三态对账 / 缺失接口候选 / 未确定 / 处置;排除 `HeroRendering`(D-18)与 `core.*`(D-12);不改任何代码,适配(04-03+)未规划)
+**Wave 3** *(blocked on Wave 2 completion)*
+
+- [ ] 04-03-PLAN.md — 勇士本体适配(渲染端被动重接):`render/map/extension/{hero,types,manager}.ts` 绑定 `IHeroLocation` / `IHeroMover`,改接现有钩子 `IHeroLocationHooks` / `IObjectMoverHooks`(D-21),按 D-20 裁剪 `IMapHeroRenderer` 的外部驱动成员;排除贴图/不透明度(D-18)、跟随者(D-22)、移动控制(D-19)、`core.*`(D-12)与裸函数(D-11),不新增数据端接口
+
**UI hint**: yes
### Phase 5: Legacy 移植
diff --git a/.planning/phases/04-render-adaptation/04-03-PLAN.md b/.planning/phases/04-render-adaptation/04-03-PLAN.md
new file mode 100644
index 0000000..d001bb8
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-03-PLAN.md
@@ -0,0 +1,423 @@
+---
+phase: 04-render-adaptation
+plan: 3
+type: execute
+wave: 3
+depends_on:
+ - 04-01
+ - 04-02
+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:
+ - REND-01
+ - REND-02
+estimate:
+ tokens: 75000
+ raw_tokens: 75000
+ tasks: 4
+ confidence: low
+must_haves:
+ truths:
+ - "本计划的**第一个 block 点**是 Task 0 的 `blocking-human` 汇报关卡:先向用户汇报本计划要解决的问题、解决方案(含逐钩子映射表与拟新增/变更的成员命名清单)、范围边界与禁令,然后必须**停下等待**用户明确回复「可以执行」四个字;收到该回复前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
+ - "渲染端勇士本体改为**绑定勇士位置对象**:`MapHeroRenderer` 的勇士引用类型为 `IHeroLocation`(`packages-user/data-base/src/hero/types.ts:303`),`MapExtensionManager.heroMap` / `addHero` / `removeHero` 的键与参数类型同为 `IHeroLocation`;坐标经 `IObjectMovable.x` / `.y`(`data-common/src/common/mover.ts:48-51`),朝向经 `IHeroLocation.getCurrentFaceDirection()`(`data-base/src/hero/location.ts:65`)与 `IHeroMover.faceDirection` / `.moveDirection`(`mover.ts:243,245`),动画播放方向经 `IHeroMover.currAnimDir` 与 `ObjectAnimDirection`(`mover.ts:40,247`)"
+ - "三个在范围文件内不再出现 `IHeroMoveController`、`IHeroMoveControllerHooks`、`HeroAnimateDirection`,也不再出现旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`(静态门禁机器判定,见 Task 3)"
+ - "钩子注册改为订阅**数据端现有钩子**(D-21):`hero.addHook(...)` 注册 `IHeroLocationHooks` 的订阅(`data-base/src/hero/types.ts:277`),`hero.mover.addHook(...)` 注册 `IObjectMoverHooks` 的订阅(`data-common/src/common/mover.ts:166`);注册对象实现的是**现有钩子成员名**(`onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onSetFaceDir` / `onSetMoveDir`),**不新增任何数据端公共接口**(D-21),也不修改任何数据端文件"
+ - "勇士渲染为**完全被动渲染**(D-20):`IMapHeroRenderer` 契约不再暴露允许外部驱动渲染状态的成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`);渲染状态只由数据端现有钩子推进,渲染端自身仍可修改它自己的内容(不与 D-18 / D-22 冲突)"
+ - "**D-18 / D-22 成员逐字保留**:`setImage` / `setAlpha` / `setFollowerAlpha` 与钩子方法中的 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,跟随者族的 `addFollower` / `removeFollower` / `removeAllFollowers` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` 均不被删除、不被改写语义(贴图 / 不透明度按 D-18 待用户先改数据端;跟随者按 D-22 暂缓)"
+ - "**范围外零改动**:`packages-user/client-modules/src/action/move.ts` 与全部移动控制 API 未改动(D-19);无 `core.*` / legacy 适配(D-12);无 `getHeroStatusOn` 类裸函数处理(D-11);无数据端文件改动(`data-common` / `data-base` / `data-state` / `data-system` / `packages` / `packages-user/client-base` 均不在 `files_modified`)"
+ - "**验证可运行且不臆造通过**:改动前先记录 `pnpm exec vue-tsc --noEmit` 的 `client-modules` 基线错误集(规划时为 exactly 2 条、与勇士移动无关);改动后该错误集必须与基线**完全一致**(不多不少),且三个在范围文件命中 0 条类型错误;`pnpm exec eslint` 对三个文件 0 错误;静态改名门禁、D-18/D-22 保留门禁、并发改动基线门禁与 CRLF 门禁全部转绿。渲染运行时无测试设施、且 `MapHeroExtension.addHero` 当前**未被接线**(`client.ts:148-152` 已注释),故本计划的实质验证为「编译 + 契约 + 静态」三类机器门禁,另附人工 UAT 代码复核;**不得**以任何无法运行的门禁冒充通过"
+ - "用户并发的未提交改动(`packages-user/client-base/src/load/{loader,types}.ts`、`packages-user/data-base/src/load/{loader,types}.ts`、`packages-user/data-state/src/{core,types}.ts`、`packages/loader/src/{progress,types}.ts`、`.planning/phases/04-render-adaptation/04-CONTEXT.md`)在 Task 1 起始记录基线(porcelain 条目 + 各文件 sha256),Task 3 复验逐条与内容未变;**不回滚 / 不暂存 / 不提交 / 不修改**它们"
+ - "REND-01 / REND-02 在本 run 后仍保持 Pending:本计划只完成「勇士本体适配(渲染端被动重接)」一个子切片,未实施其余渲染适配与移动端/桌面端双布局(见 assumptions 的两条 FLAGGED ASSUMPTION)"
+ artifacts:
+ - path: packages-user/client-modules/src/render/map/extension/hero.ts
+ provides: "勇士渲染拓展以 `IHeroLocation` 为绑定对象;经 `hero.addHook` / `hero.mover.addHook` 订阅数据端现有钩子;旧控制器类型与旧钩子方法名清除;动画方向改用 `ObjectAnimDirection` + `mover.currAnimDir`"
+ - path: packages-user/client-modules/src/render/map/extension/types.ts
+ provides: "`IMapExtensionManager` 的勇士键/参数类型改为 `IHeroLocation`;`IMapHeroRenderer` 按 D-20 去掉外部驱动成员,保留 D-18 / D-22 成员"
+ - path: packages-user/client-modules/src/render/map/extension/manager.ts
+ provides: "`heroMap` / `addHero` / `removeHero` 以 `IHeroLocation` 为键与参数,且不再带 `@ts-expect-error` 重构标记"
+ key_links:
+ - "`manager.ts` 的 `addHero(state: IHeroLocation, layer)` ↔ `new MapHeroRenderer(renderer, layer, state)` ↔ `hero.ts` 构造器 `readonly hero: IHeroLocation`(`data-base/src/hero/types.ts:303`)"
+ - "`hero.addHook(new MapHeroHook(this))` ↔ `IHeroLocation extends IHookable`(`data-base/src/hero/types.ts:303-307`);`IHeroLocationHooks.onSetPos(x, y)`(`:283`)由 `HeroLocation.setPos` 触发(`data-base/src/hero/location.ts:59-63`)"
+ - "`hero.mover.addHook(new MapHeroHook(this))` ↔ `IHeroMover extends IObjectMover extends IHookable>`(`data-base/src/hero/types.ts:399-400`、`data-common/src/common/mover.ts:166,235`);钩子在 `ObjectMover.moveProgress` 的固定时序被调用(`mover.ts:651-686`):`onMoveStart` → 每步 `onStepStart`(坐标未变)→ `tile.setPos` → `onStepEnd`(坐标已更新)→ `onMoveEnd`"
+ - "`hero.mover` 的 `currAnimDir`(`data-common/src/common/mover.ts:247`)↔ 渲染端 `HeroRenderEntity.animateDirection` 的 `ObjectAnimDirection`(`mover.ts:40`)"
+ - "`IHeroState.location: IHeroLocation`(`data-base/src/hero/types.ts:889,894`)↔ 渲染端接线点(`client.ts:148-152` 已注释的 `addHero(...)`):本计划不取消注释、不改 `client.ts`,但把 `addHero` 的入参类型固定为 `IHeroLocation`(注释中的 `this.hero.mover` 形参落差登记为后续接线增量事项)"
+ - "`pnpm exec vue-tsc --noEmit` 的 `client-modules` 错误集基线(规划时 2 条:`client.ts(66,9): error TS2554`、`render/ui/load.tsx(77,31): error TS2504`)↔ Task 1 起始复核与 Task 3 终验的一致断言"
+ assumptions:
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付勇士本体(渲染端被动重接)这一个子切片,验收判据取自 04-02 对账账本 `#04-02-R-01..12` 的「渲染端需改」项与用户裁定 D-19..D-22。用户须在推进其余渲染适配前补齐 REND-01 的边界判据。"
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局实现,也不发明其验收阈值。"
+ - "**执行前必须复核(未确定项,不猜):** 04-02 账本「未能从阅读确定(未猜测)」第 2 条(`state.hero.location` 可达性)与第 7 条(`stepEnd` 桥接)在本计划被消费。Task 1 步骤 (0) 必须在动手前打开当日 HEAD 的数据端文件逐条核对:`IHeroState.location` 存在且类型为 `IHeroLocation`;`IHeroLocation.mover` 为 `IHeroMover`;`IHeroLocationHooks.onSetPos` / `IObjectMoverHooks` 七个成员名与参数形态未变;`ObjectAnimDirection` / `ObjectMoveType` / `ObjectSpecialStep` 仍由 `@user/data-common` 导出;`HeroLocation.setPos` 触发 `onSetPos`、`ObjectMover.moveProgress` 的 `onStepEnd` 在 `tile.setPos` 之后。任一不同即**停下汇报并修订本计划**,不得按记忆改代码。"
+ - "**D-18 编译耦合的显式处理:** 把 `hero` 由旧控制器类型改为 `IHeroLocation` 后,`addHeroMoving` 中读取贴图别名(`hero.image`)的表达式不再有数据端对位。本计划**只做最小编译桥接**(把该读取收敛为一处并用项目既有 `// @ts-expect-error 需要重构` 惯用法标注,语义与分支保持不变),**不重新设计贴图来源**——贴图子系统属 D-18,由用户先改数据端后再处理。若用户对该桥接有其它裁决,在 Task 0 汇报时给出,AI 不自行改道。"
+ - "**运行时 UAT 不可得(诚实降级):** `MapExtensionManager.addHero` 目前无任何调用点(`client.ts:148-152` 被注释),故勇士渲染拓展在运行时并未启用;本计划不接线 `client.ts`。因此本计划的运行结果无法在渲染环境观察,验收依赖「scoped 类型门禁 + 契约/静态门禁 + 人工代码复核」,运行时渲染 UAT 顺延到启用接线的增量。"
+ - "**数据端实现被用户并发修改(D-05):** 接口签名视为稳定,实现可变;本计划只读引用数据端接口,不修改任何数据端文件。若执行当日数据端接口签名与规划基线不一致,按 Task 1 步骤 (0) 停下汇报,不按旧形状适配。"
+ - "**成员命名需用户同意:** 本计划引入的渲染端新成员名(`locationController`、`moverController`)与将被删除/改写的既有成员名,须在 Task 0 汇报中获得用户同意后才可落地(AGENTS.md「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」);用户未同意即改名为用户指定的写法,或停下修订计划。"
+ prohibitions:
+ - "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理(AGENTS.md 的第一个 block 点约束;不因触发方式或 `yolo` / `auto_advance` 模式而放宽)。"
+ - "不得修改任何数据端文件(`packages-user/data-common` / `data-base` / `data-system` / `data-state`),也不得新增数据端公共接口(D-21);本计划只从数据端 import 现有类型与钩子。"
+ - "不得改动 `packages-user/client-modules/src/action/move.ts` 及任何移动控制 API(D-19:`HeroKeyMover` / `IMoveController` 队列 / `oneStep` 由用户手动处理;`#04-02-G-02` 不在本计划)。"
+ - "不得改写贴图 / 不透明度相关代码的语义:`setImage` / `setAlpha` / `hero.image`(贴图别名读取)/ `IHeroRendering` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 一律保留(D-18);本计划对该处的唯一允许动作是任务规定的「读取收敛 + 既有 `// @ts-expect-error 需要重构` 惯用法标注」编译桥接。"
+ - "不得适配跟随者:`IHeroFollower` / `IHeroFollowersController` / 跟随者钩子与渲染方法(`addFollower` / `removeFollower` / `removeAllFollowers` / `setFollowerAlpha` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`)保持原样(D-22 暂缓,本次不纳入、不单列计划)。"
+ - "不得引入 `core.*` / `client.*` / legacy 全局适配(D-12,属第五阶段);不得处理 `getHeroStatusOn` 类裸函数或其它 `// @ts-expect-error 需要重构` 的 UI / 属性项(D-11)。"
+ - "不得新增任何渲染端公共接口成员(除 D-20 规定的删除外,`IMapHeroRenderer` / `IMapExtensionManager` 的成员集合只减不增);不得让渲染端出现允许外部驱动渲染状态的新方法。"
+ - "不得修改任何既有 jsDoc 注释的内容;被 D-20 删除的接口成员连同其配套 jsDoc 一并删除属「删除代码」而非「改写注释」,须在 Task 0 汇报中经用户确认。"
+ - "不得改动 `files_modified` 之外的源码;若编译链强制要求触碰第四个文件,必须停下汇报并修订本计划(不得自行扩面)。"
+ - "不得回滚 / 暂存 / 提交用户并发的未提交改动;不得把用户既有改动纳入本计划的提交。"
+ - "不得新增依赖、不得修改 `package.json` / `pnpm-lock.yaml`;出现安装需求即暂停并要求用户确认包合法性。"
+ - "不得以「无测试设施」为由弱化验证:每条 `` 必须有 ``;确实无法运行的门禁必须显式声明为不可得并降级为静态 + 人工,不得伪造绿。"
+---
+
+
+Phase 4 第三个增量(D-17 的适配半程)——**勇士本体适配:渲染端被动重接**。按 04-02 的逐成员对账(`04-HERO-MOVER-INTERFACE.md` 的 `#04-02-R-01..12`)与用户裁定 D-19..D-22,把渲染端勇士本体从已被删除的 `IHeroMoveController` 模型换成**绑定勇士位置对象** `IHeroLocation` / `IHeroMover`,并改为**订阅数据端现有钩子**(`IHeroLocationHooks` / `IObjectMoverHooks`),不新增数据端接口、不做主动驱动。
+
+**本计划是代码改动计划**(不同于 04-01 / 04-02 的只读清点):改动面严格限定为三个文件 —— `render/map/extension/hero.ts`(勇士渲染拓展本体)、`.../extension/types.ts`(拓展契约)、`.../extension/manager.ts`(拓展管理器)。三者构成 `IMapExtensionManager.addHero → MapHeroRenderer(hero: IHeroLocation) → hero.addHook / hero.mover.addHook → 钩子回调驱动渲染` 这一条最小闭环。
+
+**要解决的问题(三条,全部来自 04-02 对账的「渲染端需改」):**
+1. **类型名整体失配**(`#04-02-R-03/R-04/R-12`):渲染端 import 的 `HeroAnimateDirection`、`IHeroMoveController`、`IHeroMoveControllerHooks` 在数据端**不存在**(当前靠 `// @ts-expect-error 需要重构` 压着);`heroMap` / `addHero` / `removeHero` 的键类型是已消失的控制器。
+2. **成员形状失配**(`#04-02-R-05/R-06`):`hero.direction` 在现行接口不存在(朝向为 `faceDirection` / `moveDirection` / `getCurrentFaceDirection()`);`hero.image` 无数据端对位(D-18 待用户处理)。
+3. **钩子族整体断链**(`#04-02-R-01` 与缺失候选 `#04-02-G-01`):渲染端 `MapHeroHook` 实现的 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero` 在数据端没有同名对位,须改接现有钩子(D-21 裁决:不新增数据端接口)。
+
+**大致解决方案:**
+- **绑定对象换成位置对象**:`MapHeroRenderer` 持有 `IHeroLocation`;坐标读 `IObjectMovable.x/.y`;朝向读 `getCurrentFaceDirection()`(构造期初始化)与 `mover.faceDirection`;移动方向读 `mover.moveDirection`;动画播放方向读 `mover.currAnimDir` 并改用 `ObjectAnimDirection`(R-03/R-05/R-06)。
+- **钩子改为订阅现有钩子**(D-21):在 `hero`(`IHeroLocationHooks`)与 `hero.mover`(`IObjectMoverHooks`)上各注册一次 `MapHeroHook`,用 `load()` / `unload()` 管理(原 `controller` 字段拆为两个控制器字段)。渲染端实现现有钩子成员名,旧名全部删除;`onStepEnd` 成为逐格渲染的驱动点(此时 `tile` 坐标已更新),按 `ObjectMoveType` 分派到既有渲染内部方法。
+- **被动化**(D-20):`IMapHeroRenderer` 去掉「外部驱动渲染状态」的成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`);这些方法退为渲染端内部实现,仅由钩子回调调用。
+- **范围硬边界**:贴图 / 不透明度(D-18)、跟随者(D-22)、`action/move.ts` 与移动控制(D-19)、`core.*`(D-12)、裸函数(D-11)一律不动。
+
+**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「要解决的问题 / 大致解决方案(含逐钩子映射表)/ 拟新增或改写的成员命名清单 / 范围边界与禁令 / D-18 编译桥接的取舍」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式或配置模式如何均不得自动放行。故本计划 `autonomous: false`。
+
+**后续增量(显式延后,本计划不涉及、不实施):** 04-02 的两条缺失候选(`#04-02-G-01` 移动语义钩子族、`#04-02-G-02` `oneStep` / 控制器队列)——G-01 已由 D-21 裁决为「改接现有钩子」,本计划即其落地;G-02 由 D-19 交由用户手动处理,**不在本计划**。`client.ts` 的 `addHero` 接线启用、贴图与不透明度来源(D-18)、跟随者位置/动画归属(D-22)、其余 ① 错配(`#04-01-M-06..09`)与 42 条 ③ 多余旧路径、移动端/桌面端双布局(REND-02)均不在本 run。
+
+Purpose: 勇士移动是 04-01 / 04-02 锁定的主要失配点(D-17)。这一子切片把渲染端勇士本体从「按旧形状读已删除的控制器」改为「绑定位置对象 + 订阅现有钩子」,是后续渲染适配与双布局能建立在编译通过、契约一致的地基上的前提。
+
+Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复跑的 scoped 类型门禁、静态改名门禁、D-18/D-22 保留门禁、并发改动基线门禁与 CRLF 门禁结果 + `.planning/phases/04-render-adaptation/04-03-SUMMARY.md`。Task 0 为只读汇报关卡,不写任何文件。
+
+## Source Audit(多源覆盖审计)
+
+| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
+|--------|----|---------------------|------|--------|-------|
+| GOAL | — | Phase 4:「渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局」 | 04-03 | COVERED(增量·勇士本体子切片) | D-17 的适配半程:本 run 只做勇士本体的被动重接;其余渲染适配与双布局属后续增量(REND-02 见 assumptions) |
+| REQ | REND-01 | 渲染端适配新数据层接口 | 04-03 | COVERED(子切片;需求保持 Pending) | 落定 `#04-02-R-01..R-08`、`#04-02-R-12` 的「渲染端需改」项;验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
+| REQ | REND-02 | 渲染端同时支持移动端与桌面端布局 | 04-03 | COVERED(不涉及) | 本 run 范围不含双布局;REND-02 验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
+| CONTEXT | D-05 | 以接口为准,不受数据端实现变动影响 | 04-03 | COVERED | 只读引用数据端现行接口;签名若变则 Task 1 步骤 (0) 停下汇报 |
+| CONTEXT | D-11 | `getHeroStatusOn` 类裸函数多为待删除 | 04-03 | COVERED(边界) | 属 UI / 属性读取,非勇士本体;prohibitions 明列不处理 |
+| CONTEXT | D-12 | `core.*` 属旧引擎适配,归第五阶段 | 04-03 | COVERED(排除) | 三个在范围文件本就无 `core.*`;prohibitions 明列不得引入 |
+| CONTEXT | D-13 | 缺失接口待改时逐步确认 | 04-03 | COVERED(边界) | 04-02 的候选裁决已由 D-19 / D-21 给出,本计划不做新的缺失接口裁定 |
+| CONTEXT | D-17 | 下一步=勇士移动适配(先探索再适配) | 04-03 | COVERED | 04-02 完成探索;本计划即其适配实施(勇士本体部分) |
+| CONTEXT | D-18 | `HeroRendering` 暂不处理,用户先改数据端 | 04-03 | COVERED(排除 + 编译桥接) | 贴图 / 不透明度成员逐字保留;`hero.image` 只做最小编译桥接,不重设来源;Task 0 汇报该取舍 |
+| CONTEXT | D-19 | 移动控制由用户手动修改 | 04-03 | COVERED(排除) | `action/move.ts` 与移动控制 API 不在 `files_modified`;prohibitions 明列 |
+| CONTEXT | D-20 | 勇士渲染完全被动、旧驱动接口删除 | 04-03 | COVERED | Task 2 从 `IMapHeroRenderer` 删除 8 个外部驱动成员,改为钩子驱动 |
+| CONTEXT | D-21 | 走现有 `IObjectMoverHooks` / `IHeroLocationHooks`,不新增数据端接口 | 04-03 | COVERED | Task 1/2 的两次 `addHook` 注册与逐钩子映射即本条的落地 |
+| CONTEXT | D-22 | 跟随者暂缓 | 04-03 | COVERED(排除) | 跟随者成员与钩子方法逐字保留、不删除不改写 |
+| CONTEXT | D-10 | `HeroRendering` 删除、渲染状态存储放渲染端 | 04-03 | COVERED(一致性依据) | D-20 的被动化与 D-18 的保留范围保持一致;贴图来源的最终归属留给 D-18 增量 |
+| RESEARCH | — | (本阶段无 RESEARCH.md) | — | N/A | Phase 4 未产出 RESEARCH.md;本阶段的等价输入为 04-01 对账账本与 04-02 探索账本 |
+
+## Estimate Basis(估算依据)
+
+`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。按「执行者实际触及量」估算(`estimate.tasks: 4` = Task 0 汇报关卡 + 三个工作任务):Task 0 只读汇报并等待用户确认(约 3k,不写文件);Task 1(tracer)起始复核数据端现行接口 + 三文件类型 / import / 绑定 / 一次 `addHook` × 2 + `onSetPos` 一条通路 + scoped 类型门禁(约 30k,含 `hero.ts` 517 行与数据端基准的定向读取);Task 2 逐钩子映射实现 + D-20 契约裁剪 + manager / types 收口(约 27k);Task 3 五类门禁 + 人工 UAT 复核 + SUMMARY(约 11k);固定开销约 4k;合计约 75k。
+
+
+
+
+@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
+@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/REQUIREMENTS.md
+@.planning/phases/04-render-adaptation/04-CONTEXT.md
+@.planning/phases/04-render-adaptation/04-01-SUMMARY.md
+@.planning/phases/04-render-adaptation/04-02-SUMMARY.md
+@.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+@.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+@.planning/phases/04-render-adaptation/04-PATTERNS.md
+@AGENTS.md
+@dev.md
+
+
+
+
+
+ Task 0: 预执行汇报关卡 —— 汇报本计划的解决方案、逐钩子映射表与成员命名清单,等待用户回复「可以执行」
+ (只读汇报,不修改任何文件)
+
+ - AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行;「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈,我同意后才可以使用」)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-11 / D-12 / D-17 / D-18 / D-19 / D-20 / D-21 / D-22)
+ - .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md:152-207(本计划的输入:逐成员三态对账 `#04-02-R-01..12`、缺失候选 `#04-02-G-01/02`、7 条未确定)
+ - .planning/phases/04-render-adaptation/04-03-PLAN.md(本计划全文:objective / tasks / 映射表 / 禁令 / 门禁)
+ - packages-user/client-modules/src/render/map/extension/hero.ts:1-21,76-102,110-132,437-517(现状 import 与 `@ts-expect-error`、构造器、`addHeroMoving` 的 `hero.image`、`MapHeroHook`)
+ - packages-user/client-modules/src/render/map/extension/types.ts:13-40,63-159(待改的 `IMapExtensionManager` 与待裁剪的 `IMapHeroRenderer`)
+ - packages-user/data-common/src/common/mover.ts:166-233,235-351(`IObjectMoverHooks` / `IObjectMover`)
+ - packages-user/data-base/src/hero/types.ts:277-321,399-417,889-909(`IHeroLocationHooks` / `IHeroLocation` / `IHeroMover` / `IHeroState.location`)
+
+
+ **停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
+
+ ① **要解决的问题**:渲染端勇士本体仍按已删除的 `IHeroMoveController` 模型编写(`hero.direction` / `hero.image` / `IHeroMoveControllerHooks` 的 `onSetPosition` 等),数据端现行接口无这些成员,全靠 `// @ts-expect-error 需要重构` 压着(`#04-02-R-01/R-03/R-04/R-05/R-06/R-12`)。不改就会一直停在「编译靠压制、契约对不上」。
+
+ ② **大致解决方案(三个文件)**:`manager.ts` 与 `types.ts` 的勇士键 / 参数类型由 `IHeroMoveController` 改为 `IHeroLocation`;`hero.ts` 构造器绑定 `IHeroLocation`(坐标 `x`/`y`、朝向 `getCurrentFaceDirection()`、动画方向 `ObjectAnimDirection` + `mover.currAnimDir`),在 `hero`(`IHeroLocationHooks`)与 `hero.mover`(`IObjectMoverHooks`)上各 `addHook` 一次,`MapHeroHook` 改为实现现有钩子成员名;`IMapHeroRenderer` 按 D-20 去掉 8 个外部驱动成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`)。**逐钩子映射表须逐行汇报**:数据端钩子 → 触发点(`file:line`)→ 渲染端动作。核心几条:`IHeroLocationHooks.onSetPos(x, y)`(`data-base/src/hero/location.ts:59-63`)→ 非移动中时 `setPosition(x, y)`,移动中交给 `onStepEnd`;`IObjectMoverHooks.onMoveStart` → `startMove()`;`onMoveEnd` → `await waitMoveEnd()`;`onStepEnd(code, step, tile, mover)`(坐标已更新,`data-common/src/common/mover.ts:674-678`)→ 按 `step.type`(`ObjectMoveType`)分派:`Dir`/`DirFace` → `move(step.move, mover.currentSpeed)`(`DirFace` 另 `turn(step.face)`)、`Special` → `move(mover.moveDirection, mover.currentSpeed)`、`Face` → `turn(step.value)`、`Teleport` → `setPosition(tile.x, tile.y)`、`Jump` → `jumpTo(tile.x, tile.y, mover.currentSpeed, false)`、`AnimDir`/`Speed` → 不处理;动画方向统一在 `onStepEnd` 取 `mover.currAnimDir`;`onSetFaceDir` → `turn(dir)`;`onSetPos`(mover 级)→ `setPosition(x, y)`;`onSetMoveDir` / `onStepStart` → 不处理(并说明理由)。
+
+ ③ **成员命名清单(须用户同意,AGENTS.md 命名规则)**:**新增**渲染端字段 `locationController`(`IHookController`)与 `moverController`(`IHookController>`)——取代原单一 `controller` 字段;**不新增任何类**(沿用既有 `MapHeroHook` 类名);**删除**旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero` 与 `IMapHeroRenderer` 的 8 个驱动成员(连同其配套 jsDoc,属删除代码而非改写注释);**逐字保留** `setImage` / `setAlpha` / `setFollowerAlpha` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者族(D-18 / D-22)。备选命名(如用户不同意 `locationController` / `moverController`)请用户当场指定。
+
+ ④ **D-18 编译桥接的取舍(须用户裁定)**:`hero` 类型改为 `IHeroLocation` 后,`addHeroMoving` 内读取贴图别名的 `hero.image`(`hero.ts:115,119,121`)失去数据端对位。本计划主张**只做最小编译桥接**:把该读取收敛为一处局部变量、用项目既有 `// @ts-expect-error 需要重构` 惯用法标注,运行分支与语义保持不变(`undefined` → 既有 `logger.warn(88)` 分支),**不重新设计贴图来源**(贴图来源与 `IHeroRendering` 属 D-18,等用户先改数据端)。请用户确认该桥接,或指定其它处理方式。
+
+ ⑤ **边界与禁令**:只改三个文件(`hero.ts` / `types.ts` / `manager.ts`);不新增数据端公共接口、不改任何数据端文件(D-21);不动 `action/move.ts` 与移动控制 API(D-19);不动贴图 / 不透明度(D-18)、跟随者(D-22)、`core.*`(D-12)、`getHeroStatusOn` 类裸函数(D-11);不接线 `client.ts`(`addHero` 仍未启用);不回滚 / 不暂存 / 不提交用户并发未提交改动。
+
+ ⑥ **验证方式与诚实降级(须原样告知)**:渲染端无测试设施,且 `MapExtensionManager.addHero` 当前无调用点(`client.ts:148-152` 被注释),故运行时渲染无法观察。本计划的运行结果 = **scoped 类型门禁**(`pnpm exec vue-tsc --noEmit` 过滤到三个文件为 0 条,且 `client-modules` 错误集与记录基线完全一致)+ **静态改名门禁** + **D-18/D-22 保留门禁** + **D-20 契约门禁** + **eslint / CRLF 门禁** + **并发改动基线门禁**;另附**人工代码复核**。**不冒充任何无法运行的门禁。**
+
+ ⑦ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
+
+ ⑧ **失败语义(等同硬门禁,非自动命令)**:用户未回复「可以执行」即继续,或回复的是其它任何内容(含修订意见、沉默、`yolo` / `auto_advance` 模式下的自动推进、或子代理/执行器的自行放行),一律视为**未通过本关卡**:必须立即停止执行,不派发任何执行子代理,保持只读、不写任何文件,直到用户在对话中明确回复「可以执行」四个字后方可继续。本条失败语义只以本 `` 与 `` 的形式表达,不写成 XML 失败声明元素(人工关卡没有可运行的自动命令可与之绑定)。
+
+ ⑨ **若用户给出修订意见**(含不同意命名、不同意 D-18 桥接、或要求改映射):退出本计划、修订 `04-03-PLAN.md` 后重新汇报,不得自行改道执行。
+
+ 是否按本计划(三文件勇士本体被动重接:绑定 `IHeroLocation`、订阅现有钩子、按 D-20 裁剪 `IMapHeroRenderer`)开始执行;并确认 ③ 的成员命名清单与 ④ 的 D-18 编译桥接
+
+ AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认,且任何新增 / 改写的成员命名必须先经用户同意。本计划是 Phase 4 的首个**代码改动**增量(04-01 / 04-02 均为只读),改的是渲染端勇士契约面与钩子接线,属于「接口设计归用户」的边界;`.planning/config.json` 为 `mode: yolo`,因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`,否则计划层面不提供任何 block 点。D-18 的编译耦合若不先裁决,执行者会在「不触碰贴图」与「必须编译通过」之间自行猜测,故必须在汇报中给出取舍。
+
+
+
+
+
+
+ 回复「可以执行」四个字(如需改名或改桥接,请在回复中一并给出),或给出修订意见
+
+ 用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无自动命令,失败语义写在 action ⑦/⑧ 与 acceptance_criteria 中)
+ 用户回复的是修订意见、改名要求未获确认、或未回复即继续 —— 必须停止执行,不派发任何执行子代理
+
+
+ - 用户回复中明确包含「可以执行」四个字
+ - 汇报内容涵盖:要解决的问题(三条失配)、逐钩子映射表(数据端钩子 → 触发点 file:line → 渲染端动作)、成员命名清单(新增 `locationController` / `moverController`;删除的旧钩子方法名与 8 个 D-20 驱动成员;逐字保留的 D-18 / D-22 成员)、D-18 编译桥接取舍、范围边界与禁令、验证方式与运行时不可得的诚实降级
+ - 汇报中已原样告知关卡性质:执行者/子代理必须停下等待,不因触发方式或 yolo / auto 模式自动放行
+
+ 用户明确回复「可以执行」(如需改名或改桥接,已在回复中给出);在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
+
+
+
+ Task 1(tracer):勇士本体端到端重接最小切片 —— manager 绑定 `IHeroLocation` → 构造器注册两个现有钩子 → `onSetPos` 一条通路贯通,三文件类型 / import / 成员名改到位
+ 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, .planning/phases/04-render-adaptation/04-03-SUMMARY.md
+
+ - AGENTS.md(命名规则 / 注释规则 / 第一个 block 点)与 dev.md(双端分离;注释规范;换行 CRLF)
+ - .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md:152-197(`#04-02-R-01..12` 的对账结论与 `#04-02-G-01` 裁决背景)
+ - .planning/phases/04-render-adaptation/04-03-PLAN.md(objective 的映射表与禁令)
+ - packages-user/client-modules/src/render/map/extension/hero.ts(整文件 517 行:import :1-21、构造器 :76-102、`addHeroMoving` :110-132、`setImage` :191-196、`setHeroAnimateDirection` :437-439、`MapHeroHook` :457-517)
+ - packages-user/client-modules/src/render/map/extension/types.ts:1-40,63-159 与 manager.ts:1-44
+ - packages-user/data-common/src/common/mover.ts:40-64,142-164,166-233,235-351,522-541,646-731(`ObjectAnimDirection` / `IMoverController` / `IObjectMoverHooks` / `IObjectMover` / `setPos` / `moveProgress` 的钩子时序)
+ - packages-user/data-base/src/hero/types.ts:277-321,399-417,889-909 与 packages-user/data-base/src/hero/location.ts:53-67(`onSetPos` / `onSetFloor` 触发点与 `getCurrentFaceDirection`)
+ - packages-user/data-common/src/index.ts、packages-user/data-common/src/common/index.ts、packages-user/data-base/src/index.ts、packages-user/data-base/src/hero/index.ts(barrel 实际导出核对)
+ - packages-user/client-modules/src/action/move.ts:1-6(**只读**:确认 D-19 范围外,不得改动,仅用于核对 `@ts-expect-error` 清单口径)
+
+
+ (0) **执行前核对与基线(只读前置,必须先做)**:
+ (0a) **数据端现行接口复核(消费 04-02 的未确定项,不猜)**:打开当日 HEAD 的数据端文件,逐条确认并在 `04-03-SUMMARY.md` 的「执行前核对」段记录结论(每条一行:结论 + `file:line`):① `IHeroState.location: IHeroLocation` 存在(`data-base/src/hero/types.ts:894`);② `IHeroLocation extends ISaveableContent, IObjectMovable, IHookable, IDataCommonExtended` 且含 `readonly mover: IHeroMover`(`:303-314`);③ `IHeroLocationHooks.onSetPos?(x, y)` 与 `onSetFloor?(map)`(`:283,289`);④ `IObjectMoverHooks` 的 `onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onSetPos` / `onSetFaceDir` / `onSetMoveDir` 七个成员名与参数形态(`data-common/src/common/mover.ts:166-233`);⑤ `IObjectMover` 的 `moving` / `faceDirection` / `moveDirection` / `currAnimDir` / `currentSpeed`(`:239-249`)与 `getCurrentFaceDirection()`(`data-common/src/common/mover.ts:63`、`data-base/src/hero/location.ts:65`);⑥ `ObjectAnimDirection`(`mover.ts:40`)、`ObjectMoveType`(`:14`)、`ObjectSpecialStep`(`:33`)仍由 `@user/data-common` 导出(`data-common/src/common/index.ts:4`);⑦ `HeroLocation.setPos` 触发 `onSetPos`(`location.ts:59-63`)、`ObjectMover.moveProgress` 的 `onStepEnd` 在 `tile.setPos` 之后(`mover.ts:669-678`)。**任一不同:立即停止,不写任何代码,向用户汇报差异并修订本计划。**
+ (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)"`,把输出**原样**写入 `04-03-SUMMARY.md` 的「执行前基线」段(porcelain 条目逐行放进围栏 `text` 块,随后一行 `基线摘要 sha256=<64hex>`)。规划时该集合为用户并发的未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`、` M packages-user/data-base/src/load/loader.ts`、` M packages-user/data-base/src/load/types.ts`、` M packages-user/data-state/src/core.ts`、` M packages-user/data-state/src/types.ts`、` M packages/loader/src/progress.ts`、` M packages/loader/src/types.ts`、` M .planning/phases/04-render-adaptation/04-CONTEXT.md`、`?? .planning/milestone.lock`。**不得回滚 / 暂存 / 提交 / 修改**这些改动。
+ (0c) **类型门禁基线**:执行 `pnpm exec vue-tsc --noEmit`(整仓会以非 0 退出属预期——规划时有 122 条诊断)并记录 `client-modules` 的命中行;规划时恰为 2 条、与勇士移动无关:`packages-user/client-modules/src/client.ts(66,9): error TS2554: Expected 1 arguments, but got 0.` 与 `packages-user/client-modules/src/render/ui/load.tsx(77,31): error TS2504: Type 'ITaskManager' must have a 'Symbol.asyncIterator]()' method that returns an async iterator.`。同时确认三个在范围文件**当前 0 条诊断**。把这两条原样抄入 `04-03-SUMMARY.md` 的「类型门禁基线」段;**若与记录不一致,停止并汇报**(说明用户并发改动改变了基线,不得静默调整门禁)。
+ (0d) 建立 `04-03-SUMMARY.md` 骨架(含上面三段 + 后续任务要填的空节),保持 CRLF。
+ (1) **`manager.ts`(绑定层)**:删除首行 `// @ts-expect-error 需要重构`,把 `import { IHeroMoveController } from '@user/data-base'` 改为 `import { IHeroLocation, IMapLayer } from '@user/data-base'`(合并第三行的 `IMapLayer` import,避免重复 import 语句;若 lint 要求分行则按 lint 结果排布);`readonly heroMap: Map`;`addHero(state: IHeroLocation, layer: IMapLayer)`;`removeHero(state: IHeroLocation)`。`addDoor` / `removeDoor` / `addText` / `removeText` / `destroy` 一字不改。
+ (2) **`types.ts`(契约层,本任务只改绑定类型,不动 D-20 裁剪)**:从 `@user/data-common` 的 import 中删除 `HeroAnimateDirection` 与 `IHeroMoveController`(连带其两行 `// @ts-expect-error 需要重构` 标记),改为从 `@user/data-base` 导入 `IHeroLocation`(与既有 `IMapLayer` 合并);`IMapExtensionManager.heroMap: Map`、`addHero(state: IHeroLocation, layer: IMapLayer)`、`removeHero(state: IHeroLocation)`;`Map<` / `addHero` / `removeHero` 的 jsDoc **不改写**(参数名与含义不变)。本任务**不动** `IMapHeroRenderer`(其 8 个驱动成员的删除在 Task 2)。
+ (3) **`hero.ts` import 与绑定(tracer 主路径)**:
+ (3a) import 改造:`@user/data-common` 侧删除 `HeroAnimateDirection` / `IHeroMoveController` / `IHeroMoveControllerHooks`(连带 3 行 `// @ts-expect-error 需要重构` 标记),新增 `ObjectAnimDirection`、`IObjectMover`、`IObjectMoverHooks`、`ObjectMoveStep`、`ObjectMoveType`、`ObjectSpecialStep`;`@user/data-base` 侧把 `import { IMapLayer }` 扩为 `import { IHeroLocation, IHeroLocationHooks, IMapLayer }`(`IHeroLocation` / `IHeroLocationHooks` / `IHeroMover` 均可经该 barrel 取得,见 `data-base/src/hero/index.ts:10` 与 `data-base/src/index.ts:3`)。
+ (3b) 实体类型:`HeroRenderEntity.animateDirection` 由 `HeroAnimateDirection` 改为 `ObjectAnimDirection`(`:53`);`new MapHeroRenderer(...)` 内的默认值 `HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:97`)。
+ (3c) 构造器:`readonly hero: IHeroMoveController` → `readonly hero: IHeroLocation`(`:79`);`direction: hero.direction` → `direction: hero.getCurrentFaceDirection()`(`:89`);钩子注册由单一 `controller` 改为**两个控制器字段**:`readonly locationController: IHookController` 与 `readonly moverController: IHookController>`(替换原 `readonly controller: IHookController` 字段,`:61`);构造体内 `const hook = new MapHeroHook(this); this.locationController = hero.addHook(hook); this.locationController.load(); this.moverController = hero.mover.addHook(hook); this.moverController.load();`(同一 hook 实例注册到两个 hookable,`Hookable.addHook` 按 hookable 实例分别存储,见 `packages/common/src/hook.ts:24-31`);`destroy()`(`:452-454`)改为 `this.locationController.unload(); this.moverController.unload();`。
+ (3d) `addHeroMoving`(`:110-132`):参数 `hero: IHeroMoveController` → `IHeroLocation`;`degradeFace(hero.direction)` → `degradeFace(hero.getCurrentFaceDirection())`(`:125`);`hero.image` 的三处读取(`:115,119,121`)按 Task 0 确认的 **D-18 最小编译桥接**处理——收敛为一处局部读取(如 `const imageAlias = hero.image;`)并在其正上方加一行**新增**注释 `// @ts-expect-error 需要重构(贴图别名来源属 D-18,待用户先改数据端后处理)`,其后三处改用 `imageAlias`;分支结构与既有 `logger.warn(88/89)` 行为保持不变;**不得**改写既有注释、不得重设贴图来源。
+ (3e) `setImage`(`:191-196`):只把内部的 `degradeFace(this.hero.direction)` 改为 `degradeFace(this.hero.getCurrentFaceDirection())`(同一朝向读取口径的编译修复),其余一字不改(D-18:贴图语义不动)。
+ (3f) `setHeroAnimateDirection(direction: HeroAnimateDirection)`(`:437-439`)→ 形参类型改 `ObjectAnimDirection`;本任务保留其在 `IMapHeroRenderer` 中的声明(Task 2 再按 D-20 删除)。
+ (3g) **`MapHeroHook` 重接(tracer 只贯通一条通路)**:类声明由 `implements Partial` 改为 `implements Partial>`(`:457`);把 `onSetPosition(x, y)`(`:469-471`)改为 `onSetPos(x: number, y: number): void`,实现「移动之外的直接定位才立刻同步、移动中的逐格定位交给 `onStepEnd`」:当 `this.hero.hero.mover.moving` 为 `true` 时直接 return,否则 `this.hero.setPosition(x, y)`;**其余旧命名方法本任务先保留为类上的多余方法(不影响实现 `Partial<...>` 的可赋值性),Task 2 再整体替换**——但不得新增任何旧命名方法。
+ (3h) 全程保持文件 CRLF;改完执行 `pnpm exec eslint --fix` 对三个文件,再 `pnpm exec eslint` 必须 0 错误。
+ (4) **提交**:本任务改动是整体适配的中间态,**不单独提交**;Task 2 完成后再一次性原子提交(见 Task 2 步骤 (5))。若执行环境强制每任务提交,则提交信息为 `refactor(04-03): rebind hero renderer to IHeroLocation`,且必须与 Task 2 的提交在本次执行内连续完成。
+
+ 本任务改变的是渲染端勇士拓展的**契约面**(`IMapExtensionManager` 的键/参数类型、`IMapHeroRenderer` 后续要删成员),一旦后续增量或用户代码按新契约接线,回退需要同时回退调用方;但改动本身局限在三个文件内、无数据端影响,故为 `costly` 而非 `one-way`。
+
+ node -e "const {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 cm=lines.filter(l=>l.includes('client-modules')).map(l=>l.replace(/^.*client-modules[\\/]src[\\/]/,''));const base=['client.ts(66,9): error TS2554','render/ui/load.tsx(77,31): error TS2504'];const missing=base.filter(b=>!cm.some(l=>l.startsWith(b)));const extra=cm.filter(l=>!base.some(b=>l.startsWith(b)));if(missing.length||extra.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 '+cm.length)"
+ non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS"(三个在范围文件仍有类型错误)/ "CLIENT-MODULES ERROR SET CHANGED"(渲染端类型错误集相对记录基线发生增减,含连带打断它处)
+ node -e "const fs=require('fs');const files=['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 src=files.map(f=>fs.readFileSync(f,'utf8')).join('\n');const banned=['IHeroMoveControllerHooks','IHeroMoveController','HeroAnimateDirection','onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const hit=banned.filter(t=>src.includes(t));if(hit.length){console.error('LEGACY SYMBOLS REMAIN '+hit.join(','));process.exit(1)}const need=['IHeroLocation','ObjectAnimDirection','getCurrentFaceDirection','mover.addHook','locationController','moverController'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('MISSING NEW BINDINGS '+miss.join(','));process.exit(1)}console.log('OK static rename gate')"
+ non-zero exit, 或 stderr 含 "LEGACY SYMBOLS REMAIN"(旧类型名 / 旧钩子方法名仍出现在三个在范围文件,含注释)/ "MISSING NEW BINDINGS"(绑定、动画方向或两个钩子控制器未落地)
+ 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
+ non-zero exit 或输出含 "error"(ESLint / Prettier 违规;CRLF 由 `endOfLine: crlf` 在 `--fix` 时统一)
+ node -e "const fs=require('fs');const files=['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'];for(const f of files){const s=fs.readFileSync(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')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」)
+
+
+ - `manager.ts` 与 `types.ts` 的 `heroMap` / `addHero` / `removeHero` 均为 `IHeroLocation`,`manager.ts` 首行的 `// @ts-expect-error 需要重构` 已删除,且不再 import `IHeroMoveController`
+ - `hero.ts` 的构造器参数为 `IHeroLocation`,构造期朝向取自 `hero.getCurrentFaceDirection()`;`HeroRenderEntity.animateDirection` 与默认值为 `ObjectAnimDirection`
+ - `hero.ts` 同时存在 `hero.addHook(` 与 `hero.mover.addHook(`,并存在 `locationController` / `moverController` 两个字段;`destroy()` 对两者都 `unload()`
+ - `MapHeroHook` 声明为 `Partial>`,且已实现 `onSetPos`(旧 `onSetPosition` 已不存在)
+ - 三个文件不再出现 `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` / `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`
+ - `hero.ts` 中 `hero.image` 的读取已收敛为一处(带新增的 `// @ts-expect-error 需要重构(…D-18…)` 标注),既有 `logger.warn(88/89/90)` 分支结构未变;`setImage` / `setAlpha` / 跟随者族成员未被删除
+ - `04-03-SUMMARY.md` 含「执行前核对」(步骤 0a 七条,每条带 `file:line`)、「执行前基线」(porcelain 条目 + `基线摘要 sha256`)与「类型门禁基线」(两条 client-modules 诊断原样)三段,全文 CRLF
+ - 四条 `` 全部转绿:scoped 类型门禁(三个文件 0 条、client-modules 错误集 == 基线)、静态改名门禁、eslint 0 错误、CRLF 门禁
+
+ 绑定层(`IHeroLocation`)、构造器两个 `addHook` 注册、`onSetPos` 一条通路与三文件的类型 / import / 成员名改造全部落地,四条机器门禁转绿,且三个在范围文件不再出现任何旧类型名与旧钩子方法名
+
+
+
+ Task 2:逐钩子映射落地 + D-20 契约裁剪 —— 旧钩子方法名整体替换为现有钩子成员名;`IMapHeroRenderer` 去掉 8 个外部驱动成员(D-18 / D-22 成员逐字保留)
+ 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/hero.ts(Task 1 产出的现状全文;重点 `MapHeroHook` 与 `IMapHeroRenderer` 的实现方法)
+ - packages-user/client-modules/src/render/map/extension/types.ts:63-159(`IMapHeroRenderer` 当前 17 个成员)
+ - packages-user/data-common/src/common/mover.ts:166-233(钩子参数形态)、:641-687(`moveProgress` 的钩子时序:`onMoveStart` → 每步 `prepareStep` → `onStepStart`(坐标未变)→ `tile.setPos` → `onStepEnd`(坐标已更新)→ `onMoveEnd`)
+ - packages-user/data-common/src/common/mover.ts:40-45,14-31,33-38(`ObjectAnimDirection` / `ObjectMoveType` / `ObjectSpecialStep` 成员名)
+ - packages-user/data-base/src/hero/mover.ts:147-280(`HeroMover` 的 `onStepStart` / `onStepEnd` 与 `HeroMoveCode`,用于确认渲染侧读到的 `code` / `step` 语义)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-18 / D-19 / D-20 / D-21 / D-22)
+
+
+ (1) **`MapHeroHook` 逐钩子映射落地(D-21:只接现有钩子,旧名全删)**。把 Task 1 保留的旧命名方法整体替换为现有钩子成员名,按下列映射实现(每条都要能对上数据端触发点):
+ - `onSetPos(x: number, y: number): void`:**移动之外的直接定位**才立刻同步——当 `this.hero.hero.mover.moving` 为 `true` 时直接 return(移动中的逐格 `tile.setPos` 会触发本条,交给 `onStepEnd` 做动画),否则 `this.hero.setPosition(x, y)`。触发点:`HeroLocation.setPos`(`data-base/src/hero/location.ts:59-63`);移动中的逐格调用见 `data-common/src/common/mover.ts:671-673`。
+ - `onMoveStart(): void` → `this.hero.startMove()`;触发点 `mover.ts:651-656`。
+ - `onMoveEnd(): Promise` → `return this.hero.waitMoveEnd()`;触发点 `mover.ts:682-686`。
+ - `onStepEnd(code: number, step: Readonly, tile: IHeroLocation, mover: IObjectMover): Promise`:**逐格渲染的唯一驱动点**(此时 `tile` 坐标已更新,见 `mover.ts:669-678`)。先 `this.hero.setHeroAnimateDirection(mover.currAnimDir)`(动画播放方向统一取自数据端 `currAnimDir`,即 R-03 的 `ObjectAnimDirection` 落地),再按 `step.type` 分派:`ObjectMoveType.Dir` → `return this.hero.move(step.move, mover.currentSpeed)`;`ObjectMoveType.DirFace` → `this.hero.turn(step.face); return this.hero.move(step.move, mover.currentSpeed)`;`ObjectMoveType.Special` → `return this.hero.move(mover.moveDirection, mover.currentSpeed)`(前进/后退的移动方向已由 `ObjectMover.prepareStep` 写入 `moveDirection`,见 `mover.ts:500-512`,无需自行取反);`ObjectMoveType.Face` → `this.hero.turn(step.value)`;`ObjectMoveType.Teleport` → `this.hero.setPosition(tile.x, tile.y)`;`ObjectMoveType.Jump` → `return this.hero.jumpTo(tile.x, tile.y, mover.currentSpeed, false)`;`ObjectMoveType.AnimDir` / `ObjectMoveType.Speed` → 不处理(动画方向已在 `currAnimDir` 中体现,速度由数据端消费)。
+ - `onSetFaceDir(dir: FaceDirection): void` → `this.hero.turn(dir)`;触发点 `mover.ts:529-534`。
+ - `onSetPos`(mover 级四参调用)与 location 级两参调用共用同一个两参方法:JS 会忽略多余实参,本方法的「移动中 return」守卫同时覆盖两条触发路径。
+ - **不实现**(在 `04-03-SUMMARY.md` 写明理由):`onStepStart`(渲染只在新坐标就绪后推进,避免重复驱动)、`onSetMoveDir`(渲染朝向只随 `onSetFaceDir` 变化;`moveDirection` 已由 `onStepEnd` 读取)、`IHeroLocationHooks.onSetFloor`(楼层切换需要重绑图层的接线路径,本增量不接线 `client.ts`;登记为后续增量事项,不在本计划实现)。
+ - **逐字保留(D-18 / D-22,不得删除、改写语义或改名)**:`onSetImage(image: ImageIds)`、`onSetAlpha(alpha: number)`、`onSetFollowerAlpha(identifier: string, alpha: number)`、`onAddFollower(follower: number, identifier: string)`、`onRemoveFollower(identifier: string, animate: boolean)`、`onRemoveAllFollowers()`——它们是本类上的多余方法(不再属于任何现有钩子接口的成员),保持原样并**不注册到任何数据端对象**;其现有 jsDoc / 注释一字不改。是否在后续增量(D-18 / D-22 解开时)迁到数据端对象上,留待用户裁定。
+ (2) **`IMapHeroRenderer` 按 D-20 裁剪(`types.ts`)**:删除以下 8 个成员声明**及其配套 jsDoc**(属删除代码,Task 0 已获用户确认):`setPosition(x, y)`(`:92`)、`startMove()`(`:97`)、`waitMoveEnd(waitFollower)`(`:103`)、`stopMove()`(`:108`)、`move(direction, time)`(`:114`)、`jumpTo(x, y, time, waitFollower)`(`:123`)、`setHeroAnimateDirection(direction)`(`:147`)、`turn(direction?)`(`:153`)。保留 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy`(D-18 / D-22 成员,逐字不动)。不得新增任何成员。
+ (3) **`MapHeroRenderer` 侧配套**:上述 8 个方法在类上**保留为内部实现**(不删除、不改名),仅由 `MapHeroHook` 的回调调用;其可见性/实现细节保持不变。`waitMoveEnd()` 的实现签名(无参)与接口原声明的参数落差(04-02「未能从阅读确定」第 6 条)随接口成员移除而消解,在 `04-03-SUMMARY.md` 记为「由 D-20 契约裁剪消解」。`setHeroAnimateDirection(direction: ObjectAnimDirection)` 的形参类型在 Task 1 已改,本任务不再动它。
+ (4) **不改的事**:不改 `MapHeroRenderer` 的动画算法(`tick` / `moveEntity` / `jumpEntity` / `generateJumpFn`)、不改 `addHeroMoving` 的贴图分支结构、不改 `setImage` / `setAlpha` / 跟随者族实现体、不接线 `client.ts`、不动 `action/move.ts`。既有注释一字不改(新增注释除外)。
+ (5) **原子提交**:`refactor(04-03): rebind hero renderer to IHeroLocation and existing hooks`(或执行环境约定的等价信息),提交内容仅含 `hero.ts` / `types.ts` / `manager.ts` 三个文件;**不得**把用户的并发未提交改动纳入提交。随后执行 `pnpm exec eslint --fix` 对三个文件,再 `pnpm exec eslint` 必须 0 错误。
+
+
+ node -e "const fs=require('fs');const t=fs.readFileSync('packages-user/client-modules/src/render/map/extension/types.ts','utf8');const i=t.indexOf('export interface IMapHeroRenderer');if(i<0){console.error('NO IMapHeroRenderer');process.exit(1)}const j=t.indexOf('export interface ',i+1);const seg=t.slice(i,j<0?t.length:j);const banned=['startMove(): void;','waitMoveEnd(','stopMove(): void;','move(direction: FaceDirection, time: number): Promise;','jumpTo(','setPosition(x: number, y: number): void;','turn(direction?: FaceDirection): void;','setHeroAnimateDirection('];const hit=banned.filter(x=>seg.includes(x));if(hit.length){console.error('D20 DRIVE MEMBERS STILL EXPOSED '+hit.join(' , '));process.exit(1)}const need=['setImage(image: ITexture): void;','addFollower(image: number, id: string): void;','removeFollower(follower: string, animate: boolean): Promise;','removeAllFollowers(): void;','setAlpha(alpha: number): void;','setFollowerAlpha(identifier: string, alpha: number): void;','destroy(): void;'];const miss=need.filter(x=>!seg.includes(x));if(miss.length){console.error('D18 D22 CONTRACT MEMBERS MISSING '+miss.join(' , '));process.exit(1)}console.log('OK D20 passive contract / D18 D22 preserved')"
+ non-zero exit, 或 stderr 含 "D20 DRIVE MEMBERS STILL EXPOSED"(`IMapHeroRenderer` 仍暴露外部驱动成员,违反 D-20)/ "D18 D22 CONTRACT MEMBERS MISSING"(贴图 / 不透明度 / 跟随者成员被误删,违反 D-18 / D-22)
+ node -e "const fs=require('fs');const h=fs.readFileSync('packages-user/client-modules/src/render/map/extension/hero.ts','utf8');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 FROM hero.ts '+miss.join(' , '));process.exit(1)}const hook=['onSetPos(','onMoveStart(','onMoveEnd(','onStepEnd(','onSetFaceDir('];const hMiss=hook.filter(x=>!h.includes(x));if(hMiss.length){console.error('EXISTING HOOK HANDLERS MISSING '+hMiss.join(' , '));process.exit(1)}const legacy=['onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const lHit=legacy.filter(x=>h.includes(x));if(lHit.length){console.error('LEGACY HOOK NAMES REMAIN '+lHit.join(','));process.exit(1)}console.log('OK mapping + D18 D22 hook members')"
+ non-zero exit, 或 stderr 含 "D18 D22 MEMBERS REMOVED FROM hero.ts"(D-18 / D-22 钩子方法被误删)/ "EXISTING HOOK HANDLERS MISSING"(现有钩子成员未落地)/ "LEGACY HOOK NAMES REMAIN"(旧钩子方法名残留)
+ node -e "const {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 cm=lines.filter(l=>l.includes('client-modules')).map(l=>l.replace(/^.*client-modules[\\/]src[\\/]/,''));const base=['client.ts(66,9): error TS2554','render/ui/load.tsx(77,31): error TS2504'];const extra=cm.filter(l=>!base.some(b=>l.startsWith(b)));const missing=base.filter(b=>!cm.some(l=>l.startsWith(b)));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 '+cm.length)"
+ non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS" / "CLIENT-MODULES ERROR SET CHANGED"(D-20 裁剪或钩子替换引入新诊断)
+ 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
+ non-zero exit 或输出含 "error"(含未使用变量 / Prettier 违规)
+
+
+ - `MapHeroHook` 实现 `onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepStart`(可不实现)/ `onStepEnd` / `onSetFaceDir` / `onSetMoveDir`(可不实现)中的映射子集,且**不含**任何旧命名方法
+ - `onStepEnd` 按 `ObjectMoveType` 分派到 `move` / `turn` / `setPosition` / `jumpTo`,并在开头以 `mover.currAnimDir` 设置动画方向;`onMoveStart` / `onMoveEnd` 分别驱动 `startMove` / `waitMoveEnd`
+ - `IMapHeroRenderer` 不再声明 `setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setHeroAnimateDirection` / `turn`,且 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy` 逐字保留
+ - `hero.ts` 仍含全部 D-18 / D-22 钩子方法与实现方法(含 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者三方法),且其注释与实现体未被改写
+ - 四条 `` 全绿;未新增任何接口成员;三个文件已一个原子提交入库,且提交未包含用户并发改动
+
+ 逐钩子映射与 D-20 契约裁剪落地:旧钩子名全清、现有钩子成员全接、`IMapHeroRenderer` 只剩被动契约、D-18 / D-22 成员逐字保留,类型 / 契约 / 保留 / lint 四条门禁转绿并原子提交
+
+
+
+ Task 3:终验门禁与人工复核 —— 并发改动基线、范围门禁、CRLF、全量门禁复跑 + `04-03-SUMMARY.md` 定稿
+ .planning/phases/04-render-adaptation/04-03-SUMMARY.md
+
+ - .planning/phases/04-render-adaptation/04-03-SUMMARY.md(Task 1/2 的「执行前核对」「执行前基线」「类型门禁基线」三段)
+ - .planning/phases/04-render-adaptation/04-03-PLAN.md(本任务的全部门禁定义与成功标准)
+ - .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md:152-197(对账结论 → 本计划覆盖对照)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-19 / D-20 / D-21 / D-22 的裁定原文,用于 SUMMARY 的决策段)
+ - .planning/STATE.md:217-236(Phase 04 既有决策记录格式)
+
+
+ (1) **范围门禁(先跑,防止越界改动)**:执行 `git status --porcelain`,确认**生产代码**相对 Task 1 记录的基线只多出 `packages-user/client-modules/src/render/map/extension/{hero.ts,types.ts,manager.ts}` 三条改动;特别断言 `packages-user/client-modules/src/action/move.ts`(D-19)、`packages-user/client-base/**`、`packages-user/data-common/**`、`packages-user/data-base/**`、`packages-user/data-system/**`、`packages-user/data-state/**`、`packages/**` 均无本计划引入的新改动。若出现第四个生产文件的改动:停止、汇报、修订计划(不得自行扩面)。
+ (2) **并发改动基线门禁**:用与 Task 1 步骤 (0b) 完全相同的固定命令重算(排除 `packages-user/client-modules/src/render/map/extension/` 与 `.planning/phases/04-render-adaptation/` 两个前缀),断言 porcelain 条目集合与内容摘要 `sha256` 均与 `04-03-SUMMARY.md`「执行前基线」段记录一致——即用户的并发未提交改动**未被回滚 / 暂存 / 提交 / 修改**。
+ (3) **复跑全部机器门禁**:scoped 类型门禁(三个文件 0 条 + `client-modules` 错误集 == 两条记录基线)、静态改名门禁(`IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` 与 6 个旧钩子方法名不复现;`IHeroLocation` / `ObjectAnimDirection` / `getCurrentFaceDirection` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController` 均存在)、D-20 契约门禁、D-18 / D-22 保留门禁、`pnpm exec eslint` 三文件 0 错误、CRLF 门禁。
+ (4) **人工复核(写进 SUMMARY 的人工 UAT 段,逐条给结论)**:① 逐钩子映射是否与 Task 0 汇报并经用户确认的映射表一致(列出每条数据端触发点 `file:line` ↔ 渲染端动作);② `onSetPos` 的「移动中 return」守卫是否正确(移动中的逐格定位不应瞬时吸附,而应由 `onStepEnd` 动画推进);③ `onStepEnd` 的 `ObjectMoveType` 分派是否覆盖 `Dir` / `DirFace` / `Special` / `Face` / `Teleport` / `Jump`,`AnimDir` / `Speed` 是否确无渲染动作;④ D-18 编译桥接是否只收敛了一处 `hero.image` 读取、既有 warn 分支是否未变;⑤ D-18 / D-22 成员是否**逐字**保留(对照 Task 2 门禁输出);⑥ 是否有任何新公共接口成员被引入(应为零);⑦ 运行时 UAT 不可得的理由是否已写明(`addHero` 未被接线,`client.ts:148-152` 仍为注释)。
+ (5) **SUMMARY 定稿**:补齐 `04-04` 无(不存在)——写 `.planning/phases/04-render-adaptation/04-03-SUMMARY.md`,含:改动三文件的清单与职责、`#04-02-R-01..12` 的对账结论 → 本计划处置对照表(哪几条被本计划覆盖、哪几条属 D-19 / D-18 / D-22 / 后续增量)、执行前核对七条结论、五类门禁的实际输出摘要(含 `client-modules` 基线两条原样、scoped 0 条)、人工复核七条、未实现项清单(`onStepStart` / `onSetMoveDir` / `onSetFloor` / `client.ts` 接线 / `hero.image` 来源)、以及 REND-01 / REND-02 仍为 Pending 的明确声明。全文 CRLF。
+ (6) **提交**:`docs(04-03): record execution state`,仅含 `04-03-SUMMARY.md`(三个生产文件的改动已在 Task 2 提交;ROADMAP / STATE 的元数据更新由 orchestrator 的元数据提交处理,本任务不代劳)。
+ (7) **不做的事**:不接线 `client.ts`、不改 `action/move.ts`、不新增测试文件(渲染端无测试设施)、不改任何数据端文件、不修任何 ③ 多余旧路径或其它 ① 错配(`#04-01-M-06..09`)。
+
+
+ node -e "const {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 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)).filter(p=>!/\.planning\//.test(p));const extra=prod.filter(p=>allow.indexOf(p)<0);const missing=allow.filter(p=>prod.indexOf(p)<0);if(extra.length){console.error('OUT-OF-SCOPE FILE MODIFIED '+extra.join(' ; '));process.exit(1)}console.log('OK scope: in-scope modified '+missing.length+'/3 (committed ones absent) extra 0')"
+ non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 `files_modified` 之外的生产文件改动,含 D-19 的 `action/move.ts` 或任何数据端文件)
+ 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-03-SUMMARY.md','utf8');const rec=(t.match(/^[ MARCUD?!]{2} [^\r\n]*/gm)||[]).map(s=>s.replace(/\s+$/,'')).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)"
+ non-zero exit, 或 stderr 含 "NO BASELINE DIGEST" / "BASELINE ENTRY SET CHANGED" / "BASELINE DIGEST CHANGED"(用户并发的未提交改动被本计划回滚 / 暂存 / 提交 / 修改 —— 违反并发改动禁令)
+ 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 banned=['IHeroMoveControllerHooks','IHeroMoveController','HeroAnimateDirection','onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const hit=banned.filter(t=>src.includes(t));if(hit.length){console.error('LEGACY SYMBOLS REMAIN '+hit.join(','));process.exit(1)}const need=['IHeroLocation','ObjectAnimDirection','getCurrentFaceDirection','mover.addHook','hero.addHook','locationController','moverController'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('MISSING NEW BINDINGS '+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 d20=['startMove(): void;','waitMoveEnd(','stopMove(): void;','move(direction: FaceDirection, time: number): Promise;','jumpTo(','setPosition(x: number, y: number): void;','turn(direction?: FaceDirection): void;','setHeroAnimateDirection('];const dHit=d20.filter(x=>seg.includes(x));if(dHit.length){console.error('D20 DRIVE MEMBERS STILL EXPOSED '+dHit.join(' , '));process.exit(1)}const d18=['setImage(image: ITexture): void;','addFollower(image: number, id: string): void;','removeFollower(follower: string, animate: boolean): Promise;','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)}const h=fs.readFileSync(S+'hero.ts','utf8');const hNeed=['onSetImage(','onSetAlpha(','onSetFollowerAlpha(','onAddFollower(','onRemoveFollower(','onRemoveAllFollowers(','onSetPos(','onMoveStart(','onMoveEnd(','onStepEnd(','onSetFaceDir('];const hMiss=hNeed.filter(x=>!h.includes(x));if(hMiss.length){console.error('D18 D22 OR HOOK MEMBERS MISSING '+hMiss.join(' , '));process.exit(1)}console.log('OK final static gate: rename + D20 + D18/D22')"
+ non-zero exit, 或 stderr 含 "LEGACY SYMBOLS REMAIN" / "MISSING NEW BINDINGS" / "D20 DRIVE MEMBERS STILL EXPOSED" / "D18 D22 CONTRACT MEMBERS MISSING" / "D18 D22 OR HOOK MEMBERS MISSING"
+ node -e "const {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 cm=lines.filter(l=>l.includes('client-modules')).map(l=>l.replace(/^.*client-modules[\\/]src[\\/]/,''));const base=['client.ts(66,9): error TS2554','render/ui/load.tsx(77,31): error TS2504'];const extra=cm.filter(l=>!base.some(b=>l.startsWith(b)));const missing=base.filter(b=>!cm.some(l=>l.startsWith(b)));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 '+cm.length)"
+ non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS" / "CLIENT-MODULES ERROR SET CHANGED"(终验类型门禁未过)
+ 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
+ non-zero exit 或输出含 "error"
+ 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)}}const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-03-SUMMARY.md','utf8');const lf=(t.match(/\n/g)||[]).length;const crlf=(t.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF SUMMARY');process.exit(1)}console.log('OK CRLF all four files')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS" / "NON-CRLF SUMMARY"
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-03-SUMMARY.md','utf8');const need=['执行前核对','执行前基线','类型门禁基线','人工复核','REND-01','REND-02','Pending','addHero','client.ts:148'];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')"
+ non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线 / 人工复核 / 需求 Pending 声明 / 运行时不可得说明)
+
+
+ - 范围门禁通过:无 `files_modified` 之外的生产文件改动(`action/move.ts`、任何数据端包、`client-base`、`packages` 均无本计划引入的改动)
+ - 并发改动基线门禁通过:用户并发未提交改动的 porcelain 条目集合与内容 `sha256` 均与记录的基线一致
+ - 静态改名门禁、D-20 契约门禁、D-18 / D-22 保留门禁、scoped 类型门禁(三文件 0 条、`client-modules` 错误集 == 两条基线)、eslint 0 错误、CRLF 门禁(三个源文件 + SUMMARY)全部转绿
+ - `04-03-SUMMARY.md` 含「执行前核对」七条(带 `file:line`)、「执行前基线」(porcelain + `sha256`)、「类型门禁基线」(两条原样)、`#04-02-R-01..12` 对账 → 本计划处置对照、五类门禁输出摘要、人工复核七条、未实现项清单、REND-01 / REND-02 仍为 Pending 与运行时 UAT 不可得的明确说明
+ - SUMMARY 已以 `docs(04-03)` 提交,且提交仅含该 SUMMARY
+
+ 七条机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02)
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| 渲染端契约面 → 下游调用方 | `IMapExtensionManager` / `IMapHeroRenderer` 是 `client-base` / `client-modules` 其它模块与用户代码消费的公共契约;D-20 的裁剪会改变契约面,误删 D-18 / D-22 成员会直接扩大破坏面 |
+| 数据端接口 → 渲染端绑定 | 渲染端只读 import `data-common` / `data-base` 的接口与 const enum;本计划不改数据端,接口一旦被用户并发改动,适配即按错误形状落地 |
+| 数据端钩子回调 → 渲染状态 | 现有钩子是唯一允许推进渲染状态的入口(D-20 / D-21);把驱动能力留在公共契约上就等于开放外部改写渲染状态 |
+| 执行者/子代理 → 执行前置关卡 | AGENTS.md 要求第一个 block 点先汇报并等待用户回复「可以执行」;`yolo` / `auto_advance` 或子代理派发都不是放行理由(Task 0 为 `blocking-human`、`autonomous: false`) |
+| 执行者 → 仓库工作树 | 用户正在并发手工重构数据端(`client-base/src/load/**`、`data-base/src/load/**`、`data-state/src/{core,types}.ts`、`packages/loader/src/**`);本计划只允许改三个渲染端文件,且不得触碰这些改动 |
+| 未接线状态 → 验收结论 | `addHero` 无调用点,运行时渲染不可观察;若不显式声明,机器门禁全绿会被误读为「勇士渲染已可用」 |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-04-03-01 | Tampering | `IMapHeroRenderer` 公共契约(`types.ts`) | high | mitigate | Task 2 删除 8 个外部驱动成员(D-20)并由 Task 2 / Task 3 的 D-20 契约门禁断言其不再出现;prohibitions 禁止新增任何接口成员;D-18 / D-22 成员设独立保留门禁,防误删 |
+| T-04-03-02 | Repudiation | 钩子映射与旧名清除的可追溯性 | medium | mitigate | 逐钩子映射在 Task 0 汇报并经用户确认;Task 2 / Task 3 的静态改名门禁断言 6 个旧钩子方法名与 3 个旧类型名不复现、5 个现有钩子成员落地;映射关系写入 SUMMARY 人工复核段 |
+| T-04-03-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-03-04 | Tampering | 用户并发的未提交改动 | high | mitigate | Task 1 步骤 (0b) 记录 porcelain 条目 + 各文件内容 `sha256` 摘要;Task 3 用同一固定命令复算并断言集合与摘要未变;prohibitions 明确不得回滚 / 暂存 / 提交 / 修改;Task 3 范围门禁额外断言无范围外生产文件改动 |
+| T-04-03-05 | Spoofing | 「编译通过」被当作「运行时可用」 | medium | mitigate | 运行时 UAT 不可得(`addHero` 未被接线)作为 truths/assumptions 明写;SUMMARY 必须含运行时不可得说明(Task 3 门禁断言 `addHero` 与 `client.ts:148` 字样);严禁以不可运行的门禁冒充通过 |
+| T-04-03-06 | Elevation of Privilege | 范围蔓延(贴图 D-18 / 跟随者 D-22 / 移动控制 D-19 / legacy D-12 / 裸函数 D-11) | medium | mitigate | 范围门禁断言只有三个文件被改;D-18 / D-22 保留门禁;prohibitions 逐条列明;需要第四个文件时强制停下汇报并修订计划 |
+| T-04-03-07 | Information Disclosure | 改动引用的接口名、文件行号与类型签名 | low | accept | 改动只含渲染端源码引用,不含凭据 / 密钥 / 用户数据;无需额外缓解 |
+| T-04-03-SC | Tampering | npm / pnpm 依赖安装 | high | accept | 本计划零依赖变更、不修改 `package.json` / `pnpm-lock.yaml`,Package Legitimacy Gate 不触发;若执行中出现安装需求,立即停止并汇报 |
+
+
+
+
+0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题 / 大致解决方案(含逐钩子映射表)/ 成员命名清单 / D-18 编译桥接取舍 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
+1. 执行前核对与基线(Task 1 步骤 0):七条数据端现行接口复核结论(带 `file:line`)写入 SUMMARY;并发改动基线(porcelain 条目 + `sha256`)与类型门禁基线(两条 `client-modules` 诊断原样、三文件 0 条)写入 SUMMARY;任一与规划不一致即停止并修订计划。
+2. scoped 类型门禁(Task 1 / 2 / 3):`pnpm exec vue-tsc --noEmit` 过滤到 `render/map/extension/{hero,types,manager}.ts` 必须 0 条诊断;`client-modules` 错误集必须与记录基线完全一致(规划时 `client.ts(66,9): error TS2554`、`render/ui/load.tsx(77,31): error TS2504`);整仓其余既有诊断不属本计划,不得以「整仓非 0 退出」判定失败,也不得漏判范围文件的新诊断。
+3. 静态改名门禁(Task 1 / 2 / 3):三个文件不出现 `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` 与 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`;必须出现 `IHeroLocation` / `ObjectAnimDirection` / `getCurrentFaceDirection` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController`。
+4. D-20 契约门禁(Task 2 / 3):`IMapHeroRenderer` 不再声明 `setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setHeroAnimateDirection` / `turn`,且无任何新增接口成员。
+5. D-18 / D-22 保留门禁(Task 2 / 3):`IMapHeroRenderer` 的 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy` 与 `hero.ts` 的 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` / 跟随者三方法逐字保留。
+6. 并发改动基线门禁(Task 3):排除三个在范围文件与 `.planning/phases/04-render-adaptation/` 后,porcelain 条目集合与内容 `sha256` 与 SUMMARY 记录一致(用户改动未被回滚 / 暂存 / 提交 / 修改)。
+7. 范围门禁(Task 3):`git status --porcelain` 中除三个在范围文件外的生产代码无本计划引入的改动(`action/move.ts`、数据端各包、`client-base`、`packages` 均未被触碰)。
+8. ESLint / CRLF 门禁(Task 1 / 2 / 3):`pnpm exec eslint` 对三个文件 0 错误;三个源文件与 SUMMARY 无孤立 LF(`.prettierrc` 的 `endOfLine: crlf`)。
+9. 人工复核(Task 3):逐钩子映射与用户确认的映射表一致;`onSetPos` 的「移动中不吸附」守卫正确;`onStepEnd` 的 `ObjectMoveType` 分派完整且 `AnimDir` / `Speed` 无渲染动作;D-18 桥接只收敛一处 `hero.image` 读取且 warn 分支未变;无新增公共接口成员;运行时 UAT 不可得的理由已写明。
+10. 提交:Task 2 的 `refactor(04-03)` 仅含三个源文件;Task 3 的 `docs(04-03)` 仅含 SUMMARY;用户并发改动未被纳入任何提交。
+
+
+
+- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报计划内容、逐钩子映射表、成员命名清单与 D-18 编译桥接取舍后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行。
+- 渲染端勇士本体已绑定 `IHeroLocation`:`manager.ts` / `types.ts` 的键与参数类型、`hero.ts` 的构造器参数与朝向读取口径(`getCurrentFaceDirection()`)、动画方向(`ObjectAnimDirection` + `mover.currAnimDir`)全部对齐数据端现行接口;三个文件不再出现 `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection`。
+- 渲染端已改为**订阅数据端现有钩子**(D-21):`hero.addHook`(`IHeroLocationHooks`)与 `hero.mover.addHook`(`IObjectMoverHooks`)两处注册、两个控制器字段、`load()` / `unload()` 配套;`MapHeroHook` 实现现有钩子成员名,6 个旧钩子方法名全部消失;**未新增任何数据端公共接口,也未修改任何数据端文件**。
+- 勇士渲染为**完全被动渲染**(D-20):`IMapHeroRenderer` 不再暴露 8 个外部驱动成员,渲染状态只由钩子回调推进;无任何新增公共接口成员。
+- **D-18 / D-22 成员逐字保留**:贴图 / 不透明度成员与跟随者族成员均未被删除或改写语义;贴图别名的读取只做最小编译桥接(一处收敛 + 既有 `// @ts-expect-error 需要重构` 标注,分支与 warn 未变)。
+- **范围外零改动**:`action/move.ts` 与移动控制 API(D-19)、`core.*`(D-12)、`getHeroStatusOn` 类裸函数(D-11)、任何数据端文件、`client.ts` 接线均未被触碰;用户并发的未提交改动未被回滚 / 暂存 / 提交 / 修改。
+- **验证诚实且可运行**:scoped 类型门禁(三文件 0 条、`client-modules` 错误集 == 基线)、静态改名门禁、D-20 契约门禁、D-18 / D-22 保留门禁、eslint / CRLF 门禁、范围门禁、并发改动基线门禁全部转绿且有实际输出;运行时渲染 UAT 不可得的原因(`addHero` 未被接线)在摘要中明写,未以任何不可运行的门禁冒充通过。
+- REND-01 / REND-02 保持 Pending(两条 FLAGGED ASSUMPTION),用户可据此继续裁决后续增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02、其余 ① / ③ 项)。
+
+## Artifacts this phase produces
+
+| 类型 | 路径 | 内容 |
+|------|------|------|
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/hero.ts` | 绑定 `IHeroLocation`;两次 `addHook`(`IHeroLocationHooks` / `IObjectMoverHooks`)+ 两个控制器字段;`MapHeroHook` 改实现现有钩子成员名(含 `onStepEnd` 的 `ObjectMoveType` 分派与 `mover.currAnimDir` 动画方向);旧类型名与旧钩子名清除;`hero.image` 收敛为一处 D-18 编译桥接;D-18 / D-22 成员逐字保留 |
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/types.ts` | `IMapExtensionManager` 勇士键 / 参数 → `IHeroLocation`;`IMapHeroRenderer` 按 D-20 删除 8 个外部驱动成员,保留 D-18 / D-22 成员 |
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/manager.ts` | `heroMap` / `addHero` / `removeHero` 以 `IHeroLocation` 为键与参数;移除 `@ts-expect-error 需要重构` 标记 |
+| 摘要 | `.planning/phases/04-render-adaptation/04-03-SUMMARY.md` | 执行前核对七条(带 `file:line`)、执行前基线(porcelain + `sha256`)、类型门禁基线(两条原样)、`#04-02-R-01..12` → 本计划处置对照、五类机器门禁输出摘要、人工复核七条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
+
+
From c05fa322d01444182c97e39662451b8b65377ad7 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 14:31:09 +0800
Subject: [PATCH 026/173] fix(04-03): revise plan per checker feedback (hook
async/remove-legacy self-consistency)
---
.../phases/04-render-adaptation/04-03-PLAN.md | 49 ++++++++++---------
1 file changed, 26 insertions(+), 23 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-03-PLAN.md b/.planning/phases/04-render-adaptation/04-03-PLAN.md
index d001bb8..8ae465d 100644
--- a/.planning/phases/04-render-adaptation/04-03-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-03-PLAN.md
@@ -81,7 +81,7 @@ Phase 4 第三个增量(D-17 的适配半程)——**勇士本体适配:
**大致解决方案:**
- **绑定对象换成位置对象**:`MapHeroRenderer` 持有 `IHeroLocation`;坐标读 `IObjectMovable.x/.y`;朝向读 `getCurrentFaceDirection()`(构造期初始化)与 `mover.faceDirection`;移动方向读 `mover.moveDirection`;动画播放方向读 `mover.currAnimDir` 并改用 `ObjectAnimDirection`(R-03/R-05/R-06)。
- **钩子改为订阅现有钩子**(D-21):在 `hero`(`IHeroLocationHooks`)与 `hero.mover`(`IObjectMoverHooks`)上各注册一次 `MapHeroHook`,用 `load()` / `unload()` 管理(原 `controller` 字段拆为两个控制器字段)。渲染端实现现有钩子成员名,旧名全部删除;`onStepEnd` 成为逐格渲染的驱动点(此时 `tile` 坐标已更新),按 `ObjectMoveType` 分派到既有渲染内部方法。
-- **被动化**(D-20):`IMapHeroRenderer` 去掉「外部驱动渲染状态」的成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`);这些方法退为渲染端内部实现,仅由钩子回调调用。
+- **被动化**(D-20):`IMapHeroRenderer` 去掉「外部驱动渲染状态」的成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`;其中 `setHeroAnimateDirection` 因形参类型 `HeroAnimateDirection` 在数据端已不存在,在 Task 1 随类型改名一并删除,其余 7 个在 Task 2 删除);这些方法退为渲染端内部实现,仅由钩子回调调用。
- **范围硬边界**:贴图 / 不透明度(D-18)、跟随者(D-22)、`action/move.ts` 与移动控制(D-19)、`core.*`(D-12)、裸函数(D-11)一律不动。
**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「要解决的问题 / 大致解决方案(含逐钩子映射表)/ 拟新增或改写的成员命名清单 / 范围边界与禁令 / D-18 编译桥接的取舍」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式或配置模式如何均不得自动放行。故本计划 `autonomous: false`。
@@ -106,7 +106,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
| CONTEXT | D-17 | 下一步=勇士移动适配(先探索再适配) | 04-03 | COVERED | 04-02 完成探索;本计划即其适配实施(勇士本体部分) |
| CONTEXT | D-18 | `HeroRendering` 暂不处理,用户先改数据端 | 04-03 | COVERED(排除 + 编译桥接) | 贴图 / 不透明度成员逐字保留;`hero.image` 只做最小编译桥接,不重设来源;Task 0 汇报该取舍 |
| CONTEXT | D-19 | 移动控制由用户手动修改 | 04-03 | COVERED(排除) | `action/move.ts` 与移动控制 API 不在 `files_modified`;prohibitions 明列 |
-| CONTEXT | D-20 | 勇士渲染完全被动、旧驱动接口删除 | 04-03 | COVERED | Task 2 从 `IMapHeroRenderer` 删除 8 个外部驱动成员,改为钩子驱动 |
+| CONTEXT | D-20 | 勇士渲染完全被动、旧驱动接口删除 | 04-03 | COVERED | `IMapHeroRenderer` 删除 8 个外部驱动成员(`setHeroAnimateDirection` 随 Task 1 的类型改名一并删除,其余 7 个由 Task 2 删除),改为钩子驱动 |
| CONTEXT | D-21 | 走现有 `IObjectMoverHooks` / `IHeroLocationHooks`,不新增数据端接口 | 04-03 | COVERED | Task 1/2 的两次 `addHook` 注册与逐钩子映射即本条的落地 |
| CONTEXT | D-22 | 跟随者暂缓 | 04-03 | COVERED(排除) | 跟随者成员与钩子方法逐字保留、不删除不改写 |
| CONTEXT | D-10 | `HeroRendering` 删除、渲染状态存储放渲染端 | 04-03 | COVERED(一致性依据) | D-20 的被动化与 D-18 的保留范围保持一致;贴图来源的最终归属留给 D-18 增量 |
@@ -158,9 +158,9 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
① **要解决的问题**:渲染端勇士本体仍按已删除的 `IHeroMoveController` 模型编写(`hero.direction` / `hero.image` / `IHeroMoveControllerHooks` 的 `onSetPosition` 等),数据端现行接口无这些成员,全靠 `// @ts-expect-error 需要重构` 压着(`#04-02-R-01/R-03/R-04/R-05/R-06/R-12`)。不改就会一直停在「编译靠压制、契约对不上」。
- ② **大致解决方案(三个文件)**:`manager.ts` 与 `types.ts` 的勇士键 / 参数类型由 `IHeroMoveController` 改为 `IHeroLocation`;`hero.ts` 构造器绑定 `IHeroLocation`(坐标 `x`/`y`、朝向 `getCurrentFaceDirection()`、动画方向 `ObjectAnimDirection` + `mover.currAnimDir`),在 `hero`(`IHeroLocationHooks`)与 `hero.mover`(`IObjectMoverHooks`)上各 `addHook` 一次,`MapHeroHook` 改为实现现有钩子成员名;`IMapHeroRenderer` 按 D-20 去掉 8 个外部驱动成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`)。**逐钩子映射表须逐行汇报**:数据端钩子 → 触发点(`file:line`)→ 渲染端动作。核心几条:`IHeroLocationHooks.onSetPos(x, y)`(`data-base/src/hero/location.ts:59-63`)→ 非移动中时 `setPosition(x, y)`,移动中交给 `onStepEnd`;`IObjectMoverHooks.onMoveStart` → `startMove()`;`onMoveEnd` → `await waitMoveEnd()`;`onStepEnd(code, step, tile, mover)`(坐标已更新,`data-common/src/common/mover.ts:674-678`)→ 按 `step.type`(`ObjectMoveType`)分派:`Dir`/`DirFace` → `move(step.move, mover.currentSpeed)`(`DirFace` 另 `turn(step.face)`)、`Special` → `move(mover.moveDirection, mover.currentSpeed)`、`Face` → `turn(step.value)`、`Teleport` → `setPosition(tile.x, tile.y)`、`Jump` → `jumpTo(tile.x, tile.y, mover.currentSpeed, false)`、`AnimDir`/`Speed` → 不处理;动画方向统一在 `onStepEnd` 取 `mover.currAnimDir`;`onSetFaceDir` → `turn(dir)`;`onSetPos`(mover 级)→ `setPosition(x, y)`;`onSetMoveDir` / `onStepStart` → 不处理(并说明理由)。
+ ② **大致解决方案(三个文件)**:`manager.ts` 与 `types.ts` 的勇士键 / 参数类型由 `IHeroMoveController` 改为 `IHeroLocation`;`hero.ts` 构造器绑定 `IHeroLocation`(坐标 `x`/`y`、朝向 `getCurrentFaceDirection()`、动画方向 `ObjectAnimDirection` + `mover.currAnimDir`),在 `hero`(`IHeroLocationHooks`)与 `hero.mover`(`IObjectMoverHooks`)上各 `addHook` 一次,`MapHeroHook` 改为实现现有钩子成员名;`IMapHeroRenderer` 按 D-20 去掉 8 个外部驱动成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`;其中 `setHeroAnimateDirection` 因形参类型 `HeroAnimateDirection` 已不存在,在 Task 1 连同其 jsDoc 一并删除,其余 7 个在 Task 2 删除)。**逐钩子映射表须逐行汇报**:数据端钩子 → 触发点(`file:line`)→ 渲染端动作。核心几条:`IHeroLocationHooks.onSetPos(x, y)`(`data-base/src/hero/location.ts:59-63`)→ 非移动中时 `setPosition(x, y)`,移动中交给 `onStepEnd`;`IObjectMoverHooks.onMoveStart` → `startMove()`;`onMoveEnd` → `await waitMoveEnd()`;`onStepEnd(code, step, tile, mover)`(坐标已更新,`data-common/src/common/mover.ts:674-678`)→ 按 `step.type`(`ObjectMoveType`)分派:`Dir`/`DirFace` → `move(step.move, mover.currentSpeed)`(`DirFace` 另 `turn(step.face)`)、`Special` → `move(mover.moveDirection, mover.currentSpeed)`、`Face` → `turn(step.value)`、`Teleport` → `setPosition(tile.x, tile.y)`、`Jump` → `jumpTo(tile.x, tile.y, mover.currentSpeed, false)`、`AnimDir`/`Speed` → 不处理;动画方向统一在 `onStepEnd` 取 `mover.currAnimDir`;`onSetFaceDir` → `turn(dir)`;`onSetPos`(mover 级)→ `setPosition(x, y)`;`onSetMoveDir` / `onStepStart` → 不处理(并说明理由)。
- ③ **成员命名清单(须用户同意,AGENTS.md 命名规则)**:**新增**渲染端字段 `locationController`(`IHookController`)与 `moverController`(`IHookController>`)——取代原单一 `controller` 字段;**不新增任何类**(沿用既有 `MapHeroHook` 类名);**删除**旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero` 与 `IMapHeroRenderer` 的 8 个驱动成员(连同其配套 jsDoc,属删除代码而非改写注释);**逐字保留** `setImage` / `setAlpha` / `setFollowerAlpha` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者族(D-18 / D-22)。备选命名(如用户不同意 `locationController` / `moverController`)请用户当场指定。
+ ③ **成员命名清单(须用户同意,AGENTS.md 命名规则)**:**新增**渲染端字段 `locationController`(`IHookController`)与 `moverController`(`IHookController>`)——取代原单一 `controller` 字段;**不新增任何类**(沿用既有 `MapHeroHook` 类名);**删除**旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`(**Task 1 即删除,不留存为类上的多余方法**)与 `IMapHeroRenderer` 的 8 个驱动成员(连同其配套 jsDoc,属删除代码而非改写注释;其中 `setHeroAnimateDirection` 在 Task 1 删除,其余 7 个在 Task 2 删除);**新增**的钩子方法名 `onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir` 逐字取自数据端现有钩子接口成员名(D-21,非自创命名),`onStepEnd` 的未使用首参按项目惯例命名为 `_code`(eslint `args: 'all'` + `argsIgnorePattern: '^_'`),一并请用户确认;**逐字保留** `setImage` / `setAlpha` / `setFollowerAlpha` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者族(D-18 / D-22)。备选命名(如用户不同意 `locationController` / `moverController`)请用户当场指定。
④ **D-18 编译桥接的取舍(须用户裁定)**:`hero` 类型改为 `IHeroLocation` 后,`addHeroMoving` 内读取贴图别名的 `hero.image`(`hero.ts:115,119,121`)失去数据端对位。本计划主张**只做最小编译桥接**:把该读取收敛为一处局部变量、用项目既有 `// @ts-expect-error 需要重构` 惯用法标注,运行分支与语义保持不变(`undefined` → 既有 `logger.warn(88)` 分支),**不重新设计贴图来源**(贴图来源与 `IHeroRendering` 属 D-18,等用户先改数据端)。请用户确认该桥接,或指定其它处理方式。
@@ -197,8 +197,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
回复「可以执行」四个字(如需改名或改桥接,请在回复中一并给出),或给出修订意见
- 用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无自动命令,失败语义写在 action ⑦/⑧ 与 acceptance_criteria 中)
- 用户回复的是修订意见、改名要求未获确认、或未回复即继续 —— 必须停止执行,不派发任何执行子代理
+ 用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无可运行的 `` 命令,故不携带 ``;失败语义(用户未回复或回复其它任何内容即视为未通过本关卡、必须立即停止执行、不派发任何执行子代理、保持只读且不写任何文件,直到用户明确回复「可以执行」四个字方可继续)完整写在 Task 0 的 action ⑦/⑧ 与 acceptance_criteria 中)
- 用户回复中明确包含「可以执行」四个字
@@ -229,15 +228,15 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
(0c) **类型门禁基线**:执行 `pnpm exec vue-tsc --noEmit`(整仓会以非 0 退出属预期——规划时有 122 条诊断)并记录 `client-modules` 的命中行;规划时恰为 2 条、与勇士移动无关:`packages-user/client-modules/src/client.ts(66,9): error TS2554: Expected 1 arguments, but got 0.` 与 `packages-user/client-modules/src/render/ui/load.tsx(77,31): error TS2504: Type 'ITaskManager' must have a 'Symbol.asyncIterator]()' method that returns an async iterator.`。同时确认三个在范围文件**当前 0 条诊断**。把这两条原样抄入 `04-03-SUMMARY.md` 的「类型门禁基线」段;**若与记录不一致,停止并汇报**(说明用户并发改动改变了基线,不得静默调整门禁)。
(0d) 建立 `04-03-SUMMARY.md` 骨架(含上面三段 + 后续任务要填的空节),保持 CRLF。
(1) **`manager.ts`(绑定层)**:删除首行 `// @ts-expect-error 需要重构`,把 `import { IHeroMoveController } from '@user/data-base'` 改为 `import { IHeroLocation, IMapLayer } from '@user/data-base'`(合并第三行的 `IMapLayer` import,避免重复 import 语句;若 lint 要求分行则按 lint 结果排布);`readonly heroMap: Map`;`addHero(state: IHeroLocation, layer: IMapLayer)`;`removeHero(state: IHeroLocation)`。`addDoor` / `removeDoor` / `addText` / `removeText` / `destroy` 一字不改。
- (2) **`types.ts`(契约层,本任务只改绑定类型,不动 D-20 裁剪)**:从 `@user/data-common` 的 import 中删除 `HeroAnimateDirection` 与 `IHeroMoveController`(连带其两行 `// @ts-expect-error 需要重构` 标记),改为从 `@user/data-base` 导入 `IHeroLocation`(与既有 `IMapLayer` 合并);`IMapExtensionManager.heroMap: Map`、`addHero(state: IHeroLocation, layer: IMapLayer)`、`removeHero(state: IHeroLocation)`;`Map<` / `addHero` / `removeHero` 的 jsDoc **不改写**(参数名与含义不变)。本任务**不动** `IMapHeroRenderer`(其 8 个驱动成员的删除在 Task 2)。
+ (2) **`types.ts`(契约层:绑定类型 + 删除 `setHeroAnimateDirection` 声明)**:从 `@user/data-common` 的 import 中删除 `HeroAnimateDirection` 与 `IHeroMoveController`(连带其两行 `// @ts-expect-error 需要重构` 标记;删除后该 import 仅剩 `FaceDirection`,**不需要任何替代类型**),改为从 `@user/data-base` 导入 `IHeroLocation`(与既有 `IMapLayer` 合并);`IMapExtensionManager.heroMap: Map`、`addHero(state: IHeroLocation, layer: IMapLayer)`、`removeHero(state: IHeroLocation)`;**删除 `IMapHeroRenderer.setHeroAnimateDirection` 的成员声明及其配套 jsDoc**(原 `:143-147`;其形参类型 `HeroAnimateDirection` 在数据端已不存在,无法保留——若只删 import 而不删该成员,`types.ts` 会残留悬空引用并触发 TS2304):该成员本任务即删除,其实现方法 `MapHeroRenderer.setHeroAnimateDirection` 保留在类上(见 (3f));`IMapHeroRenderer` 的**其余 7 个驱动成员**(`setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `turn`)连同其 jsDoc 在 Task 2 删除,本任务不动。`Map<` / `addHero` / `removeHero` 的 jsDoc **不改写**(参数名与含义不变)。
(3) **`hero.ts` import 与绑定(tracer 主路径)**:
- (3a) import 改造:`@user/data-common` 侧删除 `HeroAnimateDirection` / `IHeroMoveController` / `IHeroMoveControllerHooks`(连带 3 行 `// @ts-expect-error 需要重构` 标记),新增 `ObjectAnimDirection`、`IObjectMover`、`IObjectMoverHooks`、`ObjectMoveStep`、`ObjectMoveType`、`ObjectSpecialStep`;`@user/data-base` 侧把 `import { IMapLayer }` 扩为 `import { IHeroLocation, IHeroLocationHooks, IMapLayer }`(`IHeroLocation` / `IHeroLocationHooks` / `IHeroMover` 均可经该 barrel 取得,见 `data-base/src/hero/index.ts:10` 与 `data-base/src/index.ts:3`)。
- (3b) 实体类型:`HeroRenderEntity.animateDirection` 由 `HeroAnimateDirection` 改为 `ObjectAnimDirection`(`:53`);`new MapHeroRenderer(...)` 内的默认值 `HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:97`)。
+ (3a) import 改造(**只在本任务内引入本任务用得到的名字**:项目 eslint 把 `@typescript-eslint/no-unused-vars` 设为 `error` 且 `args: 'all'`,未使用的 import 会直接报错,`--fix` 也不会删除未使用 import;反之 Task 2 也不得保留只在 Task 1 用过的名字):`@user/data-common` 侧删除 `HeroAnimateDirection` / `IHeroMoveController` / `IHeroMoveControllerHooks`(连带 3 行 `// @ts-expect-error 需要重构` 标记),新增 `ObjectAnimDirection`(本任务即消费:`:53` / `:97` / `:170` / `:372` 四处)与 `IObjectMoverHooks`(本任务即消费:`moverController` 字段类型);**不要**在本任务引入 `IObjectMover` / `ObjectMoveStep` / `ObjectMoveType`(本任务无使用点,留到 Task 2 首次使用时再引入);**不要**引入 `ObjectSpecialStep`(两个任务都不需要:`Special` 步的移动方向已由数据端 `ObjectMover.prepareStep` 写入 `mover.moveDirection`,渲染端无需读取 `step.direction`)。`@user/data-base` 侧把 `import { IMapLayer }` 扩为 `import { IHeroLocation, IHeroLocationHooks, IMapLayer }`(`IHeroLocation` / `IHeroLocationHooks` / `IHeroMover` 均可经该 barrel 取得,见 `data-base/src/hero/index.ts:10` 与 `data-base/src/index.ts:3`)。
+ (3b) 实体类型与**全部取值点**:`HeroRenderEntity.animateDirection` 由 `HeroAnimateDirection` 改为 `ObjectAnimDirection`(`:53`);两处实体初始化的默认值 `HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:97` 构造器、`:372` `addFollower`);`tick` 内的播放方向比较 `v.animateDirection === HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:170`)。**四处取值点必须全部改到**,否则改名门禁(三文件不得出现 `HeroAnimateDirection`)与 scoped 类型门禁(该名字已从 import 移除,任何残留即 TS2304)都会失败。
(3c) 构造器:`readonly hero: IHeroMoveController` → `readonly hero: IHeroLocation`(`:79`);`direction: hero.direction` → `direction: hero.getCurrentFaceDirection()`(`:89`);钩子注册由单一 `controller` 改为**两个控制器字段**:`readonly locationController: IHookController` 与 `readonly moverController: IHookController>`(替换原 `readonly controller: IHookController` 字段,`:61`);构造体内 `const hook = new MapHeroHook(this); this.locationController = hero.addHook(hook); this.locationController.load(); this.moverController = hero.mover.addHook(hook); this.moverController.load();`(同一 hook 实例注册到两个 hookable,`Hookable.addHook` 按 hookable 实例分别存储,见 `packages/common/src/hook.ts:24-31`);`destroy()`(`:452-454`)改为 `this.locationController.unload(); this.moverController.unload();`。
(3d) `addHeroMoving`(`:110-132`):参数 `hero: IHeroMoveController` → `IHeroLocation`;`degradeFace(hero.direction)` → `degradeFace(hero.getCurrentFaceDirection())`(`:125`);`hero.image` 的三处读取(`:115,119,121`)按 Task 0 确认的 **D-18 最小编译桥接**处理——收敛为一处局部读取(如 `const imageAlias = hero.image;`)并在其正上方加一行**新增**注释 `// @ts-expect-error 需要重构(贴图别名来源属 D-18,待用户先改数据端后处理)`,其后三处改用 `imageAlias`;分支结构与既有 `logger.warn(88/89)` 行为保持不变;**不得**改写既有注释、不得重设贴图来源。
(3e) `setImage`(`:191-196`):只把内部的 `degradeFace(this.hero.direction)` 改为 `degradeFace(this.hero.getCurrentFaceDirection())`(同一朝向读取口径的编译修复),其余一字不改(D-18:贴图语义不动)。
- (3f) `setHeroAnimateDirection(direction: HeroAnimateDirection)`(`:437-439`)→ 形参类型改 `ObjectAnimDirection`;本任务保留其在 `IMapHeroRenderer` 中的声明(Task 2 再按 D-20 删除)。
- (3g) **`MapHeroHook` 重接(tracer 只贯通一条通路)**:类声明由 `implements Partial` 改为 `implements Partial>`(`:457`);把 `onSetPosition(x, y)`(`:469-471`)改为 `onSetPos(x: number, y: number): void`,实现「移动之外的直接定位才立刻同步、移动中的逐格定位交给 `onStepEnd`」:当 `this.hero.hero.mover.moving` 为 `true` 时直接 return,否则 `this.hero.setPosition(x, y)`;**其余旧命名方法本任务先保留为类上的多余方法(不影响实现 `Partial<...>` 的可赋值性),Task 2 再整体替换**——但不得新增任何旧命名方法。
+ (3f) `MapHeroRenderer.setHeroAnimateDirection(direction: HeroAnimateDirection)`(`:437-439`)→ 形参类型改 `ObjectAnimDirection`,**保留为类上的内部实现方法**(本任务暂无调用点,Task 2 的 `onStepEnd` 钩子回调会调用它);其 `IMapHeroRenderer` 接口声明已在步骤 (2) 按 D-20 删除,本任务不得再保留接口侧的该成员。
+ (3g) **`MapHeroHook` 重接(tracer 只贯通一条通路,旧名在本任务全部删除)**:类声明由 `implements Partial` 改为 `implements Partial>`(`:457`);把 `onSetPosition(x, y)`(`:469-471`)改为 `onSetPos(x: number, y: number): void`,实现「移动之外的直接定位才立刻同步、移动中的逐格定位交给 `onStepEnd`」:当 `this.hero.hero.mover.moving` 为 `true` 时直接 return,否则 `this.hero.setPosition(x, y)`;**同时删除**其余 5 个旧命名方法 `onTurnHero`(`:473-475`)、`onStartMove`(`:477-479`)、`onMoveHero`(`:481-483`)、`onEndMove`(`:485-487`)、`onJumpHero`(`:489-496`)——Task 2 再按映射表补上现有钩子成员名(`onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir`);**本任务不得以「类上的多余方法」形式留存任何旧命名方法**,因为本任务的静态改名门禁(见 ``)与 acceptance 都要求三文件不再出现这些名字。D-18 / D-22 的保留方法(`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` / 跟随者三方法)本任务一字不改。
(3h) 全程保持文件 CRLF;改完执行 `pnpm exec eslint --fix` 对三个文件,再 `pnpm exec eslint` 必须 0 错误。
(4) **提交**:本任务改动是整体适配的中间态,**不单独提交**;Task 2 完成后再一次性原子提交(见 Task 2 步骤 (5))。若执行环境强制每任务提交,则提交信息为 `refactor(04-03): rebind hero renderer to IHeroLocation`,且必须与 Task 2 的提交在本次执行内连续完成。
@@ -254,9 +253,10 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- `manager.ts` 与 `types.ts` 的 `heroMap` / `addHero` / `removeHero` 均为 `IHeroLocation`,`manager.ts` 首行的 `// @ts-expect-error 需要重构` 已删除,且不再 import `IHeroMoveController`
- - `hero.ts` 的构造器参数为 `IHeroLocation`,构造期朝向取自 `hero.getCurrentFaceDirection()`;`HeroRenderEntity.animateDirection` 与默认值为 `ObjectAnimDirection`
+ - `types.ts` 的 `IMapHeroRenderer` 不再声明 `setHeroAnimateDirection`(其形参类型 `HeroAnimateDirection` 已随类型改名删除,文件内无任何悬空引用);该接口的其余 7 个驱动成员与 D-18 / D-22 成员本任务未动
+ - `hero.ts` 的构造器参数为 `IHeroLocation`,构造期朝向取自 `hero.getCurrentFaceDirection()`;`HeroRenderEntity.animateDirection` 的声明(`:53`)、两处初始化默认值(`:97` / `:372`)与 `tick` 内的比较(`:170`)均为 `ObjectAnimDirection`
- `hero.ts` 同时存在 `hero.addHook(` 与 `hero.mover.addHook(`,并存在 `locationController` / `moverController` 两个字段;`destroy()` 对两者都 `unload()`
- - `MapHeroHook` 声明为 `Partial>`,且已实现 `onSetPos`(旧 `onSetPosition` 已不存在)
+ - `MapHeroHook` 声明为 `Partial>`,且已实现 `onSetPos`;类上**不再存在任何旧命名方法**(`onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero` 本任务即全部删除,不留存为多余方法;Task 2 再按映射表补上现有钩子成员名)
- 三个文件不再出现 `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` / `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`
- `hero.ts` 中 `hero.image` 的读取已收敛为一处(带新增的 `// @ts-expect-error 需要重构(…D-18…)` 标注),既有 `logger.warn(88/89/90)` 分支结构未变;`setImage` / `setAlpha` / 跟随者族成员未被删除
- `04-03-SUMMARY.md` 含「执行前核对」(步骤 0a 七条,每条带 `file:line`)、「执行前基线」(porcelain 条目 + `基线摘要 sha256`)与「类型门禁基线」(两条 client-modules 诊断原样)三段,全文 CRLF
@@ -266,28 +266,30 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- Task 2:逐钩子映射落地 + D-20 契约裁剪 —— 旧钩子方法名整体替换为现有钩子成员名;`IMapHeroRenderer` 去掉 8 个外部驱动成员(D-18 / D-22 成员逐字保留)
+ Task 2:逐钩子映射落地 + D-20 契约裁剪收口 —— 现有钩子成员名(异步钩子以 `async` 实现)全部接上;`IMapHeroRenderer` 删除其余 7 个外部驱动成员(第 8 个 `setHeroAnimateDirection` 已在 Task 1 删除;D-18 / D-22 成员逐字保留)
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/hero.ts(Task 1 产出的现状全文;重点 `MapHeroHook` 与 `IMapHeroRenderer` 的实现方法)
- - packages-user/client-modules/src/render/map/extension/types.ts:63-159(`IMapHeroRenderer` 当前 17 个成员)
+ - packages-user/client-modules/src/render/map/extension/types.ts:63-159(`IMapHeroRenderer` 成员清单;Task 1 之后剩 16 个,本任务再删 7 个)
- packages-user/data-common/src/common/mover.ts:166-233(钩子参数形态)、:641-687(`moveProgress` 的钩子时序:`onMoveStart` → 每步 `prepareStep` → `onStepStart`(坐标未变)→ `tile.setPos` → `onStepEnd`(坐标已更新)→ `onMoveEnd`)
- packages-user/data-common/src/common/mover.ts:40-45,14-31,33-38(`ObjectAnimDirection` / `ObjectMoveType` / `ObjectSpecialStep` 成员名)
- packages-user/data-base/src/hero/mover.ts:147-280(`HeroMover` 的 `onStepStart` / `onStepEnd` 与 `HeroMoveCode`,用于确认渲染侧读到的 `code` / `step` 语义)
- .planning/phases/04-render-adaptation/04-CONTEXT.md(D-18 / D-19 / D-20 / D-21 / D-22)
- (1) **`MapHeroHook` 逐钩子映射落地(D-21:只接现有钩子,旧名全删)**。把 Task 1 保留的旧命名方法整体替换为现有钩子成员名,按下列映射实现(每条都要能对上数据端触发点):
+ (0) **`hero.ts` import 补齐(只补本任务首次使用的名字)**:在 `@user/data-common` 的 import 中加入 `IObjectMover`(`onStepEnd` 的 `mover` 形参类型)、`ObjectMoveStep`(`onStepEnd` 的 `step` 形参类型)、`ObjectMoveType`(`step.type` 的 `case` 标签,按值使用);三者在本任务均有消费点,故在本任务引入(Task 1 不引入,见 Task 1 步骤 (3a))。**不要**引入 `ObjectSpecialStep`(本任务无使用点,会让 eslint `no-unused-vars` 报错)。
+
+ (1) **`MapHeroHook` 逐钩子映射落地(D-21:只接现有钩子,旧名全删;异步钩子必须 `async`)**。按下列映射实现(每条都要能对上数据端触发点)。**返回类型硬约束**:`IObjectMoverHooks` 的 `onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` 在数据端均声明为 `(...) => Promise`(`data-common/src/common/mover.ts:172,179,188-193,202-207`),实现方法若声明成 `: void` 会触发 TS2416「Type 'void' is not assignable to type 'Promise'」;因此下面这三个(`onMoveStart` / `onMoveEnd` / `onStepEnd`)必须写成 `async` 方法并返回 `Promise`(`async` 下各分支无需都写成 `return`,函数整体仍是 `Promise`)。`onSetPos` / `onSetFaceDir` / `onSetMoveDir` 在数据端是同步 `void`,实现同样保持 `void`。`onStepEnd` 的首参 `code` 在本实现中不使用,必须命名为 `_code`(eslint 为 `args: 'all'` + `argsIgnorePattern: '^_'`,未使用的形参即使不在末尾也会报错,`_` 前缀是项目既有惯例):
- `onSetPos(x: number, y: number): void`:**移动之外的直接定位**才立刻同步——当 `this.hero.hero.mover.moving` 为 `true` 时直接 return(移动中的逐格 `tile.setPos` 会触发本条,交给 `onStepEnd` 做动画),否则 `this.hero.setPosition(x, y)`。触发点:`HeroLocation.setPos`(`data-base/src/hero/location.ts:59-63`);移动中的逐格调用见 `data-common/src/common/mover.ts:671-673`。
- - `onMoveStart(): void` → `this.hero.startMove()`;触发点 `mover.ts:651-656`。
- - `onMoveEnd(): Promise` → `return this.hero.waitMoveEnd()`;触发点 `mover.ts:682-686`。
- - `onStepEnd(code: number, step: Readonly, tile: IHeroLocation, mover: IObjectMover): Promise`:**逐格渲染的唯一驱动点**(此时 `tile` 坐标已更新,见 `mover.ts:669-678`)。先 `this.hero.setHeroAnimateDirection(mover.currAnimDir)`(动画播放方向统一取自数据端 `currAnimDir`,即 R-03 的 `ObjectAnimDirection` 落地),再按 `step.type` 分派:`ObjectMoveType.Dir` → `return this.hero.move(step.move, mover.currentSpeed)`;`ObjectMoveType.DirFace` → `this.hero.turn(step.face); return this.hero.move(step.move, mover.currentSpeed)`;`ObjectMoveType.Special` → `return this.hero.move(mover.moveDirection, mover.currentSpeed)`(前进/后退的移动方向已由 `ObjectMover.prepareStep` 写入 `moveDirection`,见 `mover.ts:500-512`,无需自行取反);`ObjectMoveType.Face` → `this.hero.turn(step.value)`;`ObjectMoveType.Teleport` → `this.hero.setPosition(tile.x, tile.y)`;`ObjectMoveType.Jump` → `return this.hero.jumpTo(tile.x, tile.y, mover.currentSpeed, false)`;`ObjectMoveType.AnimDir` / `ObjectMoveType.Speed` → 不处理(动画方向已在 `currAnimDir` 中体现,速度由数据端消费)。
+ - `async onMoveStart(): Promise` → `this.hero.startMove()`;触发点 `mover.ts:651-656`。
+ - `async onMoveEnd(): Promise` → `await this.hero.waitMoveEnd()`;触发点 `mover.ts:682-686`。
+ - `async onStepEnd(_code: number, step: Readonly, tile: IHeroLocation, mover: IObjectMover): Promise`:**逐格渲染的唯一驱动点**(此时 `tile` 坐标已更新,见 `mover.ts:669-678`)。先 `this.hero.setHeroAnimateDirection(mover.currAnimDir)`(动画播放方向统一取自数据端 `currAnimDir`,即 R-03 的 `ObjectAnimDirection` 落地),再按 `step.type` 分派:`ObjectMoveType.Dir` → `return this.hero.move(step.move, mover.currentSpeed)`;`ObjectMoveType.DirFace` → `this.hero.turn(step.face); return this.hero.move(step.move, mover.currentSpeed)`;`ObjectMoveType.Special` → `return this.hero.move(mover.moveDirection, mover.currentSpeed)`(前进/后退的移动方向已由 `ObjectMover.prepareStep` 写入 `moveDirection`,见 `mover.ts:500-512`,无需自行取反);`ObjectMoveType.Face` → `this.hero.turn(step.value)`;`ObjectMoveType.Teleport` → `this.hero.setPosition(tile.x, tile.y)`;`ObjectMoveType.Jump` → `return this.hero.jumpTo(tile.x, tile.y, mover.currentSpeed, false)`;`ObjectMoveType.AnimDir` / `ObjectMoveType.Speed` → 不处理(动画方向已在 `currAnimDir` 中体现,速度由数据端消费)。本方法为 `async`,故 `Face` / `Teleport` 这类没有返回值的分支无需写成 `return`(若写成同步方法则必须显式 `return Promise.resolve()` 才能满足 `Promise`,因此这里统一用 `async`)。
- `onSetFaceDir(dir: FaceDirection): void` → `this.hero.turn(dir)`;触发点 `mover.ts:529-534`。
- - `onSetPos`(mover 级四参调用)与 location 级两参调用共用同一个两参方法:JS 会忽略多余实参,本方法的「移动中 return」守卫同时覆盖两条触发路径。
+ - `onSetPos`(mover 级四参调用)与 location 级两参调用共用同一个两参方法:JS 会忽略多余实参,本方法的「移动中 return」守卫同时覆盖两条触发路径;该方法保持 `void` 返回(`IHeroLocationHooks.onSetPos` 与 `IObjectMoverHooks.onSetPos` 在数据端都是 `void`)。
- **不实现**(在 `04-03-SUMMARY.md` 写明理由):`onStepStart`(渲染只在新坐标就绪后推进,避免重复驱动)、`onSetMoveDir`(渲染朝向只随 `onSetFaceDir` 变化;`moveDirection` 已由 `onStepEnd` 读取)、`IHeroLocationHooks.onSetFloor`(楼层切换需要重绑图层的接线路径,本增量不接线 `client.ts`;登记为后续增量事项,不在本计划实现)。
- **逐字保留(D-18 / D-22,不得删除、改写语义或改名)**:`onSetImage(image: ImageIds)`、`onSetAlpha(alpha: number)`、`onSetFollowerAlpha(identifier: string, alpha: number)`、`onAddFollower(follower: number, identifier: string)`、`onRemoveFollower(identifier: string, animate: boolean)`、`onRemoveAllFollowers()`——它们是本类上的多余方法(不再属于任何现有钩子接口的成员),保持原样并**不注册到任何数据端对象**;其现有 jsDoc / 注释一字不改。是否在后续增量(D-18 / D-22 解开时)迁到数据端对象上,留待用户裁定。
- (2) **`IMapHeroRenderer` 按 D-20 裁剪(`types.ts`)**:删除以下 8 个成员声明**及其配套 jsDoc**(属删除代码,Task 0 已获用户确认):`setPosition(x, y)`(`:92`)、`startMove()`(`:97`)、`waitMoveEnd(waitFollower)`(`:103`)、`stopMove()`(`:108`)、`move(direction, time)`(`:114`)、`jumpTo(x, y, time, waitFollower)`(`:123`)、`setHeroAnimateDirection(direction)`(`:147`)、`turn(direction?)`(`:153`)。保留 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy`(D-18 / D-22 成员,逐字不动)。不得新增任何成员。
- (3) **`MapHeroRenderer` 侧配套**:上述 8 个方法在类上**保留为内部实现**(不删除、不改名),仅由 `MapHeroHook` 的回调调用;其可见性/实现细节保持不变。`waitMoveEnd()` 的实现签名(无参)与接口原声明的参数落差(04-02「未能从阅读确定」第 6 条)随接口成员移除而消解,在 `04-03-SUMMARY.md` 记为「由 D-20 契约裁剪消解」。`setHeroAnimateDirection(direction: ObjectAnimDirection)` 的形参类型在 Task 1 已改,本任务不再动它。
+ (2) **`IMapHeroRenderer` 按 D-20 裁剪收口(`types.ts`)**:删除以下 **7 个**成员声明**及其配套 jsDoc**(属删除代码,Task 0 已获用户确认):`setPosition(x, y)`(`:92`)、`startMove()`(`:97`)、`waitMoveEnd(waitFollower)`(`:103`)、`stopMove()`(`:108`)、`move(direction, time)`(`:114`)、`jumpTo(x, y, time, waitFollower)`(`:123`)、`turn(direction?)`(`:153`)。D-20 的第 8 个成员 `setHeroAnimateDirection` 已在 Task 1 随类型改名删除(其声明连同 jsDoc 已不在 `types.ts`),**本任务不得为它重新引入任何声明**,故本任务结束时接口内的 8 个外部驱动成员全部不存在。保留 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy`(D-18 / D-22 成员,逐字不动)。不得新增任何成员。
+ (3) **`MapHeroRenderer` 侧配套**:上述 8 个驱动方法(含 Task 1 已从 `IMapHeroRenderer` 删除声明的 `setHeroAnimateDirection`)在类上**保留为内部实现**(不删除、不改名),仅由 `MapHeroHook` 的回调调用;其可见性/实现细节保持不变。`waitMoveEnd()` 的实现签名(无参)与接口原声明的参数落差(04-02「未能从阅读确定」第 6 条)随接口成员移除而消解,在 `04-03-SUMMARY.md` 记为「由 D-20 契约裁剪消解」。`MapHeroRenderer.setHeroAnimateDirection(direction: ObjectAnimDirection)` 已在 Task 1 把形参类型改为 `ObjectAnimDirection`、并保留为类上的内部实现方法(`IMapHeroRenderer` 侧的声明已删除),本任务不再动它。
(4) **不改的事**:不改 `MapHeroRenderer` 的动画算法(`tick` / `moveEntity` / `jumpEntity` / `generateJumpFn`)、不改 `addHeroMoving` 的贴图分支结构、不改 `setImage` / `setAlpha` / 跟随者族实现体、不接线 `client.ts`、不动 `action/move.ts`。既有注释一字不改(新增注释除外)。
(5) **原子提交**:`refactor(04-03): rebind hero renderer to IHeroLocation and existing hooks`(或执行环境约定的等价信息),提交内容仅含 `hero.ts` / `types.ts` / `manager.ts` 三个文件;**不得**把用户的并发未提交改动纳入提交。随后执行 `pnpm exec eslint --fix` 对三个文件,再 `pnpm exec eslint` 必须 0 错误。
@@ -303,6 +305,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- `MapHeroHook` 实现 `onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepStart`(可不实现)/ `onStepEnd` / `onSetFaceDir` / `onSetMoveDir`(可不实现)中的映射子集,且**不含**任何旧命名方法
+ - 三个返回 `Promise` 的钩子(`onMoveStart` / `onMoveEnd` / `onStepEnd`)均以 `async` 实现,签名与数据端 `IObjectMoverHooks` 可赋值(无 TS2416「void is not assignable to Promise」);`onSetPos` / `onSetFaceDir` 保持 `void`;`onStepEnd` 的未使用首参命名为 `_code`(满足 eslint `args: 'all'` + `argsIgnorePattern: '^_'`),且 `hero.ts` 未引入任何本任务用不到的 import(尤其 `ObjectSpecialStep`)
- `onStepEnd` 按 `ObjectMoveType` 分派到 `move` / `turn` / `setPosition` / `jumpTo`,并在开头以 `mover.currAnimDir` 设置动画方向;`onMoveStart` / `onMoveEnd` 分别驱动 `startMove` / `waitMoveEnd`
- `IMapHeroRenderer` 不再声明 `setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setHeroAnimateDirection` / `turn`,且 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy` 逐字保留
- `hero.ts` 仍含全部 D-18 / D-22 钩子方法与实现方法(含 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者三方法),且其注释与实现体未被改写
@@ -374,7 +377,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-04-03-01 | Tampering | `IMapHeroRenderer` 公共契约(`types.ts`) | high | mitigate | Task 2 删除 8 个外部驱动成员(D-20)并由 Task 2 / Task 3 的 D-20 契约门禁断言其不再出现;prohibitions 禁止新增任何接口成员;D-18 / D-22 成员设独立保留门禁,防误删 |
+| T-04-03-01 | Tampering | `IMapHeroRenderer` 公共契约(`types.ts`) | high | mitigate | Task 1 删除 `setHeroAnimateDirection`(随 `HeroAnimateDirection` 类型改名,无悬空引用)、Task 2 删除其余 7 个外部驱动成员(合计 8 个,D-20),并由 Task 2 / Task 3 的 D-20 契约门禁断言其不再出现;prohibitions 禁止新增任何接口成员;D-18 / D-22 成员设独立保留门禁,防误删 |
| T-04-03-02 | Repudiation | 钩子映射与旧名清除的可追溯性 | medium | mitigate | 逐钩子映射在 Task 0 汇报并经用户确认;Task 2 / Task 3 的静态改名门禁断言 6 个旧钩子方法名与 3 个旧类型名不复现、5 个现有钩子成员落地;映射关系写入 SUMMARY 人工复核段 |
| T-04-03-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-03-04 | Tampering | 用户并发的未提交改动 | high | mitigate | Task 1 步骤 (0b) 记录 porcelain 条目 + 各文件内容 `sha256` 摘要;Task 3 用同一固定命令复算并断言集合与摘要未变;prohibitions 明确不得回滚 / 暂存 / 提交 / 修改;Task 3 范围门禁额外断言无范围外生产文件改动 |
From 3d2d6dd0b19409d61ad798e7c039e0e1a73f0f4b Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 14:46:41 +0800
Subject: [PATCH 027/173] fix(04-03): revise plan per checker feedback
(token-scoped rename gates, remove unused FaceDirection import,
baseline-aware scope gate)
---
.../phases/04-render-adaptation/04-03-PLAN.md | 45 ++++++++++---------
1 file changed, 23 insertions(+), 22 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-03-PLAN.md b/.planning/phases/04-render-adaptation/04-03-PLAN.md
index 8ae465d..5a71c07 100644
--- a/.planning/phases/04-render-adaptation/04-03-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-03-PLAN.md
@@ -23,7 +23,7 @@ must_haves:
truths:
- "本计划的**第一个 block 点**是 Task 0 的 `blocking-human` 汇报关卡:先向用户汇报本计划要解决的问题、解决方案(含逐钩子映射表与拟新增/变更的成员命名清单)、范围边界与禁令,然后必须**停下等待**用户明确回复「可以执行」四个字;收到该回复前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
- "渲染端勇士本体改为**绑定勇士位置对象**:`MapHeroRenderer` 的勇士引用类型为 `IHeroLocation`(`packages-user/data-base/src/hero/types.ts:303`),`MapExtensionManager.heroMap` / `addHero` / `removeHero` 的键与参数类型同为 `IHeroLocation`;坐标经 `IObjectMovable.x` / `.y`(`data-common/src/common/mover.ts:48-51`),朝向经 `IHeroLocation.getCurrentFaceDirection()`(`data-base/src/hero/location.ts:65`)与 `IHeroMover.faceDirection` / `.moveDirection`(`mover.ts:243,245`),动画播放方向经 `IHeroMover.currAnimDir` 与 `ObjectAnimDirection`(`mover.ts:40,247`)"
- - "三个在范围文件内不再出现 `IHeroMoveController`、`IHeroMoveControllerHooks`、`HeroAnimateDirection`,也不再出现旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`(静态门禁机器判定,见 Task 3)"
+ - "三个在范围文件内不再出现**独立 token**(词边界判定)`IHeroMoveController`、`IHeroMoveControllerHooks`、`HeroAnimateDirection`,也不再出现旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`;类上保留的内部实现方法名 `setHeroAnimateDirection` 因不构成独立 token 不在判定范围(静态门禁机器判定,见 Task 3)"
- "钩子注册改为订阅**数据端现有钩子**(D-21):`hero.addHook(...)` 注册 `IHeroLocationHooks` 的订阅(`data-base/src/hero/types.ts:277`),`hero.mover.addHook(...)` 注册 `IObjectMoverHooks` 的订阅(`data-common/src/common/mover.ts:166`);注册对象实现的是**现有钩子成员名**(`onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onSetFaceDir` / `onSetMoveDir`),**不新增任何数据端公共接口**(D-21),也不修改任何数据端文件"
- "勇士渲染为**完全被动渲染**(D-20):`IMapHeroRenderer` 契约不再暴露允许外部驱动渲染状态的成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`);渲染状态只由数据端现有钩子推进,渲染端自身仍可修改它自己的内容(不与 D-18 / D-22 冲突)"
- "**D-18 / D-22 成员逐字保留**:`setImage` / `setAlpha` / `setFollowerAlpha` 与钩子方法中的 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,跟随者族的 `addFollower` / `removeFollower` / `removeAllFollowers` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers` 均不被删除、不被改写语义(贴图 / 不透明度按 D-18 待用户先改数据端;跟随者按 D-22 暂缓)"
@@ -81,7 +81,7 @@ Phase 4 第三个增量(D-17 的适配半程)——**勇士本体适配:
**大致解决方案:**
- **绑定对象换成位置对象**:`MapHeroRenderer` 持有 `IHeroLocation`;坐标读 `IObjectMovable.x/.y`;朝向读 `getCurrentFaceDirection()`(构造期初始化)与 `mover.faceDirection`;移动方向读 `mover.moveDirection`;动画播放方向读 `mover.currAnimDir` 并改用 `ObjectAnimDirection`(R-03/R-05/R-06)。
- **钩子改为订阅现有钩子**(D-21):在 `hero`(`IHeroLocationHooks`)与 `hero.mover`(`IObjectMoverHooks`)上各注册一次 `MapHeroHook`,用 `load()` / `unload()` 管理(原 `controller` 字段拆为两个控制器字段)。渲染端实现现有钩子成员名,旧名全部删除;`onStepEnd` 成为逐格渲染的驱动点(此时 `tile` 坐标已更新),按 `ObjectMoveType` 分派到既有渲染内部方法。
-- **被动化**(D-20):`IMapHeroRenderer` 去掉「外部驱动渲染状态」的成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`;其中 `setHeroAnimateDirection` 因形参类型 `HeroAnimateDirection` 在数据端已不存在,在 Task 1 随类型改名一并删除,其余 7 个在 Task 2 删除);这些方法退为渲染端内部实现,仅由钩子回调调用。
+- **被动化**(D-20):`IMapHeroRenderer` 去掉「外部驱动渲染状态」的成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`;其中 `setHeroAnimateDirection` 的**接口声明**因形参类型 `HeroAnimateDirection` 在数据端已不存在,在 Task 1 随类型改名一并删除(`MapHeroRenderer` 上的类内实现方法保留),其余 7 个的接口声明在 Task 2 删除);这些方法退为渲染端内部实现,仅由钩子回调调用。
- **范围硬边界**:贴图 / 不透明度(D-18)、跟随者(D-22)、`action/move.ts` 与移动控制(D-19)、`core.*`(D-12)、裸函数(D-11)一律不动。
**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「要解决的问题 / 大致解决方案(含逐钩子映射表)/ 拟新增或改写的成员命名清单 / 范围边界与禁令 / D-18 编译桥接的取舍」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式或配置模式如何均不得自动放行。故本计划 `autonomous: false`。
@@ -106,7 +106,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
| CONTEXT | D-17 | 下一步=勇士移动适配(先探索再适配) | 04-03 | COVERED | 04-02 完成探索;本计划即其适配实施(勇士本体部分) |
| CONTEXT | D-18 | `HeroRendering` 暂不处理,用户先改数据端 | 04-03 | COVERED(排除 + 编译桥接) | 贴图 / 不透明度成员逐字保留;`hero.image` 只做最小编译桥接,不重设来源;Task 0 汇报该取舍 |
| CONTEXT | D-19 | 移动控制由用户手动修改 | 04-03 | COVERED(排除) | `action/move.ts` 与移动控制 API 不在 `files_modified`;prohibitions 明列 |
-| CONTEXT | D-20 | 勇士渲染完全被动、旧驱动接口删除 | 04-03 | COVERED | `IMapHeroRenderer` 删除 8 个外部驱动成员(`setHeroAnimateDirection` 随 Task 1 的类型改名一并删除,其余 7 个由 Task 2 删除),改为钩子驱动 |
+| CONTEXT | D-20 | 勇士渲染完全被动、旧驱动接口删除 | 04-03 | COVERED | `IMapHeroRenderer` 删除 8 个外部驱动成员的**接口声明**(`setHeroAnimateDirection` 随 Task 1 的类型改名一并删除,其余 7 个由 Task 2 删除;对应类内实现方法保留),改为钩子驱动 |
| CONTEXT | D-21 | 走现有 `IObjectMoverHooks` / `IHeroLocationHooks`,不新增数据端接口 | 04-03 | COVERED | Task 1/2 的两次 `addHook` 注册与逐钩子映射即本条的落地 |
| CONTEXT | D-22 | 跟随者暂缓 | 04-03 | COVERED(排除) | 跟随者成员与钩子方法逐字保留、不删除不改写 |
| CONTEXT | D-10 | `HeroRendering` 删除、渲染状态存储放渲染端 | 04-03 | COVERED(一致性依据) | D-20 的被动化与 D-18 的保留范围保持一致;贴图来源的最终归属留给 D-18 增量 |
@@ -160,7 +160,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
② **大致解决方案(三个文件)**:`manager.ts` 与 `types.ts` 的勇士键 / 参数类型由 `IHeroMoveController` 改为 `IHeroLocation`;`hero.ts` 构造器绑定 `IHeroLocation`(坐标 `x`/`y`、朝向 `getCurrentFaceDirection()`、动画方向 `ObjectAnimDirection` + `mover.currAnimDir`),在 `hero`(`IHeroLocationHooks`)与 `hero.mover`(`IObjectMoverHooks`)上各 `addHook` 一次,`MapHeroHook` 改为实现现有钩子成员名;`IMapHeroRenderer` 按 D-20 去掉 8 个外部驱动成员(`startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setPosition` / `turn` / `setHeroAnimateDirection`;其中 `setHeroAnimateDirection` 因形参类型 `HeroAnimateDirection` 已不存在,在 Task 1 连同其 jsDoc 一并删除,其余 7 个在 Task 2 删除)。**逐钩子映射表须逐行汇报**:数据端钩子 → 触发点(`file:line`)→ 渲染端动作。核心几条:`IHeroLocationHooks.onSetPos(x, y)`(`data-base/src/hero/location.ts:59-63`)→ 非移动中时 `setPosition(x, y)`,移动中交给 `onStepEnd`;`IObjectMoverHooks.onMoveStart` → `startMove()`;`onMoveEnd` → `await waitMoveEnd()`;`onStepEnd(code, step, tile, mover)`(坐标已更新,`data-common/src/common/mover.ts:674-678`)→ 按 `step.type`(`ObjectMoveType`)分派:`Dir`/`DirFace` → `move(step.move, mover.currentSpeed)`(`DirFace` 另 `turn(step.face)`)、`Special` → `move(mover.moveDirection, mover.currentSpeed)`、`Face` → `turn(step.value)`、`Teleport` → `setPosition(tile.x, tile.y)`、`Jump` → `jumpTo(tile.x, tile.y, mover.currentSpeed, false)`、`AnimDir`/`Speed` → 不处理;动画方向统一在 `onStepEnd` 取 `mover.currAnimDir`;`onSetFaceDir` → `turn(dir)`;`onSetPos`(mover 级)→ `setPosition(x, y)`;`onSetMoveDir` / `onStepStart` → 不处理(并说明理由)。
- ③ **成员命名清单(须用户同意,AGENTS.md 命名规则)**:**新增**渲染端字段 `locationController`(`IHookController`)与 `moverController`(`IHookController>`)——取代原单一 `controller` 字段;**不新增任何类**(沿用既有 `MapHeroHook` 类名);**删除**旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`(**Task 1 即删除,不留存为类上的多余方法**)与 `IMapHeroRenderer` 的 8 个驱动成员(连同其配套 jsDoc,属删除代码而非改写注释;其中 `setHeroAnimateDirection` 在 Task 1 删除,其余 7 个在 Task 2 删除);**新增**的钩子方法名 `onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir` 逐字取自数据端现有钩子接口成员名(D-21,非自创命名),`onStepEnd` 的未使用首参按项目惯例命名为 `_code`(eslint `args: 'all'` + `argsIgnorePattern: '^_'`),一并请用户确认;**逐字保留** `setImage` / `setAlpha` / `setFollowerAlpha` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者族(D-18 / D-22)。备选命名(如用户不同意 `locationController` / `moverController`)请用户当场指定。
+ ③ **成员命名清单(须用户同意,AGENTS.md 命名规则)**:**新增**渲染端字段 `locationController`(`IHookController`)与 `moverController`(`IHookController>`)——取代原单一 `controller` 字段;**不新增任何类**(沿用既有 `MapHeroHook` 类名);**删除**旧钩子方法名 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`(**Task 1 即删除,不留存为类上的多余方法**)与 `IMapHeroRenderer` 的 8 个驱动成员的**接口声明**(连同其配套 jsDoc,属删除代码而非改写注释;其中 `setHeroAnimateDirection` 的接口声明在 Task 1 删除,其余 7 个的接口声明在 Task 2 删除;`MapHeroRenderer` 上对应的 8 个实现方法保留为类内内部实现,仍由钩子回调调用);**新增**的钩子方法名 `onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir` 逐字取自数据端现有钩子接口成员名(D-21,非自创命名),`onStepEnd` 的未使用首参按项目惯例命名为 `_code`(eslint `args: 'all'` + `argsIgnorePattern: '^_'`),一并请用户确认;**逐字保留** `setImage` / `setAlpha` / `setFollowerAlpha` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者族(D-18 / D-22)。备选命名(如用户不同意 `locationController` / `moverController`)请用户当场指定。
④ **D-18 编译桥接的取舍(须用户裁定)**:`hero` 类型改为 `IHeroLocation` 后,`addHeroMoving` 内读取贴图别名的 `hero.image`(`hero.ts:115,119,121`)失去数据端对位。本计划主张**只做最小编译桥接**:把该读取收敛为一处局部变量、用项目既有 `// @ts-expect-error 需要重构` 惯用法标注,运行分支与语义保持不变(`undefined` → 既有 `logger.warn(88)` 分支),**不重新设计贴图来源**(贴图来源与 `IHeroRendering` 属 D-18,等用户先改数据端)。请用户确认该桥接,或指定其它处理方式。
@@ -228,10 +228,10 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
(0c) **类型门禁基线**:执行 `pnpm exec vue-tsc --noEmit`(整仓会以非 0 退出属预期——规划时有 122 条诊断)并记录 `client-modules` 的命中行;规划时恰为 2 条、与勇士移动无关:`packages-user/client-modules/src/client.ts(66,9): error TS2554: Expected 1 arguments, but got 0.` 与 `packages-user/client-modules/src/render/ui/load.tsx(77,31): error TS2504: Type 'ITaskManager' must have a 'Symbol.asyncIterator]()' method that returns an async iterator.`。同时确认三个在范围文件**当前 0 条诊断**。把这两条原样抄入 `04-03-SUMMARY.md` 的「类型门禁基线」段;**若与记录不一致,停止并汇报**(说明用户并发改动改变了基线,不得静默调整门禁)。
(0d) 建立 `04-03-SUMMARY.md` 骨架(含上面三段 + 后续任务要填的空节),保持 CRLF。
(1) **`manager.ts`(绑定层)**:删除首行 `// @ts-expect-error 需要重构`,把 `import { IHeroMoveController } from '@user/data-base'` 改为 `import { IHeroLocation, IMapLayer } from '@user/data-base'`(合并第三行的 `IMapLayer` import,避免重复 import 语句;若 lint 要求分行则按 lint 结果排布);`readonly heroMap: Map`;`addHero(state: IHeroLocation, layer: IMapLayer)`;`removeHero(state: IHeroLocation)`。`addDoor` / `removeDoor` / `addText` / `removeText` / `destroy` 一字不改。
- (2) **`types.ts`(契约层:绑定类型 + 删除 `setHeroAnimateDirection` 声明)**:从 `@user/data-common` 的 import 中删除 `HeroAnimateDirection` 与 `IHeroMoveController`(连带其两行 `// @ts-expect-error 需要重构` 标记;删除后该 import 仅剩 `FaceDirection`,**不需要任何替代类型**),改为从 `@user/data-base` 导入 `IHeroLocation`(与既有 `IMapLayer` 合并);`IMapExtensionManager.heroMap: Map`、`addHero(state: IHeroLocation, layer: IMapLayer)`、`removeHero(state: IHeroLocation)`;**删除 `IMapHeroRenderer.setHeroAnimateDirection` 的成员声明及其配套 jsDoc**(原 `:143-147`;其形参类型 `HeroAnimateDirection` 在数据端已不存在,无法保留——若只删 import 而不删该成员,`types.ts` 会残留悬空引用并触发 TS2304):该成员本任务即删除,其实现方法 `MapHeroRenderer.setHeroAnimateDirection` 保留在类上(见 (3f));`IMapHeroRenderer` 的**其余 7 个驱动成员**(`setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `turn`)连同其 jsDoc 在 Task 2 删除,本任务不动。`Map<` / `addHero` / `removeHero` 的 jsDoc **不改写**(参数名与含义不变)。
+ (2) **`types.ts`(契约层:绑定类型 + 删除 `setHeroAnimateDirection` 声明)**:从 `@user/data-common` 的 import 中删除 `HeroAnimateDirection` 与 `IHeroMoveController`(连带其两行 `// @ts-expect-error 需要重构` 标记;删除后该 import 仅剩 `FaceDirection`,**不需要任何替代类型**;该 `FaceDirection` 在本任务仍被 `IMapHeroRenderer.move` / `.turn` 使用,Task 2 删除这两个成员后会使其失去使用点,届时在 Task 2 步骤 (2) 一并删除该 import),改为从 `@user/data-base` 导入 `IHeroLocation`(与既有 `IMapLayer` 合并);`IMapExtensionManager.heroMap: Map`、`addHero(state: IHeroLocation, layer: IMapLayer)`、`removeHero(state: IHeroLocation)`;**删除 `IMapHeroRenderer.setHeroAnimateDirection` 的成员声明及其配套 jsDoc**(原 `:143-147`;其形参类型 `HeroAnimateDirection` 在数据端已不存在,无法保留——若只删 import 而不删该成员,`types.ts` 会残留悬空引用并触发 TS2304):该成员本任务即删除,其实现方法 `MapHeroRenderer.setHeroAnimateDirection` 保留在类上(见 (3f));`IMapHeroRenderer` 的**其余 7 个驱动成员**(`setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `turn`)连同其 jsDoc 在 Task 2 删除,本任务不动。`Map<` / `addHero` / `removeHero` 的 jsDoc **不改写**(参数名与含义不变)。
(3) **`hero.ts` import 与绑定(tracer 主路径)**:
(3a) import 改造(**只在本任务内引入本任务用得到的名字**:项目 eslint 把 `@typescript-eslint/no-unused-vars` 设为 `error` 且 `args: 'all'`,未使用的 import 会直接报错,`--fix` 也不会删除未使用 import;反之 Task 2 也不得保留只在 Task 1 用过的名字):`@user/data-common` 侧删除 `HeroAnimateDirection` / `IHeroMoveController` / `IHeroMoveControllerHooks`(连带 3 行 `// @ts-expect-error 需要重构` 标记),新增 `ObjectAnimDirection`(本任务即消费:`:53` / `:97` / `:170` / `:372` 四处)与 `IObjectMoverHooks`(本任务即消费:`moverController` 字段类型);**不要**在本任务引入 `IObjectMover` / `ObjectMoveStep` / `ObjectMoveType`(本任务无使用点,留到 Task 2 首次使用时再引入);**不要**引入 `ObjectSpecialStep`(两个任务都不需要:`Special` 步的移动方向已由数据端 `ObjectMover.prepareStep` 写入 `mover.moveDirection`,渲染端无需读取 `step.direction`)。`@user/data-base` 侧把 `import { IMapLayer }` 扩为 `import { IHeroLocation, IHeroLocationHooks, IMapLayer }`(`IHeroLocation` / `IHeroLocationHooks` / `IHeroMover` 均可经该 barrel 取得,见 `data-base/src/hero/index.ts:10` 与 `data-base/src/index.ts:3`)。
- (3b) 实体类型与**全部取值点**:`HeroRenderEntity.animateDirection` 由 `HeroAnimateDirection` 改为 `ObjectAnimDirection`(`:53`);两处实体初始化的默认值 `HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:97` 构造器、`:372` `addFollower`);`tick` 内的播放方向比较 `v.animateDirection === HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:170`)。**四处取值点必须全部改到**,否则改名门禁(三文件不得出现 `HeroAnimateDirection`)与 scoped 类型门禁(该名字已从 import 移除,任何残留即 TS2304)都会失败。
+ (3b) 实体类型与**全部取值点**:`HeroRenderEntity.animateDirection` 由 `HeroAnimateDirection` 改为 `ObjectAnimDirection`(`:53`);两处实体初始化的默认值 `HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:97` 构造器、`:372` `addFollower`);`tick` 内的播放方向比较 `v.animateDirection === HeroAnimateDirection.Forward` → `ObjectAnimDirection.Forward`(`:170`)。**四处取值点必须全部改到**,否则改名门禁(三文件不得出现**独立 token** `HeroAnimateDirection`;保留的类内实现方法名 `setHeroAnimateDirection` 不属于被禁 token,见 (3f))与 scoped 类型门禁(该名字已从 import 移除,任何残留即 TS2304)都会失败。
(3c) 构造器:`readonly hero: IHeroMoveController` → `readonly hero: IHeroLocation`(`:79`);`direction: hero.direction` → `direction: hero.getCurrentFaceDirection()`(`:89`);钩子注册由单一 `controller` 改为**两个控制器字段**:`readonly locationController: IHookController` 与 `readonly moverController: IHookController>`(替换原 `readonly controller: IHookController` 字段,`:61`);构造体内 `const hook = new MapHeroHook(this); this.locationController = hero.addHook(hook); this.locationController.load(); this.moverController = hero.mover.addHook(hook); this.moverController.load();`(同一 hook 实例注册到两个 hookable,`Hookable.addHook` 按 hookable 实例分别存储,见 `packages/common/src/hook.ts:24-31`);`destroy()`(`:452-454`)改为 `this.locationController.unload(); this.moverController.unload();`。
(3d) `addHeroMoving`(`:110-132`):参数 `hero: IHeroMoveController` → `IHeroLocation`;`degradeFace(hero.direction)` → `degradeFace(hero.getCurrentFaceDirection())`(`:125`);`hero.image` 的三处读取(`:115,119,121`)按 Task 0 确认的 **D-18 最小编译桥接**处理——收敛为一处局部读取(如 `const imageAlias = hero.image;`)并在其正上方加一行**新增**注释 `// @ts-expect-error 需要重构(贴图别名来源属 D-18,待用户先改数据端后处理)`,其后三处改用 `imageAlias`;分支结构与既有 `logger.warn(88/89)` 行为保持不变;**不得**改写既有注释、不得重设贴图来源。
(3e) `setImage`(`:191-196`):只把内部的 `degradeFace(this.hero.direction)` 改为 `degradeFace(this.hero.getCurrentFaceDirection())`(同一朝向读取口径的编译修复),其余一字不改(D-18:贴图语义不动)。
@@ -244,8 +244,8 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
node -e "const {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 cm=lines.filter(l=>l.includes('client-modules')).map(l=>l.replace(/^.*client-modules[\\/]src[\\/]/,''));const base=['client.ts(66,9): error TS2554','render/ui/load.tsx(77,31): error TS2504'];const missing=base.filter(b=>!cm.some(l=>l.startsWith(b)));const extra=cm.filter(l=>!base.some(b=>l.startsWith(b)));if(missing.length||extra.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 '+cm.length)"
non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS"(三个在范围文件仍有类型错误)/ "CLIENT-MODULES ERROR SET CHANGED"(渲染端类型错误集相对记录基线发生增减,含连带打断它处)
- node -e "const fs=require('fs');const files=['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 src=files.map(f=>fs.readFileSync(f,'utf8')).join('\n');const banned=['IHeroMoveControllerHooks','IHeroMoveController','HeroAnimateDirection','onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const hit=banned.filter(t=>src.includes(t));if(hit.length){console.error('LEGACY SYMBOLS REMAIN '+hit.join(','));process.exit(1)}const need=['IHeroLocation','ObjectAnimDirection','getCurrentFaceDirection','mover.addHook','locationController','moverController'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('MISSING NEW BINDINGS '+miss.join(','));process.exit(1)}console.log('OK static rename gate')"
- non-zero exit, 或 stderr 含 "LEGACY SYMBOLS REMAIN"(旧类型名 / 旧钩子方法名仍出现在三个在范围文件,含注释)/ "MISSING NEW BINDINGS"(绑定、动画方向或两个钩子控制器未落地)
+ node -e "const fs=require('fs');const files=['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 src=files.map(f=>fs.readFileSync(f,'utf8')).join('\n');const banned=['IHeroMoveControllerHooks','IHeroMoveController','HeroAnimateDirection','onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const hasTok=(s,t)=>new RegExp('(?hasTok(src,t));if(hit.length){console.error('LEGACY SYMBOLS REMAIN '+hit.join(','));process.exit(1)}const need=['IHeroLocation','ObjectAnimDirection','getCurrentFaceDirection','mover.addHook','locationController','moverController'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('MISSING NEW BINDINGS '+miss.join(','));process.exit(1)}console.log('OK static rename gate')"
+ non-zero exit, 或 stderr 含 "LEGACY SYMBOLS REMAIN"(旧类型名 / 旧钩子方法名仍以**独立 token**(词边界 lookbehind/lookahead)出现在三个在范围文件,含注释;保留的类内实现方法名 `setHeroAnimateDirection` 中的 `HeroAnimateDirection` 前接标识符字符 `t`,不构成独立 token,不触发本门禁)/ "MISSING NEW BINDINGS"(绑定、动画方向或两个钩子控制器未落地)
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
non-zero exit 或输出含 "error"(ESLint / Prettier 违规;CRLF 由 `endOfLine: crlf` 在 `--fix` 时统一)
node -e "const fs=require('fs');const files=['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'];for(const f of files){const s=fs.readFileSync(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')"
@@ -257,12 +257,12 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- `hero.ts` 的构造器参数为 `IHeroLocation`,构造期朝向取自 `hero.getCurrentFaceDirection()`;`HeroRenderEntity.animateDirection` 的声明(`:53`)、两处初始化默认值(`:97` / `:372`)与 `tick` 内的比较(`:170`)均为 `ObjectAnimDirection`
- `hero.ts` 同时存在 `hero.addHook(` 与 `hero.mover.addHook(`,并存在 `locationController` / `moverController` 两个字段;`destroy()` 对两者都 `unload()`
- `MapHeroHook` 声明为 `Partial>`,且已实现 `onSetPos`;类上**不再存在任何旧命名方法**(`onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero` 本任务即全部删除,不留存为多余方法;Task 2 再按映射表补上现有钩子成员名)
- - 三个文件不再出现 `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` / `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`
+ - 三个文件不再出现**独立 token**(词边界判定)`IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` / `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`;类上保留的内部实现方法名 `setHeroAnimateDirection`(形参类型已改为 `ObjectAnimDirection`)不是被禁 token,不在判定范围内
- `hero.ts` 中 `hero.image` 的读取已收敛为一处(带新增的 `// @ts-expect-error 需要重构(…D-18…)` 标注),既有 `logger.warn(88/89/90)` 分支结构未变;`setImage` / `setAlpha` / 跟随者族成员未被删除
- `04-03-SUMMARY.md` 含「执行前核对」(步骤 0a 七条,每条带 `file:line`)、「执行前基线」(porcelain 条目 + `基线摘要 sha256`)与「类型门禁基线」(两条 client-modules 诊断原样)三段,全文 CRLF
- 四条 `` 全部转绿:scoped 类型门禁(三个文件 0 条、client-modules 错误集 == 基线)、静态改名门禁、eslint 0 错误、CRLF 门禁
- 绑定层(`IHeroLocation`)、构造器两个 `addHook` 注册、`onSetPos` 一条通路与三文件的类型 / import / 成员名改造全部落地,四条机器门禁转绿,且三个在范围文件不再出现任何旧类型名与旧钩子方法名
+ 绑定层(`IHeroLocation`)、构造器两个 `addHook` 注册、`onSetPos` 一条通路与三文件的类型 / import / 成员名改造全部落地,四条机器门禁转绿,且三个在范围文件不再出现任何旧类型名 token 与旧钩子方法名(保留的类内实现方法名 `setHeroAnimateDirection` 除外)
@@ -288,7 +288,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- `onSetPos`(mover 级四参调用)与 location 级两参调用共用同一个两参方法:JS 会忽略多余实参,本方法的「移动中 return」守卫同时覆盖两条触发路径;该方法保持 `void` 返回(`IHeroLocationHooks.onSetPos` 与 `IObjectMoverHooks.onSetPos` 在数据端都是 `void`)。
- **不实现**(在 `04-03-SUMMARY.md` 写明理由):`onStepStart`(渲染只在新坐标就绪后推进,避免重复驱动)、`onSetMoveDir`(渲染朝向只随 `onSetFaceDir` 变化;`moveDirection` 已由 `onStepEnd` 读取)、`IHeroLocationHooks.onSetFloor`(楼层切换需要重绑图层的接线路径,本增量不接线 `client.ts`;登记为后续增量事项,不在本计划实现)。
- **逐字保留(D-18 / D-22,不得删除、改写语义或改名)**:`onSetImage(image: ImageIds)`、`onSetAlpha(alpha: number)`、`onSetFollowerAlpha(identifier: string, alpha: number)`、`onAddFollower(follower: number, identifier: string)`、`onRemoveFollower(identifier: string, animate: boolean)`、`onRemoveAllFollowers()`——它们是本类上的多余方法(不再属于任何现有钩子接口的成员),保持原样并**不注册到任何数据端对象**;其现有 jsDoc / 注释一字不改。是否在后续增量(D-18 / D-22 解开时)迁到数据端对象上,留待用户裁定。
- (2) **`IMapHeroRenderer` 按 D-20 裁剪收口(`types.ts`)**:删除以下 **7 个**成员声明**及其配套 jsDoc**(属删除代码,Task 0 已获用户确认):`setPosition(x, y)`(`:92`)、`startMove()`(`:97`)、`waitMoveEnd(waitFollower)`(`:103`)、`stopMove()`(`:108`)、`move(direction, time)`(`:114`)、`jumpTo(x, y, time, waitFollower)`(`:123`)、`turn(direction?)`(`:153`)。D-20 的第 8 个成员 `setHeroAnimateDirection` 已在 Task 1 随类型改名删除(其声明连同 jsDoc 已不在 `types.ts`),**本任务不得为它重新引入任何声明**,故本任务结束时接口内的 8 个外部驱动成员全部不存在。保留 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy`(D-18 / D-22 成员,逐字不动)。不得新增任何成员。
+ (2) **`IMapHeroRenderer` 按 D-20 裁剪收口(`types.ts`)**:删除以下 **7 个**成员声明**及其配套 jsDoc**(属删除代码,Task 0 已获用户确认):`setPosition(x, y)`(`:92`)、`startMove()`(`:97`)、`waitMoveEnd(waitFollower)`(`:103`)、`stopMove()`(`:108`)、`move(direction, time)`(`:114`)、`jumpTo(x, y, time, waitFollower)`(`:123`)、`turn(direction?)`(`:153`)。D-20 的第 8 个成员 `setHeroAnimateDirection` 已在 Task 1 随类型改名删除(其声明连同 jsDoc 已不在 `types.ts`),**本任务不得为它重新引入任何声明**,故本任务结束时接口内的 8 个外部驱动成员全部不存在。保留 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy`(D-18 / D-22 成员,逐字不动)。不得新增任何成员。**同步收口未使用 import**:`move`(`:114`)与 `turn`(`:153`)是 `types.ts` 内 `FaceDirection` 的全部使用点,删除后 Task 1 保留的 `import { FaceDirection } from '@user/data-common';` 会成为未使用 import(项目 eslint `@typescript-eslint/no-unused-vars` 为 error 且无 fixer,会让本任务与 Task 3 的 eslint 门禁失败)。故本步骤必须先用 `rg -n "FaceDirection" packages-user/client-modules/src/render/map/extension/types.ts` 复核确认删除 `move` / `turn` 后 `types.ts` 内已无 `FaceDirection` 引用,随后**删除该 import 语句**(删除后 `types.ts` 不再从 `@user/data-common` import 任何名字);若复核发现仍有其它使用点则保留该 import 并在 `04-03-SUMMARY.md` 说明。
(3) **`MapHeroRenderer` 侧配套**:上述 8 个驱动方法(含 Task 1 已从 `IMapHeroRenderer` 删除声明的 `setHeroAnimateDirection`)在类上**保留为内部实现**(不删除、不改名),仅由 `MapHeroHook` 的回调调用;其可见性/实现细节保持不变。`waitMoveEnd()` 的实现签名(无参)与接口原声明的参数落差(04-02「未能从阅读确定」第 6 条)随接口成员移除而消解,在 `04-03-SUMMARY.md` 记为「由 D-20 契约裁剪消解」。`MapHeroRenderer.setHeroAnimateDirection(direction: ObjectAnimDirection)` 已在 Task 1 把形参类型改为 `ObjectAnimDirection`、并保留为类上的内部实现方法(`IMapHeroRenderer` 侧的声明已删除),本任务不再动它。
(4) **不改的事**:不改 `MapHeroRenderer` 的动画算法(`tick` / `moveEntity` / `jumpEntity` / `generateJumpFn`)、不改 `addHeroMoving` 的贴图分支结构、不改 `setImage` / `setAlpha` / 跟随者族实现体、不接线 `client.ts`、不动 `action/move.ts`。既有注释一字不改(新增注释除外)。
(5) **原子提交**:`refactor(04-03): rebind hero renderer to IHeroLocation and existing hooks`(或执行环境约定的等价信息),提交内容仅含 `hero.ts` / `types.ts` / `manager.ts` 三个文件;**不得**把用户的并发未提交改动纳入提交。随后执行 `pnpm exec eslint --fix` 对三个文件,再 `pnpm exec eslint` 必须 0 错误。
@@ -308,6 +308,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- 三个返回 `Promise` 的钩子(`onMoveStart` / `onMoveEnd` / `onStepEnd`)均以 `async` 实现,签名与数据端 `IObjectMoverHooks` 可赋值(无 TS2416「void is not assignable to Promise」);`onSetPos` / `onSetFaceDir` 保持 `void`;`onStepEnd` 的未使用首参命名为 `_code`(满足 eslint `args: 'all'` + `argsIgnorePattern: '^_'`),且 `hero.ts` 未引入任何本任务用不到的 import(尤其 `ObjectSpecialStep`)
- `onStepEnd` 按 `ObjectMoveType` 分派到 `move` / `turn` / `setPosition` / `jumpTo`,并在开头以 `mover.currAnimDir` 设置动画方向;`onMoveStart` / `onMoveEnd` 分别驱动 `startMove` / `waitMoveEnd`
- `IMapHeroRenderer` 不再声明 `setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setHeroAnimateDirection` / `turn`,且 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy` 逐字保留
+ - `types.ts` 删除 `move` / `turn` 后无未使用 import:`import { FaceDirection } from '@user/data-common';` 已随之删除(已 grep 复核 `types.ts` 内无其余 `FaceDirection` 引用),`pnpm exec eslint` 对三个文件 0 错误
- `hero.ts` 仍含全部 D-18 / D-22 钩子方法与实现方法(含 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者三方法),且其注释与实现体未被改写
- 四条 `` 全绿;未新增任何接口成员;三个文件已一个原子提交入库,且提交未包含用户并发改动
@@ -325,21 +326,21 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- .planning/STATE.md:217-236(Phase 04 既有决策记录格式)
- (1) **范围门禁(先跑,防止越界改动)**:执行 `git status --porcelain`,确认**生产代码**相对 Task 1 记录的基线只多出 `packages-user/client-modules/src/render/map/extension/{hero.ts,types.ts,manager.ts}` 三条改动;特别断言 `packages-user/client-modules/src/action/move.ts`(D-19)、`packages-user/client-base/**`、`packages-user/data-common/**`、`packages-user/data-base/**`、`packages-user/data-system/**`、`packages-user/data-state/**`、`packages/**` 均无本计划引入的新改动。若出现第四个生产文件的改动:停止、汇报、修订计划(不得自行扩面)。
+ (1) **范围门禁(基线感知,先跑,防止越界改动)**:执行 `git status --porcelain`,把其中的**生产路径**(`^packages(-user)?/`)与 `04-03-SUMMARY.md`「执行前基线」段记录的 porcelain 条目做**差集**:只有**基线之外**、且不属于三个在范围文件(`render/map/extension/{hero.ts,types.ts,manager.ts}`)的生产改动才算越界。Task 1 记录的并发用户改动(`client-base/src/load/*`、`data-base/src/load/*`、`data-state/src/{core,types}.ts`、`packages/loader/src/*` 等 8 个生产文件)必须原样放行——它们同时受步骤 (2) 的基线摘要门禁约束。特别断言 `packages-user/client-modules/src/action/move.ts`(D-19)、`packages-user/client-base/**`、`packages-user/data-common/**`、`packages-user/data-base/**`、`packages-user/data-system/**`、`packages-user/data-state/**`、`packages/**` 均无**基线之外**的新改动。若出现基线之外的第四个生产文件的改动:停止、汇报、修订计划(不得自行扩面)。
(2) **并发改动基线门禁**:用与 Task 1 步骤 (0b) 完全相同的固定命令重算(排除 `packages-user/client-modules/src/render/map/extension/` 与 `.planning/phases/04-render-adaptation/` 两个前缀),断言 porcelain 条目集合与内容摘要 `sha256` 均与 `04-03-SUMMARY.md`「执行前基线」段记录一致——即用户的并发未提交改动**未被回滚 / 暂存 / 提交 / 修改**。
- (3) **复跑全部机器门禁**:scoped 类型门禁(三个文件 0 条 + `client-modules` 错误集 == 两条记录基线)、静态改名门禁(`IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` 与 6 个旧钩子方法名不复现;`IHeroLocation` / `ObjectAnimDirection` / `getCurrentFaceDirection` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController` 均存在)、D-20 契约门禁、D-18 / D-22 保留门禁、`pnpm exec eslint` 三文件 0 错误、CRLF 门禁。
+ (3) **复跑全部机器门禁**:scoped 类型门禁(三个文件 0 条 + `client-modules` 错误集 == 两条记录基线)、静态改名门禁(`IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` 与 6 个旧钩子方法名以**独立 token**(词边界)不复现,保留的类内实现方法名 `setHeroAnimateDirection` 不触发;`IHeroLocation` / `ObjectAnimDirection` / `getCurrentFaceDirection` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController` 均存在)、D-20 契约门禁、D-18 / D-22 保留门禁、`pnpm exec eslint` 三文件 0 错误、CRLF 门禁。
(4) **人工复核(写进 SUMMARY 的人工 UAT 段,逐条给结论)**:① 逐钩子映射是否与 Task 0 汇报并经用户确认的映射表一致(列出每条数据端触发点 `file:line` ↔ 渲染端动作);② `onSetPos` 的「移动中 return」守卫是否正确(移动中的逐格定位不应瞬时吸附,而应由 `onStepEnd` 动画推进);③ `onStepEnd` 的 `ObjectMoveType` 分派是否覆盖 `Dir` / `DirFace` / `Special` / `Face` / `Teleport` / `Jump`,`AnimDir` / `Speed` 是否确无渲染动作;④ D-18 编译桥接是否只收敛了一处 `hero.image` 读取、既有 warn 分支是否未变;⑤ D-18 / D-22 成员是否**逐字**保留(对照 Task 2 门禁输出);⑥ 是否有任何新公共接口成员被引入(应为零);⑦ 运行时 UAT 不可得的理由是否已写明(`addHero` 未被接线,`client.ts:148-152` 仍为注释)。
(5) **SUMMARY 定稿**:补齐 `04-04` 无(不存在)——写 `.planning/phases/04-render-adaptation/04-03-SUMMARY.md`,含:改动三文件的清单与职责、`#04-02-R-01..12` 的对账结论 → 本计划处置对照表(哪几条被本计划覆盖、哪几条属 D-19 / D-18 / D-22 / 后续增量)、执行前核对七条结论、五类门禁的实际输出摘要(含 `client-modules` 基线两条原样、scoped 0 条)、人工复核七条、未实现项清单(`onStepStart` / `onSetMoveDir` / `onSetFloor` / `client.ts` 接线 / `hero.image` 来源)、以及 REND-01 / REND-02 仍为 Pending 的明确声明。全文 CRLF。
(6) **提交**:`docs(04-03): record execution state`,仅含 `04-03-SUMMARY.md`(三个生产文件的改动已在 Task 2 提交;ROADMAP / STATE 的元数据更新由 orchestrator 的元数据提交处理,本任务不代劳)。
(7) **不做的事**:不接线 `client.ts`、不改 `action/move.ts`、不新增测试文件(渲染端无测试设施)、不改任何数据端文件、不修任何 ③ 多余旧路径或其它 ① 错配(`#04-01-M-06..09`)。
- node -e "const {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 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)).filter(p=>!/\.planning\//.test(p));const extra=prod.filter(p=>allow.indexOf(p)<0);const missing=allow.filter(p=>prod.indexOf(p)<0);if(extra.length){console.error('OUT-OF-SCOPE FILE MODIFIED '+extra.join(' ; '));process.exit(1)}console.log('OK scope: in-scope modified '+missing.length+'/3 (committed ones absent) extra 0')"
- non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 `files_modified` 之外的生产文件改动,含 D-19 的 `action/move.ts` 或任何数据端文件)
+ 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-03-SUMMARY.md','utf8');const base=(t.match(/^[ MARCUD?!]{2} [^\r\n]*/gm)||[]).map(s=>s.replace(/\s+$/,'')).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)).filter(p=>!/\.planning\//.test(p));const extra=prod.filter(p=>allow.indexOf(p)<0&&base.indexOf(p)<0);const missing=allow.filter(p=>prod.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; in-scope modified '+missing.length+'/3 (committed ones absent)')"
+ non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 `files_modified` 之外、且**不在 Task 1 记录的并发改动基线内**的生产文件改动,含 D-19 的 `action/move.ts`、任何数据端文件或 `packages/**` 的新改动;基线内的用户并发改动不触发本门禁)/ "GIT STATUS FAILED"
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-03-SUMMARY.md','utf8');const rec=(t.match(/^[ MARCUD?!]{2} [^\r\n]*/gm)||[]).map(s=>s.replace(/\s+$/,'')).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)"
non-zero exit, 或 stderr 含 "NO BASELINE DIGEST" / "BASELINE ENTRY SET CHANGED" / "BASELINE DIGEST CHANGED"(用户并发的未提交改动被本计划回滚 / 暂存 / 提交 / 修改 —— 违反并发改动禁令)
- 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 banned=['IHeroMoveControllerHooks','IHeroMoveController','HeroAnimateDirection','onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const hit=banned.filter(t=>src.includes(t));if(hit.length){console.error('LEGACY SYMBOLS REMAIN '+hit.join(','));process.exit(1)}const need=['IHeroLocation','ObjectAnimDirection','getCurrentFaceDirection','mover.addHook','hero.addHook','locationController','moverController'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('MISSING NEW BINDINGS '+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 d20=['startMove(): void;','waitMoveEnd(','stopMove(): void;','move(direction: FaceDirection, time: number): Promise;','jumpTo(','setPosition(x: number, y: number): void;','turn(direction?: FaceDirection): void;','setHeroAnimateDirection('];const dHit=d20.filter(x=>seg.includes(x));if(dHit.length){console.error('D20 DRIVE MEMBERS STILL EXPOSED '+dHit.join(' , '));process.exit(1)}const d18=['setImage(image: ITexture): void;','addFollower(image: number, id: string): void;','removeFollower(follower: string, animate: boolean): Promise;','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)}const h=fs.readFileSync(S+'hero.ts','utf8');const hNeed=['onSetImage(','onSetAlpha(','onSetFollowerAlpha(','onAddFollower(','onRemoveFollower(','onRemoveAllFollowers(','onSetPos(','onMoveStart(','onMoveEnd(','onStepEnd(','onSetFaceDir('];const hMiss=hNeed.filter(x=>!h.includes(x));if(hMiss.length){console.error('D18 D22 OR HOOK MEMBERS MISSING '+hMiss.join(' , '));process.exit(1)}console.log('OK final static gate: rename + D20 + D18/D22')"
- non-zero exit, 或 stderr 含 "LEGACY SYMBOLS REMAIN" / "MISSING NEW BINDINGS" / "D20 DRIVE MEMBERS STILL EXPOSED" / "D18 D22 CONTRACT MEMBERS MISSING" / "D18 D22 OR HOOK MEMBERS MISSING"
+ 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 banned=['IHeroMoveControllerHooks','IHeroMoveController','HeroAnimateDirection','onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const hasTok=(s,t)=>new RegExp('(?hasTok(src,t));if(hit.length){console.error('LEGACY SYMBOLS REMAIN '+hit.join(','));process.exit(1)}const need=['IHeroLocation','ObjectAnimDirection','getCurrentFaceDirection','mover.addHook','hero.addHook','locationController','moverController'];const miss=need.filter(t=>!src.includes(t));if(miss.length){console.error('MISSING NEW BINDINGS '+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 d20=['startMove(): void;','waitMoveEnd(','stopMove(): void;','move(direction: FaceDirection, time: number): Promise;','jumpTo(','setPosition(x: number, y: number): void;','turn(direction?: FaceDirection): void;','setHeroAnimateDirection('];const dHit=d20.filter(x=>seg.includes(x));if(dHit.length){console.error('D20 DRIVE MEMBERS STILL EXPOSED '+dHit.join(' , '));process.exit(1)}const d18=['setImage(image: ITexture): void;','addFollower(image: number, id: string): void;','removeFollower(follower: string, animate: boolean): Promise;','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)}const h=fs.readFileSync(S+'hero.ts','utf8');const hNeed=['onSetImage(','onSetAlpha(','onSetFollowerAlpha(','onAddFollower(','onRemoveFollower(','onRemoveAllFollowers(','onSetPos(','onMoveStart(','onMoveEnd(','onStepEnd(','onSetFaceDir('];const hMiss=hNeed.filter(x=>!h.includes(x));if(hMiss.length){console.error('D18 D22 OR HOOK MEMBERS MISSING '+hMiss.join(' , '));process.exit(1)}console.log('OK final static gate: rename + D20 + D18/D22')"
+ non-zero exit, 或 stderr 含 "LEGACY SYMBOLS REMAIN"(旧类型名 / 旧钩子方法名仍以**独立 token**(词边界判定)出现;保留的类内实现方法名 `setHeroAnimateDirection` 不触发)/ "MISSING NEW BINDINGS" / "D20 DRIVE MEMBERS STILL EXPOSED" / "D18 D22 CONTRACT MEMBERS MISSING" / "D18 D22 OR HOOK MEMBERS MISSING"
node -e "const {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 cm=lines.filter(l=>l.includes('client-modules')).map(l=>l.replace(/^.*client-modules[\\/]src[\\/]/,''));const base=['client.ts(66,9): error TS2554','render/ui/load.tsx(77,31): error TS2504'];const extra=cm.filter(l=>!base.some(b=>l.startsWith(b)));const missing=base.filter(b=>!cm.some(l=>l.startsWith(b)));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 '+cm.length)"
non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS" / "CLIENT-MODULES ERROR SET CHANGED"(终验类型门禁未过)
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
@@ -350,7 +351,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线 / 人工复核 / 需求 Pending 声明 / 运行时不可得说明)
- - 范围门禁通过:无 `files_modified` 之外的生产文件改动(`action/move.ts`、任何数据端包、`client-base`、`packages` 均无本计划引入的改动)
+ - 范围门禁(基线感知)通过:`git status --porcelain` 的生产路径与 Task 1 记录的基线条目差集为空——即除三个在范围文件与基线内的并发用户改动外,无其它生产文件改动(`action/move.ts`、任何数据端包、`client-base`、`packages` 均无基线之外的新改动)
- 并发改动基线门禁通过:用户并发未提交改动的 porcelain 条目集合与内容 `sha256` 均与记录的基线一致
- 静态改名门禁、D-20 契约门禁、D-18 / D-22 保留门禁、scoped 类型门禁(三文件 0 条、`client-modules` 错误集 == 两条基线)、eslint 0 错误、CRLF 门禁(三个源文件 + SUMMARY)全部转绿
- `04-03-SUMMARY.md` 含「执行前核对」七条(带 `file:line`)、「执行前基线」(porcelain + `sha256`)、「类型门禁基线」(两条原样)、`#04-02-R-01..12` 对账 → 本计划处置对照、五类门禁输出摘要、人工复核七条、未实现项清单、REND-01 / REND-02 仍为 Pending 与运行时 UAT 不可得的明确说明
@@ -377,7 +378,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-04-03-01 | Tampering | `IMapHeroRenderer` 公共契约(`types.ts`) | high | mitigate | Task 1 删除 `setHeroAnimateDirection`(随 `HeroAnimateDirection` 类型改名,无悬空引用)、Task 2 删除其余 7 个外部驱动成员(合计 8 个,D-20),并由 Task 2 / Task 3 的 D-20 契约门禁断言其不再出现;prohibitions 禁止新增任何接口成员;D-18 / D-22 成员设独立保留门禁,防误删 |
+| T-04-03-01 | Tampering | `IMapHeroRenderer` 公共契约(`types.ts`) | high | mitigate | Task 1 删除 `setHeroAnimateDirection` 的接口声明(随 `HeroAnimateDirection` 类型改名,无悬空引用;类内实现方法保留)、Task 2 删除其余 7 个外部驱动成员的接口声明(合计 8 个,D-20),并由 Task 2 / Task 3 的 D-20 契约门禁断言其不再出现;prohibitions 禁止新增任何接口成员;D-18 / D-22 成员设独立保留门禁,防误删 |
| T-04-03-02 | Repudiation | 钩子映射与旧名清除的可追溯性 | medium | mitigate | 逐钩子映射在 Task 0 汇报并经用户确认;Task 2 / Task 3 的静态改名门禁断言 6 个旧钩子方法名与 3 个旧类型名不复现、5 个现有钩子成员落地;映射关系写入 SUMMARY 人工复核段 |
| T-04-03-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-03-04 | Tampering | 用户并发的未提交改动 | high | mitigate | Task 1 步骤 (0b) 记录 porcelain 条目 + 各文件内容 `sha256` 摘要;Task 3 用同一固定命令复算并断言集合与摘要未变;prohibitions 明确不得回滚 / 暂存 / 提交 / 修改;Task 3 范围门禁额外断言无范围外生产文件改动 |
@@ -392,11 +393,11 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题 / 大致解决方案(含逐钩子映射表)/ 成员命名清单 / D-18 编译桥接取舍 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
1. 执行前核对与基线(Task 1 步骤 0):七条数据端现行接口复核结论(带 `file:line`)写入 SUMMARY;并发改动基线(porcelain 条目 + `sha256`)与类型门禁基线(两条 `client-modules` 诊断原样、三文件 0 条)写入 SUMMARY;任一与规划不一致即停止并修订计划。
2. scoped 类型门禁(Task 1 / 2 / 3):`pnpm exec vue-tsc --noEmit` 过滤到 `render/map/extension/{hero,types,manager}.ts` 必须 0 条诊断;`client-modules` 错误集必须与记录基线完全一致(规划时 `client.ts(66,9): error TS2554`、`render/ui/load.tsx(77,31): error TS2504`);整仓其余既有诊断不属本计划,不得以「整仓非 0 退出」判定失败,也不得漏判范围文件的新诊断。
-3. 静态改名门禁(Task 1 / 2 / 3):三个文件不出现 `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` 与 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`;必须出现 `IHeroLocation` / `ObjectAnimDirection` / `getCurrentFaceDirection` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController`。
+3. 静态改名门禁(Task 1 / 2 / 3):三个文件不出现**独立 token**(词边界判定)`IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection` 与 `onSetPosition` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onTurnHero`(保留的类内实现方法名 `setHeroAnimateDirection` 不构成独立 token,不触发);必须出现 `IHeroLocation` / `ObjectAnimDirection` / `getCurrentFaceDirection` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController`。
4. D-20 契约门禁(Task 2 / 3):`IMapHeroRenderer` 不再声明 `setPosition` / `startMove` / `waitMoveEnd` / `stopMove` / `move` / `jumpTo` / `setHeroAnimateDirection` / `turn`,且无任何新增接口成员。
5. D-18 / D-22 保留门禁(Task 2 / 3):`IMapHeroRenderer` 的 `setImage` / `addFollower` / `removeFollower` / `removeAllFollowers` / `setAlpha` / `setFollowerAlpha` / `destroy` 与 `hero.ts` 的 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` / 跟随者三方法逐字保留。
6. 并发改动基线门禁(Task 3):排除三个在范围文件与 `.planning/phases/04-render-adaptation/` 后,porcelain 条目集合与内容 `sha256` 与 SUMMARY 记录一致(用户改动未被回滚 / 暂存 / 提交 / 修改)。
-7. 范围门禁(Task 3):`git status --porcelain` 中除三个在范围文件外的生产代码无本计划引入的改动(`action/move.ts`、数据端各包、`client-base`、`packages` 均未被触碰)。
+7. 范围门禁(Task 3,基线感知):将当前 `git status --porcelain` 的生产路径(`^packages(-user)?/`)与 Task 1 记录的基线条目做差集;只有基线之外、且不属于三个在范围文件的生产改动才判越界。Task 1 记录的并发用户改动原样放行(同时受第 6 条基线摘要门禁约束);`action/move.ts`、数据端各包、`client-base`、`packages` 均无基线之外的新改动。
8. ESLint / CRLF 门禁(Task 1 / 2 / 3):`pnpm exec eslint` 对三个文件 0 错误;三个源文件与 SUMMARY 无孤立 LF(`.prettierrc` 的 `endOfLine: crlf`)。
9. 人工复核(Task 3):逐钩子映射与用户确认的映射表一致;`onSetPos` 的「移动中不吸附」守卫正确;`onStepEnd` 的 `ObjectMoveType` 分派完整且 `AnimDir` / `Speed` 无渲染动作;D-18 桥接只收敛一处 `hero.image` 读取且 warn 分支未变;无新增公共接口成员;运行时 UAT 不可得的理由已写明。
10. 提交:Task 2 的 `refactor(04-03)` 仅含三个源文件;Task 3 的 `docs(04-03)` 仅含 SUMMARY;用户并发改动未被纳入任何提交。
@@ -404,7 +405,7 @@ Output: 三个在范围文件的适配改动(原子提交)+ 会话内可复
- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报计划内容、逐钩子映射表、成员命名清单与 D-18 编译桥接取舍后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行。
-- 渲染端勇士本体已绑定 `IHeroLocation`:`manager.ts` / `types.ts` 的键与参数类型、`hero.ts` 的构造器参数与朝向读取口径(`getCurrentFaceDirection()`)、动画方向(`ObjectAnimDirection` + `mover.currAnimDir`)全部对齐数据端现行接口;三个文件不再出现 `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection`。
+- 渲染端勇士本体已绑定 `IHeroLocation`:`manager.ts` / `types.ts` 的键与参数类型、`hero.ts` 的构造器参数与朝向读取口径(`getCurrentFaceDirection()`)、动画方向(`ObjectAnimDirection` + `mover.currAnimDir`)全部对齐数据端现行接口;三个文件不再出现独立 token `IHeroMoveController` / `IHeroMoveControllerHooks` / `HeroAnimateDirection`(词边界判定;保留的类内实现方法名 `setHeroAnimateDirection` 不在判定范围)。
- 渲染端已改为**订阅数据端现有钩子**(D-21):`hero.addHook`(`IHeroLocationHooks`)与 `hero.mover.addHook`(`IObjectMoverHooks`)两处注册、两个控制器字段、`load()` / `unload()` 配套;`MapHeroHook` 实现现有钩子成员名,6 个旧钩子方法名全部消失;**未新增任何数据端公共接口,也未修改任何数据端文件**。
- 勇士渲染为**完全被动渲染**(D-20):`IMapHeroRenderer` 不再暴露 8 个外部驱动成员,渲染状态只由钩子回调推进;无任何新增公共接口成员。
- **D-18 / D-22 成员逐字保留**:贴图 / 不透明度成员与跟随者族成员均未被删除或改写语义;贴图别名的读取只做最小编译桥接(一处收敛 + 既有 `// @ts-expect-error 需要重构` 标注,分支与 warn 未变)。
From f6cd9588767ab779d5f6114bf9167b9b4039b4f0 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 14:55:01 +0800
Subject: [PATCH 028/173] docs(04): plan hero-body adaptation (04-03)
---
.planning/STATE.md | 8 ++++----
.planning/phases/04-render-adaptation/04-CONTEXT.md | 6 ++++++
.planning/state.json | 2 +-
3 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/.planning/STATE.md b/.planning/STATE.md
index bf96453..6ab029b 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -5,14 +5,14 @@ current_phase: 04
current_phase_name: 渲染适配与双布局
status: executing
stopped_at: Completed 04-02-PLAN.md(勇士移动只读接口探索账本已交付;缺失候选待用户裁决)
-last_updated: "2026-09-18T04:51:30.189Z"
+last_updated: "2026-09-18T06:54:26.243Z"
last_activity: 2026-09-18
last_activity_desc: Phase 04 execution started
-state_head: d4fe24c9dd1cd272d054c872e5c15f36777b105b
+state_head: 3d2d6dd0b19409d61ad798e7c039e0e1a73f0f4b
progress:
total_phases: 7
completed_phases: 0
- total_plans: 73
+ total_plans: 74
completed_plans: 72
milestone_name: milestone
---
@@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
-Phase: 04 (渲染适配与双布局) — EXECUTING
+Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
Plan: 2 of 2
Status: Ready to execute
Last activity: 2026-09-18 — Phase 04 execution started
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
index b8c2772..4be29af 100644
--- a/.planning/phases/04-render-adaptation/04-CONTEXT.md
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -49,6 +49,12 @@
- **D-17:** 由对账结果看,主要失配点在**勇士移动**部分。下一步规划:把**勇士移动适配到新接口**——先进行**接口探索**(并找出缺失的接口),再进行**适配工作**。
- **D-18:** `HeroRendering` 相关暂不处理:用户先修改数据端,之后再处理。
+### 设计补充二(用户裁定,2026-09-18;仅记录)
+- **D-19:** 移动控制(`startMove` / `oneStep` / `IMoveController` 队列等,即探索文档 `#04-02-G-02`)由用户**手动修改**,AI 暂不处理。
+- **D-20:** 勇士渲染为**完全被动渲染**:不得提供允许渲染状态被外部修改的公共接口;旧版 `startMove` 等接口应**删除**,渲染端只需**绑定对应的勇士位置对象**即可。只有渲染端自身有需要的内容(如不透明度、贴图等),才允许在渲染端修改(与 D-10「`HeroRendering` 删除、渲染状态存储放渲染端」一致)。
+- **D-21:** 探索文档 `#04-02-G-01`(移动语义钩子)裁决:**按用户新增的 `IObjectMoverHooks.onStepStart` / `onStepEnd` 等现有数据端钩子走**,渲染端改为接入这些已有钩子,**不新增数据端公共接口**(与 D-20 被动渲染一致)。
+- **D-22:** **跟随者暂缓**(`IHeroFollower` / `IHeroFollowersController` 及其 hooks):用户尚未确定「跟随者的位置/动画由数据端驱动还是渲染端计算」以及 `onRemoveAllFollowers` 的处置,需再考虑。本次**不纳入** 04-03,也**不单列**计划;待用户想清楚后再规划。
+
### the agent's Discretion
- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
diff --git a/.planning/state.json b/.planning/state.json
index 28e2b0f..30f7295 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -44,5 +44,5 @@
"label": "Advance to the next step",
"reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-18T04:49:55.928Z"
+ "updated_at": "2026-09-18T06:54:26.345Z"
}
From a3826bfa6a7b45e89c9336edd2c5dca7de0a5a1b Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 15:55:02 +0800
Subject: [PATCH 029/173] refactor(04-03): rebind hero renderer to
IHeroLocation and existing hooks
- manager.ts / types.ts: heroMap, addHero, removeHero keyed by IHeroLocation
- hero.ts: bind IHeroLocation, read getCurrentFaceDirection, use ObjectAnimDirection
- MapHeroHook subscribes IHeroLocationHooks / IObjectMoverHooks with existing member names (D-21)
- IMapHeroRenderer drops the 8 external drive members per D-20; D-18 / D-22 members preserved
- hero.image read converged to one minimal compile bridge annotated for D-18
---
.../src/render/map/extension/hero.ts | 125 +++++++++++-------
.../src/render/map/extension/manager.ts | 13 +-
.../src/render/map/extension/types.ts | 71 +---------
3 files changed, 88 insertions(+), 121 deletions(-)
diff --git a/packages-user/client-modules/src/render/map/extension/hero.ts b/packages-user/client-modules/src/render/map/extension/hero.ts
index dc0c003..af8c0a7 100644
--- a/packages-user/client-modules/src/render/map/extension/hero.ts
+++ b/packages-user/client-modules/src/render/map/extension/hero.ts
@@ -2,15 +2,14 @@ import {
degradeFace,
FaceDirection,
getFaceMovement,
- // @ts-expect-error 需要重构
- HeroAnimateDirection,
- // @ts-expect-error 需要重构
- IHeroMoveController,
- // @ts-expect-error 需要重构
- IHeroMoveControllerHooks,
- nextFaceDirection
+ IObjectMover,
+ IObjectMoverHooks,
+ nextFaceDirection,
+ ObjectAnimDirection,
+ ObjectMoveStep,
+ ObjectMoveType
} from '@user/data-common';
-import { IMapLayer } from '@user/data-base';
+import { IHeroLocation, IHeroLocationHooks, IMapLayer } from '@user/data-base';
import { IMapRenderer, IMapRendererTicker, IMovingBlock } from '../types';
import { isNil } from 'lodash-es';
import { IHookController, logger } from '@motajs/common';
@@ -50,15 +49,17 @@ interface HeroRenderEntity {
/** 移动的 `Promise`,移动完成时兑现,如果停止,则一直是兑现状态 */
promise: Promise;
/** 勇士移动的动画方向 */
- animateDirection: HeroAnimateDirection;
+ animateDirection: ObjectAnimDirection;
}
export class MapHeroRenderer implements IMapHeroRenderer {
private static readonly splitter: ITextureSplitter =
new TextureRowSplitter();
- /** 勇士钩子 */
- readonly controller: IHookController;
+ /** 勇士位置钩子 */
+ readonly locationController: IHookController;
+ /** 勇士移动钩子 */
+ readonly moverController: IHookController>;
/** 勇士每个朝向的贴图对象 */
readonly textureMap: Map = new Map();
/** 勇士渲染实体,与 `entities[0]` 同引用 */
@@ -76,17 +77,20 @@ export class MapHeroRenderer implements IMapHeroRenderer {
constructor(
readonly renderer: IMapRenderer,
readonly layer: IMapLayer,
- readonly hero: IHeroMoveController
+ readonly hero: IHeroLocation
) {
- this.controller = hero.addHook(new MapHeroHook(this));
- this.controller.load();
+ const hook = new MapHeroHook(this);
+ this.locationController = hero.addHook(hook);
+ this.locationController.load();
+ this.moverController = hero.mover.addHook(hook);
+ this.moverController.load();
const moving = this.addHeroMoving(renderer, layer, hero);
const heroEntity: HeroRenderEntity = {
block: moving,
identifier: '',
targetX: hero.x,
targetY: hero.y,
- direction: hero.direction,
+ direction: hero.getCurrentFaceDirection(),
nextDirection: FaceDirection.Unknown,
moving: false,
animating: false,
@@ -94,7 +98,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
lastAnimateTime: 0,
animateFrame: 0,
promise: Promise.resolve(),
- animateDirection: HeroAnimateDirection.Forward
+ animateDirection: ObjectAnimDirection.Forward
};
this.heroEntity = heroEntity;
this.entities.push(heroEntity);
@@ -110,19 +114,23 @@ export class MapHeroRenderer implements IMapHeroRenderer {
private addHeroMoving(
renderer: IMapRenderer,
layer: IMapLayer,
- hero: IHeroMoveController
+ hero: IHeroLocation
) {
- if (isNil(hero.image)) {
+ // @ts-expect-error 需要重构(贴图别名来源属 D-18,待用户先改数据端后处理)
+ const imageAlias = hero.image;
+ if (isNil(imageAlias)) {
logger.warn(88);
return renderer.addMovingBlock(layer, 0, hero.x, hero.y);
}
- const image = this.renderer.manager.getImageByAlias(hero.image);
+ const image = this.renderer.manager.getImageByAlias(imageAlias);
if (!image) {
- logger.warn(89, hero.image);
+ logger.warn(89, imageAlias);
return renderer.addMovingBlock(layer, 0, hero.x, hero.y);
}
this.updateHeroTexture(image);
- const tex = this.textureMap.get(degradeFace(hero.direction));
+ const tex = this.textureMap.get(
+ degradeFace(hero.getCurrentFaceDirection())
+ );
if (!tex) {
return renderer.addMovingBlock(layer, 0, hero.x, hero.y);
}
@@ -167,7 +175,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
if (v.animating) {
const dt = time - v.lastAnimateTime;
if (dt > v.animateInterval) {
- if (v.animateDirection === HeroAnimateDirection.Forward) {
+ if (v.animateDirection === ObjectAnimDirection.Forward) {
v.animateFrame++;
} else {
v.animateFrame--;
@@ -190,7 +198,9 @@ export class MapHeroRenderer implements IMapHeroRenderer {
setImage(image: ITexture): void {
this.updateHeroTexture(image);
- const tex = this.textureMap.get(degradeFace(this.hero.direction));
+ const tex = this.textureMap.get(
+ degradeFace(this.hero.getCurrentFaceDirection())
+ );
if (!tex) return;
this.heroEntity.block.setTexture(tex);
}
@@ -369,7 +379,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
lastAnimateTime: 0,
animateFrame: 0,
promise: Promise.resolve(),
- animateDirection: HeroAnimateDirection.Forward
+ animateDirection: ObjectAnimDirection.Forward
};
moving.useSpecifiedFrame(0);
this.entities.push(entity);
@@ -434,7 +444,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
follower.block.setAlpha(alpha);
}
- setHeroAnimateDirection(direction: HeroAnimateDirection): void {
+ setHeroAnimateDirection(direction: ObjectAnimDirection): void {
this.heroEntity.animateDirection = direction;
}
@@ -450,11 +460,14 @@ export class MapHeroRenderer implements IMapHeroRenderer {
}
destroy() {
- this.controller.unload();
+ this.locationController.unload();
+ this.moverController.unload();
}
}
-class MapHeroHook implements Partial {
+class MapHeroHook implements Partial<
+ IHeroLocationHooks & IObjectMoverHooks
+> {
constructor(readonly hero: MapHeroRenderer) {}
onSetImage(image: ImageIds): void {
@@ -466,33 +479,55 @@ class MapHeroHook implements Partial {
this.hero.setImage(texture);
}
- onSetPosition(x: number, y: number): void {
+ onSetPos(x: number, y: number): void {
+ if (this.hero.hero.mover.moving) return;
this.hero.setPosition(x, y);
}
- onTurnHero(direction: FaceDirection): void {
- this.hero.turn(direction);
- }
-
- onStartMove(): void {
+ async onMoveStart(): Promise {
this.hero.startMove();
}
- onMoveHero(direction: FaceDirection, time: number): Promise {
- return this.hero.move(direction, time);
+ async onMoveEnd(): Promise {
+ await this.hero.waitMoveEnd();
}
- onEndMove(): Promise {
- return this.hero.waitMoveEnd();
- }
-
- onJumpHero(
- x: number,
- y: number,
- time: number,
- waitFollower: boolean
+ async onStepEnd(
+ _code: number,
+ step: Readonly,
+ tile: IHeroLocation,
+ mover: IObjectMover
): Promise {
- return this.hero.jumpTo(x, y, time, waitFollower);
+ this.hero.setHeroAnimateDirection(mover.currAnimDir);
+ switch (step.type) {
+ case ObjectMoveType.Dir:
+ return this.hero.move(step.move, mover.currentSpeed);
+ case ObjectMoveType.DirFace:
+ this.hero.turn(step.face);
+ return this.hero.move(step.move, mover.currentSpeed);
+ case ObjectMoveType.Special:
+ return this.hero.move(mover.moveDirection, mover.currentSpeed);
+ case ObjectMoveType.Face:
+ this.hero.turn(step.value);
+ break;
+ case ObjectMoveType.Teleport:
+ this.hero.setPosition(tile.x, tile.y);
+ break;
+ case ObjectMoveType.Jump:
+ return this.hero.jumpTo(
+ tile.x,
+ tile.y,
+ mover.currentSpeed,
+ false
+ );
+ case ObjectMoveType.AnimDir:
+ case ObjectMoveType.Speed:
+ break;
+ }
+ }
+
+ onSetFaceDir(dir: FaceDirection): void {
+ this.hero.turn(dir);
}
onSetAlpha(alpha: number): void {
diff --git a/packages-user/client-modules/src/render/map/extension/manager.ts b/packages-user/client-modules/src/render/map/extension/manager.ts
index 46825aa..a261e8a 100644
--- a/packages-user/client-modules/src/render/map/extension/manager.ts
+++ b/packages-user/client-modules/src/render/map/extension/manager.ts
@@ -1,6 +1,4 @@
-// @ts-expect-error 需要重构
-import { IHeroMoveController } from '@user/data-base';
-import { IMapLayer } from '@user/data-base';
+import { IHeroLocation, IMapLayer } from '@user/data-base';
import {
IMapDoorRenderer,
IMapExtensionManager,
@@ -15,7 +13,7 @@ import { IOnMapTextRenderer } from './types';
export class MapExtensionManager implements IMapExtensionManager {
/** 勇士状态至勇士渲染器的映射 */
- readonly heroMap: Map = new Map();
+ readonly heroMap: Map = new Map();
/** 地图图层到门渲染器的映射 */
readonly doorMap: Map = new Map();
/** 单例的文字渲染拓展(独立图层) */
@@ -23,10 +21,7 @@ export class MapExtensionManager implements IMapExtensionManager {
constructor(readonly renderer: IMapRenderer) {}
- addHero(
- state: IHeroMoveController,
- layer: IMapLayer
- ): IMapHeroRenderer | null {
+ addHero(state: IHeroLocation, layer: IMapLayer): IMapHeroRenderer | null {
if (this.heroMap.has(state)) {
logger.error(45, 'hero renderer');
return null;
@@ -36,7 +31,7 @@ export class MapExtensionManager implements IMapExtensionManager {
return heroRenderer;
}
- removeHero(state: IHeroMoveController): void {
+ removeHero(state: IHeroLocation): void {
const renderer = this.heroMap.get(state);
if (!renderer) return;
renderer.destroy();
diff --git a/packages-user/client-modules/src/render/map/extension/types.ts b/packages-user/client-modules/src/render/map/extension/types.ts
index ffeb09a..a72b983 100644
--- a/packages-user/client-modules/src/render/map/extension/types.ts
+++ b/packages-user/client-modules/src/render/map/extension/types.ts
@@ -1,18 +1,11 @@
import { ITexture, Font } from '@motajs/render';
-import {
- FaceDirection,
- // @ts-expect-error 需要重构
- HeroAnimateDirection,
- // @ts-expect-error 需要重构
- IHeroMoveController
-} from '@user/data-common';
-import { IMapLayer } from '@user/data-base';
+import { IHeroLocation, IMapLayer } from '@user/data-base';
import { IMapRenderResult } from '../types';
export interface IMapExtensionManager {
/** 勇士状态至勇士渲染器的映射 */
- readonly heroMap: Map;
+ readonly heroMap: Map;
/** 地图图层到门渲染器的映射 */
readonly doorMap: Map;
/** 单例的文字渲染拓展(独立图层) */
@@ -23,16 +16,13 @@ export interface IMapExtensionManager {
* @param state 勇士状态
* @param layer 勇士所在图层
*/
- addHero(
- state: IHeroMoveController,
- layer: IMapLayer
- ): IMapHeroRenderer | null;
+ addHero(state: IHeroLocation, layer: IMapLayer): IMapHeroRenderer | null;
/**
* 移除勇士渲染拓展
* @param state 勇士状态
*/
- removeHero(state: IHeroMoveController): void;
+ removeHero(state: IHeroLocation): void;
/**
* 添加开门动画拓展
@@ -86,47 +76,6 @@ export interface IMapHeroRenderer {
*/
removeAllFollowers(): void;
- /**
- * 设置勇士位置
- */
- setPosition(x: number, y: number): void;
-
- /**
- * 开始移动,在移动前需要调用此方法切换勇士状态
- */
- startMove(): void;
-
- /**
- * 等待勇士移动停止后,将移动状态切换为停止
- * @param waitFollower 是否也等待跟随者移动结束
- */
- waitMoveEnd(waitFollower: boolean): Promise;
-
- /**
- * 立刻停止移动,勇士和跟随者瞬移到目标点
- */
- stopMove(): void;
-
- /**
- * 勇士朝某个方向移动
- * @param direction 移动方向
- */
- move(direction: FaceDirection, time: number): Promise;
-
- /**
- * 跳跃勇士至目标点
- * @param x 目标点横坐标
- * @param y 目标点纵坐标
- * @param time 跳跃时长
- * @param waitFollower 是否等待跟随者也跳跃完毕
- */
- jumpTo(
- x: number,
- y: number,
- time: number,
- waitFollower: boolean
- ): Promise;
-
/**
* 设置勇士不透明度
* @param alpha 不透明度
@@ -140,18 +89,6 @@ export interface IMapHeroRenderer {
*/
setFollowerAlpha(identifier: string, alpha: number): void;
- /**
- * 设置勇士移动的动画播放方向,一般后退会使用反向播放的动画,前进使用正向播放的动画
- * @param direction 动画方向
- */
- setHeroAnimateDirection(direction: HeroAnimateDirection): void;
-
- /**
- * 设置勇士朝向
- * @param direction 勇士朝向,不填表示顺时针旋转
- */
- turn(direction?: FaceDirection): void;
-
/**
* 摧毁这个勇士渲染拓展,释放相关资源
*/
From 85637b06f61dcdddab27028641a11b08faee17ca Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 15:58:50 +0800
Subject: [PATCH 030/173] docs(04-03): record execution state
- 04-03-SUMMARY.md: pre-execution interface recheck, concurrent-edit baseline, type baseline
- reconciliation R-01..12 mapping, machine gate outputs, manual review, deferred items
- REND-01 / REND-02 remain Pending; runtime UAT unavailable (addHero not wired)
---
.../04-render-adaptation/04-03-SUMMARY.md | 176 ++++++++++++++++++
1 file changed, 176 insertions(+)
create mode 100644 .planning/phases/04-render-adaptation/04-03-SUMMARY.md
diff --git a/.planning/phases/04-render-adaptation/04-03-SUMMARY.md b/.planning/phases/04-render-adaptation/04-03-SUMMARY.md
new file mode 100644
index 0000000..1e54de2
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-03-SUMMARY.md
@@ -0,0 +1,176 @@
+---
+phase: 04-render-adaptation
+plan: 3
+subsystem: render
+tags: [hero, renderer, hooks, IHeroLocation, passive-render]
+requires: [{ phase: "04-render-adaptation", provides: "04-01 只读对账账本与 04-02 勇士移动接口探索账本(#04-02-R-01..12)" }]
+provides:
+- "勇士渲染拓展绑定 IHeroLocation 并订阅数据端现有钩子 IHeroLocationHooks / IObjectMoverHooks(D-21)"
+- "IMapHeroRenderer 按 D-20 裁剪外部驱动成员的完全被动契约"
+affects: [render-adaptation, dual-layout, client-modules]
+key-files:
+ created: []
+ 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]
+key-decisions: ["渲染端勇士本体绑定 IHeroLocation,经 hero.addHook / hero.mover.addHook 订阅数据端现有钩子(D-21),不新增数据端接口", "IMapHeroRenderer 按 D-20 删除 8 个外部驱动成员声明,勇士渲染改为完全被动,类内实现方法保留", "D-18 贴图别名 hero.image 只做最小编译桥接(读取收敛为一处 + 既有 // @ts-expect-error 惯用法标注),不重设贴图来源"]
+requirements-completed: []
+coverage:
+- id: D1
+ description: "三个在范围文件把勇士渲染绑定到 IHeroLocation 并通过数据端现有钩子被动驱动渲染"
+ requirement: REND-01
+ verification: [{ kind: unit, ref: "pnpm exec vue-tsc --noEmit(scoped render/map/extension/{hero,types,manager}.ts 命中 0 条)", status: pass }]
+ human_judgment: true
+ rationale: "MapExtensionManager.addHero 未被接线(client.ts:148-152 为注释),运行时渲染无法观察;本计划验收为编译 + 契约 + 静态机器门禁 + 人工代码复核,钩子映射语义需人工确认。"
+- id: D2
+ description: "REND-01 / REND-02 保持 Pending;本增量仅交付勇士本体子切片,未实施其余渲染适配与双布局"
+ verification: []
+ human_judgment: true
+ rationale: "REND-01 / REND-02 的验收边界未提供(FLAGGED ASSUMPTION),本计划不发明阈值,需用户补齐边界判据。"
+actuals:
+ tokens: 6288
+ tasks: 4
+ commits: 3
+ plan_head_before: f6cd9588767ab779d5f6114bf9167b9b4039b4f0
+metrics:
+ duration: ~40min
+ completed: 2026-09-18
+ status: complete
+---
+
+# Phase 04 Plan 03: 勇士本体被动适配 Summary
+
+**渲染端勇士本体绑定 `IHeroLocation` 并改为订阅数据端现有钩子(`IHeroLocationHooks` / `IObjectMoverHooks`),`IMapHeroRenderer` 按 D-20 裁剪为完全被动契约,三个在范围文件清零旧控制器类型与旧钩子名。**
+
+## 改动文件清单与职责
+
+- `packages-user/client-modules/src/render/map/extension/manager.ts` — 绑定层:`heroMap` / `addHero` / `removeHero` 的键与参数由 `IHeroMoveController` 改为 `IHeroLocation`;删除首行 `// @ts-expect-error 需要重构` 并合并 import。
+- `packages-user/client-modules/src/render/map/extension/types.ts` — 契约层:`IMapExtensionManager` 勇士键 / 参数改为 `IHeroLocation`;`IMapHeroRenderer` 按 D-20 删除 8 个外部驱动成员声明(`setHeroAnimateDirection` 随类型改名在 Task 1 删除,其余 7 个在 Task 2 删除),保留 D-18 / D-22 成员。
+- `packages-user/client-modules/src/render/map/extension/hero.ts` — 勇士渲染拓展本体:构造器绑定 `IHeroLocation`;`hero.addHook`(`IHeroLocationHooks`)+ `hero.mover.addHook`(`IObjectMoverHooks`)两次注册与 `locationController` / `moverController` 两字段;`MapHeroHook` 实现现有钩子成员名(`onSetPos` / `onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir`),删除 6 个旧钩子方法名;动画方向改用 `ObjectAnimDirection` + `mover.currAnimDir`;`hero.image` 收敛为一处 D-18 编译桥接。
+
+## 执行前核对(Task 1 步骤 0a)
+
+逐条打开当日 HEAD 数据端文件确认(结论 + `file:line`):
+
+| # | 复核项 | 结论 | 锚点 |
+|---|--------|------|------|
+| ① | `IHeroState.location: IHeroLocation` 存在 | 一致 | `packages-user/data-base/src/hero/types.ts:894` |
+| ② | `IHeroLocation extends ISaveableContent, IObjectMovable, IHookable, IDataCommonExtended` 且含 `readonly mover: IHeroMover` | 一致 | `packages-user/data-base/src/hero/types.ts:303-314` |
+| ③ | `IHeroLocationHooks.onSetPos?(x, y)` 与 `onSetFloor?(map)` | 一致 | `packages-user/data-base/src/hero/types.ts:283` / `:289` |
+| ④ | `IObjectMoverHooks` 七成员 `onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` / `onSetPos` / `onSetFaceDir` / `onSetMoveDir` 与参数形态 | 一致 | `packages-user/data-common/src/common/mover.ts:166-233` |
+| ⑤ | `IObjectMover` 的 `moving` / `tile` / `faceDirection` / `moveDirection` / `currAnimDir` / `currentSpeed`;`getCurrentFaceDirection()` | 一致 | `packages-user/data-common/src/common/mover.ts:239-249`;`mover.ts:63`、`packages-user/data-base/src/hero/location.ts:65` |
+| ⑥ | `ObjectAnimDirection` / `ObjectMoveType` / `ObjectSpecialStep` 仍由 `@user/data-common` 导出 | 一致 | `packages-user/data-common/src/common/mover.ts:40` / `:14` / `:33`;barrel `data-common/src/common/index.ts:4`、`data-common/src/index.ts:1` |
+| ⑦ | `HeroLocation.setPos` 触发 `onSetPos`;`ObjectMover.moveProgress` 的 `onStepEnd` 在 `tile.setPos` 之后 | 一致 | `packages-user/data-base/src/hero/location.ts:59-63`;`packages-user/data-common/src/common/mover.ts:671-678` |
+
+**结论:** 七条全部与规划基线一致,未发现差异,继续执行。
+
+## 执行前基线(Task 1 步骤 0b)
+
+Task 1 起始 `git status --porcelain`(排除 `packages-user/client-modules/src/render/map/extension/` 与 `.planning/phases/04-render-adaptation/` 两个前缀后)**原样**记录:
+
+```text
+ M .planning/STATE.md
+ M .planning/state.json
+ M packages-user/client-base/src/load/loader.ts
+ M packages-user/client-base/src/load/types.ts
+ M packages-user/data-base/src/load/loader.ts
+ M packages-user/data-base/src/load/types.ts
+ M packages-user/data-state/src/core.ts
+ M packages-user/data-state/src/types.ts
+ M packages/loader/src/progress.ts
+ M packages/loader/src/types.ts
+?? .planning/milestone.lock
+```
+
+基线摘要 sha256=d61b8915cca70783bfd2cc89f072bb4d8761e7290a028696f57ec3ae11cc761b
+
+说明:执行起始时工作树与规划时预期集合略有漂移——规划时列出的 `.planning/phases/04-render-adaptation/04-CONTEXT.md` 已不再处于未提交状态;新增 `.planning/STATE.md`、`.planning/state.json` 两条(GSD 编排状态文件)。本计划仅记录当日实际基线,**不回滚 / 不暂存 / 不提交 / 不修改**其中任何用户并发改动。
+
+## 类型门禁基线(Task 1 步骤 0c)
+
+执行 `pnpm exec vue-tsc --noEmit`(整仓非 0 退出属预期,共 122 条诊断)。`client-modules` 命中行恰为 2 条,与规划记录一致:
+
+```text
+packages-user/client-modules/src/client.ts(66,9): error TS2554: Expected 1 arguments, but got 0.
+packages-user/client-modules/src/render/ui/load.tsx(77,31): error TS2504: Type 'ITaskManager' must have a '[Symbol.asyncIterator]()' method that returns an async iterator.
+```
+
+三个在范围文件(`render/map/extension/{hero,types,manager}.ts`)当前 0 条诊断。
+
+## #04-02-R-01..12 对账 → 本计划处置对照
+
+| ID | 对账结论 | 本计划处置 |
+|----|----------|------------|
+| R-01 `IHeroMoveControllerHooks` 钩子族 | 渲染端需改 | 已覆盖:改接 `IHeroLocationHooks` / `IObjectMoverHooks` 现有成员名(D-21),旧名删尽 |
+| R-02 `HeroMover` / `IMoveController`(`action/move.ts`) | 渲染端需改 | 不在本计划(D-19:移动控制交由用户手动处理) |
+| R-03 `HeroAnimateDirection` | 渲染端需改 | 已覆盖:改用 `ObjectAnimDirection` |
+| R-04 `IHeroMoveController` | 渲染端需改 | 已覆盖:改用 `IHeroLocation` |
+| R-05 `hero.x` / `hero.y` | 匹配 | 保持(`IHeroLocation extends IObjectMovable`) |
+| R-06 `hero.direction` | 渲染端需改 | 已覆盖:构造期 `getCurrentFaceDirection()`;移动/朝向读 `mover.faceDirection` / `moveDirection` |
+| R-07 `hero.addHook` | 匹配 | 保持,并新增 `hero.mover.addHook` 一次注册 |
+| R-08 跟随者增删钩子 | 渲染端需改 | 不在本计划(D-22 暂缓;跟随者成员与钩子方法逐字保留) |
+| R-09 `oneStep` / `startMove(false,false,false,true)` | 渲染端需改 | 不在本计划(D-19 / #04-02-G-02) |
+| R-10 `controller.queue` / `controller.push({type:'dir'})` | 渲染端需改 | 不在本计划(D-19) |
+| R-11 `controller.onEnd` / `stop()` / `mover.on('stepEnd')` | 渲染端需改 | 部分:`action/move.ts` 属 D-19 不在本计划;渲染端钩子订阅改接现有钩子(`onMoveEnd` / `onStepEnd`) |
+| R-12 `heroMap` / `addHero` / `removeHero` | 渲染端需改 | 已覆盖:键与参数改为 `IHeroLocation` |
+
+未确定项消费:04-02「未能从阅读确定」第 2 条(`state.hero.location` 可达性)由 Task 1 步骤 0a ①/② 确认;第 7 条(`stepEnd` 桥接)由 `IObjectMoverHooks.onStepEnd` 在 `tile.setPos` 之后触发确认;第 6 条(`IMapHeroRenderer.waitMoveEnd` 签名落差)随 D-20 删除接口声明而消解。
+
+## 机器门禁输出摘要
+
+Task 1 / Task 2 / Task 3 均复跑,实际输出如下:
+
+- scoped 类型门禁:`OK in-scope type errors 0; client-modules baseline 2`(三个在范围文件 0 条;`client-modules` 错误集 == 两条记录基线)
+- 静态改名门禁:`OK static rename gate`(旧类型名 / 旧钩子方法名以独立 token 判定不复现;`IHeroLocation` / `ObjectAnimDirection` / `getCurrentFaceDirection` / `hero.addHook` / `hero.mover.addHook` / `locationController` / `moverController` 均存在)
+- D-20 契约门禁:`OK D20 passive contract / D18 D22 preserved`
+- D-18 / D-22 保留门禁:`OK mapping + D18 D22 hook members`
+- ESLint:三文件 0 错误(`pnpm exec eslint` 退出码 0,含 `--fix` 后复跑)
+- CRLF 门禁:`OK CRLF`(三个源文件与 SUMMARY 均无孤立 LF)
+- 范围门禁(基线感知):`OK scope (baseline-aware)`(无基线之外的范围外生产文件改动)
+- 并发改动基线门禁:`OK user concurrent edits unchanged`(porcelain 条目集合与内容 sha256 与记录基线一致)
+
+`client-modules` 基线两条原样:
+
+```text
+client.ts(66,9): error TS2554
+render/ui/load.tsx(77,31): error TS2504
+```
+
+## 人工复核(Task 3 步骤 4)
+
+1. 逐钩子映射与 Task 0 汇报并经用户确认的映射表一致:`IHeroLocationHooks.onSetPos(x,y)`(`data-base/src/hero/location.ts:59-63`)→ 非移动中 `setPosition`;`IObjectMoverHooks.onMoveStart`(`mover.ts:651-656`)→ `startMove()`;`onMoveEnd`(`mover.ts:682-686`)→ `await waitMoveEnd()`;`onStepEnd`(`mover.ts:675-678`,坐标已更新)→ 设 `currAnimDir` 动画方向 + 按 `step.type` 分派;`onSetFaceDir`(`mover.ts:529-534`)→ `turn(dir)`。
+2. `onSetPos` 的「移动中 return」守卫正确:`this.hero.hero.mover.moving` 为真时不瞬时吸附,逐格定位交由 `onStepEnd` 做移动动画。
+3. `onStepEnd` 的 `ObjectMoveType` 分派覆盖 `Dir` / `DirFace` / `Special` / `Face` / `Teleport` / `Jump`;`AnimDir` / `Speed` 确无渲染动作(动画方向已在开头由 `currAnimDir` 设置,速度由数据端消费)。
+4. D-18 编译桥接只收敛了一处 `hero.image` 读取(`const imageAlias = hero.image;` + 新增 `// @ts-expect-error 需要重构(…D-18…)`),既有 `logger.warn(88/89)` 分支结构未变。
+5. D-18 / D-22 成员逐字保留:`setImage` / `setAlpha` / `setFollowerAlpha` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 与跟随者族 `addFollower` / `removeFollower` / `removeAllFollowers` / 三钩子方法均未被删除或改写语义(由 D-18 / D-22 保留门禁输出确认)。
+6. 未新增任何公共接口成员:`IMapHeroRenderer` / `IMapExtensionManager` 的成员集合只减不增。
+7. 运行时 UAT 不可得已写明:`MapExtensionManager.addHero` 当前无调用点,`client.ts:148-152` 的 `addHero(this.hero.mover, layer)` 仍为注释,故勇士渲染拓展在运行时未启用,本计划不接线 `client.ts`。
+
+## 未实现项清单
+
+- `onStepStart`:不实现(渲染只在新坐标就绪后由 `onStepEnd` 推进,避免重复驱动)。
+- `onSetMoveDir`:不实现(渲染朝向只随 `onSetFaceDir` 变化;`moveDirection` 已由 `onStepEnd` 读取)。
+- `IHeroLocationHooks.onSetFloor`:不实现(楼层切换需要重绑图层的接线路径,本增量不接线 `client.ts`;登记为后续增量事项)。
+- `client.ts` 的 `addHero` 接线启用:未做(本计划明确不接线)。
+- `hero.image` 贴图来源:未重设(D-18,待用户先改数据端后处理)。
+- `action/move.ts` 与 `#04-02-G-02`(`oneStep` / 控制器队列):未做(D-19,交由用户手动处理)。
+- 跟随者位置 / 动画归属:未做(D-22 暂缓)。
+- 其余 ① 错配(`#04-01-M-06..09`)与 42 条 ③ 多余旧路径、移动端 / 桌面端双布局(REND-02):未做。
+
+## REND-01 / REND-02 状态
+
+REND-01 / REND-02 在本 run 后**仍保持 Pending**:本计划只完成「勇士本体适配(渲染端被动重接)」一个子切片,未实施其余渲染适配与移动端 / 桌面端双布局(两条 FLAGGED ASSUMPTION)。
+
+**运行时 UAT 不可得(诚实降级):** 渲染端无测试设施,且 `MapExtensionManager.addHero` 当前**未被接线**(`client.ts:148-152` 仍为注释),故运行时渲染无法观察。本计划实际验证为「编译 + 契约 + 静态」三类机器门禁(scoped 类型 / 静态改名 / D-20 契约 / D-18·D-22 保留 / eslint / CRLF / 范围 / 并发改动基线)加人工代码复核;**未以任何不可运行的门禁冒充通过**。运行时渲染 UAT 顺延到启用接线的增量。
+
+## Task Commits
+
+- `a3826bf` — `refactor(04-03): rebind hero renderer to IHeroLocation and existing hooks`(仅三个源文件)
+- 本 SUMMARY 由计划内的 `docs(04-03): record execution state` 提交(仅本文件)
+- 计划元数据(STATE.md / ROADMAP.md)由后续元数据提交处理
+
+## Deviations from Plan
+
+None - 计划按原文执行(三个文件、命名清单、D-18 编译桥接与全部机器门禁均照计划落地)。
+
+## Known Stubs
+
+None。`addHeroMoving` 中的 `// @ts-expect-error 需要重构(…D-18…)` 是计划预批准的**最小编译桥接**,不是占位实现:其运行分支与既有 `logger.warn(88/89)` 语义保持不变,贴图来源归属留待 D-18 增量。
From bff8060ad27fb3fbf26b68ea3b7166c273479241 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 16:01:09 +0800
Subject: [PATCH 031/173] =?UTF-8?q?docs(04-03):=20complete=20=E5=8B=87?=
=?UTF-8?q?=E5=A3=AB=E6=9C=AC=E4=BD=93=E8=A2=AB=E5=8A=A8=E9=80=82=E9=85=8D?=
=?UTF-8?q?=20plan?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.planning/ROADMAP.md | 6 +++---
.planning/STATE.md | 20 ++++++++++++--------
2 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 213fe24..2637785 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 3 plans(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已完成;第三步·勇士本体适配(渲染端被动重接)已规划,待执行 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18/D-19/D-20/D-21/D-22;其余渲染适配与移动端/桌面端双布局待后续增量另行规划)
+**Plans**: 3/3 plans executed(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已完成;第三步·勇士本体适配(渲染端被动重接)已规划,待执行 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18/D-19/D-20/D-21/D-22;其余渲染适配与移动端/桌面端双布局待后续增量另行规划)
Plans:
**Wave 1**
@@ -194,7 +194,7 @@ Plans:
**Wave 3** *(blocked on Wave 2 completion)*
-- [ ] 04-03-PLAN.md — 勇士本体适配(渲染端被动重接):`render/map/extension/{hero,types,manager}.ts` 绑定 `IHeroLocation` / `IHeroMover`,改接现有钩子 `IHeroLocationHooks` / `IObjectMoverHooks`(D-21),按 D-20 裁剪 `IMapHeroRenderer` 的外部驱动成员;排除贴图/不透明度(D-18)、跟随者(D-22)、移动控制(D-19)、`core.*`(D-12)与裸函数(D-11),不新增数据端接口
+- [x] 04-03-PLAN.md — 勇士本体适配(渲染端被动重接):`render/map/extension/{hero,types,manager}.ts` 绑定 `IHeroLocation` / `IHeroMover`,改接现有钩子 `IHeroLocationHooks` / `IObjectMoverHooks`(D-21),按 D-20 裁剪 `IMapHeroRenderer` 的外部驱动成员;排除贴图/不透明度(D-18)、跟随者(D-22)、移动控制(D-19)、`core.*`(D-12)与裸函数(D-11),不新增数据端接口
**UI hint**: yes
@@ -389,7 +389,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 2/2 | In Progress| |
+| 4. 渲染适配与双布局 | 3/3 | In Progress| |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 6ab029b..712ada5 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -4,16 +4,16 @@ milestone: v1.0
current_phase: 04
current_phase_name: 渲染适配与双布局
status: executing
-stopped_at: Completed 04-02-PLAN.md(勇士移动只读接口探索账本已交付;缺失候选待用户裁决)
-last_updated: "2026-09-18T06:54:26.243Z"
+stopped_at: Completed 04-03-PLAN.md(勇士本体被动适配:绑定 IHeroLocation + 订阅现有钩子,三文件机器门禁全绿)
+last_updated: "2026-09-18T08:00:56.234Z"
last_activity: 2026-09-18
last_activity_desc: Phase 04 execution started
-state_head: 3d2d6dd0b19409d61ad798e7c039e0e1a73f0f4b
+state_head: 85637b06f61dcdddab27028641a11b08faee17ca
progress:
total_phases: 7
completed_phases: 0
total_plans: 74
- completed_plans: 72
+ completed_plans: 73
milestone_name: milestone
---
@@ -28,8 +28,8 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
-Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
-Plan: 2 of 2
+Phase: 04 (渲染适配与双布局) — EXECUTING
+Plan: 3 of 3
Status: Ready to execute
Last activity: 2026-09-18 — Phase 04 execution started
@@ -98,6 +98,7 @@ Progress: [█████░░░░░] 50%
| Phase 07 P14 | 3min | 3 tasks | 1 files |
| Phase 04 P01 | 11min | 3 tasks | 1 files |
| Phase 04 P2 | 23min | 4 tasks | 1 files |
+| Phase 04 P3 | 40min | 4 tasks | 3 files |
## Accumulated Context
@@ -221,6 +222,9 @@ Recent decisions affecting current work:
- [Phase 04]: 04-02:勇士移动只读接口探索交付 04-HERO-MOVER-INTERFACE.md(8 段骨架 + 12 条三态对账 #04-02-R-01..12 + 2 条缺失候选 + 7 条未确定),被查两包相对基线零变化、零生产代码改动。
- [Phase 04]: 04-02:缺失接口候选 2 条——移动语义钩子族(#04-02-G-01)与 HeroKeyMover 依赖的 oneStep/控制器 queue(#04-02-G-02)——只记录不设计不修复,待用户裁决(D-13)。
- [Phase 04]: 04-02:HeroRendering 渲染状态(D-18)与 legacy core.*(D-12)为显式排除项;REND-01/REND-02 保持 Pending(本 run 只探索未实施)。
+- [Phase 04]: [Phase 04]: 04-03:渲染端勇士本体绑定 IHeroLocation,经 hero.addHook / hero.mover.addHook 订阅数据端现有钩子(D-21),不新增数据端接口、不改数据端文件
+- [Phase 04]: [Phase 04]: 04-03:IMapHeroRenderer 按 D-20 删除 8 个外部驱动成员声明,勇士渲染改为完全被动;对应类内实现方法保留并只由钩子回调调用
+- [Phase 04]: [Phase 04]: 04-03:D-18 贴图别名 hero.image 只做最小编译桥接(读取收敛为一处 + 既有 // @ts-expect-error 惯用法标注),不重设贴图来源;D-18 / D-22 成员逐字保留
### Roadmap Evolution
@@ -258,6 +262,6 @@ None yet.
## Session Continuity
-Last session: 2026-09-18T04:50:15.723Z
-Stopped at: Completed 04-02-PLAN.md(勇士移动只读接口探索账本已交付;缺失候选待用户裁决)
+Last session: 2026-09-18T08:00:17.538Z
+Stopped at: Completed 04-03-PLAN.md(勇士本体被动适配:绑定 IHeroLocation + 订阅现有钩子,三文件机器门禁全绿)
Resume file: None
From d3598e9e276e6907a6102f7728abeea38035b766 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 16:02:59 +0800
Subject: [PATCH 032/173] docs(04-03): record execution state
---
.planning/state.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.planning/state.json b/.planning/state.json
index 30f7295..b4283bb 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -44,5 +44,5 @@
"label": "Advance to the next step",
"reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-18T06:54:26.345Z"
+ "updated_at": "2026-09-18T08:00:56.330Z"
}
From f5a6ed8cf688ccb36ce1e256a34d933717b7e473 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 20:30:40 +0800
Subject: [PATCH 033/173] =?UTF-8?q?refactor:=20=E5=8A=A0=E8=BD=BD=E7=B3=BB?=
=?UTF-8?q?=E7=BB=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages-user/client-base/src/load/loader.ts | 14 +-
packages-user/client-base/src/load/types.ts | 4 +-
packages-user/client-modules/src/client.ts | 2 +-
.../client-modules/src/render/ui/load.tsx | 1 +
packages-user/data-base/src/load/loader.ts | 4 +-
packages-user/data-base/src/load/types.ts | 4 +-
packages-user/data-state/src/core.ts | 14 +-
packages-user/data-state/src/ins.ts | 2 +-
packages-user/data-state/src/types.ts | 6 +-
.../data-state/test/tileLegacy.test.ts | 44 ++++
packages/common/src/logger.json | 2 +-
packages/loader/src/index.ts | 1 +
packages/loader/src/progress.ts | 58 +++--
packages/loader/src/starter.ts | 11 +
packages/loader/src/task.ts | 185 +++++++++++----
packages/loader/src/types.ts | 223 +++++++++++++-----
16 files changed, 430 insertions(+), 145 deletions(-)
create mode 100644 packages-user/data-state/test/tileLegacy.test.ts
create mode 100644 packages/loader/src/starter.ts
diff --git a/packages-user/client-base/src/load/loader.ts b/packages-user/client-base/src/load/loader.ts
index 28d95f2..f3599e5 100644
--- a/packages-user/client-base/src/load/loader.ts
+++ b/packages-user/client-base/src/load/loader.ts
@@ -1,5 +1,5 @@
import {
- ILoadProgressTotal,
+ ILoadManager,
LoadDataType,
ILoadTask,
LoadTask,
@@ -51,7 +51,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
private readonly rowSplitter: ITextureSplitter;
constructor(
- readonly progress: ILoadProgressTotal,
+ readonly progress: ILoadManager,
private readonly dataLoader: IMotaDataLoader,
private readonly ac: IMotaAudioContext,
private readonly sounds: ISoundPlayer,
@@ -207,9 +207,11 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url,
identifier: `@system-font/${font}`,
dataType: LoadDataType.ArrayBuffer,
+ // @ts-expect-error 需要重构
processor: this.fontProcessor,
progress: this.progress
});
+ task.setProcessor(this.fontProcessor);
this.addCustomLoadTask(task, data => this.fontLoaded(font, data));
});
@@ -220,6 +222,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url,
identifier: `@system-image/${image}`,
dataType: LoadDataType.Blob,
+ // @ts-expect-error 需要重构
processor: this.imageProcessor,
progress: this.progress
});
@@ -238,6 +241,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url,
identifier: `@system-sound/${sound}`,
dataType: LoadDataType.Uint8Array,
+ // @ts-expect-error 需要重构
processor: this.audioProcessor,
progress: this.progress
});
@@ -251,6 +255,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url,
identifier: `@system-tileset/${tileset}`,
dataType: LoadDataType.Blob,
+ // @ts-expect-error 需要重构
processor: this.imageProcessor,
progress: this.progress
});
@@ -268,6 +273,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url,
identifier: `@system-autotile/${key}`,
dataType: LoadDataType.Blob,
+ // @ts-expect-error 需要重构
processor: this.imageProcessor,
progress: this.progress
});
@@ -294,6 +300,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url,
identifier: `@system-material/${materialName}`,
dataType: LoadDataType.Blob,
+ // @ts-expect-error 需要重构
processor: this.imageProcessor,
progress: this.progress
});
@@ -308,6 +315,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url: animatesUrl,
identifier: '@system-animates',
dataType: LoadDataType.Text,
+ // @ts-expect-error 需要重构
processor: this.textProcessor,
progress: this.progress
});
@@ -454,6 +462,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url: `loadList.json`,
dataType: LoadDataType.JSON,
identifier: '@system-loadList',
+ // @ts-expect-error 需要重构
processor: this.dataLoader.jsonProcessor,
progress: { onProgress() {} }
});
@@ -465,6 +474,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader {
url: loadList.file,
identifier: `@system-zip/${loadList.file}`,
dataType: LoadDataType.ArrayBuffer,
+ // @ts-expect-error 需要重构
processor: this.zipProcessor,
progress: this.progress
});
diff --git a/packages-user/client-base/src/load/types.ts b/packages-user/client-base/src/load/types.ts
index 516c8e1..e1c707d 100644
--- a/packages-user/client-base/src/load/types.ts
+++ b/packages-user/client-base/src/load/types.ts
@@ -1,5 +1,5 @@
import {
- ILoadProgressTotal,
+ ILoadManager,
ILoadTask,
ILoadTaskProcessor,
LoadDataType
@@ -37,7 +37,7 @@ export interface ICompressedMotaAssetsLoadList {
export interface IMotaAssetsLoader {
/** 加载进度对象 */
- readonly progress: ILoadProgressTotal;
+ readonly progress: ILoadManager;
/** 当前是否正在加载 */
readonly loading: boolean;
/** 当前是否已经加载完毕 */
diff --git a/packages-user/client-modules/src/client.ts b/packages-user/client-modules/src/client.ts
index 1a579aa..e5388b5 100644
--- a/packages-user/client-modules/src/client.ts
+++ b/packages-user/client-modules/src/client.ts
@@ -63,7 +63,7 @@ export class ClientCore extends CoreState implements IClientCore {
readonly bgmPlayer: IBGMPlayer;
constructor() {
- super();
+ super({});
//#region Layer 4
diff --git a/packages-user/client-modules/src/render/ui/load.tsx b/packages-user/client-modules/src/render/ui/load.tsx
index c8e43b6..1b20bb7 100644
--- a/packages-user/client-modules/src/render/ui/load.tsx
+++ b/packages-user/client-modules/src/render/ui/load.tsx
@@ -74,6 +74,7 @@ export const LoadScene = defineComponent(props => {
loader.load().then(() => {
loadEnd();
});
+ // @ts-expect-error 需要重构
for await (const _ of loader.progress) {
taskProgress.set(loader.progress.getLoadedTasks());
byteProgress.set(loader.progress.getLoadedByte());
diff --git a/packages-user/data-base/src/load/loader.ts b/packages-user/data-base/src/load/loader.ts
index 25b300f..263825a 100644
--- a/packages-user/data-base/src/load/loader.ts
+++ b/packages-user/data-base/src/load/loader.ts
@@ -1,5 +1,5 @@
import {
- ILoadProgressTotal,
+ ILoadManager,
LoadDataType,
ILoadTask,
ILoadTaskProcessor
@@ -24,7 +24,7 @@ export class MotaDataLoader implements IMotaDataLoader {
readonly jsonProcessor: ILoadTaskProcessor;
- constructor(readonly progress: ILoadProgressTotal) {
+ constructor(readonly progress: ILoadManager) {
this.jsonProcessor = new LoadJSONProcessor();
}
diff --git a/packages-user/data-base/src/load/types.ts b/packages-user/data-base/src/load/types.ts
index b56c3b7..4e11cb7 100644
--- a/packages-user/data-base/src/load/types.ts
+++ b/packages-user/data-base/src/load/types.ts
@@ -1,5 +1,5 @@
import {
- ILoadProgressTotal,
+ ILoadManager,
ILoadTask,
ILoadTaskProcessor,
LoadDataType
@@ -9,7 +9,7 @@ export type CustomLoadFunc = (data: R) => Promise;
export interface IMotaDataLoader {
/** 加载进度对象 */
- readonly progress: ILoadProgressTotal;
+ readonly progress: ILoadManager;
/** json 处理器 */
readonly jsonProcessor: ILoadTaskProcessor;
diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts
index 168007f..62f31f9 100644
--- a/packages-user/data-state/src/core.ts
+++ b/packages-user/data-state/src/core.ts
@@ -1,5 +1,5 @@
import { logger } from '@motajs/common';
-import { ILoadProgressTotal, LoadProgressTotal } from '@motajs/loader';
+import { ILoadManager, LoadManager } from '@motajs/loader';
import {
IRoleFaceBinder,
IFaceManager,
@@ -48,7 +48,7 @@ import {
IPathfindingSystem,
PathfindingSystem
} from '@user/data-system';
-import { ICoreState, ISaveableExecutor } from './types';
+import { ICoreState, ICoreStateConfig, ISaveableExecutor } from './types';
import {
CommonAuraConverter,
EnemyLegacyBridge,
@@ -93,7 +93,7 @@ export class CoreState implements ICoreState {
readonly pathfinding: IPathfindingSystem;
// Layer 3 用户层,也就是最顶层的内容,一般仅用于初始化以及仅供渲染端调用的顶层模块
- readonly loadProgress: ILoadProgressTotal;
+ readonly loadProgress: ILoadManager;
readonly dataLoader: IMotaDataLoader;
/** 可存档对象映射 */
@@ -106,7 +106,7 @@ export class CoreState implements ICoreState {
ISaveableExecutor
> = new Map();
- constructor() {
+ constructor(config: Readonly) {
//#region L0 初始化
// 朝向
@@ -143,7 +143,7 @@ export class CoreState implements ICoreState {
const heroState = new HeroState(this, dir8, heroAttribute);
this.hero = heroState;
- this.loadProgress = new LoadProgressTotal();
+ this.loadProgress = new LoadManager();
this.dataLoader = new MotaDataLoader(this.loadProgress);
// 怪物管理器
@@ -323,7 +323,3 @@ export class CoreState implements ICoreState {
//#endregion
}
-
-export function createCoreState(): CoreState {
- return new CoreState();
-}
diff --git a/packages-user/data-state/src/ins.ts b/packages-user/data-state/src/ins.ts
index ae88703..46053b5 100644
--- a/packages-user/data-state/src/ins.ts
+++ b/packages-user/data-state/src/ins.ts
@@ -7,4 +7,4 @@ import { CoreState } from './core';
* 此对象是数据端状态,本身不负责任何渲染操作,仅会向渲染端发送数据让渲染端渲染,不要把渲染操作直接放到此对象上,
* 否则可能导致录像验证失败。
*/
-export const state = new CoreState();
+export const state = new CoreState({});
diff --git a/packages-user/data-state/src/types.ts b/packages-user/data-state/src/types.ts
index 84329a1..37a4010 100644
--- a/packages-user/data-state/src/types.ts
+++ b/packages-user/data-state/src/types.ts
@@ -1,8 +1,10 @@
import { IMotaDataLoader, IStateBase } from '@user/data-base';
-import { ILoadProgressTotal } from '@motajs/loader';
+import { ILoadManager } from '@motajs/loader';
import { IStateSystem } from '@user/data-system';
import { ISaveableContent } from '@user/data-common';
+export interface ICoreStateConfig {}
+
export interface ISaveableExecutor {
/**
* 当数据读取后执行的函数,允许对其他存档对象进行读取
@@ -15,7 +17,7 @@ export interface ISaveableExecutor {
export interface ICoreState
extends IStateSystem, ISaveableContent> {
/** 加载进度对象 */
- readonly loadProgress: ILoadProgressTotal;
+ readonly loadProgress: ILoadManager;
/** 数据端加载对象 */
readonly dataLoader: IMotaDataLoader;
diff --git a/packages-user/data-state/test/tileLegacy.test.ts b/packages-user/data-state/test/tileLegacy.test.ts
new file mode 100644
index 0000000..0d301bb
--- /dev/null
+++ b/packages-user/data-state/test/tileLegacy.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, it } from 'vitest';
+import { TileStore } from '@user/data-common';
+import { LegacyTileData, TileLegacyBridge } from '../src/legacy/tile';
+
+describe('TileLegacyBridge events-map contract', () => {
+ // 验证旧样板事件对象会转换为 TileStore 可消费的默认事件映射
+ it('converts legacy events into the TileStore event map', () => {
+ const bridge = new TileLegacyBridge();
+ const store = new TileStore();
+ store.attachLegacyConverter(bridge);
+ const legacy: LegacyTileData = {
+ id: 'yellowWall',
+ cls: 'terrains',
+ events: { 10: 'onEnter', 20: 'onTouch' },
+ noPass: true
+ };
+
+ const converted = store.fromLegacy(1, legacy);
+
+ expect(converted.events).toEqual({ 10: 'onEnter', 20: 'onTouch' });
+ expect(store.getEvent(1)).toEqual(
+ new Map([
+ [10, 'onEnter'],
+ [20, 'onTouch']
+ ])
+ );
+ expect(converted.eventPass).toBe(false);
+ expect(converted).not.toHaveProperty('trigger');
+ });
+
+ // 验证旧样板缺少事件输入时仍生成合法的空事件映射
+ it('uses an empty events map when legacy input has no events', () => {
+ const bridge = new TileLegacyBridge();
+ const legacy: LegacyTileData = {
+ id: 'whiteWall',
+ cls: 'terrains'
+ };
+
+ const converted = bridge.fromLegacy(2, legacy);
+
+ expect(converted.events).toEqual({});
+ expect(converted).not.toHaveProperty('trigger');
+ });
+});
diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json
index 41f2c94..a3ef83f 100644
--- a/packages/common/src/logger.json
+++ b/packages/common/src/logger.json
@@ -29,7 +29,7 @@
"27": "No autotile connection data, please ensure you have created autotile connection map.",
"28": "Cannot compile map render shader.",
"29": "Cannot get uniform location of map render shader.",
- "30": "",
+ "30": "Load task needs a load starter binding before loading.",
"31": "No asset data is specified when rending map.",
"32": "Every layer added to map renderer must share the same size. Different layer: $1.",
"33": "Map layer transfered to vertex generator must belong to the renderer that the generator use.",
diff --git a/packages/loader/src/index.ts b/packages/loader/src/index.ts
index 2030c0c..fd7d204 100644
--- a/packages/loader/src/index.ts
+++ b/packages/loader/src/index.ts
@@ -1,4 +1,5 @@
export * from './progress';
+export * from './starter';
export * from './stream';
export * from './task';
export * from './types';
diff --git a/packages/loader/src/progress.ts b/packages/loader/src/progress.ts
index 0b6ec80..0141a3b 100644
--- a/packages/loader/src/progress.ts
+++ b/packages/loader/src/progress.ts
@@ -1,17 +1,22 @@
import { clamp } from 'lodash-es';
-import { ILoadProgressTotal, ILoadTask, LoadDataType } from './types';
-import { logger } from '@motajs/common';
+import { ILoadManager, ILoadManagerHooks, ILoadTask } from './types';
+import {
+ Hookable,
+ HookController,
+ IHookController,
+ logger
+} from '@motajs/common';
-export class LoadProgressTotal<
- T extends LoadDataType = LoadDataType,
- R = any
-> implements ILoadProgressTotal {
+export class LoadManager
+ extends Hookable
+ implements ILoadManager
+{
/** 当前已经附着的加载任务 */
- private readonly attached: Map, number> = new Map();
+ private readonly attached: Map = new Map();
/** 当前已经加载完毕的任务 */
- readonly loadedTasks: Set> = new Set();
+ readonly loadedTasks: Set = new Set();
/** 当前已经添加的任务 */
- readonly addedTasks: Set> = new Set();
+ readonly addedTasks: Set = new Set();
/** 总加载量 */
private total: number = 0;
@@ -23,26 +28,37 @@ export class LoadProgressTotal<
/** 兑现当前的 `nextPromise` */
private nextResolve: () => void;
- async *[Symbol.asyncIterator]() {
- while (true) {
- if (this.loadedTasks.size === this.addedTasks.size) {
- return;
- }
- yield this.nextPromise;
- }
- }
-
constructor() {
+ super();
const { promise, resolve } = Promise.withResolvers();
this.nextPromise = promise;
this.nextResolve = resolve;
}
- addTask(task: ILoadTask) {
- this.addedTasks.add(task);
+ protected createController(
+ hook: Partial
+ ): IHookController {
+ return new HookController(this, hook);
}
- onProgress(task: ILoadTask, loaded: number, total: number): void {
+ addTask(task: ILoadTask) {
+ this.addedTasks.add(task);
+ task.addHook({
+ onProgress: (loaded, total) => this.onProgress(task, loaded, total)
+ });
+ }
+
+ async *load(): AsyncIterable {
+ while (true) {
+ if (this.loadedTasks.size === this.addedTasks.size) {
+ return;
+ }
+ await this.nextPromise;
+ yield this.loaded;
+ }
+ }
+
+ onProgress(task: ILoadTask, loaded: number, total: number): void {
if (!this.addedTasks.has(task)) {
logger.warn(95);
return;
diff --git a/packages/loader/src/starter.ts b/packages/loader/src/starter.ts
new file mode 100644
index 0000000..96154c2
--- /dev/null
+++ b/packages/loader/src/starter.ts
@@ -0,0 +1,11 @@
+import { ILoadTaskRequest, ILoadTaskStarter } from './types';
+
+export class WebLoadStarter implements ILoadTaskStarter {
+ start(request: ILoadTaskRequest): Promise {
+ return fetch(request.url, {
+ method: request.method,
+ body: request.body,
+ headers: request.headers
+ });
+ }
+}
diff --git a/packages/loader/src/task.ts b/packages/loader/src/task.ts
index 4e1eb3c..4954a8e 100644
--- a/packages/loader/src/task.ts
+++ b/packages/loader/src/task.ts
@@ -2,26 +2,39 @@ import { sumBy } from 'lodash-es';
import {
ILoadDataTypeMap,
ILoadTask,
+ ILoadTaskHooks,
ILoadTaskInit,
ILoadTaskProcessor,
- ILoadTaskProgress,
+ ILoadTaskStarter,
+ IResponseLike,
+ IResponseReaderLike,
LoadDataType,
RequestMethod
} from './types';
+import {
+ Hookable,
+ HookController,
+ IHookController,
+ logger
+} from '@motajs/common';
/** 文字解码 */
const loadTextDecoder = new TextDecoder();
-export class LoadTask implements ILoadTask {
+export class LoadTask
+ extends Hookable>
+ implements ILoadTask
+{
readonly dataType: T;
readonly identifier: string;
- readonly url: string | URL;
- readonly processor: ILoadTaskProcessor;
- readonly progress: ILoadTaskProgress;
+ readonly url: string;
readonly method?: RequestMethod;
readonly body?: BodyInit;
readonly headers?: HeadersInit;
+ processor: ILoadTaskProcessor | null = null;
+ starter: ILoadTaskStarter | null = null;
+
contentLoaded: boolean = false;
loadedByte: number = 0;
totalByte: number = 0;
@@ -34,12 +47,12 @@ export class LoadTask implements ILoadTask {
/** 加载结果 */
private loadedData: R | null = null;
- constructor(init: ILoadTaskInit) {
+ constructor(init: ILoadTaskInit) {
+ super();
+
this.dataType = init.dataType;
this.identifier = init.identifier;
- this.url = this.resolveURL(init.url);
- this.processor = init.processor;
- this.progress = init.progress;
+ this.url = init.url;
this.method = init.method;
this.body = init.body;
this.headers = init.headers;
@@ -49,16 +62,26 @@ export class LoadTask implements ILoadTask {
this.loadResolve = resolve;
}
- private resolveURL(url: string | URL) {
- if (typeof url === 'string') {
- return `${import.meta.env.BASE_URL}${url}`;
- } else {
- return url;
- }
+ protected createController(
+ hook: Partial>
+ ): IHookController> {
+ return new HookController(this, hook);
}
+ setProcessor(processor: ILoadTaskProcessor | null): void {
+ this.processor = processor;
+ }
+
+ setStarter(starter: ILoadTaskStarter): void {
+ this.starter = starter;
+ }
+
+ /**
+ * 当不使用流加载时,使用此方法直接处理响应对象
+ * @param response 响应体对象
+ */
private processUnstreamableResponse(
- response: Response
+ response: IResponseLike
): Promise {
switch (this.dataType) {
case LoadDataType.ArrayBuffer:
@@ -74,6 +97,10 @@ export class LoadTask implements ILoadTask {
}
}
+ /**
+ * 当使用流加载时,处理每次流加载的分块
+ * @param chunks 流式加载的二进制分块
+ */
private processStreamChunkResponse(
chunks: Uint8Array[]
): ILoadDataTypeMap[T] {
@@ -102,23 +129,44 @@ export class LoadTask implements ILoadTask {
}
}
- private async processResponse(response: Response) {
- const reader = response.body?.getReader();
- const contentLength = response.headers.get('Content-Length') ?? '0';
- const total = parseInt(contentLength, 10);
- this.loadedByte = 0;
- this.totalByte = total;
- this.progress.onProgress(this, 0, total);
- if (!reader) {
- const data = await this.processUnstreamableResponse(response);
- this.loadedByte = this.totalByte;
- this.contentLoaded = true;
- this.progress.onProgress(this, this.loadedByte, this.totalByte);
- const processed = await this.processor.process(data, this);
- this.loadedData = processed;
- this.loadResolve(processed);
- return;
+ /**
+ * 处理加载数据
+ * @param data 加载获取的原始数据
+ */
+ private processData(data: ILoadDataTypeMap[T]): Promise {
+ if (this.processor) {
+ return this.processor.process(data, this);
+ } else {
+ return Promise.resolve(data as R);
}
+ }
+
+ /**
+ * 使用非流式加载方式进行加载
+ * @param response 响应体
+ * @param total 加载总字节数
+ */
+ private async *loadUnstream(
+ response: IResponseLike,
+ total: number
+ ): AsyncGenerator {
+ const data = await this.processUnstreamableResponse(response);
+ this.loadedByte = this.totalByte;
+ this.contentLoaded = true;
+ this.forEachHook(hook => hook.onProgress?.(total, total));
+ yield total;
+ return data;
+ }
+
+ /**
+ * 使用流式加载方式进行加载
+ * @param reader 流式读取器
+ * @param total 加载总字节数
+ */
+ private async *loadStream(
+ reader: IResponseReaderLike,
+ total: number
+ ): AsyncGenerator {
let received = 0;
const chunks: Uint8Array[] = [];
while (true) {
@@ -129,23 +177,70 @@ export class LoadTask implements ILoadTask {
}
if (done) this.contentLoaded = true;
this.loadedByte = received;
- this.progress.onProgress(this, received, total);
- if (done) break;
+ this.forEachHook(hook => hook.onProgress?.(received, total));
+ yield received;
+ if (done) {
+ this.loadedByte = total;
+ yield total;
+ break;
+ }
}
const data = this.processStreamChunkResponse(chunks);
- const processed = await this.processor.process(data, this);
- this.loadedData = processed;
- this.loadResolve(processed);
+ return data;
}
- async start(): Promise {
- const response = await fetch(this.url, {
- method: this.method,
- body: this.body,
- headers: this.headers
- });
- this.processResponse(response);
- return;
+ /**
+ * 执行加载任务,自动决定使用流式加载或非流式加载
+ * @param response 响应体
+ * @param total 加载总字节数
+ */
+ private loadTask(
+ response: IResponseLike,
+ total: number
+ ): AsyncGenerator {
+ const reader = response.body?.getReader();
+ if (reader) {
+ return this.loadStream(reader, total);
+ } else {
+ return this.loadUnstream(response, total);
+ }
+ }
+
+ /**
+ * 处理响应体,并进行加载工作
+ * @param response 响应体
+ */
+ private async *processResponse(
+ response: IResponseLike
+ ): AsyncIterable {
+ const contentLength = response.headers.get('Content-Length') ?? '0';
+ const total = parseInt(contentLength, 10);
+ this.loadedByte = 0;
+ this.totalByte = total;
+ this.forEachHook(hook => hook.onLoadStart?.(total));
+ this.forEachHook(hook => hook.onProgress?.(0, total));
+
+ const iter = this.loadTask(response, total);
+ const data = yield* iter;
+
+ const processed = await this.processData(data);
+ this.loadedData = processed;
+ this.loadResolve(processed);
+ this.forEachHook(hook => hook.onLoadEnd?.(processed, data, total));
+ }
+
+ async *start(): AsyncIterable {
+ if (this.loadedData) return;
+
+ if (!this.starter) {
+ logger.error(30);
+ return;
+ }
+ const response = await this.starter.start(this);
+ yield* this.processResponse(response);
+
+ // 加载完毕后就可以清空钩子了
+ this.forEachHook(hook => hook).forEach(v => this.removeHook(v));
}
loaded(): Promise {
diff --git a/packages/loader/src/types.ts b/packages/loader/src/types.ts
index f477db5..aec1986 100644
--- a/packages/loader/src/types.ts
+++ b/packages/loader/src/types.ts
@@ -1,5 +1,7 @@
//#region 流传输
+import { IHookable, IHookBase } from '@motajs/common';
+
export interface IStreamController {
/** 当前是否正在加载 */
readonly loading: boolean;
@@ -85,33 +87,6 @@ export const enum LoadDataType {
JSON
}
-export interface ILoadDataTypeMap {
- [LoadDataType.ArrayBuffer]: ArrayBuffer;
- [LoadDataType.Uint8Array]: Uint8Array;
- [LoadDataType.Blob]: Blob;
- [LoadDataType.Text]: string;
- [LoadDataType.JSON]: any;
-}
-
-export interface ILoadTaskProcessor {
- /**
- * 处理加载内容
- * @param response 处理前加载结果
- * @param task 加载任务对象
- */
- process(response: ILoadDataTypeMap[T], task: ILoadTask): Promise;
-}
-
-export interface ILoadTaskProgress {
- /**
- * 更新加载进度
- * @param task 加载任务对象
- * @param loaded 已加载的字节数
- * @param total 文件总计字节数,如果此值为零说明无法读取到 `Content-Length`
- */
- onProgress(task: ILoadTask, loaded: number, total: number): void;
-}
-
export const enum RequestMethod {
GET = 'GET',
POST = 'POST',
@@ -124,17 +99,17 @@ export const enum RequestMethod {
PATCH = 'PATCH'
}
-export interface ILoadTaskInit {
- /** 请求响应格式 */
- readonly dataType: T;
- /** 加载任务标识符 */
- readonly identifier: string;
+export interface ILoadDataTypeMap {
+ [LoadDataType.ArrayBuffer]: ArrayBuffer;
+ [LoadDataType.Uint8Array]: Uint8Array;
+ [LoadDataType.Blob]: Blob;
+ [LoadDataType.Text]: string;
+ [LoadDataType.JSON]: any;
+}
+
+export interface ILoadTaskRequest {
/** 加载目标 URL */
- readonly url: string | URL;
- /** 加载的处理对象,用于处理加载结果等 */
- readonly processor: ILoadTaskProcessor;
- /** 加载进度对象,用于监控加载进度 */
- readonly progress: ILoadTaskProgress;
+ readonly url: string;
/** 请求模式 */
readonly method?: RequestMethod;
/** 请求体 */
@@ -143,21 +118,141 @@ export interface ILoadTaskInit {
readonly headers?: HeadersInit;
}
-export interface ILoadTask extends ILoadTaskInit<
- T,
- R
-> {
+export interface ILoadTaskProcessor {
+ /**
+ * 处理加载内容
+ * @param response 处理前加载结果
+ * @param task 加载任务对象
+ */
+ process(response: ILoadDataTypeMap[T], task: ILoadTask): Promise;
+}
+
+export interface IResponseReaderLike {
+ /**
+ * 进行下一个分块的读取
+ */
+ read(): Promise>>;
+}
+
+export interface IResponseBodyLike {
+ /**
+ * 获取流式读取器
+ */
+ getReader(): ReadableStreamDefaultReader;
+}
+
+export interface IResponseHeadersLike {
+ /**
+ * 获取指定响应头的内容
+ * @param name 响应头字段名称
+ */
+ get(name: string): string | null;
+}
+
+export interface IResponseLike {
+ /** 请求地址 */
+ readonly url: string;
+ /** 响应体 */
+ readonly body: IResponseBodyLike | null;
+ /** 响应头 */
+ readonly headers: IResponseHeadersLike;
+
+ /**
+ * 输出为 `ArrayBuffer`
+ */
+ arrayBuffer(): Promise;
+
+ /**
+ * 输出为 `Blob`
+ */
+ blob(): Promise;
+
+ /**
+ * 输出为 `Uint8Array`
+ */
+ bytes(): Promise>;
+
+ /**
+ * 输出为 `JSON`
+ */
+ json(): Promise;
+
+ /**
+ * 输出为纯文本
+ */
+ text(): Promise;
+}
+
+export interface ILoadTaskStarter {
+ /**
+ * 进行加载请求,获取 `Response` 对象
+ * @param request 请求配置
+ */
+ start(request: ILoadTaskRequest): Promise;
+}
+
+export interface ILoadTaskInit<
+ T extends LoadDataType
+> extends ILoadTaskRequest {
+ /** 请求响应格式 */
+ readonly dataType: T;
+ /** 加载任务标识符 */
+ readonly identifier: string;
+}
+
+export interface ILoadTaskHooks extends IHookBase {
+ /**
+ * 当加载开始时触发
+ * @param total 加载总字节数
+ */
+ onLoadStart?(total: number): void;
+
+ /**
+ * 当加载进度变动时触发
+ * @param loaded 已加载的字节数
+ * @param total 加载总字节数
+ */
+ onProgress?(loaded: number, total: number): void;
+
+ /**
+ * 当加载完成时触发
+ * @param data 经过处理后的加载内容
+ * @param raw 加载完毕时获取的原始数据
+ * @param total 加载总字节数
+ */
+ onLoadEnd?(data: R, raw: ILoadDataTypeMap[T], total: number): void;
+}
+
+export interface ILoadTask
+ extends ILoadTaskInit, IHookable> {
/** 当前是否加载完毕 */
readonly contentLoaded: boolean;
/** 已经加载的字节数 */
readonly loadedByte: number;
/** 该加载任务的总体字节数 */
readonly totalByte: number;
+ /** 当前任务使用的数据处理器 */
+ readonly processor: ILoadTaskProcessor | null;
+ /** 当前任务使用的加载启动器 */
+ readonly starter: ILoadTaskStarter | null;
+
+ /**
+ * 设置当前任务使用的数据处理器
+ * @param processor 数据处理器
+ */
+ setProcessor(processor: ILoadTaskProcessor | null): void;
+
+ /**
+ * 设置当前任务使用的加载启动器
+ * @param starter 加载启动器
+ */
+ setStarter(starter: ILoadTaskStarter): void;
/**
* 开始此加载计划,返回一个 `Promise`,当得到服务器的响应后兑现
+ * @returns 异步迭代器,当加载进度变动时进行一次迭代,迭代值为当前加载的字节数
*/
- start(): Promise;
+ start(): AsyncIterable;
/**
* 返回一个 `Promise`,当本计划加载完毕后兑现,兑现结果是加载结果
@@ -170,29 +265,43 @@ export interface ILoadTask extends ILoadTaskInit<
getLoadedData(): R | null;
}
-//#endregion
-
-//#region 内置组件
-
-export interface ILoadProgressTotal<
- T extends LoadDataType = LoadDataType,
- R = any
-> extends ILoadTaskProgress {
- /** 已经添加的加载任务对象 */
- readonly addedTasks: Set>;
- /** 当前已经加载完毕的任务对象 */
- readonly loadedTasks: Set>;
+export interface ILoadManagerHooks extends IHookBase {
+ /**
+ * 当开始加载时触发
+ */
+ onStartLoad?(): void;
/**
- * 迭代加载进度,当 `yield` 的值被兑现时,说明加载进度更新
+ * 当加载进度发生变化时触发
+ * @param loaded 已加载的字节数
+ * @param total 加载总字节数,此数值可能不准确,因为必须等所有的加载全部连接完成时才能够得知总字节数
*/
- [Symbol.asyncIterator](): AsyncGenerator;
+ onProgress?(loaded: number, total: number): void;
+
+ /**
+ * 当所有加载任务完成时触发
+ * @param total 加载的总字节数
+ */
+ onLoadEnd?(total: number): void;
+}
+
+export interface ILoadManager extends IHookable {
+ /** 已经添加的加载任务对象 */
+ readonly addedTasks: Set;
+ /** 当前已经加载完毕的任务对象 */
+ readonly loadedTasks: Set;
/**
* 向该进度监听器添加加载任务对象
* @param task 加载任务对象
*/
- addTask(task: ILoadTask): void;
+ addTask(task: ILoadTask): void;
+
+ /**
+ * 开始执行加载
+ * @returns 加载的异步迭代器,当加载进度更新时迭代,迭代值为当前已加载的总字节数
+ */
+ load(): AsyncIterable;
/**
* 获取总体已加载的字节数
From ed0b95b1c4ab0c48b301a8bf1f6e93794e823359 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 20:36:03 +0800
Subject: [PATCH 034/173] =?UTF-8?q?feat:=20CoreState=20=E9=85=8D=E7=BD=AE?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=20loadStarter?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages-user/client-modules/src/client.ts | 5 ++++-
packages-user/data-state/src/ins.ts | 5 ++++-
packages-user/data-state/src/types.ts | 10 ++++++++--
3 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/packages-user/client-modules/src/client.ts b/packages-user/client-modules/src/client.ts
index e5388b5..6c485b1 100644
--- a/packages-user/client-modules/src/client.ts
+++ b/packages-user/client-modules/src/client.ts
@@ -42,6 +42,7 @@ import {
} from './shared';
import { loading } from '@user/data-base';
import { fallbackLoad } from './fallback/load';
+import { WebLoadStarter } from '@motajs/loader';
export class ClientCore extends CoreState implements IClientCore {
// Layer 4 渲染基础层
@@ -63,7 +64,9 @@ export class ClientCore extends CoreState implements IClientCore {
readonly bgmPlayer: IBGMPlayer;
constructor() {
- super({});
+ super({
+ loadStarter: new WebLoadStarter()
+ });
//#region Layer 4
diff --git a/packages-user/data-state/src/ins.ts b/packages-user/data-state/src/ins.ts
index 46053b5..69e1955 100644
--- a/packages-user/data-state/src/ins.ts
+++ b/packages-user/data-state/src/ins.ts
@@ -1,3 +1,4 @@
+import { WebLoadStarter } from '@motajs/loader';
import { CoreState } from './core';
// TODO: 逐渐弱化 CoreState 的单例概念,每个接口都通过参数传入 ICoreState 对象
@@ -7,4 +8,6 @@ import { CoreState } from './core';
* 此对象是数据端状态,本身不负责任何渲染操作,仅会向渲染端发送数据让渲染端渲染,不要把渲染操作直接放到此对象上,
* 否则可能导致录像验证失败。
*/
-export const state = new CoreState({});
+export const state = new CoreState({
+ loadStarter: new WebLoadStarter()
+});
diff --git a/packages-user/data-state/src/types.ts b/packages-user/data-state/src/types.ts
index 37a4010..e2bb398 100644
--- a/packages-user/data-state/src/types.ts
+++ b/packages-user/data-state/src/types.ts
@@ -1,9 +1,15 @@
import { IMotaDataLoader, IStateBase } from '@user/data-base';
-import { ILoadManager } from '@motajs/loader';
+import { ILoadManager, ILoadTaskStarter } from '@motajs/loader';
import { IStateSystem } from '@user/data-system';
import { ISaveableContent } from '@user/data-common';
-export interface ICoreStateConfig {}
+export interface ICoreStateConfig {
+ /**
+ * 加载启动器,用于适配不同加载环境。
+ * 例如网页端会使用 Web 加载器,而 node 端会使用专门的 node 加载器
+ */
+ readonly loadStarter: ILoadTaskStarter;
+}
export interface ISaveableExecutor {
/**
From 7d5f5d90c78b9aceda9e05b483b590c7d21731cd Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 20:37:55 +0800
Subject: [PATCH 035/173] docs(04): record design rulings D-19..D-23
---
.planning/phases/04-render-adaptation/04-CONTEXT.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
index 4be29af..fd12158 100644
--- a/.planning/phases/04-render-adaptation/04-CONTEXT.md
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -54,6 +54,7 @@
- **D-20:** 勇士渲染为**完全被动渲染**:不得提供允许渲染状态被外部修改的公共接口;旧版 `startMove` 等接口应**删除**,渲染端只需**绑定对应的勇士位置对象**即可。只有渲染端自身有需要的内容(如不透明度、贴图等),才允许在渲染端修改(与 D-10「`HeroRendering` 删除、渲染状态存储放渲染端」一致)。
- **D-21:** 探索文档 `#04-02-G-01`(移动语义钩子)裁决:**按用户新增的 `IObjectMoverHooks.onStepStart` / `onStepEnd` 等现有数据端钩子走**,渲染端改为接入这些已有钩子,**不新增数据端公共接口**(与 D-20 被动渲染一致)。
- **D-22:** **跟随者暂缓**(`IHeroFollower` / `IHeroFollowersController` 及其 hooks):用户尚未确定「跟随者的位置/动画由数据端驱动还是渲染端计算」以及 `onRemoveAllFollowers` 的处置,需再考虑。本次**不纳入** 04-03,也**不单列**计划;待用户想清楚后再规划。
+- **D-23:** **不使用 `client` / `state` 全局单例**(`client-modules/src/core.ts` 的 `client`、`data-state/src/ins.ts` 的 `state`)。若有必须调用它们的地方,**停止执行并告知用户**,由用户决定解决方案;不得自行选择绕过方案。**适用范围:新增/后续改动**;存量单例使用由用户**自己逐步整改**,AI 不主动清除、不据此阻塞当前工作。
### the agent's Discretion
- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
From 68c2f40161964a84eba673b9b55a62ff9cf659df Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Fri, 18 Sep 2026 21:11:18 +0800
Subject: [PATCH 036/173] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=E4=BE=9D?=
=?UTF-8?q?=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pnpm-lock.yaml | 901 ++++++++++++++++++++++----------------------
pnpm-workspace.yaml | 10 +-
2 files changed, 459 insertions(+), 452 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 605dcfe..8c7c78a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -22,7 +22,7 @@ importers:
version: 3.2.20(vue@3.5.29(typescript@6.0.3))
axios:
specifier: ^1.13.6
- version: 1.13.6
+ version: 1.13.6(debug@4.4.3(supports-color@8.1.1))
chart.js:
specifier: ^4.5.1
version: 4.5.1
@@ -65,19 +65,19 @@ importers:
devDependencies:
'@babel/cli':
specifier: ^7.28.6
- version: 7.28.6(@babel/core@7.29.0)
+ version: 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/core':
specifier: ^7.29.0
- version: 7.29.0
+ version: 7.29.0(supports-color@8.1.1)
'@babel/preset-env':
specifier: ^7.29.0
- version: 7.29.0(@babel/core@7.29.0)
+ version: 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@eslint/js':
specifier: ^9.39.4
version: 9.39.4
'@rollup/plugin-babel':
specifier: ^6.1.0
- version: 6.1.0(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@4.59.0)
+ version: 6.1.0(@babel/core@7.29.0(supports-color@8.1.1))(@types/babel__core@7.20.5)(rollup@4.59.0)(supports-color@8.1.1)
'@rollup/plugin-commonjs':
specifier: ^25.0.8
version: 25.0.8(rollup@4.59.0)
@@ -125,13 +125,13 @@ importers:
version: 8.18.1
'@vitejs/plugin-legacy':
specifier: ^7.2.1
- version: 7.2.1(terser@5.46.0)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))
+ version: 7.2.1(supports-color@8.1.1)(terser@5.46.0)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))
'@vitejs/plugin-vue':
specifier: ^6.0.4
version: 6.0.4(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.29(typescript@6.0.3))
'@vitejs/plugin-vue-jsx':
specifier: ^5.1.4
- version: 5.1.4(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.29(typescript@6.0.3))
+ version: 5.1.4(supports-color@8.1.1)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.29(typescript@6.0.3))
archiver:
specifier: ^7.0.1
version: 7.0.1
@@ -143,25 +143,25 @@ importers:
version: 1.10.4
eslint:
specifier: ^9.39.4
- version: 9.39.4
+ version: 9.39.4(supports-color@8.1.1)
eslint-config-prettier:
specifier: ^10.1.8
- version: 10.1.8(eslint@9.39.4)
+ version: 10.1.8(eslint@9.39.4(supports-color@8.1.1))
eslint-plugin-prettier:
specifier: ^5.5.5
- version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.1)
+ version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(supports-color@8.1.1)))(eslint@9.39.4(supports-color@8.1.1))(prettier@3.8.1)
eslint-plugin-react:
specifier: ^7.37.5
- version: 7.37.5(eslint@9.39.4)
+ version: 7.37.5(eslint@9.39.4(supports-color@8.1.1))
eslint-plugin-vue:
specifier: ^9.33.0
- version: 9.33.0(eslint@9.39.4)
+ version: 9.33.0(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)
express:
specifier: ^5.2.1
- version: 5.2.1
+ version: 5.2.1(supports-color@8.1.1)
fontmin:
specifier: ^2.0.3
- version: 2.0.3
+ version: 2.0.3(supports-color@8.1.1)
fs-extra:
specifier: ^11.3.4
version: 11.3.4
@@ -176,7 +176,7 @@ importers:
version: 4.5.1
madge:
specifier: ^8.0.0
- version: 8.0.0(typescript@6.0.3)
+ version: 8.0.0(supports-color@8.1.1)(typescript@6.0.3)
markdown-it-mathjax3:
specifier: ^4.3.2
version: 4.3.2(encoding@0.1.13)
@@ -203,19 +203,19 @@ importers:
version: 6.0.3
typescript-eslint:
specifier: ^8.58.2
- version: 8.58.2(eslint@9.39.4)(typescript@6.0.3)
+ version: 8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
vite:
specifier: ^7.3.1
version: 7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)
vite-plugin-dts:
specifier: ^4.5.4
- version: 4.5.4(@types/node@22.19.15)(rollup@4.59.0)(typescript@6.0.3)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))
+ version: 4.5.4(@types/node@22.19.15)(rollup@4.59.0)(supports-color@8.1.1)(typescript@6.0.3)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))
vitepress:
specifier: ^1.6.4
- version: 1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6)(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3)
+ version: 1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6(debug@4.4.3(supports-color@8.1.1)))(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3)
vitepress-plugin-mermaid:
specifier: ^2.0.17
- version: 2.0.17(mermaid@11.12.3)(vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6)(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3))
+ version: 2.0.17(mermaid@11.12.3)(vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6(debug@4.4.3(supports-color@8.1.1)))(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3))
vitest:
specifier: ^4.0.18
version: 4.0.18(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)
@@ -273,6 +273,9 @@ importers:
'@user/data-base':
specifier: workspace:*
version: link:../data-base
+ '@user/data-common':
+ specifier: workspace:*
+ version: link:../data-common
'@user/data-state':
specifier: workspace:*
version: link:../data-state
@@ -6401,9 +6404,9 @@ snapshots:
package-manager-detector: 1.6.0
tinyexec: 1.0.2
- '@babel/cli@7.28.6(@babel/core@7.29.0)':
+ '@babel/cli@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@jridgewell/trace-mapping': 0.3.31
commander: 6.2.1
convert-source-map: 2.0.0
@@ -6423,20 +6426,20 @@ snapshots:
'@babel/compat-data@7.29.0': {}
- '@babel/core@7.29.0':
+ '@babel/core@7.29.0(supports-color@8.1.1)':
dependencies:
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
'@babel/helper-compilation-targets': 7.28.6
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helpers': 7.28.6
'@babel/parser': 7.29.0
'@babel/template': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
'@babel/types': 7.29.0
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -6463,32 +6466,32 @@ snapshots:
lru-cache: 5.1.1
semver: 6.3.1
- '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@8.1.1)
'@babel/helper-optimise-call-expression': 7.27.1
- '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/traverse': 7.29.0
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1)
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)':
+ '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-annotate-as-pure': 7.27.3
regexpu-core: 6.4.0
semver: 6.3.1
- '@babel/helper-define-polyfill-provider@0.6.7(@babel/core@7.29.0)':
+ '@babel/helper-define-polyfill-provider@0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-compilation-targets': 7.28.6
'@babel/helper-plugin-utils': 7.28.6
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
lodash.debounce: 4.0.8
resolve: 1.22.11
transitivePeerDependencies:
@@ -6496,26 +6499,26 @@ snapshots:
'@babel/helper-globals@7.28.0': {}
- '@babel/helper-member-expression-to-functions@7.28.5':
+ '@babel/helper-member-expression-to-functions@7.28.5(supports-color@8.1.1)':
dependencies:
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
'@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-imports@7.28.6':
+ '@babel/helper-module-imports@7.28.6(supports-color@8.1.1)':
dependencies:
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
'@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-imports': 7.28.6
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1)
'@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -6525,27 +6528,27 @@ snapshots:
'@babel/helper-plugin-utils@7.28.6': {}
- '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)':
+ '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-wrap-function': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/helper-wrap-function': 7.28.6(supports-color@8.1.1)
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@8.1.1)
'@babel/helper-optimise-call-expression': 7.27.1
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1(supports-color@8.1.1)':
dependencies:
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
'@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
@@ -6556,10 +6559,10 @@ snapshots:
'@babel/helper-validator-option@7.27.1': {}
- '@babel/helper-wrap-function@7.28.6':
+ '@babel/helper-wrap-function@7.28.6(supports-color@8.1.1)':
dependencies:
'@babel/template': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
'@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
@@ -6573,494 +6576,494 @@ snapshots:
dependencies:
'@babel/types': 7.29.0
- '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)':
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1)
+ '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)':
+ '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
- '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)':
+ '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0)
- '@babel/traverse': 7.29.0
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-imports': 7.28.6
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0)
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-annotate-as-pure': 7.27.3
'@babel/helper-compilation-targets': 7.28.6
'@babel/helper-globals': 7.28.0
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
- '@babel/traverse': 7.29.0
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
'@babel/template': 7.28.6
- '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)':
+ '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)':
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0)
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-compilation-targets': 7.28.6
'@babel/helper-plugin-utils': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)':
+ '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
'@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)':
+ '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-compilation-targets': 7.28.6
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0)
- '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0)
- '@babel/traverse': 7.29.0
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)':
+ '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)':
+ '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1)
+ '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)':
+ '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/helper-plugin-utils': 7.28.6
- '@babel/preset-env@7.29.0(@babel/core@7.29.0)':
+ '@babel/preset-env@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
'@babel/compat-data': 7.29.0
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-compilation-targets': 7.28.6
'@babel/helper-plugin-utils': 7.28.6
'@babel/helper-validator-option': 7.27.1
- '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0)
- '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)
- '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0)
- '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0)
- '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0)
- '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0)
- '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0)
- '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0)
- '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0)
- '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0)
- '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0)
- '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0)
- babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0)
- babel-plugin-polyfill-corejs3: 0.14.1(@babel/core@7.29.0)
- babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0)
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0(supports-color@8.1.1))
+ babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ babel-plugin-polyfill-corejs3: 0.14.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
core-js-compat: 3.48.0
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)':
+ '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0(supports-color@8.1.1))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
'@babel/types': 7.29.0
esutils: 2.0.3
@@ -7073,7 +7076,7 @@ snapshots:
'@babel/parser': 7.29.0
'@babel/types': 7.29.0
- '@babel/traverse@7.29.0':
+ '@babel/traverse@7.29.0(supports-color@8.1.1)':
dependencies:
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
@@ -7081,7 +7084,7 @@ snapshots:
'@babel/parser': 7.29.0
'@babel/template': 7.28.6
'@babel/types': 7.29.0
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -7535,17 +7538,17 @@ snapshots:
'@eshaz/web-worker@1.2.2': {}
- '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)':
+ '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(supports-color@8.1.1))':
dependencies:
- eslint: 9.39.4
+ eslint: 9.39.4(supports-color@8.1.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
- '@eslint/config-array@0.21.2':
+ '@eslint/config-array@0.21.2(supports-color@8.1.1)':
dependencies:
'@eslint/object-schema': 2.1.7
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
minimatch: 3.1.5
transitivePeerDependencies:
- supports-color
@@ -7558,10 +7561,10 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/eslintrc@3.3.5':
+ '@eslint/eslintrc@3.3.5(supports-color@8.1.1)':
dependencies:
ajv: 6.14.0
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
@@ -7699,13 +7702,13 @@ snapshots:
'@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3':
optional: true
- '@npmcli/agent@2.2.2':
+ '@npmcli/agent@2.2.2(supports-color@8.1.1)':
dependencies:
agent-base: 7.1.4
- http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6
+ http-proxy-agent: 7.0.2(supports-color@8.1.1)
+ https-proxy-agent: 7.0.6(supports-color@8.1.1)
lru-cache: 10.4.3
- socks-proxy-agent: 8.0.5
+ socks-proxy-agent: 8.0.5(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -7722,10 +7725,10 @@ snapshots:
'@rolldown/pluginutils@1.0.0-rc.7': {}
- '@rollup/plugin-babel@6.1.0(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@4.59.0)':
+ '@rollup/plugin-babel@6.1.0(@babel/core@7.29.0(supports-color@8.1.1))(@types/babel__core@7.20.5)(rollup@4.59.0)(supports-color@8.1.1)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-imports': 7.28.6
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1)
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
optionalDependencies:
'@types/babel__core': 7.20.5
@@ -8227,15 +8230,15 @@ snapshots:
dependencies:
'@types/node': 22.19.15
- '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3))(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.58.2(eslint@9.39.4)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.58.2
- '@typescript-eslint/type-utils': 8.58.2(eslint@9.39.4)(typescript@6.0.3)
- '@typescript-eslint/utils': 8.58.2(eslint@9.39.4)(typescript@6.0.3)
+ '@typescript-eslint/type-utils': 8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.58.2
- eslint: 9.39.4
+ eslint: 9.39.4(supports-color@8.1.1)
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -8243,32 +8246,32 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.58.2(eslint@9.39.4)(typescript@6.0.3)':
+ '@typescript-eslint/parser@8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.58.2
'@typescript-eslint/types': 8.58.2
- '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
+ '@typescript-eslint/typescript-estree': 8.58.2(supports-color@8.1.1)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.58.2
- debug: 4.4.3
- eslint: 9.39.4
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 9.39.4(supports-color@8.1.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.58.2(typescript@5.9.3)':
+ '@typescript-eslint/project-service@8.58.2(supports-color@8.1.1)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3)
'@typescript-eslint/types': 8.58.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.58.2(typescript@6.0.3)':
+ '@typescript-eslint/project-service@8.58.2(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.3)
'@typescript-eslint/types': 8.58.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -8286,13 +8289,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.58.2(eslint@9.39.4)(typescript@6.0.3)':
+ '@typescript-eslint/type-utils@8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.58.2
- '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
- '@typescript-eslint/utils': 8.58.2(eslint@9.39.4)(typescript@6.0.3)
- debug: 4.4.3
- eslint: 9.39.4
+ '@typescript-eslint/typescript-estree': 8.58.2(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 9.39.4(supports-color@8.1.1)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -8300,13 +8303,13 @@ snapshots:
'@typescript-eslint/types@8.58.2': {}
- '@typescript-eslint/typescript-estree@8.58.2(typescript@5.9.3)':
+ '@typescript-eslint/typescript-estree@8.58.2(supports-color@8.1.1)(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/project-service': 8.58.2(typescript@5.9.3)
+ '@typescript-eslint/project-service': 8.58.2(supports-color@8.1.1)(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3)
'@typescript-eslint/types': 8.58.2
'@typescript-eslint/visitor-keys': 8.58.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
minimatch: 10.2.5
semver: 7.7.4
tinyglobby: 0.2.16
@@ -8315,13 +8318,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.3)':
+ '@typescript-eslint/typescript-estree@8.58.2(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/project-service': 8.58.2(typescript@6.0.3)
+ '@typescript-eslint/project-service': 8.58.2(supports-color@8.1.1)(typescript@6.0.3)
'@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.3)
'@typescript-eslint/types': 8.58.2
'@typescript-eslint/visitor-keys': 8.58.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
minimatch: 10.2.5
semver: 7.7.4
tinyglobby: 0.2.16
@@ -8330,13 +8333,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.58.2(eslint@9.39.4)(typescript@6.0.3)':
+ '@typescript-eslint/utils@8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(supports-color@8.1.1))
'@typescript-eslint/scope-manager': 8.58.2
'@typescript-eslint/types': 8.58.2
- '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
- eslint: 9.39.4
+ '@typescript-eslint/typescript-estree': 8.58.2(supports-color@8.1.1)(typescript@6.0.3)
+ eslint: 9.39.4(supports-color@8.1.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -8348,14 +8351,14 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
- '@vitejs/plugin-legacy@7.2.1(terser@5.46.0)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))':
+ '@vitejs/plugin-legacy@7.2.1(supports-color@8.1.1)(terser@5.46.0)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0)
- '@babel/preset-env': 7.29.0(@babel/core@7.29.0)
- babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0)
- babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/preset-env': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
+ babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
browserslist: 4.28.1
browserslist-to-esbuild: 2.1.1(browserslist@4.28.1)
core-js: 3.48.0
@@ -8367,13 +8370,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vitejs/plugin-vue-jsx@5.1.4(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.29(typescript@6.0.3))':
+ '@vitejs/plugin-vue-jsx@5.1.4(supports-color@8.1.1)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.29(typescript@6.0.3))':
dependencies:
- '@babel/core': 7.29.0
- '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
+ '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@rolldown/pluginutils': 1.0.0-rc.7
- '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0)
+ '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
vite: 7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)
vue: 3.5.29(typescript@6.0.3)
transitivePeerDependencies:
@@ -8455,27 +8458,27 @@ snapshots:
'@vue/babel-helper-vue-transform-on@2.0.1': {}
- '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.0)':
+ '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))
'@babel/template': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@8.1.1)
'@babel/types': 7.29.0
'@vue/babel-helper-vue-transform-on': 2.0.1
- '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0)
+ '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
'@vue/shared': 3.5.29
optionalDependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.0)':
+ '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
'@babel/code-frame': 7.29.0
- '@babel/core': 7.29.0
- '@babel/helper-module-imports': 7.28.6
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1)
'@babel/helper-plugin-utils': 7.28.6
'@babel/parser': 7.29.0
'@vue/compiler-sfc': 3.5.29
@@ -8594,14 +8597,14 @@ snapshots:
transitivePeerDependencies:
- typescript
- '@vueuse/integrations@12.8.2(async-validator@4.2.5)(axios@1.13.6)(focus-trap@7.8.0)(typescript@6.0.3)':
+ '@vueuse/integrations@12.8.2(async-validator@4.2.5)(axios@1.13.6(debug@4.4.3(supports-color@8.1.1)))(focus-trap@7.8.0)(typescript@6.0.3)':
dependencies:
'@vueuse/core': 12.8.2(typescript@6.0.3)
'@vueuse/shared': 12.8.2(typescript@6.0.3)
vue: 3.5.29(typescript@6.0.3)
optionalDependencies:
async-validator: 4.2.5
- axios: 1.13.6
+ axios: 1.13.6(debug@4.4.3(supports-color@8.1.1))
focus-trap: 7.8.0
transitivePeerDependencies:
- typescript
@@ -8859,9 +8862,9 @@ snapshots:
dependencies:
possible-typed-array-names: 1.1.0
- axios@1.13.6:
+ axios@1.13.6(debug@4.4.3(supports-color@8.1.1)):
dependencies:
- follow-redirects: 1.15.11
+ follow-redirects: 1.15.11(debug@4.4.3(supports-color@8.1.1))
form-data: 4.0.5
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -8871,35 +8874,35 @@ snapshots:
b4a@1.8.0: {}
- babel-plugin-polyfill-corejs2@0.4.16(@babel/core@7.29.0):
+ babel-plugin-polyfill-corejs2@0.4.16(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
'@babel/compat-data': 7.29.0
- '@babel/core': 7.29.0
- '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0):
+ babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
core-js-compat: 3.48.0
transitivePeerDependencies:
- supports-color
- babel-plugin-polyfill-corejs3@0.14.1(@babel/core@7.29.0):
+ babel-plugin-polyfill-corejs3@0.14.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
core-js-compat: 3.48.0
transitivePeerDependencies:
- supports-color
- babel-plugin-polyfill-regenerator@0.6.7(@babel/core@7.29.0):
+ babel-plugin-polyfill-regenerator@0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@8.1.1)
+ '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -8969,11 +8972,11 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
- body-parser@2.2.2:
+ body-parser@2.2.2(supports-color@8.1.1):
dependencies:
bytes: 3.1.2
content-type: 1.0.5
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
http-errors: 2.0.1
iconv-lite: 0.7.2
on-finished: 2.4.1
@@ -9527,9 +9530,11 @@ snapshots:
de-indent@1.0.2: {}
- debug@4.4.3:
+ debug@4.4.3(supports-color@8.1.1):
dependencies:
ms: 2.1.3
+ optionalDependencies:
+ supports-color: 8.1.1
deep-extend@0.6.0: {}
@@ -9561,11 +9566,11 @@ snapshots:
depd@2.0.0: {}
- dependency-tree@11.4.0:
+ dependency-tree@11.4.0(supports-color@8.1.1):
dependencies:
commander: 12.1.0
filing-cabinet: 5.2.0
- precinct: 12.2.0
+ precinct: 12.2.0(supports-color@8.1.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -9606,16 +9611,16 @@ snapshots:
detective-stylus@5.0.1: {}
- detective-typescript@14.0.0(typescript@5.9.3):
+ detective-typescript@14.0.0(supports-color@8.1.1)(typescript@5.9.3):
dependencies:
- '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.58.2(supports-color@8.1.1)(typescript@5.9.3)
ast-module-types: 6.0.1
node-source-walk: 7.0.1
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- detective-vue2@2.2.0(typescript@5.9.3):
+ detective-vue2@2.2.0(supports-color@8.1.1)(typescript@5.9.3):
dependencies:
'@dependents/detective-less': 5.0.1
'@vue/compiler-sfc': 3.5.29
@@ -9623,7 +9628,7 @@ snapshots:
detective-sass: 6.0.1
detective-scss: 5.0.1
detective-stylus: 5.0.1
- detective-typescript: 14.0.0(typescript@5.9.3)
+ detective-typescript: 14.0.0(supports-color@8.1.1)(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -9893,20 +9898,20 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
- eslint-config-prettier@10.1.8(eslint@9.39.4):
+ eslint-config-prettier@10.1.8(eslint@9.39.4(supports-color@8.1.1)):
dependencies:
- eslint: 9.39.4
+ eslint: 9.39.4(supports-color@8.1.1)
- eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.1):
+ eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(supports-color@8.1.1)))(eslint@9.39.4(supports-color@8.1.1))(prettier@3.8.1):
dependencies:
- eslint: 9.39.4
+ eslint: 9.39.4(supports-color@8.1.1)
prettier: 3.8.1
prettier-linter-helpers: 1.0.1
synckit: 0.11.12
optionalDependencies:
- eslint-config-prettier: 10.1.8(eslint@9.39.4)
+ eslint-config-prettier: 10.1.8(eslint@9.39.4(supports-color@8.1.1))
- eslint-plugin-react@7.37.5(eslint@9.39.4):
+ eslint-plugin-react@7.37.5(eslint@9.39.4(supports-color@8.1.1)):
dependencies:
array-includes: 3.1.9
array.prototype.findlast: 1.2.5
@@ -9914,7 +9919,7 @@ snapshots:
array.prototype.tosorted: 1.1.4
doctrine: 2.1.0
es-iterator-helpers: 1.2.2
- eslint: 9.39.4
+ eslint: 9.39.4(supports-color@8.1.1)
estraverse: 5.3.0
hasown: 2.0.2
jsx-ast-utils: 3.3.5
@@ -9928,16 +9933,16 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
- eslint-plugin-vue@9.33.0(eslint@9.39.4):
+ eslint-plugin-vue@9.33.0(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
- eslint: 9.39.4
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(supports-color@8.1.1))
+ eslint: 9.39.4(supports-color@8.1.1)
globals: 13.24.0
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 6.1.2
semver: 7.7.4
- vue-eslint-parser: 9.4.3(eslint@9.39.4)
+ vue-eslint-parser: 9.4.3(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)
xml-name-validator: 4.0.0
transitivePeerDependencies:
- supports-color
@@ -9958,14 +9963,14 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@9.39.4:
+ eslint@9.39.4(supports-color@8.1.1):
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(supports-color@8.1.1))
'@eslint-community/regexpp': 4.12.2
- '@eslint/config-array': 0.21.2
+ '@eslint/config-array': 0.21.2(supports-color@8.1.1)
'@eslint/config-helpers': 0.4.2
'@eslint/core': 0.17.0
- '@eslint/eslintrc': 3.3.5
+ '@eslint/eslintrc': 3.3.5(supports-color@8.1.1)
'@eslint/js': 9.39.4
'@eslint/plugin-kit': 0.4.1
'@humanfs/node': 0.16.7
@@ -9975,7 +9980,7 @@ snapshots:
ajv: 6.14.0
chalk: 4.1.2
cross-spawn: 7.0.6
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -10049,20 +10054,20 @@ snapshots:
exponential-backoff@3.1.3: {}
- express@5.2.1:
+ express@5.2.1(supports-color@8.1.1):
dependencies:
accepts: 2.0.0
- body-parser: 2.2.2
+ body-parser: 2.2.2(supports-color@8.1.1)
content-disposition: 1.0.1
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
- finalhandler: 2.1.1
+ finalhandler: 2.1.1(supports-color@8.1.1)
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
@@ -10073,9 +10078,9 @@ snapshots:
proxy-addr: 2.0.7
qs: 6.15.0
range-parser: 1.2.1
- router: 2.2.0
- send: 1.2.1
- serve-static: 2.2.1
+ router: 2.2.0(supports-color@8.1.1)
+ send: 1.2.1(supports-color@8.1.1)
+ serve-static: 2.2.1(supports-color@8.1.1)
statuses: 2.0.2
type-is: 2.0.1
vary: 1.1.2
@@ -10142,9 +10147,9 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
- finalhandler@2.1.1:
+ finalhandler@2.1.1(supports-color@8.1.1):
dependencies:
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
@@ -10171,13 +10176,15 @@ snapshots:
dependencies:
tabbable: 6.4.0
- follow-redirects@1.15.11: {}
+ follow-redirects@1.15.11(debug@4.4.3(supports-color@8.1.1)):
+ optionalDependencies:
+ debug: 4.4.3(supports-color@8.1.1)
fonteditor-core@2.6.3:
dependencies:
'@xmldom/xmldom': 0.8.11
- fontmin@2.0.3:
+ fontmin@2.0.3(supports-color@8.1.1):
dependencies:
'@types/node': 22.19.15
'@types/through2': 2.0.41
@@ -10196,7 +10203,7 @@ snapshots:
replace-ext: 2.0.0
stream-combiner: 0.2.2
through2: 4.0.2
- ttf2woff2: 6.0.1
+ ttf2woff2: 6.0.1(supports-color@8.1.1)
vinyl-fs: 4.0.2
transitivePeerDependencies:
- bare-abort-controller
@@ -10465,17 +10472,17 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
- http-proxy-agent@7.0.2:
+ http-proxy-agent@7.0.2(supports-color@8.1.1):
dependencies:
agent-base: 7.1.4
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- https-proxy-agent@7.0.6:
+ https-proxy-agent@7.0.6(supports-color@8.1.1):
dependencies:
agent-base: 7.1.4
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -10867,13 +10874,13 @@ snapshots:
lz-string@1.5.0: {}
- madge@8.0.0(typescript@6.0.3):
+ madge@8.0.0(supports-color@8.1.1)(typescript@6.0.3):
dependencies:
chalk: 4.1.2
commander: 7.2.0
commondir: 1.0.1
- debug: 4.4.3
- dependency-tree: 11.4.0
+ debug: 4.4.3(supports-color@8.1.1)
+ dependency-tree: 11.4.0(supports-color@8.1.1)
ora: 5.4.1
pluralize: 8.0.0
pretty-ms: 7.0.1
@@ -10895,9 +10902,9 @@ snapshots:
pify: 4.0.1
semver: 5.7.2
- make-fetch-happen@13.0.1:
+ make-fetch-happen@13.0.1(supports-color@8.1.1):
dependencies:
- '@npmcli/agent': 2.2.2
+ '@npmcli/agent': 2.2.2(supports-color@8.1.1)
cacache: 18.0.4
http-cache-semantics: 4.2.0
is-lambda: 1.0.1
@@ -11145,13 +11152,13 @@ snapshots:
optionalDependencies:
encoding: 0.1.13
- node-gyp@10.3.1:
+ node-gyp@10.3.1(supports-color@8.1.1):
dependencies:
env-paths: 2.2.1
exponential-backoff: 3.1.3
glob: 10.4.5
graceful-fs: 4.2.11
- make-fetch-happen: 13.0.1
+ make-fetch-happen: 13.0.1(supports-color@8.1.1)
nopt: 7.2.1
proc-log: 4.2.0
semver: 7.7.4
@@ -11608,7 +11615,7 @@ snapshots:
preact@10.28.4: {}
- precinct@12.2.0:
+ precinct@12.2.0(supports-color@8.1.1):
dependencies:
'@dependents/detective-less': 5.0.1
commander: 12.1.0
@@ -11619,8 +11626,8 @@ snapshots:
detective-sass: 6.0.1
detective-scss: 5.0.1
detective-stylus: 5.0.1
- detective-typescript: 14.0.0(typescript@5.9.3)
- detective-vue2: 2.2.0(typescript@5.9.3)
+ detective-typescript: 14.0.0(supports-color@8.1.1)(typescript@5.9.3)
+ detective-vue2: 2.2.0(supports-color@8.1.1)(typescript@5.9.3)
module-definition: 6.0.1
node-source-walk: 7.0.1
postcss: 8.5.8
@@ -11884,9 +11891,9 @@ snapshots:
points-on-curve: 0.2.0
points-on-path: 0.2.1
- router@2.2.0:
+ router@2.2.0(supports-color@8.1.1):
dependencies:
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
@@ -11945,9 +11952,9 @@ snapshots:
semver@7.7.4: {}
- send@1.2.1:
+ send@1.2.1(supports-color@8.1.1):
dependencies:
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
@@ -11965,12 +11972,12 @@ snapshots:
dependencies:
randombytes: 2.1.0
- serve-static@2.2.1:
+ serve-static@2.2.1(supports-color@8.1.1):
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
- send: 1.2.1
+ send: 1.2.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -12063,10 +12070,10 @@ snapshots:
smob@1.6.1: {}
- socks-proxy-agent@8.0.5:
+ socks-proxy-agent@8.0.5(supports-color@8.1.1):
dependencies:
agent-base: 7.1.4
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
socks: 2.8.7
transitivePeerDependencies:
- supports-color
@@ -12389,13 +12396,13 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
- ttf2woff2@6.0.1:
+ ttf2woff2@6.0.1(supports-color@8.1.1):
dependencies:
bindings: 1.5.0
bufferstreams: 4.0.0
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
nan: 2.25.0
- node-gyp: 10.3.1
+ node-gyp: 10.3.1(supports-color@8.1.1)
yerror: 8.0.0
transitivePeerDependencies:
- supports-color
@@ -12447,13 +12454,13 @@ snapshots:
typedarray@0.0.6: {}
- typescript-eslint@8.58.2(eslint@9.39.4)(typescript@6.0.3):
+ typescript-eslint@8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@6.0.3)
- '@typescript-eslint/parser': 8.58.2(eslint@9.39.4)(typescript@6.0.3)
- '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
- '@typescript-eslint/utils': 8.58.2(eslint@9.39.4)(typescript@6.0.3)
- eslint: 9.39.4
+ '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3))(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/typescript-estree': 8.58.2(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)
+ eslint: 9.39.4(supports-color@8.1.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -12609,14 +12616,14 @@ snapshots:
- bare-abort-controller
- react-native-b4a
- vite-plugin-dts@4.5.4(@types/node@22.19.15)(rollup@4.59.0)(typescript@6.0.3)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)):
+ vite-plugin-dts@4.5.4(@types/node@22.19.15)(rollup@4.59.0)(supports-color@8.1.1)(typescript@6.0.3)(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)):
dependencies:
'@microsoft/api-extractor': 7.57.6(@types/node@22.19.15)
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
'@volar/typescript': 2.4.28
'@vue/language-core': 2.2.0(typescript@6.0.3)
compare-versions: 6.1.1
- debug: 4.4.3
+ debug: 4.4.3(supports-color@8.1.1)
kolorist: 1.8.0
local-pkg: 1.1.2
magic-string: 0.30.21
@@ -12654,14 +12661,14 @@ snapshots:
terser: 5.46.0
tsx: 4.21.0
- vitepress-plugin-mermaid@2.0.17(mermaid@11.12.3)(vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6)(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3)):
+ vitepress-plugin-mermaid@2.0.17(mermaid@11.12.3)(vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6(debug@4.4.3(supports-color@8.1.1)))(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3)):
dependencies:
mermaid: 11.12.3
- vitepress: 1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6)(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3)
+ vitepress: 1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6(debug@4.4.3(supports-color@8.1.1)))(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3)
optionalDependencies:
'@mermaid-js/mermaid-mindmap': 9.3.0
- vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6)(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3):
+ vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6(debug@4.4.3(supports-color@8.1.1)))(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@6.0.3):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.49.1)(search-insights@2.17.3)
@@ -12674,7 +12681,7 @@ snapshots:
'@vue/devtools-api': 7.7.9
'@vue/shared': 3.5.29
'@vueuse/core': 12.8.2(typescript@6.0.3)
- '@vueuse/integrations': 12.8.2(async-validator@4.2.5)(axios@1.13.6)(focus-trap@7.8.0)(typescript@6.0.3)
+ '@vueuse/integrations': 12.8.2(async-validator@4.2.5)(axios@1.13.6(debug@4.4.3(supports-color@8.1.1)))(focus-trap@7.8.0)(typescript@6.0.3)
focus-trap: 7.8.0
mark.js: 8.11.1
minisearch: 7.2.0
@@ -12765,10 +12772,10 @@ snapshots:
vscode-uri@3.1.0: {}
- vue-eslint-parser@9.4.3(eslint@9.39.4):
+ vue-eslint-parser@9.4.3(eslint@9.39.4(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
- debug: 4.4.3
- eslint: 9.39.4
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 9.39.4(supports-color@8.1.1)
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index d9f30ac..2c35590 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -2,13 +2,13 @@ packages:
- packages/*
- packages-user/*
- src/
+allowBuilds:
+ core-js: true
+ esbuild: true
+ less: true
+ ttf2woff2: true
ignore:
- packages/**/dist
- packages/**/node_modules
- packages-user/**/dist
- packages-user/**/node_modules
-onlyBuiltDependencies:
- - core-js
- - esbuild
- - ttf2woff2
- - vue-demi
From 4e649533ee75f8fbae2272f81514fb64d6c528f1 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Sat, 19 Sep 2026 12:33:10 +0800
Subject: [PATCH 037/173] =?UTF-8?q?chore:=20=E5=86=85=E9=83=A8=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E6=94=B9=E5=90=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/render/map/extension/hero.ts | 16 ++++++++--------
.../src/render/map/extension/types.ts | 1 -
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/packages-user/client-modules/src/render/map/extension/hero.ts b/packages-user/client-modules/src/render/map/extension/hero.ts
index af8c0a7..4a965b1 100644
--- a/packages-user/client-modules/src/render/map/extension/hero.ts
+++ b/packages-user/client-modules/src/render/map/extension/hero.ts
@@ -22,7 +22,7 @@ import { state } from '@user/data-state';
/** 默认的移动时长 */
const DEFAULT_TIME = 100;
-interface HeroRenderEntity {
+interface IHeroRenderEntity {
/** 移动图块对象 */
readonly block: IMovingBlock;
/** 标识符,用于判定跟随者 */
@@ -63,13 +63,13 @@ export class MapHeroRenderer implements IMapHeroRenderer {
/** 勇士每个朝向的贴图对象 */
readonly textureMap: Map = new Map();
/** 勇士渲染实体,与 `entities[0]` 同引用 */
- readonly heroEntity: HeroRenderEntity;
+ readonly heroEntity: IHeroRenderEntity;
/**
* 渲染实体,索引 0 表示勇士,后续索引依次表示跟随的跟随者。
* 整体是一个状态机,而且下一个跟随者只与上一个跟随者有关,下一个跟随者移动的方向就是上一个跟随者上一步移动后指向的方向。
*/
- readonly entities: HeroRenderEntity[] = [];
+ readonly entities: IHeroRenderEntity[] = [];
/** 每帧执行的帧动画对象 */
readonly ticker: IMapRendererTicker;
@@ -85,7 +85,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
this.moverController = hero.mover.addHook(hook);
this.moverController.load();
const moving = this.addHeroMoving(renderer, layer, hero);
- const heroEntity: HeroRenderEntity = {
+ const heroEntity: IHeroRenderEntity = {
block: moving,
identifier: '',
targetX: hero.x,
@@ -223,7 +223,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
* @param time 移动时长
*/
private moveEntity(
- entity: HeroRenderEntity,
+ entity: IHeroRenderEntity,
direction: FaceDirection,
time: number
) {
@@ -272,7 +272,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
* @param time 跳跃时长
*/
private jumpEntity(
- entity: HeroRenderEntity,
+ entity: IHeroRenderEntity,
x: number,
y: number,
time: number
@@ -295,7 +295,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
this.heroEntity.lastAnimateTime = this.ticker.timestamp;
}
- private endEntityMoving(entity: HeroRenderEntity) {
+ private endEntityMoving(entity: IHeroRenderEntity) {
entity.moving = false;
entity.animating = false;
entity.animateFrame = 0;
@@ -366,7 +366,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
const x = last.block.x - dxn;
const y = last.block.y - dyn;
const moving = this.renderer.addMovingBlock(this.layer, tex, x, y);
- const entity: HeroRenderEntity = {
+ const entity: IHeroRenderEntity = {
block: moving,
identifier: id,
targetX: last.targetX - dx,
diff --git a/packages-user/client-modules/src/render/map/extension/types.ts b/packages-user/client-modules/src/render/map/extension/types.ts
index a72b983..ce6e198 100644
--- a/packages-user/client-modules/src/render/map/extension/types.ts
+++ b/packages-user/client-modules/src/render/map/extension/types.ts
@@ -1,6 +1,5 @@
import { ITexture, Font } from '@motajs/render';
import { IHeroLocation, IMapLayer } from '@user/data-base';
-
import { IMapRenderResult } from '../types';
export interface IMapExtensionManager {
From f99ad0cbe99fcd22b22e2bddcc119fe7e2a091a7 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Sat, 19 Sep 2026 12:56:01 +0800
Subject: [PATCH 038/173] docs(04): create phase plan 04-04 (hero render
correction, D-24..D-28)
---
.planning/ROADMAP.md | 6 +-
.../phases/04-render-adaptation/04-04-PLAN.md | 496 ++++++++++++++++++
2 files changed, 501 insertions(+), 1 deletion(-)
create mode 100644 .planning/phases/04-render-adaptation/04-04-PLAN.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 2637785..656df71 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 3/3 plans executed(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已完成;第三步·勇士本体适配(渲染端被动重接)已规划,待执行 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18/D-19/D-20/D-21/D-22;其余渲染适配与移动端/桌面端双布局待后续增量另行规划)
+**Plans**: 4 plans(第一步·只读对账已完成;第二步·勇士移动接口探索(只读)已完成;第三步·勇士本体适配已执行;第四步·勇士渲染修正(04-03 人工审查反馈 D-24..D-28)已规划,待执行 — 04-CONTEXT D-01/D-02/D-13/D-17/D-18/D-19/D-20/D-21/D-22/D-24..D-28;其余渲染适配与移动端/桌面端双布局待后续增量另行规划)
Plans:
**Wave 1**
@@ -196,6 +196,10 @@ Plans:
- [x] 04-03-PLAN.md — 勇士本体适配(渲染端被动重接):`render/map/extension/{hero,types,manager}.ts` 绑定 `IHeroLocation` / `IHeroMover`,改接现有钩子 `IHeroLocationHooks` / `IObjectMoverHooks`(D-21),按 D-20 裁剪 `IMapHeroRenderer` 的外部驱动成员;排除贴图/不透明度(D-18)、跟随者(D-22)、移动控制(D-19)、`core.*`(D-12)与裸函数(D-11),不新增数据端接口
+**Wave 4** *(blocked on Wave 3 completion)*
+
+- [ ] 04-04-PLAN.md — 勇士渲染修正(04-03 人工审查反馈 D-24..D-28):`render/map/extension/{hero,manager}.ts` 注入 `IFaceHandler` 并替换三个弃用 helper(D-24)、拆分 `MapHeroLocationHook` / `MapHeroMoverHook` 且 `onSetPos` 无条件(D-25)、补 `AnimDir`(D-26)、改 `@motajs/animate` 的 `ExcitationCurve2D`(D-27)、保留存量 `state` 用法(D-28);不改 `types.ts`、不新增数据端接口,排除 D-18/D-19/D-22
+
**UI hint**: yes
### Phase 5: Legacy 移植
diff --git a/.planning/phases/04-render-adaptation/04-04-PLAN.md b/.planning/phases/04-render-adaptation/04-04-PLAN.md
new file mode 100644
index 0000000..c51390c
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-04-PLAN.md
@@ -0,0 +1,496 @@
+---
+phase: 04-render-adaptation
+plan: 4
+type: execute
+wave: 4
+depends_on:
+ - 04-01
+ - 04-02
+ - 04-03
+files_modified:
+ - packages-user/client-modules/src/render/map/extension/hero.ts
+ - packages-user/client-modules/src/render/map/extension/manager.ts
+autonomous: false
+requirements:
+ - REND-01
+ - REND-02
+estimate:
+ tokens: 73000
+ raw_tokens: 73000
+ tasks: 4
+ 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` 的 `movement(dir)` / `degrade(dir)` / `next(dir)`;`MapHeroRenderer` 构造器新增 `IFaceHandler` 参数,并在 `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),与旧 helper 的 `unknown` 参数逐字等价;该处理已在 Task 0 汇报"
+ - "**D-25 落地**:`MapHeroHook` 拆为**两个内部类**(`MapHeroLocationHook` 实现 `Partial`,`MapHeroMoverHook` 实现 `Partial>`),各自用 `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:148-152` 已注释),故本计划的实质验证为「编译 + 契约 + 静态」三类机器门禁,另附人工 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` 并以 `movement` / `degrade` / `next` 取代三个弃用 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/manager.ts
+ provides: "`addHero` 把 `state.mover.faceHandler` 作为第四个实参注入 `new MapHeroRenderer(this.renderer, layer, state, state.mover.faceHandler)`;无其它改动"
+ key_links:
+ - "`manager.ts` 的 `addHero(state: IHeroLocation, layer)` → `new MapHeroRenderer(this.renderer, layer, state, state.mover.faceHandler)` ↔ `hero.ts` 构造器 `readonly faceHandler: IFaceHandler`(`packages/common/src/utils/types.ts:152`)"
+ - "`IObjectMover.faceHandler: IFaceHandler`(`data-common/src/common/mover.ts:251`)↔ `IHeroMover extends IObjectMover`(`data-base/src/hero/types.ts:399`)↔ `IHeroLocation.mover: IHeroMover`(`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`)"
+ - "`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`)"
+ - "`generateJumpFn(dx, dy): ExcitationCurve2D`(`@motajs/animate`)↔ `IMovingBlock.moveRelative(curve: TimingFn<2>, time)`(`render/map/moving.ts:162-166`):`ExcitationCurve2D = (progress: number) => [number, number]`(`packages/animate/src/types.ts:12`)与 `TimingFn<2> = (input: number) => [number, number]`(`node_modules/mutate-animate/index.d.ts:12-14`)结构等价"
+ - "`pnpm exec vue-tsc --noEmit` 的 `client-modules` 错误集基线(Task 1 当日实测,逐行入 SUMMARY)↔ Task 1 / 2 / 3 门禁的一致断言"
+ 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` 七成员、`movement` 返回 `IFaceDescriptor`、`degrade` 对 Unknown 返回 Unknown;② `IObjectMover.faceHandler` 存在且类型为 `IFaceHandler`;③ `IHeroLocation.mover` 为 `IHeroMover` 且 `HeroLocation` 由 `IFaceHandler` 构造 `HeroMover`;④ 三个弃用 helper 仍导出且签名如记录(「待替换物尚在」的核对,不是要求保留它们);⑤ `@motajs/animate` 导出 `ExcitationCurve2D`、`mutate-animate` 的 `TimingFn<2>` 与 `MovingBlock.moveRelative` 的形参形态;⑥ `IObjectMoveAnimDir.dir`、`prepareStep` 对 AnimDir 的处理、`moveProgress` 的钩子时序。任一不同即**停下汇报并修订本计划**,不得按记忆改代码。"
+ - "**`IFaceHandler` 语义等价性(Task 0 陈述、Task 1 编译佐证):** 弃用 helper 面向八方向全集,而 `hero.mover` 注入的处理器是有限方向组(`docs/dev/hero/hero-mover.md:74` 记载为 `Dir4FaceHandler`,`data-common/src/common/faceManager.ts:169`)。替换后:① `degrade` 与 `degradeFace(dir)`(unknown 默认 Unknown)对 Unknown 同返回 Unknown;② `movement` / `next` 会先 `degrade`,对落在本组内的正交朝向与旧 helper 逐字一致;③ `nextFaceDirection(dir, false, false)`(默认 face8=false)对**对角输入**与 `IFaceHandler.next`(先 degrade 再 90°)不同,但 `hero.ts:453` 的输入 `this.heroEntity.direction` 只可能来自 `getCurrentFaceDirection()` / `turn(...)` / `degrade(...)`,均为本组朝向,故实际不触发差异;若执行当日实测处理器为 `Dir8FaceHandler`,须在 SUMMARY 记录并复核 `next` / `movement` 差异。"
+ - "**运行时 UAT 不可得(诚实降级):** `MapExtensionManager.addHero` 目前无任何调用点(`client.ts:148-152` 被注释),故勇士渲染拓展在运行时并未启用;本计划不接线 `client.ts`。因此本计划的运行结果无法在渲染环境观察,验收依赖「scoped 类型门禁 + 契约 / 静态门禁 + 人工代码复核」,运行时渲染 UAT 顺延到启用接线的增量。"
+ - "**数据端实现可能被用户并发修改(D-05):** 接口签名视为稳定,实现可变;本计划只读引用数据端接口,不修改任何数据端文件。若执行当日数据端接口签名与规划基线不一致,按 Task 1 步骤 (0a) 停下汇报,不按旧形状适配。"
+ - "**成员命名需用户同意:** 本计划引入 / 删除的渲染端成员名(两个钩子类 `MapHeroLocationHook` / `MapHeroMoverHook`、`MapHeroRenderer` 的新构造器参数 / 字段 `faceHandler`、被删除的类名 `MapHeroHook`,以及 `onSetPos` 与 D-18/D-22 多余方法的归属)须在 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/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`);不得让渲染端出现允许外部驱动渲染状态的新方法。"
+ - "不得修改任何既有 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`);出现安装需求即暂停并要求用户确认包合法性。"
+ - "不得以「无测试设施」为由弱化验证:每条 `` 必须有 ``;确实无法运行的门禁必须显式声明为不可得并降级为静态 + 人工,不得伪造绿。"
+---
+
+
+Phase 4 第四个增量 —— **勇士渲染修正:04-03 人工审查反馈落地**。按用户对 04-03 产出的裁定 D-24..D-28,修正 `render/map/extension/hero.ts`(以及注入所需的 `.../manager.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`)。
+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>`。
+5. **D-28 存量 `state`**:`hero.ts` 的 `state.roleFace.getFaceOf(...)`(`:235`/`:358`/`:417`)属存量单例使用——D-23 只约束新增 / 后续代码;本计划**保留**该用法,只记录、不处理。
+
+**大致解决方案(逐调用替换映射表):**
+
+| 旧调用(hero.ts) | 替换 | 说明 |
+|---|---|---|
+| `degradeFace(dir)` `:132` / `:202` | `this.faceHandler.degrade(dir)` | 旧 helper 默认 `unknown = Unknown`,与 `IFaceHandler.degrade` 对 Unknown 返回 Unknown 一致 |
+| `degradeFace(dir, FaceDirection.Down)` `:357` | `this.faceHandler.degrade(dir)`,随后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果` | `IFaceHandler.degrade` 无 `unknown` 形参,Down 回退须显式保留(行为逐字等价) |
+| `getFaceMovement(dir)` `:230` / `:364` / `:365` | `this.faceHandler.movement(dir)` | 返回 `IFaceDescriptor { x, y }`,解构用法不变 |
+| `nextFaceDirection(dir)` `:453` | `this.faceHandler.next(dir)` | 默认顺时针;旧 helper 的 `face8=false` / `anticlockwise=false` 默认与此一致,`face8` 在本处未被使用 |
+
+- **D-24 注入**:`MapHeroRenderer` 构造器新增 `IFaceHandler` 参数(拟名 `faceHandler`);`manager.ts` 的 `new MapHeroRenderer(...)` 由 `state.mover.faceHandler` 注入(`IObjectMover.faceHandler`,`data-common/src/common/mover.ts:251`;`state` 是 `addHero` 的 `IHeroLocation` 形参,不是全局单例,符合 D-23)。
+- **D-25 拆分**:把 `MapHeroHook` 拆成 `MapHeroLocationHook`(`Partial`;实现 `onSetPos` **且只实现一次**,另承载 D-18/D-22 的非接口多余方法)与 `MapHeroMoverHook`(`Partial>`;实现 `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` **不改**(新参数只在构造器里,不进任何接口);无新增公共接口成员。
+- **范围硬边界**:贴图 / 不透明度(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`。
+
+**显式延后(本计划不涉及、不实施):** `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 为只读汇报关卡,不写任何文件。
+
+## Source Audit(多源覆盖审计)
+
+| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
+|--------|----|---------------------|------|--------|-------|
+| GOAL | — | Phase 4:「渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局」 | 04-04 | COVERED(增量·勇士渲染修正子切片) | 本 run 只落地 04-03 人工审查反馈 D-24..D-28;其余渲染适配与双布局属后续增量(REND-02 见 assumptions) |
+| 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-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-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 保留 |
+| CONTEXT | D-05 | 以接口为准,不受数据端实现变动影响 | 04-04 | COVERED | Task 1 步骤 (0a) 复核;签名若变则停下汇报 |
+| CONTEXT | D-11 | `getHeroStatusOn` 类裸函数多为待删除 | 04-04 | COVERED(边界) | 非勇士本体;prohibitions 明列不处理 |
+| CONTEXT | D-12 | `core.*` 属旧引擎适配,归第五阶段 | 04-04 | COVERED(排除) | prohibitions 明列不得引入 |
+| CONTEXT | D-01..D-17 | 阶段 4 增量方式 / 对账 / 探索 / 聚焦 | 04-01 / 04-02 / 04-03 | COVERED(前序计划) | 由前序计划落地;本计划是其审查修正 |
+| CONTEXT | D-13 | 缺失接口待改时逐步确认 | 04-04 | COVERED(边界) | 本计划不做新的缺失接口裁定 |
+| CONTEXT | D-14..D-16 | hook / loading / 存档 UI / 结构化重构 | 后续阶段 | DEFERRED(CONTEXT 未纳入本阶段) | 非本增量范围 |
+
+## Estimate Basis(估算依据)
+
+`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。按「执行者实际触及量」估算(`estimate.tasks: 4` = Task 0 汇报关卡 + 三个工作任务):Task 0 只读汇报并等待用户确认(约 4k,不写文件);Task 1(tracer)起始复核数据端现行接口 + 注入 + 7 处替换 + scoped 类型门禁(约 28k,含 `hero.ts` 552 行与数据端基准的定向读取);Task 2 拆钩子类 + AnimDir + 门禁(约 22k);Task 3 D-27 替换 + 全部门禁 + 人工 UAT 复核 + SUMMARY(约 15k);固定开销约 4k;合计约 73k。
+
+
+
+
+@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
+@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/REQUIREMENTS.md
+@.planning/phases/04-render-adaptation/04-CONTEXT.md
+@.planning/phases/04-render-adaptation/04-03-PLAN.md
+@.planning/phases/04-render-adaptation/04-03-SUMMARY.md
+@.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+@.planning/phases/04-render-adaptation/04-PATTERNS.md
+@AGENTS.md
+@dev.md
+
+
+
+
+
+ Task 0: 预执行汇报关卡 —— 汇报 D-24..D-28 的解决方案、逐调用替换映射表、成员命名清单与 `onSetPos` / D-18·D-22 方法归属,等待用户回复「可以执行」
+ 用户对 04-03 的人工审查裁定 D-24..D-28 已写入 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(以文件内实际存在 D-24..D-28 为准)
+ (只读汇报,不修改任何文件)
+
+ - AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行;「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈,我同意后才可以使用」)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-24 / D-25 / D-26 / D-27 / D-28 裁定原文;D-18 / D-19 / D-20 / D-21 / D-22 / D-23 边界)
+ - .planning/phases/04-render-adaptation/04-03-PLAN.md 与 04-03-SUMMARY.md(04-03 的产出与命名基线:`locationController` / `moverController` 已获批;本计划是其后修正)
+ - .planning/phases/04-render-adaptation/04-04-PLAN.md(本计划全文:objective 的映射表 / tasks / 禁令 / 门禁)
+ - 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/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>`)
+
+
+ **停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
+
+ ① **要解决的问题(来自用户对 04-03 的人工审查,五条)**:
+ - D-24:`hero.ts` 仍在用已弃用 helper `getFaceMovement` / `degradeFace` / `nextFaceDirection`(import `:2`/`:4`/`:7`,使用 `:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453`),数据端现行接口是 `IFaceHandler`。
+ - 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` 参数并保存为字段;映射表:
+ `degradeFace(dir)` `:132`/`:202` → `faceHandler.degrade(dir)`;
+ `degradeFace(dir, FaceDirection.Down)` `:357` → `faceHandler.degrade(dir)` 后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果`(Down 回退显式保留,行为逐字等价);
+ `getFaceMovement(dir)` `:230`/`:364`/`:365` → `faceHandler.movement(dir)`(`IFaceDescriptor { x, y }`);
+ `nextFaceDirection(dir)` `:453` → `faceHandler.next(dir)`(默认顺时针;本处未用 `face8`);随后删除三个弃用 import。
+ 说明语义等价性:弃用 helper 面向八方向全集,注入的处理器是有限方向组(`docs/dev/hero/hero-mover.md:74` 记载为 `Dir4FaceHandler`);对 Unknown 二者同返回 Unknown,对落在本组内的正交朝向逐字一致;`nextFaceDirection(..., face8=false)` 仅在对角输入上与 `IFaceHandler.next`(先 degrade 再 90°)不同,而 `:453` 的输入恒为本组朝向,故不触发差异。
+ - D-25:把 `MapHeroHook` 拆成 `MapHeroLocationHook`(`Partial`)与 `MapHeroMoverHook`(`Partial>`);前者实现 `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`。
+ - D-28:`import { state } from '@user/data-state';` 与三处 `roleFace.getFaceOf` 一字不动。
+
+ ③ **成员命名清单(须用户同意,AGENTS.md 命名规则)**:
+ - **新增**内部类 `MapHeroLocationHook`(实现 `Partial`)与 `MapHeroMoverHook`(实现 `Partial>`);**删除**内部类名 `MapHeroHook`(其逻辑分散到上述两类)。
+ - **新增** `MapHeroRenderer` 构造器参数 / 只读字段 `faceHandler`(类型 `IFaceHandler`);备选名请用户当场指定(如 `face` / `directionHandler`,但 `face` 与数据端语义易混,推荐 `faceHandler`,与 `IObjectMover.faceHandler` 同名)。
+ - **重新使用**(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`(最贴近旧语义、不引入新接口)。请用户确认,或指定其它处理(如抽一个渲染端私有小函数)。
+
+ ⑤ **范围边界与禁令**:只改 `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`;不回滚 / 不暂存 / 不提交用户并发未提交改动。
+
+ ⑥ **验证方式与诚实降级(须原样告知)**:渲染端无测试设施,且 `MapExtensionManager.addHero` 当前无调用点(`client.ts:148-152` 被注释),故运行时渲染无法观察。本计划的运行结果 = **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 门禁** + **基线感知范围门禁 + 并发改动基线门禁**;另附**人工代码复核**。**不冒充任何无法运行的门禁。**
+
+ ⑦ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
+
+ ⑧ **失败语义(等同硬门禁,非自动命令)**:用户未回复「可以执行」即继续,或回复的是其它任何内容(含修订意见、沉默、`yolo` / `auto_advance` 模式下的自动推进、或子代理/执行器的自行放行),一律视为**未通过本关卡**:必须立即停止执行,不派发任何执行子代理,保持只读、不写任何文件,直到用户在对话中明确回复「可以执行」四个字后方可继续。
+
+ ⑨ **若用户给出修订意见**(含不同意命名 / 不同意方法归属 / 不同意 Down 回退):退出本计划、修订 `04-04-PLAN.md` 后重新汇报,不得自行改道执行。
+
+ 是否按本计划(两文件勇士渲染修正:D-24 注入 `IFaceHandler` 并替换弃用 helper、D-25 拆分钩子类且 `onSetPos` 无条件、D-26 补 AnimDir、D-27 换 `@motajs/animate`、D-28 保留存量 `state`)开始执行;并确认 ③ 的成员命名与归属、④ 的 Down 回退处理
+
+ AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认,且任何新增 / 改写的成员命名必须先经用户同意。本计划改的是渲染端勇士本体的钩子接线与内部命名,属于「接口 / 命名设计归用户」的边界;`.planning/config.json` 为 `mode: yolo`,因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`。`onSetPos` 的归属与 D-18/D-22 多余方法的归属若不先裁决,执行者会在「避免重复触发」与「逐字保留」之间自行猜测。
+
+
+
+
+
+
+ 回复「可以执行」四个字(如需改名 / 改归属 / 改回退处理,请在回复中一并给出),或给出修订意见
+
+ 用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无可运行的 `` 命令,故不携带 ``;失败语义完整写在 Task 0 的 action ⑧ 与 acceptance_criteria 中)
+
+
+ - 用户回复中明确包含「可以执行」四个字
+ - 汇报内容涵盖:要解决的问题(D-24..D-28 五条)、逐调用替换映射表(旧 helper 调用行 → `IFaceHandler` 方法 → 语义等价说明)、成员命名清单(新增 `MapHeroLocationHook` / `MapHeroMoverHook` / `faceHandler`;删除 `MapHeroHook`;方法归属)、Down 回退处理、范围边界与禁令、验证方式与运行时不可得的诚实降级
+ - 汇报中已原样告知关卡性质:执行者/子代理必须停下等待,不因触发方式或 yolo / auto 模式自动放行
+
+ 用户明确回复「可以执行」(如需改名 / 改归属 / 改回退处理,已在回复中给出);在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
+
+
+
+ Task 1(tracer):D-24 端到端最小切片 —— `manager.ts` 注入 `state.mover.faceHandler` → `MapHeroRenderer` 构造器保存 → 7 处弃用 helper 全部改走 `IFaceHandler`
+ 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
+
+ - 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/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`)
+ - packages-user/data-base/src/hero/location.ts:33-45(`HeroLocation` 以 `IFaceHandler` 构造 `HeroMover`)
+ - packages-user/data-common/src/common/faceManager.ts:169-202(`Dir4FaceHandler` 的 `degrade` / `movement` / `next` 实现,核对等价性)
+ - packages-user/client-modules/src/action/move.ts:1-6(**只读**:确认 D-19 范围外,不得改动)
+
+
+ (0) **执行前核对与基线(只读前置,必须先做)**:
+ (0a) **数据端现行接口复核(消费未确定项,不猜)**:打开当日 HEAD 的基准文件逐条确认,并把结论(每条一行:结论 + `file:line`)写入 `04-04-SUMMARY.md` 的「执行前核对」段:① `IFaceHandler` 七成员(`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` 存在(`data-common/src/common/mover.ts:251`);③ `IHeroMover extends IObjectMover`(`data-base/src/hero/types.ts:399`)且 `IHeroLocation.mover` 类型为 `IHeroMover`(`:303-314`);④ `HeroLocation` 由传入的 `IFaceHandler` 构造 `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`。**任一不同:立即停止,不写任何代码,向用户汇报差异并修订本计划。**
+ (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,格式固定为:
+ ```
+
+
+
+ 基线摘要 sha256=<64hex>
+ ```
+ **不得沿用 04-03 SUMMARY 的旧列表**(那批改动已随 `4e64953 chore: 内部接口改名` 落库);一律以本次实测为准。**不得回滚 / 暂存 / 提交 / 修改**其中任何条目。
+ (0c) **类型门禁基线**:执行 `pnpm exec vue-tsc --noEmit`(整仓会以非 0 退出属预期)并把 `client-modules` 的命中行**归一化**(每行形如 `<相对 client-modules/src 的路径>(line,col): error TSxxxx`)后原样写入 SUMMARY,格式固定为:
+ ```
+
+ <归一化后的 client-modules 命中行,逐行>
+
+ ```
+ 同时确认三个在范围文件(`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`(字段名以 Task 0 用户确认为准)。不改构造体既有钩子注册逻辑(本任务**保留**单一 `MapHeroHook` 现状,Task 2 再拆)。
+ (4) **7 处弃用 helper 替换(逐处对齐,行号为改动前锚点)**:
+ (4a) `:132` `degradeFace(hero.getCurrentFaceDirection())` → `this.faceHandler.degrade(hero.getCurrentFaceDirection())`(`addHeroMoving`)。
+ (4b) `:202` `degradeFace(this.hero.getCurrentFaceDirection())` → `this.faceHandler.degrade(this.hero.getCurrentFaceDirection())`(`setImage`)。
+ (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 回退显式保留**,行为与旧 helper 的 `unknown` 形参逐字等价)。
+ (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`;默认顺时针,与旧 helper 的 `face8=false` / `anticlockwise=false` 默认一致)。
+ **不得**改动这些方法里与其无关的分支 / 注释 / 变量名;**不得**改动 `: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 错误。
+
+ 本任务只把渲染端内部朝向计算从弃用 helper 换到注入的 `IFaceHandler`,并把新参数加在 `MapHeroRenderer` 构造器(不在任何接口里);不改变公共契约面,回退只需还原两个文件,故为 `reversible`。
+
+ 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(/([\s\S]*?)/);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)"
+ non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS"(三个在范围文件仍有类型错误)/ "CLIENT-MODULES ERROR SET CHANGED"(渲染端类型错误集相对基线增减)/ "NO TYPE BASELINE"(SUMMARY 未按固定格式记录基线)
+ 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('(?hasTok(src,t));if(hit.length){console.error('DEPRECATED FACE HELPERS REMAIN '+hit.join(','));process.exit(1)}const miss=['IFaceHandler','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)')"
+ non-zero exit, 或 stderr 含 "DEPRECATED FACE HELPERS REMAIN"(三个弃用 helper 名仍以**独立 token**(词边界)出现在三个在范围文件)/ "FACE HANDLER INJECTION MISSING"(`IFaceHandler` 或 `faceHandler` 未出现)/ "MANAGER NOT PASSING mover.faceHandler"(注入点未接)/ "FACE DIRECTION READ LOST"(朝向读取口径丢失)
+ pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/manager.ts
+ non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用 import / 未使用变量)
+ 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')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」)
+
+
+ - `manager.ts` 的 `addHero` 以 `state.mover.faceHandler` 作为 `new MapHeroRenderer(...)` 的第四个实参;`addHero` 签名与 jsDoc 未变
+ - `hero.ts` 构造器新增 `IFaceHandler` 参数(`faceHandler`),且 `@motajs/common` 的 import 含 `IFaceHandler`
+ - 弃用 helper 的 7 处调用(`:132`/`:202`/`:230`/`:357`/`:364`/`:365`/`:453` 对应位置)全部改走 `this.faceHandler.degrade` / `.movement` / `.next`;`:357` 的 Unknown→Down 回退显式保留;三个弃用名字不再出现在三个在范围文件的任何位置(含 import)
+ - `:235`/`:358`/`:417` 的 `state.roleFace.getFaceOf(...)` 与 `import { state } from '@user/data-state'` 一字未动(D-28)
+ - 本任务**未**触碰 `MapHeroHook` / 钩子注册 / 贴图 / 跟随者 / `types.ts`;未提交任何改动
+ - 四条 `` 全部转绿:scoped 类型门禁(三个文件 0 条、`client-modules` 错误集 == SUMMARY 记录的 `TYPE-BASELINE`)、D-24 注入门禁、eslint 0 错误、CRLF 门禁
+
+ `state.mover.faceHandler` 经 `manager.ts` 注入到 `MapHeroRenderer`,7 处弃用 helper 全部改走 `IFaceHandler`(含 Down 回退显式保留),D-28 的 `state` 用法与 D-18/D-22 成员未动,四条机器门禁转绿
+
+
+
+ Task 2:D-25 钩子拆两类(各自注册、各自 controller、`onSetPos` 无条件且只出现一次)+ D-26 补 AnimDir 设置
+ packages-user/client-modules/src/render/map/extension/hero.ts
+
+ - packages-user/client-modules/src/render/map/extension/hero.ts(Task 1 产出的现状全文;重点构造器 `:77-106`、`destroy` `:462-465`、`MapHeroHook` `:468-552`)
+ - packages-user/data-base/src/hero/types.ts:277-300(`IHeroLocationHooks`:`onSetPos(x, y)` / `onSetFloor(map)`)
+ - packages-user/data-common/src/common/mover.ts:166-232(`IObjectMoverHooks` 七成员与返回类型:`onMoveStart` / `onMoveEnd` / `onStepEnd` 为 `Promise`;`onSetPos` / `onSetFaceDir` / `onSetMoveDir` 为 `void`)
+ - packages-user/data-common/src/common/mover.ts:103-108,513-527,646-687(`IObjectMoveAnimDir { type, dir }`;`prepareStep` 对 AnimDir 写 `currAnimDir`;`moveProgress` 时序中 `mover.setPos` 会先 `tile.setPos` → 触发 location 钩子)
+ - packages-user/data-base/src/hero/location.ts:59-63(`HeroLocation.setPos` 触发 `IHeroLocationHooks.onSetPos`)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-18 / D-21 / D-22 / D-25 / D-26)
+ - .planning/phases/04-render-adaptation/04-03-PLAN.md:282-290(04-03 对 `onStepEnd` 分派与 D-18/D-22 保留方法的既有约定,本任务在其上叠加 D-26)
+
+
+ (1) **D-25 拆分 `MapHeroHook` 为两个内部类**(类名以 Task 0 用户确认为准;`MapHeroHook` 删除):
+ (1a) `class MapHeroLocationHook implements Partial`:`constructor(readonly hero: MapHeroRenderer) {}`;实现 `onSetPos(x: number, y: number): void { this.hero.setPosition(x, y); }`——**无条件设置,不得出现 `moving` 守卫**(旧守卫 `if (this.hero.hero.mover.moving) return;` 整行删除)。`onSetFloor` 仍不实现(既有约定)。该类同时**逐字承载** D-18/D-22 的非接口多余方法:`onSetImage(image: ImageIds)` / `onSetAlpha(alpha: number)` / `onSetFollowerAlpha(identifier: string, alpha: number)` / `onAddFollower(follower: number, identifier: string)` / `onRemoveFollower(identifier: string, animate: boolean)` / `onRemoveAllFollowers()`——方法体、参数名、注释与 jsDoc **一字不改**(它们不属于任何现有钩子接口,也不注册到任何数据端对象)。
+ (1b) `class MapHeroMoverHook implements Partial>`:`constructor(readonly hero: MapHeroRenderer) {}`;逐字搬运 / 保留移动族钩子:`async onMoveStart(): Promise { this.hero.startMove(); }`、`async onMoveEnd(): Promise { await this.hero.waitMoveEnd(); }`、`async onStepEnd(_code, step, tile, mover): Promise`(内部分派逻辑见 (2))、`onSetFaceDir(dir: FaceDirection): void { this.hero.turn(dir); }`。**刻意不实现 `onSetPos`**(`onSetPos` 只出现在 location 类一次;`mover.setPos` 会先 `tile.setPos` 触发 location 钩子,location 触发面是超集,从而同一次定位不会重复触发 `setPosition`)。`onStepStart` / `onSetMoveDir` 仍不实现(既有约定,理由照 04-03)。`onStepEnd` 的未使用首参命名保持 `_code`(eslint `args: 'all'` + `argsIgnorePattern: '^_'`)。
+ (2) **D-26 补 AnimDir**:在 `MapHeroMoverHook.onStepEnd` 中,保留开头既有的 `this.hero.setHeroAnimateDirection(mover.currAnimDir);`,并把 `case ObjectMoveType.AnimDir:` 由 `break;`(no-op)改为 `this.hero.setHeroAnimateDirection(step.dir); break;`(`step` 在 `case ObjectMoveType.AnimDir` 下被收窄为 `IObjectMoveAnimDir`,`step.dir: ObjectAnimDirection`)。`ObjectMoveType.Speed` 分支保持 `break;`(无渲染动作)。
+ (3) **构造器注册(拆成两次、各自 `load()`)**:把原来「一个 `hook` 实例注册到 `hero` 与 `hero.mover`」改为:`this.locationController = hero.addHook(new MapHeroLocationHook(this)); this.locationController.load(); this.moverController = hero.mover.addHook(new MapHeroMoverHook(this)); this.moverController.load();`。`locationController` / `moverController` 字段声明不变;`destroy()`(`locationController.unload(); moverController.unload();`)不变。
+ (4) **不得改动的事**:不改 `MapHeroRenderer` 的动画算法(`tick` / `moveEntity` / `jumpEntity` / `generateJumpFn`)、不改 `addHeroMoving` 的贴图分支结构、不改 `setImage` / `setAlpha` / 跟随者族实现体、不改 `:235`/`:358`/`:417` 的 `state.roleFace.getFaceOf(...)`(D-28)、不接线 `client.ts`、不动 `action/move.ts`。既有注释 / jsDoc 一字不改(新增注释除外)。
+ (5) **提交**:不单独提交(Task 3 整体原子提交)。若执行环境强制每任务提交,提交信息用 `fix(04-04): split hero hooks and handle AnimDir`。
+ (6) 全程保持文件 CRLF;改完执行 `pnpm exec eslint --fix`,再 `pnpm exec eslint` 必须 0 错误。
+
+
+ node -e "const fs=require('fs');const h=fs.readFileSync('packages-user/client-modules/src/render/map/extension/hero.ts','utf8');const hasTok=(s,t)=>new RegExp('(?!h.includes(t));if(miss.length){console.error('SPLIT HOOKS MISSING '+miss.join(','));process.exit(1)}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')){console.error('onSetPos STILL GUARDED BY moving');process.exit(1)}if(!/setPosition\(/.test(posBody)){console.error('onSetPos DOES NOT SET POSITION');process.exit(1)}const se=h.indexOf('async onStepEnd(');if(se<0){console.error('NO onStepEnd');process.exit(1)}const seBody=h.slice(se,h.indexOf('\n }',se)+6);if(!seBody.includes('ObjectMoveType.AnimDir')){console.error('ANIMDIR CASE MISSING');process.exit(1)}if(!/setHeroAnimateDirection\(step\.dir\)/.test(seBody)){console.error('ANIMDIR NOT APPLIED');process.exit(1)}console.log('OK D25 split hooks / D26 AnimDir')"
+ non-zero exit, 或 stderr 含 "OLD SINGLE HOOK CLASS REMAINS"(`MapHeroHook` 仍以独立 token 存在)/ "SPLIT HOOKS MISSING"(两个钩子类 / 两次 `addHook` / 两个 controller 未齐)/ "NO onSetPos" / "onSetPos STILL GUARDED BY moving"(`onSetPos` 方法体内仍出现 `moving`,守卫未删)/ "onSetPos DOES NOT SET POSITION"(未直接设置位置)/ "ANIMDIR CASE MISSING" / "ANIMDIR NOT APPLIED"(AnimDir 分支未按 `step.dir` 设置动画方向)
+ node -e "const fs=require('fs');const h=fs.readFileSync('packages-user/client-modules/src/render/map/extension/hero.ts','utf8');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 FROM hero.ts '+miss.join(' , '));process.exit(1)}const legacy=['onSetPosition','onStartMove','onMoveHero','onEndMove','onJumpHero','onTurnHero'];const lHit=legacy.filter(x=>h.includes(x));if(lHit.length){console.error('LEGACY HOOK NAMES REMAIN '+lHit.join(','));process.exit(1)}console.log('OK D18 D22 members preserved')"
+ non-zero exit, 或 stderr 含 "D18 D22 MEMBERS REMOVED FROM hero.ts"(贴图 / 不透明度 / 跟随者成员被误删,违反 D-18 / D-22)/ "LEGACY HOOK NAMES REMAIN"(04-03 已清除的旧钩子名回潮)
+ 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(/([\s\S]*?)/);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)"
+ non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS" / "CLIENT-MODULES ERROR SET CHANGED" / "NO TYPE BASELINE"(拆分钩子类或 AnimDir 分支引入新诊断)
+ pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts
+ non-zero exit 或输出含 "error"(含未使用变量 / 未使用 import / Prettier 违规)
+ node -e "const fs=require('fs');const s=fs.readFileSync('packages-user/client-modules/src/render/map/extension/hero.ts','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 hero.ts');process.exit(1)}console.log('OK CRLF')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF)
+
+
+ - `MapHeroHook` 已不存在;存在 `MapHeroLocationHook`(`Partial`)与 `MapHeroMoverHook`(`Partial>`)两个内部类
+ - 构造器分别 `hero.addHook(new MapHeroLocationHook(this))` 与 `hero.mover.addHook(new MapHeroMoverHook(this))`,两个 controller 各自 `load()`;`destroy()` 各自 `unload()`
+ - `onSetPos(x, y)` 只在 `MapHeroLocationHook` 上出现一次,方法体为直接的 `this.hero.setPosition(x, y)`,**不含 `moving` 守卫**;`MapHeroMoverHook` 不含 `onSetPos`
+ - `MapHeroMoverHook.onStepEnd` 保留开头 `mover.currAnimDir` 设置,且 `ObjectMoveType.AnimDir` 分支调用 `this.hero.setHeroAnimateDirection(step.dir)`
+ - D-18/D-22 成员(`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` / 跟随者三方法,以及 `setImage` / `setAlpha` / `setFollowerAlpha` / 跟随者三实现)逐字保留;04-03 已清除的旧钩子名未回潮
+ - `:235`/`:358`/`:417` 的 `state.roleFace.getFaceOf(...)` 未动(D-28)
+ - 五条 `` 全部转绿(拆分类门禁、D-18/D-22 保留门禁、scoped 类型门禁、eslint、CRLF)
+
+ 两个钩子类各自注册、`onSetPos` 无条件且只出现一次、AnimDir 分支按 `step.dir` 设置动画方向,D-18/D-22/D-28 保留,五条机器门禁转绿
+
+
+
+ Task 3:D-27 换 `@motajs/animate` + 原子提交 + 终验门禁(范围 / 并发基线 / 静态 / 类型 / eslint / CRLF)+ 人工复核 + `04-04-SUMMARY.md` 定稿
+ packages-user/client-modules/src/render/map/extension/hero.ts, .planning/phases/04-render-adaptation/04-04-SUMMARY.md
+
+ - packages-user/client-modules/src/render/map/extension/hero.ts(Task 1/2 产出的现状;`import { TimingFn } from 'mutate-animate';` 与 `generateJumpFn` 及其返回类型)
+ - packages/animate/src/types.ts:7-12 与 packages/animate/src/index.ts:1-5(`ExcitationCurve2D` 与其 barrel 导出)
+ - node_modules/mutate-animate/index.d.ts:8-31(`TimingFn` = `(input: number) => [number, number]`(N=2);`PathFn = TimingFn<2>`)
+ - packages-user/client-modules/src/render/map/moving.ts:162-187(`moveRelative(curve: TimingFn<2>, time, timing?)` 的形参,确认 `ExcitationCurve2D` 可赋值)
+ - .planning/phases/04-render-adaptation/04-04-SUMMARY.md(Task 1 写入的执行前核对 / 并发基线 / 类型基线三段)
+ - .planning/phases/04-render-adaptation/04-04-PLAN.md(本任务的全部门禁定义与成功标准)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-24..D-28 的裁定原文,用于 SUMMARY 的决策段)
+
+
+ (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/**` 均无基线之外的新改动。若出现基线之外的第三个生产文件改动:停止、汇报、修订计划(不得自行扩面)。
+ (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` 调用)与语义等价性结论(含当日实测注入的处理器是 `Dir4FaceHandler` 还是 `Dir8FaceHandler`);② `:357` 的 Unknown→Down 回退是否与旧 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` 是否未改;⑨ 运行时 UAT 不可得的理由已写明(`addHero` 未被接线,`client.ts:148-152` 仍为注释)。
+ (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。
+ (8) **提交 SUMMARY**:`docs(04-04): record execution state`,仅含 `04-04-SUMMARY.md`。
+ (9) **不做的事**:不接线 `client.ts`、不改 `action/move.ts`、不改 `types.ts`、不新增测试文件(渲染端无测试设施)、不改任何数据端文件、不修任何 ③ 多余旧路径或其它 ① 错配、不迁移 `hero.ts` 之外的 `mutate-animate`。
+
+
+ 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(/([\s\S]*?)/);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')"
+ 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"
+ 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(/([\s\S]*?)/);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)"
+ non-zero exit, 或 stderr 含 "NO BASELINE DIGEST" / "BASELINE ENTRY SET CHANGED" / "BASELINE DIGEST CHANGED"(用户并发的未提交改动被本计划回滚 / 暂存 / 提交 / 修改)
+ 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('(?hasTok(src,t));if(hit.length){console.error('FORBIDDEN TOKENS REMAIN '+hit.join(','));process.exit(1)}const need=['IFaceHandler','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')"
+ non-zero exit, 或 stderr 含 "FORBIDDEN TOKENS REMAIN"(弃用 helper / 旧单一钩子类名仍以独立 token 出现)/ "REQUIRED BINDINGS MISSING" / "D25 onSetPos WRONG"(`onSetPos` 仍有守卫或未直接设置位置)/ "D28 STATE USAGE LOST"(存量 `state.roleFace` 用法被误删)
+ 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('(?!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;','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')"
+ 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` 契约被误改)
+ 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(/([\s\S]*?)/);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)"
+ non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS" / "CLIENT-MODULES ERROR SET CHANGED" / "NO TYPE BASELINE"(终验类型门禁未过)
+ pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/manager.ts
+ non-zero exit 或输出含 "error"
+ 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')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(两个源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」/ `.prettierrc` 的 `endOfLine: crlf`;SUMMARY 不在本机器门禁范围——04-03-SUMMARY 实测为混合行尾,故不对工具写出的 markdown 施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)
+ 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:148','@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')"
+ non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线段 / 人工复核 / 需求 Pending 声明 / 运行时不可得说明 / D-24..D-27 关键项)
+
+
+ - `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` 均无基线之外的新改动)
+ - 并发改动基线门禁通过: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)
+
+ D-27 落地并原子提交;七类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02)
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| 渲染端契约面 → 下游调用方 | `IMapHeroRenderer` / `IMapExtensionManager` 是 `client-base` / `client-modules` 其它模块与用户代码消费的公共契约;本计划声称不改契约面,误改 `types.ts` 或误删 D-18/D-22 成员会直接扩大破坏面 |
+| 数据端接口 → 渲染端绑定 | 渲染端只读 import `data-common` / `data-base` 的类型并读取 `state.mover.faceHandler`;本计划不改数据端,接口一旦被用户并发改动,适配即按错误形状落地 |
+| 数据端钩子回调 → 渲染状态 | 现有钩子是唯一允许推进渲染状态的入口(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-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:148` 字样);严禁以不可运行的门禁冒充通过 |
+| 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-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 不触发;若执行中出现安装需求,立即停止并汇报 |
+
+
+
+
+
+
+
+
+
+
+0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题(D-24..D-28)/ 大致解决方案(含逐调用替换映射表)/ 成员命名清单与方法归属 / Down 回退处理 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
+1. 执行前核对与基线(Task 1 步骤 0):六条数据端现行接口复核结论(带 `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` / `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'`。
+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 处替换逐条对位且语义等价(含注入处理器实际方向组的记录);Down 回退与旧语义一致;两钩子类分别注册且 `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;用户并发改动未被纳入任何提交。
+
+
+
+- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报计划内容、逐调用替换映射表、成员命名清单与 `onSetPos` / D-18·D-22 方法归属、Down 回退处理后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行。
+- **D-24:** 勇士渲染已绑定注入的 `IFaceHandler`(`manager.ts` 传入 `state.mover.faceHandler`),三个弃用 helper(含 import)在三个在范围文件内以独立 token 判定彻底消失;7 处调用逐条改走 `degrade` / `movement` / `next`,`:357` 的 Unknown→Down 回退显式保留。
+- **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.*` 均未被触碰;用户并发的未提交改动未被回滚 / 暂存 / 提交 / 修改。
+- **验证诚实且可运行:** 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 其余存量迁移、其余 ① / ③ 项)。
+
+## Artifacts this phase produces
+
+| 类型 | 路径 | 内容 |
+|------|------|------|
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/hero.ts` | `MapHeroRenderer` 持有注入的 `IFaceHandler` 并以 `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 处替换映射、门禁输出摘要、人工复核九条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
+
+
+
From bac3e46fba57ea42fa39460810e8dc8c4c8a406c Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Sat, 19 Sep 2026 13:08:49 +0800
Subject: [PATCH 039/173] fix(04-04): revise plan per checker feedback
---
.../phases/04-render-adaptation/04-04-PLAN.md | 62 ++++++++++---------
1 file changed, 32 insertions(+), 30 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-04-PLAN.md b/.planning/phases/04-render-adaptation/04-04-PLAN.md
index c51390c..bd59349 100644
--- a/.planning/phases/04-render-adaptation/04-04-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-04-PLAN.md
@@ -23,7 +23,7 @@ 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` 的 `movement(dir)` / `degrade(dir)` / `next(dir)`;`MapHeroRenderer` 构造器新增 `IFaceHandler` 参数,并在 `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),与旧 helper 的 `unknown` 参数逐字等价;该处理已在 Task 0 汇报"
+ - "**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 汇报并由用户裁定"
- "**D-25 落地**:`MapHeroHook` 拆为**两个内部类**(`MapHeroLocationHook` 实现 `Partial`,`MapHeroMoverHook` 实现 `Partial>`),各自用 `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` 使用一律不动**"
@@ -31,7 +31,7 @@ must_haves:
- "**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:148-152` 已注释),故本计划的实质验证为「编译 + 契约 + 静态」三类机器门禁,另附人工 UAT 代码复核;**不得**以任何无法运行的门禁冒充通过"
+ - "**验证可运行且不臆造通过**:改动前先记录 `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 代码复核;**不得**以任何无法运行的门禁冒充通过"
- "用户并发的未提交改动(以 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:
@@ -52,8 +52,8 @@ must_haves:
- "**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` 七成员、`movement` 返回 `IFaceDescriptor`、`degrade` 对 Unknown 返回 Unknown;② `IObjectMover.faceHandler` 存在且类型为 `IFaceHandler`;③ `IHeroLocation.mover` 为 `IHeroMover` 且 `HeroLocation` 由 `IFaceHandler` 构造 `HeroMover`;④ 三个弃用 helper 仍导出且签名如记录(「待替换物尚在」的核对,不是要求保留它们);⑤ `@motajs/animate` 导出 `ExcitationCurve2D`、`mutate-animate` 的 `TimingFn<2>` 与 `MovingBlock.moveRelative` 的形参形态;⑥ `IObjectMoveAnimDir.dir`、`prepareStep` 对 AnimDir 的处理、`moveProgress` 的钩子时序。任一不同即**停下汇报并修订本计划**,不得按记忆改代码。"
- - "**`IFaceHandler` 语义等价性(Task 0 陈述、Task 1 编译佐证):** 弃用 helper 面向八方向全集,而 `hero.mover` 注入的处理器是有限方向组(`docs/dev/hero/hero-mover.md:74` 记载为 `Dir4FaceHandler`,`data-common/src/common/faceManager.ts:169`)。替换后:① `degrade` 与 `degradeFace(dir)`(unknown 默认 Unknown)对 Unknown 同返回 Unknown;② `movement` / `next` 会先 `degrade`,对落在本组内的正交朝向与旧 helper 逐字一致;③ `nextFaceDirection(dir, false, false)`(默认 face8=false)对**对角输入**与 `IFaceHandler.next`(先 degrade 再 90°)不同,但 `hero.ts:453` 的输入 `this.heroEntity.direction` 只可能来自 `getCurrentFaceDirection()` / `turn(...)` / `degrade(...)`,均为本组朝向,故实际不触发差异;若执行当日实测处理器为 `Dir8FaceHandler`,须在 SUMMARY 记录并复核 `next` / `movement` 差异。"
- - "**运行时 UAT 不可得(诚实降级):** `MapExtensionManager.addHero` 目前无任何调用点(`client.ts:148-152` 被注释),故勇士渲染拓展在运行时并未启用;本计划不接线 `client.ts`。因此本计划的运行结果无法在渲染环境观察,验收依赖「scoped 类型门禁 + 契约 / 静态门禁 + 人工代码复核」,运行时渲染 UAT 顺延到启用接线的增量。"
+ - "**`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 如实汇报该差异,由用户裁定。"
+ - "**运行时 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「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」);用户未同意即改名为用户指定的写法,或停下修订计划。"
prohibitions:
@@ -87,10 +87,10 @@ Phase 4 第四个增量 —— **勇士渲染修正:04-03 人工审查反馈
| 旧调用(hero.ts) | 替换 | 说明 |
|---|---|---|
-| `degradeFace(dir)` `:132` / `:202` | `this.faceHandler.degrade(dir)` | 旧 helper 默认 `unknown = Unknown`,与 `IFaceHandler.degrade` 对 Unknown 返回 Unknown 一致 |
-| `degradeFace(dir, FaceDirection.Down)` `:357` | `this.faceHandler.degrade(dir)`,随后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果` | `IFaceHandler.degrade` 无 `unknown` 形参,Down 回退须显式保留(行为逐字等价) |
-| `getFaceMovement(dir)` `:230` / `:364` / `:365` | `this.faceHandler.movement(dir)` | 返回 `IFaceDescriptor { x, y }`,解构用法不变 |
-| `nextFaceDirection(dir)` `:453` | `this.faceHandler.next(dir)` | 默认顺时针;旧 helper 的 `face8=false` / `anticlockwise=false` 默认与此一致,`face8` 在本处未被使用 |
+| `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()` 无参路径可达,仓库内无内部调用者 |
- **D-24 注入**:`MapHeroRenderer` 构造器新增 `IFaceHandler` 参数(拟名 `faceHandler`);`manager.ts` 的 `new MapHeroRenderer(...)` 由 `state.mover.faceHandler` 注入(`IObjectMover.faceHandler`,`data-common/src/common/mover.ts:251`;`state` 是 `addHero` 的 `IHeroLocation` 形参,不是全局单例,符合 D-23)。
- **D-25 拆分**:把 `MapHeroHook` 拆成 `MapHeroLocationHook`(`Partial`;实现 `onSetPos` **且只实现一次**,另承载 D-18/D-22 的非接口多余方法)与 `MapHeroMoverHook`(`Partial>`;实现 `onMoveStart` / `onMoveEnd` / `onStepEnd` / `onSetFaceDir`,**不**实现 `onSetPos`);`hero.addHook(...)` / `hero.mover.addHook(...)` 分别注册,各持 `locationController` / `moverController` 字段;`onSetPos(x, y)` 无条件 `this.hero.setPosition(x, y)`(删除 `mover.moving` 守卫)。
@@ -191,11 +191,11 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
② **大致解决方案(两个文件)+ 逐调用替换映射表(须逐行汇报)**:
- `manager.ts`:`new MapHeroRenderer(this.renderer, layer, state, state.mover.faceHandler)`(第 4 实参为新增)。
- `hero.ts`:构造器新增 `IFaceHandler` 参数并保存为字段;映射表:
- `degradeFace(dir)` `:132`/`:202` → `faceHandler.degrade(dir)`;
- `degradeFace(dir, FaceDirection.Down)` `:357` → `faceHandler.degrade(dir)` 后 `=== FaceDirection.Unknown ? FaceDirection.Down : 结果`(Down 回退显式保留,行为逐字等价);
- `getFaceMovement(dir)` `:230`/`:364`/`:365` → `faceHandler.movement(dir)`(`IFaceDescriptor { x, y }`);
- `nextFaceDirection(dir)` `:453` → `faceHandler.next(dir)`(默认顺时针;本处未用 `face8`);随后删除三个弃用 import。
- 说明语义等价性:弃用 helper 面向八方向全集,注入的处理器是有限方向组(`docs/dev/hero/hero-mover.md:74` 记载为 `Dir4FaceHandler`);对 Unknown 二者同返回 Unknown,对落在本组内的正交朝向逐字一致;`nextFaceDirection(..., face8=false)` 仅在对角输入上与 `IFaceHandler.next`(先 degrade 再 90°)不同,而 `:453` 的输入恒为本组朝向,故不触发差异。
+ `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 照常替换,须由用户在关卡上确认接受、或指定补偿方式。
- D-25:把 `MapHeroHook` 拆成 `MapHeroLocationHook`(`Partial`)与 `MapHeroMoverHook`(`Partial>`);前者实现 `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`。
@@ -212,7 +212,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
⑤ **范围边界与禁令**:只改 `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`;不回滚 / 不暂存 / 不提交用户并发未提交改动。
- ⑥ **验证方式与诚实降级(须原样告知)**:渲染端无测试设施,且 `MapExtensionManager.addHero` 当前无调用点(`client.ts:148-152` 被注释),故运行时渲染无法观察。本计划的运行结果 = **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 清零、`IFaceHandler` 注入、两钩子类分别注册、`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`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
@@ -247,7 +247,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
- 用户回复中明确包含「可以执行」四个字
- - 汇报内容涵盖:要解决的问题(D-24..D-28 五条)、逐调用替换映射表(旧 helper 调用行 → `IFaceHandler` 方法 → 语义等价说明)、成员命名清单(新增 `MapHeroLocationHook` / `MapHeroMoverHook` / `faceHandler`;删除 `MapHeroHook`;方法归属)、Down 回退处理、范围边界与禁令、验证方式与运行时不可得的诚实降级
+ - 汇报内容涵盖:要解决的问题(D-24..D-28 五条)、逐调用替换映射表(旧 helper 调用行 → `IFaceHandler` 方法 → 语义映射与差异说明,含注入的 `Dir8FaceHandler` 与 `degrade` / `next` 差异)、成员命名清单(新增 `MapHeroLocationHook` / `MapHeroMoverHook` / `faceHandler`;删除 `MapHeroHook`;方法归属)、Down 回退处理、范围边界与禁令、验证方式与运行时不可得的诚实降级
- 汇报中已原样告知关卡性质:执行者/子代理必须停下等待,不因触发方式或 yolo / auto 模式自动放行
用户明确回复「可以执行」(如需改名 / 改归属 / 改回退处理,已在回复中给出);在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
@@ -261,16 +261,18 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
- 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/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/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`)
- packages-user/data-base/src/hero/location.ts:33-45(`HeroLocation` 以 `IFaceHandler` 构造 `HeroMover`)
- - packages-user/data-common/src/common/faceManager.ts:169-202(`Dir4FaceHandler` 的 `degrade` / `movement` / `next` 实现,核对等价性)
+ - packages-user/data-common/src/common/faceManager.ts:94-127(`Dir8FaceHandler` 的 `degrade`(恒等)/ `movement`(八方向 + `Unknown`)/ `next`(`DIR8_CW` 45°)实现);对照 `:129-202` 的 `Dir4FaceHandler` 以区分两者
+ - 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 范围外,不得改动)
(0) **执行前核对与基线(只读前置,必须先做)**:
- (0a) **数据端现行接口复核(消费未确定项,不猜)**:打开当日 HEAD 的基准文件逐条确认,并把结论(每条一行:结论 + `file:line`)写入 `04-04-SUMMARY.md` 的「执行前核对」段:① `IFaceHandler` 七成员(`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` 存在(`data-common/src/common/mover.ts:251`);③ `IHeroMover extends IObjectMover`(`data-base/src/hero/types.ts:399`)且 `IHeroLocation.mover` 类型为 `IHeroMover`(`:303-314`);④ `HeroLocation` 由传入的 `IFaceHandler` 构造 `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`。**任一不同:立即停止,不写任何代码,向用户汇报差异并修订本计划。**
+ (0a) **数据端现行接口复核(消费未确定项,不猜)**:打开当日 HEAD 的基准文件逐条确认,并把结论(每条一行:结论 + `file:line`)写入 `04-04-SUMMARY.md` 的「执行前核对」段:① `IFaceHandler` 七成员(`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` 存在(`data-common/src/common/mover.ts:251`);③ `IHeroMover extends IObjectMover`(`data-base/src/hero/types.ts:399`)且 `IHeroLocation.mover` 类型为 `IHeroMover`(`:303-314`);④ `HeroLocation` 由传入的 `IFaceHandler` 构造 `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` 前提继续,也不得沿用文档示例)。**任一不同:立即停止,不写任何代码,向用户汇报差异并修订本计划。**
(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,格式固定为:
```
@@ -291,13 +293,13 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
(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`(字段名以 Task 0 用户确认为准)。不改构造体既有钩子注册逻辑(本任务**保留**单一 `MapHeroHook` 现状,Task 2 再拆)。
(4) **7 处弃用 helper 替换(逐处对齐,行号为改动前锚点)**:
- (4a) `:132` `degradeFace(hero.getCurrentFaceDirection())` → `this.faceHandler.degrade(hero.getCurrentFaceDirection())`(`addHeroMoving`)。
- (4b) `:202` `degradeFace(this.hero.getCurrentFaceDirection())` → `this.faceHandler.degrade(this.hero.getCurrentFaceDirection())`(`setImage`)。
+ (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 回退显式保留**,行为与旧 helper 的 `unknown` 形参逐字等价)。
+ (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`;默认顺时针,与旧 helper 的 `face8=false` / `anticlockwise=false` 默认一致)。
+ (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 裁定。
**不得**改动这些方法里与其无关的分支 / 注释 / 变量名;**不得**改动 `: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 错误。
@@ -388,8 +390,8 @@ Output: `hero.ts` / `manager.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/**` 均无基线之外的新改动。若出现基线之外的第三个生产文件改动:停止、汇报、修订计划(不得自行扩面)。
(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` 调用)与语义等价性结论(含当日实测注入的处理器是 `Dir4FaceHandler` 还是 `Dir8FaceHandler`);② `:357` 的 Unknown→Down 回退是否与旧 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` 是否未改;⑨ 运行时 UAT 不可得的理由已写明(`addHero` 未被接线,`client.ts:148-152` 仍为注释)。
- (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。
+ (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。
(8) **提交 SUMMARY**:`docs(04-04): record execution state`,仅含 `04-04-SUMMARY.md`。
(9) **不做的事**:不接线 `client.ts`、不改 `action/move.ts`、不改 `types.ts`、不新增测试文件(渲染端无测试设施)、不改任何数据端文件、不修任何 ③ 多余旧路径或其它 ① 错配、不迁移 `hero.ts` 之外的 `mutate-animate`。
@@ -408,7 +410,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
non-zero exit 或输出含 "error"
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')"
non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(两个源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」/ `.prettierrc` 的 `endOfLine: crlf`;SUMMARY 不在本机器门禁范围——04-03-SUMMARY 实测为混合行尾,故不对工具写出的 markdown 施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)
- 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:148','@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')"
+ 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')"
non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线段 / 人工复核 / 需求 Pending 声明 / 运行时不可得说明 / D-24..D-27 关键项)
@@ -417,7 +419,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
- 范围门禁(基线感知)通过:除两个在范围文件与基线内的并发用户改动外,无其它生产文件改动(`extension/types.ts`、`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)
+ - `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)
D-27 落地并原子提交;七类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02)
@@ -444,7 +446,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
| 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:148` 字样);严禁以不可运行的门禁冒充通过 |
+| 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-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 不触发;若执行中出现安装需求,立即停止并汇报 |
@@ -459,7 +461,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题(D-24..D-28)/ 大致解决方案(含逐调用替换映射表)/ 成员命名清单与方法归属 / Down 回退处理 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
-1. 执行前核对与基线(Task 1 步骤 0):六条数据端现行接口复核结论(带 `file:line`)写入 SUMMARY;并发改动基线(`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`)与类型门禁基线(`TYPE-BASELINE` 段,逐行原样)写入 SUMMARY;任一与规划不一致即停止并修订计划;**不得沿用 04-03 SUMMARY 的旧并发列表**(已随 `4e64953` 落库)。
+1. 执行前核对与基线(Task 1 步骤 0):七条数据端现行接口与注入处理器复核结论(含 `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` / `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'`。
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` 逐字保留。
@@ -467,7 +469,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
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 处替换逐条对位且语义等价(含注入处理器实际方向组的记录);Down 回退与旧语义一致;两钩子类分别注册且 `onSetPos` 只出现一次、无条件;AnimDir 按 `step.dir` 设置且 `Speed` 仍 no-op;D-27 仅在 `hero.ts` 生效且 `ExcitationCurve2D` 被编译接受;D-28 未动;D-18/D-22 逐字保留;无新增公共接口成员且 `types.ts` 未改;运行时 UAT 不可得的理由已写明。
+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;用户并发改动未被纳入任何提交。
@@ -488,7 +490,7 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
|------|------|------|
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/hero.ts` | `MapHeroRenderer` 持有注入的 `IFaceHandler` 并以 `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 处替换映射、门禁输出摘要、人工复核九条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
+| 摘要 | `.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 不可得说明 |
- 用户回复中明确包含「可以执行」四个字
- - 汇报内容涵盖:要解决的问题(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 模式自动放行
用户明确回复「可以执行」(如需改名 / 改归属 / 改回退处理,已在回复中给出);在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
- Task 1(tracer):D-24 端到端最小切片 —— `manager.ts` 注入 `state.mover.faceHandler` → `MapHeroRenderer` 构造器保存 → 7 处弃用 helper 全部改走 `IFaceHandler`
- 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
+ Task 1(tracer):D-24 / D-29 端到端最小切片 —— `types.ts` / `manager.ts` 注入 `faceManager` → `MapHeroRenderer` 构造器保存 `IFaceManager` 并派生 `Dir4FaceHandler` → 7 处弃用 helper 改走 `dir4` 与 `hero.mover.faceHandler`
+ 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
- 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`)
- packages-user/data-base/src/hero/location.ts:33-45(`HeroLocation` 以 `IFaceHandler` 构造 `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`;`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 范围外,不得改动)
(0) **执行前核对与基线(只读前置,必须先做)**:
- (0a) **数据端现行接口复核(消费未确定项,不猜)**:打开当日 HEAD 的基准文件逐条确认,并把结论(每条一行:结论 + `file:line`)写入 `04-04-SUMMARY.md` 的「执行前核对」段:① `IFaceHandler` 七成员(`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` 存在(`data-common/src/common/mover.ts:251`);③ `IHeroMover extends IObjectMover`(`data-base/src/hero/types.ts:399`)且 `IHeroLocation.mover` 类型为 `IHeroMover`(`:303-314`);④ `HeroLocation` 由传入的 `IFaceHandler` 构造 `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` 七成员(`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(group: number): IFaceHandler | 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` 存在(`data-common/src/common/mover.ts:251`);⑥ `IHeroMover extends IObjectMover`(`data-base/src/hero/types.ts:399`)且 `IHeroLocation.mover` 类型为 `IHeroMover`(`:303-314`);⑦ `HeroLocation` 由传入的 `IFaceHandler` 构造 `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,格式固定为:
```
@@ -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`(字段名以 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`,名字以 Task 0 为准),在构造体开头派生一次:`this.dir4 = faceManager.get(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 错误。
- 本任务只把渲染端内部朝向计算从弃用 helper 换到注入的 `IFaceHandler`,并把新参数加在 `MapHeroRenderer` 构造器(不在任何接口里);不改变公共契约面,回退只需还原两个文件,故为 `reversible`。
+ 本任务把渲染端内部朝向计算从弃用 helper 换到注入的 `IFaceManager`(经 `faceManager.get(FaceGroup.Dir4)` 取四向处理器做 `degrade`/`next`,经 `hero.mover.faceHandler` 做 `movement`),并按 D-29(已获用户批准)为 `IMapExtensionManager.addHero` 增加 `faceManager` 形参——这是本计划唯一一处契约面变化,但仅增一个转发形参、成员集合不变,回退只需还原三个在范围文件,故为 `reversible`。
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(/([\s\S]*?)/);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)"
non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS"(三个在范围文件仍有类型错误)/ "CLIENT-MODULES ERROR SET CHANGED"(渲染端类型错误集相对基线增减)/ "NO TYPE BASELINE"(SUMMARY 未按固定格式记录基线)
- 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('(?hasTok(src,t));if(hit.length){console.error('DEPRECATED FACE HELPERS REMAIN '+hit.join(','));process.exit(1)}const miss=['IFaceHandler','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)')"
- non-zero exit, 或 stderr 含 "DEPRECATED FACE HELPERS REMAIN"(三个弃用 helper 名仍以**独立 token**(词边界)出现在三个在范围文件)/ "FACE HANDLER INJECTION MISSING"(`IFaceHandler` 或 `faceHandler` 未出现)/ "MANAGER NOT PASSING mover.faceHandler"(注入点未接)/ "FACE DIRECTION READ LOST"(朝向读取口径丢失)
- pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/manager.ts
+ 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('(?hasTok(src,t));if(hit.length){console.error('DEPRECATED FACE HELPERS REMAIN '+hit.join(','));process.exit(1)}const need=['FaceGroup.Dir4','faceManager.get(FaceGroup.Dir4)','this.hero.mover.faceHandler.movement(','IFaceHandler','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)')"
+ non-zero exit, 或 stderr 含 "DEPRECATED FACE HELPERS REMAIN"(三个弃用 helper 名仍以**独立 token**(词边界)出现在三个在范围文件)/ "D29 BINDINGS MISSING"(`FaceGroup.Dir4` / `faceManager.get(FaceGroup.Dir4)` / `this.hero.mover.faceHandler.movement(` / `IFaceHandler` / `IFaceManager` 有缺)/ "ADD_HERO SIGNATURE NOT UPDATED"(`types.ts` 的 `addHero` 未加 `faceManager` 形参)/ "MANAGER NOT FORWARDING faceManager"(`manager.ts` 未把 `faceManager` 转发给构造器)/ "FACE DIRECTION READ LOST"(朝向读取口径丢失)
+ 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
non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用 import / 未使用变量)
- 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')"
+ 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')"
non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」)
- - `manager.ts` 的 `addHero` 以 `state.mover.faceHandler` 作为 `new MapHeroRenderer(...)` 的第四个实参;`addHero` 签名与 jsDoc 未变
- - `hero.ts` 构造器新增 `IFaceHandler` 参数(`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(FaceGroup.Dir4)!`,类型 `IFaceHandler`);`@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`;未提交任何改动
- - 四条 `` 全部转绿:scoped 类型门禁(三个文件 0 条、`client-modules` 错误集 == SUMMARY 记录的 `TYPE-BASELINE`)、D-24 注入门禁、eslint 0 错误、CRLF 门禁
+ - 本任务**未**触碰 `MapHeroHook` / 钩子注册 / 贴图 / 跟随者;未提交任何改动
+ - 四条 `` 全部转绿:scoped 类型门禁(三个文件 0 条、`client-modules` 错误集 == SUMMARY 记录的 `TYPE-BASELINE`)、D-24/D-29 注入门禁、eslint 0 错误、CRLF 门禁
- `state.mover.faceHandler` 经 `manager.ts` 注入到 `MapHeroRenderer`,7 处弃用 helper 全部改走 `IFaceHandler`(含 Down 回退显式保留),D-28 的 `state` 用法与 D-18/D-22 成员未动,四条机器门禁转绿
+ `faceManager` 经 `types.ts` 契约与 `manager.ts` 注入到 `MapHeroRenderer`,7 处弃用 helper 全部改走 `dir4`(Dir4)与 `hero.mover.faceHandler`(Dir8)(含 Down 回退显式保留),D-28 的 `state` 用法与 D-18/D-22 成员未动,四条机器门禁转绿
@@ -386,42 +400,42 @@ Output: `hero.ts` / `manager.ts` 的修正改动(原子提交)+ 会话内可
(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(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`。
- 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(/([\s\S]*?)/);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')"
- 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"
+ 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(/([\s\S]*?)/);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')"
+ non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 `files_modified` 之外、且不在并发基线内的生产文件改动,含 D-19 的 `action/move.ts`、任何数据端文件或 `packages/**` 的新改动;基线内的用户并发改动不触发)/ "GIT STATUS FAILED" / "NO CONCURRENT BASELINE"
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(/([\s\S]*?)/);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)"
non-zero exit, 或 stderr 含 "NO BASELINE DIGEST" / "BASELINE ENTRY SET CHANGED" / "BASELINE DIGEST CHANGED"(用户并发的未提交改动被本计划回滚 / 暂存 / 提交 / 修改)
- 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('(?hasTok(src,t));if(hit.length){console.error('FORBIDDEN TOKENS REMAIN '+hit.join(','));process.exit(1)}const need=['IFaceHandler','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')"
- non-zero exit, 或 stderr 含 "FORBIDDEN TOKENS REMAIN"(弃用 helper / 旧单一钩子类名仍以独立 token 出现)/ "REQUIRED BINDINGS MISSING" / "D25 onSetPos WRONG"(`onSetPos` 仍有守卫或未直接设置位置)/ "D28 STATE USAGE LOST"(存量 `state.roleFace` 用法被误删)
+ 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('(?hasTok(src,t));if(hit.length){console.error('FORBIDDEN TOKENS REMAIN '+hit.join(','));process.exit(1)}const need=['FaceGroup.Dir4','faceManager.get(FaceGroup.Dir4)','this.hero.mover.faceHandler.movement(','IFaceHandler','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')"
+ non-zero exit, 或 stderr 含 "FORBIDDEN TOKENS REMAIN"(弃用 helper / 旧单一钩子类名仍以独立 token 出现)/ "REQUIRED BINDINGS MISSING"(`FaceGroup.Dir4` / `faceManager.get(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` 用法被误删)
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('(?!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;','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')"
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` 契约被误改)
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(/([\s\S]*?)/);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)"
non-zero exit, 或 stderr 含 "IN-SCOPE TYPE ERRORS" / "CLIENT-MODULES ERROR SET CHANGED" / "NO TYPE BASELINE"(终验类型门禁未过)
- pnpm exec eslint packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/map/extension/manager.ts
+ 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
non-zero exit 或输出含 "error"
- 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')"
- non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(两个源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」/ `.prettierrc` 的 `endOfLine: crlf`;SUMMARY 不在本机器门禁范围——04-03-SUMMARY 实测为混合行尾,故不对工具写出的 markdown 施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)
- 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')"
+ 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')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(三个源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」/ `.prettierrc` 的 `endOfLine: crlf`;SUMMARY 不在本机器门禁范围——04-03-SUMMARY 实测为混合行尾,故不对工具写出的 markdown 施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)
+ 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')"
non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线段 / 人工复核 / 需求 Pending 声明 / 运行时不可得说明 / D-24..D-27 关键项)
- `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)
- D-27 落地并原子提交;七类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02)
+ D-27 落地并原子提交(三个在范围文件);七类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(贴图 D-18、跟随者 D-22、接线启用、双布局 REND-02)
@@ -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 不触发;若执行中出现安装需求,立即停止并汇报 |
-
+
-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` / `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(FaceGroup.Dir4)` / `this.hero.mover.faceHandler.movement(` / `IFaceHandler` / `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;用户并发改动未被纳入任何提交。
- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报计划内容、逐调用替换映射表、成员命名清单与 `onSetPos` / D-18·D-22 方法归属、Down 回退处理后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行。
-- **D-24:** 勇士渲染已绑定注入的 `IFaceHandler`(`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(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` 并以 `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(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 不可得说明 |
- 用户回复中明确包含「可以执行」四个字
- - 汇报内容涵盖:要解决的问题(D-30/D-31/D-32 第一步)、`4e305e3` 的三类差异(移除 7 符号 / 改名 3 接口 / 新增 `textures` 与 4 处 `ICoreStateExtended`)、规划日实测的影响面(20 条 material 相关类型错误与 A–F 六类的具体命中)、大致解决方案(11 节结构 + 固定列序 + A/D 分列规则 + F 只登记不判定)、范围与硬边界(`packages-user` 限界、`packages/` 不纳入、`src/` 仅报告、生产代码零改动)、唯一交付物与两步走
+ - 汇报内容涵盖:要解决的问题(D-30/D-31/D-32 第一步)、`4e305e3` 的三类差异(移除 7 符号 / 改名 3 接口 / 新增 `textures` 与 4 处 `ICoreStateExtended`)、规划日实测的影响面(19 条 material 相关类型错误与 A–F 六类的具体命中)、大致解决方案(11 节结构 + 固定列序 + A/D 分列规则 + F 只登记不判定)、范围与硬边界(`packages-user` 限界、`packages/` 不纳入、`src/` 仅报告、生产代码零改动)、唯一交付物与两步走
- 汇报中已原样告知**计划号冲突**:`04-05-PLAN.md` / `04-05-SUMMARY.md` 已存在且已执行,本计划编号为 04-06,04-05 不得改写/覆盖/改号,并请用户确认编号
- 汇报中已原样告知**验证方式与只读证明**(只读门禁 / 类型门禁一致性 / 静态内容门禁 / 既有计划保护门禁 / 计划范围门禁 / CRLF / 人工复核),并声明无运行时行为可观察
- 汇报中已原样告知**关卡性质**:执行者/子代理必须停下等待,不因触发方式或 `yolo` / `auto_advance` 模式自动放行
@@ -258,7 +258,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
(0a) **起始基线快照(只读前置,先做这一步)**:执行 `git status --porcelain -- packages packages-user src`,把输出**原样逐行**记为「只读起始基线」写入 `## 方法`:先写 bullet `- **只读起始基线(packages / packages-user / src)**:`,其后逐行抄录输出(每条一行,行首为两字符状态码;输出为空则写 `(无)`)。规划日 2026-09-21 实测该输出为**空**(工作树内唯一的未提交改动是 `.planning/phases/04-render-adaptation/04-CONTEXT.md`,不在该路径集合内)。**不得回滚、暂存、提交或修改**任何既有改动;若当日实测出现 `packages` / `packages-user` / `src` 下的条目,逐条记录并原样保留(属本次清点范围内的既有改动,需在 `## 方法` 注明)。同时记下 `git rev-parse HEAD` 的当日值写入元信息块。
- (0b) **类型门禁实测基线(只读前置,与 (0a) 同批完成)**:执行 `pnpm exec vue-tsc --noEmit`,把完整输出的**错误总行数**与**material 相关错误行数**两个数字写入 `## 类型门禁实测基线`:bullet 逐字写 `- **错误总行数**:` 与 `- **material 相关错误行数**:`,其中 N = `node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出值(与 Task 3 门禁使用**同一正则**,保证可逐字比对)。规划日 2026-09-21 实测 **N = 199**、**M = 20**。同时在该节**逐行列出全部 material 相关错误**(`file:line` + `error TSxxxx` + 原文摘要)并按以下规划日归类核对:`client-base` 7 条 —— `material/autotile.ts:42` TS2420、`material/builder.ts:12` TS2420、`material/builder.ts:91` TS2741、`material/builder.ts:99` TS2420、`material/manager.ts:20` TS2305、`material/manager.ts:38` TS2420、`material/manager.ts:86` TS2741;`client-modules` 13 条 —— `client.ts:84` TS2739、`client.ts:85` TS2741、`render/map/extension/door.ts:36` 与 `:46` TS2339、`render/map/extension/hero.ts:239` / `:364` / `:428` TS2339、`render/map/renderer.ts:234` TS2741、`render/map/renderer.ts:652` TS2551、`render/map/renderer.ts:653` TS2551、`render/map/renderer.ts:1250` TS2339、`render/map/vertex.ts:546` TS2339。**单列**与本改动无关的 179 条既有错误(`data-state` / `data-base` / `data-system` 的测试与寻路、`client-base/src/load/loader.ts` 6 条与 `client-modules/src/core.ts:6` / `render/ui/load.tsx` 8 条属用户加载系统重构),并明写该「material / 非 material」划分口径**须用户确认**(见 assumptions)。**以当日实测为准**,与规划日数值不同即按实测写入并在 `## 处置` 说明差异,不得按规划日数值硬写。
+ (0b) **类型门禁实测基线(只读前置,与 (0a) 同批完成)**:执行 `pnpm exec vue-tsc --noEmit`,把完整输出的**错误总行数**与**material 相关错误行数**两个数字写入 `## 类型门禁实测基线`:bullet 逐字写 `- **错误总行数**:` 与 `- **material 相关错误行数**:`,其中 N = `node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出值(与 Task 3 门禁使用**同一正则**,保证可逐字比对)。规划日 2026-09-21 实测 **N = 199**、**M = 19**(material 19 = `client-base` 7 条 + `client-modules` 12 条;非 material 180 条,其中 `client-base/src/load/loader.ts` 6 + `client-modules/src/core.ts` 1 + `render/ui/load.tsx` 9 = 16 条加载系统重构所致)。同时在该节**逐行列出全部 material 相关错误**(`file:line` + `error TSxxxx` + 原文摘要)并按以下规划日归类核对:`client-base` 7 条 —— `material/autotile.ts:42` TS2420、`material/builder.ts:12` TS2420、`material/builder.ts:91` TS2741、`material/builder.ts:99` TS2420、`material/manager.ts:20` TS2305、`material/manager.ts:38` TS2420、`material/manager.ts:86` TS2741;`client-modules` 12 条 —— `client.ts:84` TS2739、`client.ts:85` TS2741、`render/map/extension/door.ts:36` 与 `:46` TS2339、`render/map/extension/hero.ts:239` / `:364` / `:428` TS2339、`render/map/renderer.ts:234` TS2741、`render/map/renderer.ts:652` TS2551、`render/map/renderer.ts:653` TS2551、`render/map/renderer.ts:1250` TS2339、`render/map/vertex.ts:546` TS2339(共 12 行,逐行与本节登记的 `client-modules` 错误一致)。**单列**与本改动无关的 180 条既有错误(`data-state` / `data-base` / `data-system` 的测试与寻路、`client-base/src/load/loader.ts` 6 条与 `client-modules/src/core.ts:6` / `render/ui/load.tsx` 9 条属用户加载系统重构),并明写该「material / 非 material」划分口径**须用户确认**(见 assumptions)。**以当日实测为准**,与规划日数值不同即按实测写入并在 `## 处置` 说明差异,不得按规划日数值硬写。
(1) **新建交付文档** `.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md`,先写标题与 6 行元信息块:`**日期:**`(当日日期);`**范围:**` 被查对象 `packages-user` 全量(含新包 `packages-user/client-system`),`packages/` 不纳入(D-32),`src/` 命中仅报告;`**性质:**` 只读清点(D-30 / D-31 / D-32 第一步),未修改任何生产代码;`**接口基准:**` `packages-user/client-base/src/material/types.ts` 与 `packages-user/client-base/src/types.ts`(`4e305e3` 后)以及 `packages-user/data-state/src/types.ts`(`ICoreStateExtended` 定义源);`**基准提交:**` `4e305e3 refactor(type): material types.`(当日工作树 HEAD);`**下一步:**` 第二步(material 接口适应实施)未规划,待用户审阅本文件后另行规划(D-32)。**全文使用 CRLF 换行**(dev.md「换行使用 CRLF 格式」);若写入工具产出 LF,用 `node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md';fs.writeFileSync(p,fs.readFileSync(p,'utf8').replace(/\r?\n/g,'\r\n'))"` 统一转换后落盘;Task 2 / Task 3 追加内容同样保持 CRLF,不得出现孤立 LF。
@@ -266,7 +266,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
(3) 写 `## 方法`((0a) 的「只读起始基线」bullet 也在本节):枚举两侧面 ——(a)**基准面** = `packages-user/client-base/src/material/types.ts`(`ITextureManager` / `ITextureGetter` / `ITextureAliasGetter` / `IAutotileProcessor` / `IAssetBuilder` / `ITrackedAssetData` / `IMaterialData` / `IMaterialFramedData` 等现行成员)+ `packages-user/client-base/src/types.ts`(`IClientBase.materials`)+ `packages-user/data-state/src/types.ts`(`ICoreStateExtended`);(b)**命中面** = `packages-user` 全量(`client-base` / `client-modules` / `client-system` / `entry-*` / `legacy-plugin-*` / `data-fallback`),另对 `packages/` 与 `src/` 做**对照扫描**(只登记、不修改)。判定口径固定为 **A 已移除符号的引用点 / B 改名的剩余引用 / C 新增与收紧约束的未实现 / D big-image 残留(实现与消费者)/ E 范围外命中 / F 未能从阅读确定** 六类,**一条记录只归一类**,跨类拆条并互相引用;**A 与 D 的分列规则写死**:A 记「消费者调用点」(`client-modules` 对 `manager.*` 的调用 + `manager.ts:20` 的类型 import),D 记「`client-base` 内已不属接口的实现残留」与「走 legacy 全局的 `cache.ts` 概念路径」。证据纪律:每行含 `接口/成员名 + 命中处 file:line + 基准侧锚点 file:line + 问题描述 + 影响 + 置信`,**每行至少两个锚点**;静态阅读不能定论者一律进 F 类,**不得写成错配 / 缺失 / 残留**。允许按 **D-09** 派**只读**子代理做重型整文件扫描(子代理只回传事实表「成员名 + 所属 `file:line` + 形态」,**不得写文件、不得贴大段源码**),最终由主执行者按 `file:line` 切片复核。同时记录 (0b) 的类型门禁命令与两个数字的口径(正则 `error TS\d+` 的匹配数)。
- (4) 补齐 11 个二级标题(`## 背景` / `## 方法` / `## 基准变更(4e305e3)` / `## 类型门禁实测基线` 已在前几步写入,此处补齐其余 7 个占位,其后 Task 2 在同一骨架上横向补齐,不再改结构):`## A 已移除符号的剩余引用` / `## B 改名的剩余引用` / `## C 新增与收紧约束的未实现` / `## D big-image 残留(实现与消费者)` / `## E 范围外命中(仅报告)` / `## F 未能从阅读确定(未猜测)` / `## 处置`。
+ (4) 补齐 11 个二级标题(`## 背景` / `## 方法` / `## 基准变更(4e305e3)` / `## 类型门禁实测基线` 已在前几步写入,此处补齐其余 7 个占位,其后 Task 2 在同一骨架上横向补齐,不再改结构):`## A 已移除符号的剩余引用` / `## B 改名的剩余引用` / `## C 新增与收紧约束的未实现` / `## D big-image 残留(实现与消费者)` / `## E 范围外命中(仅报告)` / `## F 未能从阅读确定(未猜测)` / `## 处置`。**每个类别标题下先写一行记录编号模板**(供 Task 2 横向补齐时对齐):A 类 `#04-06-A-NN`(消费者调用点;表行形如 `| #04-06-A-NN | 接口/成员名 | 命中处 file:line | 基准侧锚点 file:line | 问题描述 | 影响 | 置信 |`)、C 类 `#04-06-C-NN`(约束未实现)、D 类 `#04-06-D-NN`(实现残留,含 `cache.ts` legacy 概念路径)、E 类 `#04-06-E-NN`(范围外仅报告);B 类为结论行、**不编记录号**;F 类条目形如 `- **#04-06-F-NN**:现象 + 为什么读不出来 + 需要用户裁决的点`。模板行用 `NN` 占位、不含数字,故不计入任何行数门禁。
(5) **`## 基准变更(4e305e3)` 定稿(本任务的核心横向切片)**:以 `git show 4e305e3 -- packages-user/client-base/src/types.ts packages-user/client-base/src/material/types.ts` 为依据,并与当日工作树**逐条比对**后写入三类差异:① **移除 7 个符号** —— `IBigImageReturn`(原定义在 `material/types.ts`)、`isBigImage` / `getBigImage` / `getIfBigImage`(原在 `IMaterialGetter`,现 `ITextureGetter` 的成员集合不含)、`getBigImageByAlias`(原在 `IMaterialAliasGetter`,现 `ITextureAliasGetter` 不含)、`setBigImage`(原在 `IMaterialManager`,现 `ITextureManager` 不含)、`bigImageStore`(同上),每条附**现行基准侧锚点**(即该符号原所属接口在 `material/types.ts` 的现行行,例如 `ITextureGetter` `:196`、`ITextureAliasGetter` `:228`、`ITextureManager` `:260`);② **改名 3 个接口** —— `IMaterialManager` → `ITextureManager`(`:260`)、`IMaterialGetter` → `ITextureGetter`(`:196`)、`IMaterialAliasGetter` → `ITextureAliasGetter`(`:228`),并列出该提交内同步改名的消费者文件(`client-base/src/load/loader.ts`、`client-base/src/material/{autotile,builder,manager}.ts`、`client-modules/src/{client.ts,fallback/load.ts,render/map/moving.ts,render/map/renderer.ts,render/map/types.ts,types.ts}`);③ **新增 / 收紧约束** —— `ITextureManager` 新增 `readonly textures: ITextureStore`(`:262-263`,附 jsDoc 原文),以及四处 `extends ICoreStateExtended`(`ITextureManager` `:261` / `IAutotileProcessor` `:104` / `IAssetBuilder` `:430` / `ITrackedAssetData` `:462-463`),并**点名定义源** `packages-user/data-state/src/types.ts:45-49`(`readonly state: ICoreState`)。另记录同提交内的**相邻变更**(登记,不作本步裁定):`client-base/src/types.ts` 新增 `rafExcitation` / `excitationDivider`,新增 `packages-user/client-system` 包与 `IClientSystem`。**不得**修改任何被引用文件。
@@ -297,7 +297,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
- `## 方法` 含「只读起始基线」条目,**逐行原样**记录起始 `git status --porcelain -- packages packages-user src` 的输出(规划日为空 → 写「(无)」);门禁断言记录条目与当前状态逐条一致
- `## 方法` 写死 A/B/C/D/E/F 六类判定口径与 **A(消费者调用点)/ D(实现残留与 legacy 概念路径)分列规则**,并写明每行须含命中处与基准侧两个 `file:line` 锚点、未定项一律进 F 类
- `## 基准变更(4e305e3)` 覆盖 7 个已移除符号(含各自现行基准侧锚点)、3 个改名接口、`readonly textures: ITextureStore`、四处 `extends ICoreStateExtended`,并点名定义源 `packages-user/data-state/src/types.ts:45-49`;另登记同提交的相邻变更(`rafExcitation` / `excitationDivider` / `client-system`)
- - `## 类型门禁实测基线` 逐字记录 `- **错误总行数**:` 与 `- **material 相关错误行数**:`(规划日 199 / 20,执行日以实测为准),逐行列出全部 material 相关错误,并单列与本改动无关的既有错误与该划分口径「须用户确认」的说明
+ - `## 类型门禁实测基线` 逐字记录 `- **错误总行数**:` 与 `- **material 相关错误行数**:`(规划日 199 / 19,执行日以实测为准),逐行列出全部 material 相关错误,并单列与本改动无关的既有错误与该划分口径「须用户确认」的说明
- A 类含固定列序表头与至少 1 条 `#04-06-A-*` 行,该行含命中处与基准侧两个 `file:line` 锚点,且行内无修复建议
- 相对记录在 `## 方法` 的只读起始基线零变化(porcelain 条目逐条一致);未修改任何生产源码;未新增计划文件;全文 CRLF
@@ -373,7 +373,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
- dev.md(「其他要求」的 CRLF 要求;「双端分离」)
- (1) **`## 处置` 定稿**:明写 —— 本文件是 material 接口适应(D-30 / D-31)**第一步(只读清点)**的交付物;本步**未修改任何生产代码**、`files_modified` 仅本文件;相对 `## 方法` 记录的只读起始基线零变化;**第二步(material 接口适应实施)未规划**,待用户审阅本文件后另行规划(D-32);范围限 `packages-user`,`packages/` 不纳入、`src/` 命中仅报告(D-32);A / B / C / D / E / F 六类中只有 A / C / D 是 `packages-user` 内的待处置项,B 类已清零、E 类仅报告、F 类须用户裁决;`state` 的注入方式、`textures` 的承担者、`getIfBigImage` 的替代一律由用户裁决(不得由 AI 裁定,D-23 亦约束新增代码不得取全局单例);`core.*` legacy 适配(D-12)与移动控制(D-19)、跟随者(D-22)、`HeroRendering` 语义(D-18)不属本步;`REND-01` / `REND-02` 保持 Pending;若当日实测数值与规划日(199 / 20)不同,在本节说明差异与原因。
+ (1) **`## 处置` 定稿**:明写 —— 本文件是 material 接口适应(D-30 / D-31)**第一步(只读清点)**的交付物;本步**未修改任何生产代码**、`files_modified` 仅本文件;相对 `## 方法` 记录的只读起始基线零变化;**第二步(material 接口适应实施)未规划**,待用户审阅本文件后另行规划(D-32);范围限 `packages-user`,`packages/` 不纳入、`src/` 命中仅报告(D-32);A / B / C / D / E / F 六类中只有 A / C / D 是 `packages-user` 内的待处置项,B 类已清零、E 类仅报告、F 类须用户裁决;`state` 的注入方式、`textures` 的承担者、`getIfBigImage` 的替代一律由用户裁决(不得由 AI 裁定,D-23 亦约束新增代码不得取全局单例);`core.*` legacy 适配(D-12)与移动控制(D-19)、跟随者(D-22)、`HeroRendering` 语义(D-18)不属本步;`REND-01` / `REND-02` 保持 Pending;若当日实测数值与规划日(199 / 19)不同,在本节说明差异与原因。
(2) **运行六道门禁并按实测结果如实记录**:① 只读门禁(`git status --porcelain -- packages packages-user src` 与 `## 方法` 记录逐条一致);② 类型门禁一致性(重跑 `node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"`,输出必须等于文档记录的**错误总行数**);③ 类别门禁(A/C/D/E 行数下限、每行双锚点、A 覆盖 7 符号、B 三个旧名、C 五个关键词、D 关键残留与 `cache.ts`、E 的 `src/types/declaration` 与 `.planning/graphs`、F ≥ 5 条);④ B 类 0 命中复验(`git grep` 源码面仍为 0);⑤ 既有计划保护 + 计划范围门禁(`04-05-PLAN.md` / `04-05-SUMMARY.md` 的 porcelain 为空;phase 目录内 `-PLAN.md` 仅允许 `04-01..04-06`);⑥ CRLF 门禁。任一门禁失败即停下修正文档后重跑,**不得**改门禁口径或伪造通过。
@@ -441,7 +441,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
- **只读门禁**:`node -e "const{execSync}=require('child_process');console.log(JSON.stringify(execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})))"` 的输出与交付文档 `## 方法` 的「只读起始基线」条目逐条一致(不新增、不消失、状态码不变)。
-- **类型门禁一致性**:`node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出等于文档记录的「错误总行数」(规划日 199),且「material 相关错误行数」(规划日 20)在文档中逐行有对应的数据行。
+- **类型门禁一致性**:`node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出等于文档记录的「错误总行数」(规划日 199),且「material 相关错误行数」(规划日 19 = `client-base` 7 + `client-modules` 12)在文档中逐行有对应的数据行。
- **类别门禁**:交付文档的 A ≥ 9 行 / C ≥ 5 行 / D ≥ 9 行 / E ≥ 5 行 / F ≥ 5 条,A–E 每行 ≥2 个 `file:line` 锚点,A 覆盖全部 7 个已移除符号,B 覆盖三个旧接口名,C 覆盖 `textures` 与四个实现类,D 覆盖关键残留成员与 `cache.ts`,E 覆盖 `src/types/declaration` 与 `.planning/graphs`。
- **B 类 0 命中复验**:`git grep -n -E "IMaterialManager|IMaterialGetter|IMaterialAliasGetter" -- packages packages-user src` 无输出。
- **既有计划保护 + 计划范围门禁**:`git status --porcelain -- .planning/phases/04-render-adaptation/04-05-PLAN.md .planning/phases/04-render-adaptation/04-05-SUMMARY.md` 为空;phase 目录内 `-PLAN.md` 仅 `04-01..04-06`;`04-06-SUMMARY.md` 已存在。
From 3fadf34fff51c15c9f105806a278e6a8478fb6af Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Mon, 21 Sep 2026 23:15:22 +0800
Subject: [PATCH 061/173] fix(04): revise plan 04-06 verification-strength
claim (must_haves truth vs Task 3 gate)
---
.../phases/04-render-adaptation/04-06-PLAN.md | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-06-PLAN.md b/.planning/phases/04-render-adaptation/04-06-PLAN.md
index 77e9341..1662ea6 100644
--- a/.planning/phases/04-render-adaptation/04-06-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-06-PLAN.md
@@ -23,7 +23,7 @@ must_haves:
truths:
- "**计划号冲突(必须在 Task 0 原样汇报)**:`packages-user`/phase 目录下**已存在** `.planning/phases/04-render-adaptation/04-05-PLAN.md` 与 `04-05-SUMMARY.md`(第五个增量·`#04-01-M-06` / `#04-01-M-09` 收口,2026-09-20 已执行且改动在工作树中:`RendererLayerHook`、`getFinalAttribute`),故本增量按顺序号落为 **04-06**(`depends_on` 仍列 04-01..04-04 以保持 `wave: 5`,与编排给定的 wave 一致;04-05 的已完成改动属本计划的**基线**,不是本计划的产出)。`04-05-PLAN.md` / `04-05-SUMMARY.md` 一律**不得改写、不得覆盖、不得改号**;Task 0 必须把该冲突原样汇报并请用户确认编号(若用户要求改号,须先裁决 04-05 存量产物的归属,再由用户指定文件名,不得自行覆盖)。"
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡**:先向用户汇报「要解决的问题(material 接口在 `4e305e3` 后的影响面未清点,剩余实现/消费者仍在引用已删除或已改名的成员,且新增约束未实现)、大致解决方案(只读清点,唯一交付物 `04-MATERIAL-INTERFACE-IMPACT.md`,11 节结构 / A–F 六类分列 / 每行精确到符号 + file:line)、`4e305e3` 基准的三类差异(移除 7 个符号、改名 3 个接口、新增 `textures` 与 4 处 `ICoreStateExtended` 约束)、范围与禁令、**计划号冲突(04-05 已被占用 → 本计划为 04-06)**、验证方式与只读证明」,然后必须**停下等待**用户明确回复「可以执行」四个字;收到该回复前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `mode: yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
- - "**只读硬约束(生产代码零改动)**:本 run 生产源码改动为 **0**——`git status --porcelain -- packages packages-user src` 在 Task 1 步骤 (0a) 起始逐行原样记录进 `## 方法`(规划日 2026-09-21 实测为「(无)」),Task 1 / Task 3 门禁断言其后**逐条一致**(不新增、不消失、状态码不变);`pnpm exec vue-tsc --noEmit` 的**错误总行数**与 **material 相关错误行数**在 Task 1 步骤 (0b) 记录(规划日实测 **199 / 19**,其中 material 19 = `client-base` 7 + `client-modules` 12,非 material 180),Task 3 门禁重跑并断言两个数字**完全一致**。`files_modified` 只含 `04-MATERIAL-INTERFACE-IMPACT.md` 一个交付物;本 PLAN 与 `04-06-SUMMARY.md` 属计划/执行簿记,不算生产改动。"
+ - "**只读硬约束(生产代码零改动)**:本 run 生产源码改动为 **0**——`git status --porcelain -- packages packages-user src` 在 Task 1 步骤 (0a) 起始逐行原样记录进 `## 方法`(规划日 2026-09-21 实测为「(无)」),Task 1 / Task 3 门禁断言其后**逐条一致**(不新增、不消失、状态码不变);`pnpm exec vue-tsc --noEmit` 在 Task 1 步骤 (0b) 记录**错误总行数**与 **material 相关错误行数**两个数字(规划日实测 **199 / 19**,其中 material 19 = `client-base` 7 + `client-modules` 12,非 material 180);**可失败的机器门禁只覆盖错误总行数**——Task 3 重跑 `pnpm exec vue-tsc --noEmit` 并以同一正则统计,断言其等于 Task 1 记录的错误总行数(不等即非零退出);**material / 非 material 的划分是规划日的人工归类**(口径须用户确认,见 assumptions),执行日以当日实测重新归类并逐行列出,其数字由人工复核保证、**不由任何门禁断言**(门禁只要求文档中逐行列出 material 相关错误锚点)。`files_modified` 只含 `04-MATERIAL-INTERFACE-IMPACT.md` 一个交付物;本 PLAN 与 `04-06-SUMMARY.md` 属计划/执行簿记,不算生产改动。"
- "**A 类(已移除符号的剩余引用·引用侧)**:必须逐一登记 D-31 点名的 **7 个已移除符号** `IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`,每个符号给出**引用侧 `file:line`**;`client-modules` 侧 9 处调用点一条不漏——`getIfBigImage` 7 处(`render/map/extension/door.ts:36,46`;`render/map/extension/hero.ts:239,364,428`;`render/map/renderer.ts:1250`;`render/map/vertex.ts:546`)、`bigImageStore` 1 处(`render/map/renderer.ts:652`)、`getBigImage` 1 处(`render/map/renderer.ts:653`);无调用点的 `setBigImage` / `isBigImage` / `getBigImageByAlias` 必须**显式登记为「调用点 0 处」**(不遗漏、不臆造);`IBigImageReturn` 的引用侧为 `client-base/src/material/manager.ts:20`(import 自 `./types`,该符号已不在基准导出中)。"
- "**B 类(改名的剩余引用)必须给出机器证据的「0 命中」结论**:`IMaterialManager` → `ITextureManager`、`IMaterialGetter` → `ITextureGetter`、`IMaterialAliasGetter` → `ITextureAliasGetter`,三个旧名在**源码面**(`packages`、`packages-user`、`src`,排除 `node_modules`)的当日命中数必须为 **0**,并原样记录核对命令 `git grep -n -E \"IMaterialManager|IMaterialGetter|IMaterialAliasGetter\" -- packages packages-user src` 的输出;`.planning/graphs/graph.json` 与 `.planning/graphs/.last-build-snapshot.json` 中仍存在的 `IMaterialManager` / `IMaterialGetter` 标签属**过期工装态**(快照时间戳 2026-09-07,早于 `4e305e3` 的 2026-09-21),只作 E 类登记,**不得**算作源码命中、**不得**作为基准。"
- "**C 类(新增与收紧约束的未实现)必须逐实现类登记 5 项,并带当日实测错误码**:① `ITextureManager.textures`(基准 `client-base/src/material/types.ts:262-263`)未实现于 `MaterialManager`(`material/manager.ts:38`,成员停在 `tileStore`/`tilesetStore`/`imageStore`/`assetStore`/`bigImageStore`,无 `textures`)→ 实测 `client.ts:84` 报 TS2739;②③④⑤ `ICoreStateExtended`(定义源 `packages-user/data-state/src/types.ts:45-49`)被 `ITextureManager`(`material/types.ts:261`)、`IAutotileProcessor`(`:104`)、`IAssetBuilder`(`:430`)、`ITrackedAssetData`(`:462-463`)四处 `extends`,而四个实现类 `MaterialManager`(`manager.ts:38`)、`AutotileProcessor`(`autotile.ts:42`)、`AssetBuilder`(`builder.ts:12`)、`TrackedAssetData`(`builder.ts:99`)**均无 `state` 成员** → 实测 `manager.ts:38`/`autotile.ts:42`/`builder.ts:12`/`builder.ts:99` 报 TS2420,构造点 `manager.ts:86`(`new AssetBuilder(this)`)、`builder.ts:91`(`TrackedAssetData`)、`client.ts:85`(`new AutotileProcessor(this.materials)`)、`render/map/renderer.ts:234`(`new AutotileProcessor(manager)`)报 TS2741。`state` 的语义来源与注入方式**不在本步裁定**(进 F 类)。"
@@ -33,7 +33,7 @@ must_haves:
- "**`## 基准变更(4e305e3)` 与 `## 类型门禁实测基线` 两节必须齐备**:前者逐条记录三类差异(移除 7 符号 / 改名 3 接口 / 新增 `textures` 与 4 处 `ICoreStateExtended` 约束)并点名定义源 `packages-user/data-state/src/types.ts:45-49`,核对方式为 `git show 4e305e3 -- packages-user/client-base/src/types.ts packages-user/client-base/src/material/types.ts` 与当日工作树逐条比对;后者逐字记录 `pnpm exec vue-tsc --noEmit` 的 `- **错误总行数**:` 与 `- **material 相关错误行数**:`(规划日实测 199 / 19,执行日以实测为准)并逐行列出 material 相关错误,同时单列与本改动无关的既有错误(data-state / data-base / data-system 的测试与寻路、`client-base/src/load/loader.ts` 与 `client-modules/src/{core.ts,render/ui/load.tsx}` 的加载系统重构结果)——**不得**把它们算作本次 material 改动的后果。"
- "**第二步(修改工作)不被规划**:文档只清点、分类、分组,**不含**修复方案、不含代码改动建议、不产出 `04-07-PLAN.md` 或任何新 `*-PLAN.md`;`## 处置` 明写「第一步只读清点完成;第二步(material 接口适应实施)待用户审阅本文件后另行规划」(D-32),并保留第一条清点步骤与第六条材料的原话口径。"
- "**REND-01 / REND-02 在本 run 后仍保持 Pending**:本计划只清点 material 接口的影响面,既未实施 material 适应,也未实施移动端 / 桌面端双布局(见 assumptions 的两条 FLAGGED ASSUMPTION)。"
- - "**验证可运行且不臆造通过**:本计划的「运行结果」= 只读门禁(porcelain 基线逐条一致 + 类型错误总数与 material 子集一致)+ 静态内容门禁(11 节结构、A–F 六类行数下限与关键词、每行 ≥2 个 `file:line` 锚点、B 类 0 命中、E 类只报告口径)+ 既有计划保护门禁(`04-05-PLAN.md` / `04-05-SUMMARY.md` porcelain 为空)+ 计划文件范围门禁(不得出现 `04-07+`)+ CRLF 门禁 + 人工代码复核;本步为只读清点,**无运行时行为可观察**,故不得以任何无法运行的门禁冒充通过。"
+ - "**验证可运行且不臆造通过**:本计划的「运行结果」= 只读门禁(porcelain 基线逐条一致 + 类型错误**总行数**与记录值一致;material 子集为规划日人工归类、逐行列出并由人工复核,**不由门禁断言**)+ 静态内容门禁(11 节结构、A–F 六类行数下限与关键词、每行 ≥2 个 `file:line` 锚点、B 类 0 命中、E 类只报告口径)+ 既有计划保护门禁(`04-05-PLAN.md` / `04-05-SUMMARY.md` porcelain 为空)+ 计划文件范围门禁(不得出现 `04-07+`)+ CRLF 门禁 + 人工代码复核;本步为只读清点,**无运行时行为可观察**,故不得以任何无法运行的门禁冒充通过。"
artifacts:
- path: .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md
provides: "`4e305e3` material 接口基准在 `packages-user` 全量的只读影响台帐:元信息块 + 背景 / 方法 / 基准变更(4e305e3)/ 类型门禁实测基线 / A 已移除符号的剩余引用 / B 改名的剩余引用 / C 新增与收紧约束的未实现 / D big-image 残留(实现与消费者)/ E 范围外命中(仅报告)/ F 未能从阅读确定(未猜测)/ 处置;每行含接口或成员名 + 命中处 `file:line` + 基准侧锚点 + 问题描述 + 影响 + 置信,供用户审阅后另行规划第二步"
@@ -44,7 +44,7 @@ must_haves:
- "`client-modules` 的消费者调用点(`render/map/extension/door.ts:36,46`、`render/map/extension/hero.ts:239,364,428`、`render/map/renderer.ts:652,653,1250`、`render/map/vertex.ts:546`)↔ 基准 `ITextureManager` 上已不存在的成员 → 实测 9 条 `error TS2339` / `TS2551`"
- "构造 / 接线面(`client-modules/src/client.ts:84` 的 `new MaterialManager()`、`client.ts:85` 的 `new AutotileProcessor(this.materials)`、`render/map/renderer.ts:234` 的 `new AutotileProcessor(manager)`、`manager.ts:86` 的 `new AssetBuilder(this)`、`builder.ts:28` 的 `new TrackedAssetData(materials, this)`)—— `state` 与 `textures` 的注入/实现面必须在本清点文件中登记,本步不作裁定"
- "`git status --porcelain -- packages packages-user src`(Task 1 步骤 (0a) 逐行记录)↔ Task 1 / Task 3 门禁的逐条一致断言 ↔ 只读(生产代码零改动)证明"
- - "`pnpm exec vue-tsc --noEmit` 的错误总行数与 material 相关子集(Task 1 步骤 (0b) 记录、规划日实测 199 / 19)↔ Task 3 门禁的重跑一致断言 ↔ 只读证明"
+ - "`pnpm exec vue-tsc --noEmit` 的错误**总行数**(Task 1 步骤 (0b) 记录、规划日实测 199)↔ Task 3 门禁的重跑一致断言 ↔ 只读证明;material 相关子集(规划日 19 = `client-base` 7 + `client-modules` 12)为规划日人工归类、逐行列出,执行日按当日实测重新归类并由人工复核,**不是门禁断言项**"
assumptions:
- "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付 material 接口影响的只读清点(D-30 / D-31 / D-32 第一步)。用户须在推进第二步与其余渲染适配前补齐 REND-01 的边界判据。"
- "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局,也不发明其验收阈值。"
@@ -95,7 +95,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
| E | 范围外(`src/`、`packages/`、`.planning/graphs/*`、依赖声明) | 只报告,不修改、不建议 |
| F | 未能从阅读确定 | 只登记,不得写成错配 / 缺失 / 残留 |
-**规划日实测(作为起点与门禁下限,执行日以当日实测为准):**
+**规划日实测(A–F 各类命中数为内容门禁下限;类型错误总行数为机器门禁断言值、material 子集为规划日人工归类;执行日一律以当日实测为准):**
| 类别 | 规划日实测结果(2026-09-21,HEAD `4e305e3`,工作树) |
|---|---|
@@ -186,7 +186,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
- **改名 3 个接口**:`IMaterialManager` → `ITextureManager`、`IMaterialGetter` → `ITextureGetter`、`IMaterialAliasGetter` → `ITextureAliasGetter`。
- **新增 / 收紧约束**:`ITextureManager` 新增 `readonly textures: ITextureStore`(`material/types.ts:262-263`),且由 `ITextureManager`(`:261`)、`IAutotileProcessor`(`:104`)、`IAssetBuilder`(`:430`)、`ITrackedAssetData`(`:462-463`)四处 `extends ICoreStateExtended`(定义源 `packages-user/data-state/src/types.ts:45-49`:`readonly state: ICoreState`)。
- ③ **规划日实测的影响面(作为起点与门禁下限)**:`pnpm exec vue-tsc --noEmit` 当前 **199** 条错误,其中 **19** 条由本次 material 接口改动直接造成(`client-base` 7 条 + `client-modules` 12 条);其余 180 条属用户既有的加载系统重构 / 寻路 / 测试等,**不算**本改动的后果(该划分口径须用户确认)。具体:A 类 `client-modules` 侧 **9** 处调用点(`getIfBigImage` 7 处:`door.ts:36,46`、`hero.ts:239,364,428`、`renderer.ts:1250`、`vertex.ts:546`;`bigImageStore` 1 处:`renderer.ts:652`;`getBigImage` 1 处:`renderer.ts:653`)+ `manager.ts:20` 的 `IBigImageReturn` import;B 类源码面 **0** 命中;C 类 **5** 项(`textures` 未实现 + 4 个实现类缺 `state`);D 类 `MaterialManager` **10** 项 + `cache.ts` legacy 路径 1 项;E 类 `src/types/declaration/*` **5** 处、`packages/` **0** 命中、`.planning/graphs/*` 过期标签、`client-base/package.json` 未声明 `@user/data-state`。
+ ③ **规划日实测的影响面(A–F 各类命中数为内容门禁下限;类型错误总行数为机器门禁断言值、material 子集为规划日人工归类)**:`pnpm exec vue-tsc --noEmit` 当前 **199** 条错误,其中 **19** 条由本次 material 接口改动直接造成(`client-base` 7 条 + `client-modules` 12 条);其余 180 条属用户既有的加载系统重构 / 寻路 / 测试等,**不算**本改动的后果(该划分口径须用户确认)。具体:A 类 `client-modules` 侧 **9** 处调用点(`getIfBigImage` 7 处:`door.ts:36,46`、`hero.ts:239,364,428`、`renderer.ts:1250`、`vertex.ts:546`;`bigImageStore` 1 处:`renderer.ts:652`;`getBigImage` 1 处:`renderer.ts:653`)+ `manager.ts:20` 的 `IBigImageReturn` import;B 类源码面 **0** 命中;C 类 **5** 项(`textures` 未实现 + 4 个实现类缺 `state`);D 类 `MaterialManager` **10** 项 + `cache.ts` legacy 路径 1 项;E 类 `src/types/declaration/*` **5** 处、`packages/` **0** 命中、`.planning/graphs/*` 过期标签、`client-base/package.json` 未声明 `@user/data-state`。
④ **大致解决方案**:只读清点,唯一交付物 `.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md`;11 节结构(`背景` / `方法` / `基准变更(4e305e3)` / `类型门禁实测基线` / `A 已移除符号的剩余引用` / `B 改名的剩余引用` / `C 新增与收紧约束的未实现` / `D big-image 残留(实现与消费者)` / `E 范围外命中(仅报告)` / `F 未能从阅读确定(未猜测)` / `处置`);A–E 每节固定列序表(`ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信`),每行 ≥2 个 `file:line` 锚点;**分列规则**:A = 消费者调用点、D = `client-base` 内已不属接口的实现残留与 `cache.ts` 的 legacy 概念路径(二者分列并互相引用);F 类只登记不判定。重型整文件读取可按 **D-09** 派**只读**子代理(仅回传事实表,不得写文件)。
@@ -194,7 +194,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
⑥ **计划号冲突(必须原样告知用户)**:phase 目录下**已存在** `04-05-PLAN.md` 与 `04-05-SUMMARY.md`(第五个增量·`#04-01-M-06` / `#04-01-M-09` 收口,2026-09-20 已执行;其改动 `RendererLayerHook`(`render/map/renderer.ts:435,1775`)与 `client.hero.attribute.getFinalAttribute`(`render/ui/main.tsx:103+`)在工作树中)。编排给定的「04-05」与该存量冲突,故本计划按顺序号落为 **04-06**,`depends_on` 列 04-01..04-04 以保持 `wave: 5`;**04-05 一律不得改写 / 覆盖 / 改号**。请用户确认 04-06 这一编号(或指定其它编号,但须先裁决 04-05 存量产物的归属)。
- ⑦ **验证方式与只读证明(须原样告知)**:本步为只读清点,**无运行时行为可观察**。证明方式 = ①**只读门禁**:`git status --porcelain -- packages packages-user src` 在 Task 1 起始逐行记录(规划日实测为「(无)」),Task 1 / Task 3 断言其后逐条一致;②**类型门禁一致性**:`pnpm exec vue-tsc --noEmit` 的错误总行数与 material 子集(规划日 199 / 19)记录后,Task 3 重跑断言完全一致;③**静态内容门禁**:11 节结构、A–F 行数下限与关键词、每行 ≥2 个 `file:line` 锚点、B 类 0 命中、E 类只报告口径;④**既有计划保护门禁**:`04-05-PLAN.md` / `04-05-SUMMARY.md` 的 porcelain 为空;⑤**计划范围门禁**:不得出现 `04-07+`;⑥**CRLF 门禁**;⑦**人工代码复核**。**不冒充任何无法运行的门禁。**
+ ⑦ **验证方式与只读证明(须原样告知)**:本步为只读清点,**无运行时行为可观察**。证明方式 = ①**只读门禁**:`git status --porcelain -- packages packages-user src` 在 Task 1 起始逐行记录(规划日实测为「(无)」),Task 1 / Task 3 断言其后逐条一致;②**类型门禁一致性**:`pnpm exec vue-tsc --noEmit` 在 Task 1 起始记录**错误总行数**与 material 子集两个数字(规划日 199 / 19),Task 3 重跑**只断言错误总行数**与记录值一致;material / 非 material 的划分属规划日人工归类(口径须用户确认),执行日按实测重新归类并逐行列出,由人工复核保证,**不作为机器门禁**;③**静态内容门禁**:11 节结构、A–F 行数下限与关键词、每行 ≥2 个 `file:line` 锚点、B 类 0 命中、E 类只报告口径;④**既有计划保护门禁**:`04-05-PLAN.md` / `04-05-SUMMARY.md` 的 porcelain 为空;⑤**计划范围门禁**:不得出现 `04-07+`;⑥**CRLF 门禁**;⑦**人工代码复核**。**不冒充任何无法运行的门禁。**
⑧ **第二步不被规划**:本 run **不含**修复方案、不含代码改动建议、不产出 `04-07+`;第二步(material 接口适应实施)待用户审阅本清点文件后另行规划(D-32)。
@@ -341,6 +341,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
(8) **只修改该交付文档**;不得改动任何生产源码、不得新增计划文件、不得修改 Task 1 已定稿的数字与结构。
+
node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md','utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const A=seg('## A \u5df2\u79fb\u9664\u7b26\u53f7');const B=seg('## B \u6539\u540d\u7684\u5269\u4f59\u5f15\u7528');const C=seg('## C \u65b0\u589e\u4e0e\u6536\u7d27\u7ea6\u675f');const D=seg('## D big-image');const E=seg('## E \u8303\u56f4\u5916\u547d\u4e2d');const F=seg('## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a');const cnt=(s,re)=>(s.match(re)||[]).length;const rows=s=>cnt(s,/^\|\s*#04-06-[ABCDE]-\d+\s*\|/gm);const anch=l=>cnt(l,/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g);const problems=[];if(rows(A)<9)problems.push('A ROWS '+rows(A));if(rows(C)<5)problems.push('C ROWS '+rows(C));if(rows(D)<9)problems.push('D ROWS '+rows(D));if(rows(E)<5)problems.push('E ROWS '+rows(E));if(cnt(F,/^- \*\*#04-06-F-\d+/gm)<5)problems.push('F ITEMS '+cnt(F,/^- \*\*#04-06-F-\d+/gm));for(const pair of [['A',A],['C',C],['D',D],['E',E]]){const bad=pair[1].split(/\r?\n/).filter(l=>/^\|\s*#04-06-[ABCDE]-\d+\s*\|/.test(l)).filter(l=>anch(l)<2);if(bad.length>0)problems.push(pair[0]+' ROWS WITHOUT TWO-SIDED ANCHOR '+bad.length)}const rm=['IBigImageReturn','isBigImage','getBigImage','getIfBigImage','getBigImageByAlias','setBigImage','bigImageStore'];const missA=rm.filter(k=>A.indexOf(k)<0);if(missA.length>0)problems.push('A MISSING SYMBOLS '+missA.join('|'));const missB=['IMaterialManager','IMaterialGetter','IMaterialAliasGetter'].filter(k=>B.indexOf(k)<0);if(missB.length>0)problems.push('B MISSING OLD NAMES '+missB.join('|'));const missC=['textures','MaterialManager','AutotileProcessor','AssetBuilder','TrackedAssetData','ICoreStateExtended'].filter(k=>C.indexOf(k)<0);if(missC.length>0)problems.push('C MISSING '+missC.join('|'));const missD=['bigImageStore','bigImageData','bigImageId','setBigImage','isBigImage','getBigImageByAlias','IBigImageReturn','cache.ts'].filter(k=>D.indexOf(k)<0);if(missD.length>0)problems.push('D MISSING '+missD.join('|'));if(E.indexOf('src/types/declaration')<0)problems.push('E MISSING SRC DECL');if(E.indexOf('.planning/graphs')<0)problems.push('E MISSING GRAPH ARTIFACT');if(problems.length>0){console.error('CATEGORY GATE FAILED '+problems.join(' ; '));process.exit(1)}console.log('OK A '+rows(A)+' C '+rows(C)+' D '+rows(D)+' E '+rows(E)+' F '+cnt(F,/^- \*\*#04-06-F-\d+/gm))"
non-zero exit, 或 stderr 含 "CATEGORY GATE FAILED"(A/C/D/E 任一类行数低于下限、任一行缺命中处或基准侧锚点、A 未覆盖 7 个已移除符号、B 未列三个旧名、C 未覆盖 `textures` 与四个实现类、D 未覆盖关键残留成员或 `cache.ts`、E 未覆盖 `src/types/declaration` 与 `.planning/graphs`、F 少于 5 条未确定项)
@@ -441,7 +442,7 @@ Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一
- **只读门禁**:`node -e "const{execSync}=require('child_process');console.log(JSON.stringify(execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})))"` 的输出与交付文档 `## 方法` 的「只读起始基线」条目逐条一致(不新增、不消失、状态码不变)。
-- **类型门禁一致性**:`node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出等于文档记录的「错误总行数」(规划日 199),且「material 相关错误行数」(规划日 19 = `client-base` 7 + `client-modules` 12)在文档中逐行有对应的数据行。
+- **类型门禁一致性**:`node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出等于文档记录的「错误总行数」(规划日 199);「material 相关错误行数」(规划日 19 = `client-base` 7 + `client-modules` 12)为规划日人工归类、执行日按当日实测重新归类,只要求文档逐行列出(**非机器断言**,由人工复核保证)。
- **类别门禁**:交付文档的 A ≥ 9 行 / C ≥ 5 行 / D ≥ 9 行 / E ≥ 5 行 / F ≥ 5 条,A–E 每行 ≥2 个 `file:line` 锚点,A 覆盖全部 7 个已移除符号,B 覆盖三个旧接口名,C 覆盖 `textures` 与四个实现类,D 覆盖关键残留成员与 `cache.ts`,E 覆盖 `src/types/declaration` 与 `.planning/graphs`。
- **B 类 0 命中复验**:`git grep -n -E "IMaterialManager|IMaterialGetter|IMaterialAliasGetter" -- packages packages-user src` 无输出。
- **既有计划保护 + 计划范围门禁**:`git status --porcelain -- .planning/phases/04-render-adaptation/04-05-PLAN.md .planning/phases/04-render-adaptation/04-05-SUMMARY.md` 为空;phase 目录内 `-PLAN.md` 仅 `04-01..04-06`;`04-06-SUMMARY.md` 已存在。
From 58a8e6806eb9e33334d30dce4e6e367804d4eb51 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Mon, 21 Sep 2026 23:17:21 +0800
Subject: [PATCH 062/173] docs(04): register 04-05/04-06 and sync progress
metadata
---
.planning/ROADMAP.md | 4 ++--
.planning/STATE.md | 10 +++++-----
.planning/phases/04-render-adaptation/04-CONTEXT.md | 6 ++++++
.planning/state.json | 2 +-
4 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 6011a8a..8321138 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 5/5 plans executed(04-01..04-05)+ 1 planned(04-06,material 接口影响范围清点·只读,未执行)
+**Plans**: 5 of 6 executed(04-01..04-05)+ 04-06 planned(material 接口影响范围清点·只读,未执行)
**Plan register note (2026-09-21):** 第五个增量 `04-05`(收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性改经 `client.hero.attribute.getFinalAttribute(...)` 读取 + 逐图层钩子类 `RendererLayerHook`,2026-09-20 执行)此前未登记进本路线图,本次一并补登;第六个增量 `04-06` = material 接口适应(D-30/D-31/D-32)**第一步·只读影响清点**,产出 `04-MATERIAL-INTERFACE-IMPACT.md`,第二步(实施)待用户审阅后另行规划。
Plans:
@@ -399,7 +399,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 4/4 | In Progress| |
+| 4. 渲染适配与双布局 | 5/6 | In Progress| |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 8f2ad4a..34b790b 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -5,14 +5,14 @@ current_phase: 04
current_phase_name: 渲染适配与双布局
status: executing
stopped_at: Completed 04-04-PLAN.md(勇士渲染修正:注入 IFaceManager、拆钩子类、补 AnimDir、换 @motajs/animate,三文件机器门禁全绿;REND-01/REND-02 保持 Pending)
-last_updated: "2026-09-19T08:59:27.618Z"
-last_activity: 2026-09-18
+last_updated: "2026-09-21T15:16:11.920Z"
+last_activity: 2026-09-21
last_activity_desc: Phase 04 execution started
-state_head: 0ad933368c4b2245bba179acdcda4a7b45e5eb7c
+state_head: 3fadf34fff51c15c9f105806a278e6a8478fb6af
progress:
total_phases: 7
completed_phases: 0
- total_plans: 75
+ total_plans: 77
completed_plans: 74
milestone_name: milestone
---
@@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
-Plan: 4 of 4
+Plan: 5 of 6
Status: Ready to execute
Last activity: 2026-09-21 — Completed quick task 260921-lwa: Upgrade toolchain to pnpm 12.5.1 and Node 24
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
index b571e94..ad7061e 100644
--- a/.planning/phases/04-render-adaptation/04-CONTEXT.md
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -64,6 +64,12 @@
- **D-28:** **存量 `state` 使用暂不处理**(含 `hero.ts` 的 `state.roleFace.getFaceOf`):这涉及引擎整体的底层架构,由用户后续统一整改。本阶段**不因「不得使用单例」而清除存量**;改动只约束新增/后续代码。
- **D-29:** **`degrade` 与 `next` 均用 `Dir4FaceHandler`**(贴图只有四向;与移动 (Dir8) 失配,需单独处理)。因构造器参数已多,**`MapHeroRenderer` 构造器改为直接传入 `IFaceManager`**(`data-common/src/common/faceManager.ts:13`;`core.ts:117-120` 注册 `FaceGroup.Dir4`/`Dir8`):`degrade` / `next` 经 `faceManager.get(FaceGroup.Dir4)` 取四向处理器;移动相关的 `movement` 仍取勇士自身 `mover.faceHandler`(Dir8)。**注入路径走 B**:`IMapExtensionManager.addHero(state, layer, faceManager)` 新增 `faceManager: IFaceManager` 参数(**改 `types.ts` 接口**),`manager.ts` 转发给构造器;未来接线处再传。后续用户再进一步改进。
+### 下一个目标:material 接口适应(用户裁定,2026-09-21;待规划)
+- **D-30:** 下一个任务 = **完成 material 相关的接口适应**。用户已自行修改接口 `packages-user/client-base/src/types.ts`(提交 `4e305e3 refactor(type): material types.`,涉及 `material/types.ts`、`types.ts`、多个消费者的同步改名)并改完受影响的一部分内容;剩余未适应的实现与消费者由本任务处理。
+- **D-31:** 本次改动的根因:用户**重写了 Texture 的底层管理器,删除 `big-image` 概念**(旧样板概念,新引擎不再需要;旧兼容可**无痛丢弃**)。`4e305e3` 已从接口删除 `IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`,并把 `IMaterialManager`→`ITextureManager`、`IMaterialGetter`→`ITextureGetter`、`IMaterialAliasGetter`→`ITextureAliasGetter`,新增 `textures` 与 `ICoreStateExtended` 约束。**影响面可能很大。**
+- **D-32:** 计划分**两步**:**第一步**收集影响范围(只读清点);**第二步**进行修改。第二步骤第一步结果与用户审阅后再规划。
+ - **边界确认(2026-09-21):** 范围限 `packages-user`;`packages` 不纳入(理应不受本改动影响)。**以 `packages-user/client-base/src/types.ts`(及 `material/types.ts`)为基准**。**若发现其余 big-image 残留,必须在清点文档中报告。** `src/` 或 `packages/` 内若命中,仅报告、不修改。第一步产出 `.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md`(只读,允许派只读子代理);第二步待用户审阅第一步结果后再规划。
+
### the agent's Discretion
- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
diff --git a/.planning/state.json b/.planning/state.json
index 45708db..602c80e 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -44,5 +44,5 @@
"label": "Advance to the next step",
"reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-19T08:58:48.919Z"
+ "updated_at": "2026-09-21T15:16:12.042Z"
}
From 85f65b8f551e700992664b4f14700cd3cf0f4074 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 10:26:29 +0800
Subject: [PATCH 063/173] =?UTF-8?q?docs(04-06):=20material=20=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E5=BD=B1=E5=93=8D=E8=8C=83=E5=9B=B4=E6=B8=85=E7=82=B9?=
=?UTF-8?q?=EF=BC=88=E5=8F=AA=E8=AF=BB=EF=BC=8CD-30/D-31/D-32=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 唯一交付物 .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md(11 节 / A–F 六类 / 每行双 file:line 锚点)
- A 类 12 行(7 个已移除符号、9 处调用点、3 符号 0 调用);B 类源码面 0 命中;C 类 5 项约束未实现;D 类 12 行 big-image 残留;E 类 8 行仅报告;F 类 6 条未确定
- 类型门禁 199 / 19(与规划日一致);只读基线逐条一致;生产源码零改动;用户并发改动未触碰
- 04-05-PLAN.md / 04-05-SUMMARY.md 未改写;第二步未规划(D-32)
Refs: REND-01, REND-02 (仍保持 Pending)
---
.../04-render-adaptation/04-06-SUMMARY.md | 201 ++++++++++++++
.../04-MATERIAL-INTERFACE-IMPACT.md | 261 ++++++++++++++++++
2 files changed, 462 insertions(+)
create mode 100644 .planning/phases/04-render-adaptation/04-06-SUMMARY.md
create mode 100644 .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md
diff --git a/.planning/phases/04-render-adaptation/04-06-SUMMARY.md b/.planning/phases/04-render-adaptation/04-06-SUMMARY.md
new file mode 100644
index 0000000..dfe4832
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-06-SUMMARY.md
@@ -0,0 +1,201 @@
+---
+phase: 04-render-adaptation
+plan: 6
+subsystem: render
+tags: [material, ITextureManager, ITextureGetter, big-image, read-only-audit, interface-impact, client-base, client-modules]
+requires:
+ - phase: 04-render-adaptation
+ provides: "04-01 只读对账账本 / 04-02 勇士移动接口探索 / 04-03、04-04 增量 / 04-05 收口;本计划基线为 `4e305e3 refactor(type): material types.` 的 material 接口改动(D-30/D-31/D-32 第一步)"
+provides:
+ - "material 接口(`4e305e3` 基准)在 `packages-user` 全量的只读影响台帐 `04-MATERIAL-INTERFACE-IMPACT.md`(11 节 / A–F 六类 / 每行双 file:line 锚点)"
+ - "A 类:7 个已移除符号的引用点(`getIfBigImage` 7 处 + `bigImageStore` 1 处 + `getBigImage` 1 处 + `IBigImageReturn` import 1 处;`setBigImage`/`isBigImage`/`getBigImageByAlias` 消费者侧 0 处)"
+ - "B 类:三个旧接口名源码面 0 命的机器证据"
+ - "C 类:5 项新增/收紧约束未实现(`textures` + 四个实现类缺 `state`),带当日实测错误码"
+ - "D 类:`MaterialManager` big-image 实现残留 10 项 + `cache.ts` legacy 概念路径"
+affects: [material-adaptation, client-base, client-modules, phase-5-legacy]
+tech-stack:
+ added: []
+ patterns:
+ - "只读清点台账范式:固定列序 `ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信` + 每行 ≥2 个 file:line 锚点 + A(消费者调用点)/ D(实现残留与 legacy 概念路径)分列互引 + F 类只登记不判定"
+key-files:
+ created:
+ - .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md
+ modified: []
+key-decisions:
+ - "唯一交付物为只读清点文档;生产源码零改动(`packages`/`packages-user`/`src` 的 porcelain 自空基线起逐条一致),不触碰用户并发未提交改动(`04-CONTEXT.md`)"
+ - "A/D 分列写死:A 记消费者调用点(`client-modules` + `manager.ts:20` import),D 记 `client-base` 内已不属接口的实现残留与走 legacy 全局的 `cache.ts` 概念路径;同一成员两侧互引"
+ - "`state` 的注入方式、`textures` 的承担者、`getIfBigImage` 的替代一律进 F 类,不作裁定(D-23 亦约束新增代码不得取全局单例)"
+ - "第二步(material 接口适应实施)未规划,待用户审阅本文件后另行规划(D-32);不产出任何 `04-07+` 计划"
+ - "`cache.ts` 的 legacy bigImage 路径归属(本 material 增量 vs Phase 5 legacy 清退)进 F 类,不提清退计划(D-12 边界)"
+requirements-completed: [] # REND-01 / REND-02 本 run 后仍保持 Pending(只清点、未实施适配/双布局)
+actuals:
+ tokens: 10847
+ tasks: 4
+ commits: 1
+ plan_head_before: 58a8e6806eb9e33334d30dce4e6e367804d4eb51
+metrics:
+ duration: ~60min
+ completed: 2026-09-22
+ status: complete
+---
+
+# Phase 04 Plan 06: material 接口影响范围清点(只读)Summary
+
+**在 `4e305e3` 的 material 接口基准下,对 `packages-user` 全量做只读影响清点:产出 11 节 / A–F 六类的 `04-MATERIAL-INTERFACE-IMPACT.md`(7 个已移除符号的引用点、3 个改名接口的 0 命中证据、5 项新增/收紧约束未实现、`MaterialManager` 与 `cache.ts` 的 big-image 残留、范围外仅报告、未确定项只登记),生产源码零改动。**
+
+## Performance
+
+- **Duration:** ~60min
+- **Started:** 2026-09-22
+- **Completed:** 2026-09-22
+- **Tasks:** 4(Task 0 汇报关卡由用户在会话内回复「可以执行」闭环 + Task 1(tracer)/ 2 / 3)
+- **Files modified:** 1 交付文档(`04-MATERIAL-INTERFACE-IMPACT.md`)+ 本 SUMMARY;生产源码 0
+
+## 执行前核对(Task 1 步骤 0a/0b 前置)
+
+逐条打开当日工作树核对(结论 + `file:line`):
+
+| # | 复核项 | 结论 | 锚点 |
+|---|--------|------|------|
+| ① | 7 个已移除符号确不在 `material/types.ts` 现行导出/成员集合中 | 一致(`IBigImageReturn` 无定义;`ITextureGetter` 无 `isBigImage`/`getBigImage`/`getIfBigImage`;`ITextureAliasGetter` 无 `getBigImageByAlias`;`ITextureManager` 无 `setBigImage`/`bigImageStore`) | packages-user/client-base/src/material/types.ts:196,228,260 |
+| ② | 改名后名称与新增约束的现行位置 | 一致(`ITextureManager` `:260`、`ITextureGetter` `:196`、`ITextureAliasGetter` `:228`、`readonly textures` `:262-263`、四处 `extends ICoreStateExtended` `:104`/`:260-261`/`:430`/`:462-463`) | packages-user/client-base/src/material/types.ts |
+| ③ | `ICoreStateExtended` 定义源 | 一致 | packages-user/data-state/src/types.ts:45-49 |
+| ④ | 四个实现类缺 `state`、`MaterialManager` 缺 `textures` | 一致 | packages-user/client-base/src/material/{manager.ts:38-43, autotile.ts:42, builder.ts:12, builder.ts:99} |
+| ⑤ | `client-modules` 侧 9 处调用点当日行号 | 一致(`door.ts:36,46`;`hero.ts:239,364,428`;`renderer.ts:652,653,1250`;`vertex.ts:546`) | packages-user/client-modules/src/render/map/{extension/door.ts, extension/hero.ts, renderer.ts, vertex.ts} |
+| ⑥ | `cache.ts` 与 `src/types/declaration/*` 命中行号 | 一致(`cache.ts:36,48,144,153,157,159-160,190,215,243,276`;`enemy.d.ts:65`/`core.d.ts:1286`/`map.d.ts:354,1443`/`status.d.ts:391`) | 见清点文档 D / E 类 |
+| ⑦ | `packages/` 零命中 | 一致(`git grep -n -i bigImage -- packages` 无输出,退出码 1) | — |
+| ⑧ | `pnpm exec vue-tsc --noEmit` 当日总数与 material 子集 | 一致(**199 / 19**,与规划日相同) | 见「类型门禁实测基线」 |
+
+**结论:** 八项均与规划基线一致,可继续执行。
+
+## 执行前基线(Task 1 步骤 0a/0b)
+
+初始只读基线(`git status --porcelain -- packages packages-user src`)**原样**记录:
+
+
+(无)
+
+
+当日工作树内唯一的未提交改动为 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(不在该路径集合内),属用户既有改动。**未回滚 / 未暂存 / 未提交 / 未修改**任何用户改动。
+
+## 类型门禁实测基线(Task 1 步骤 0b)
+
+`pnpm exec vue-tsc --noEmit`(整仓非 0 退出属预期);统计口径 = 输出中正则 `error TS\d+` 的匹配数:
+
+- **错误总行数**:199
+- **material 相关错误行数**:19
+
+**与规划日一致**(规划日 199 / 19)。19 = `client-base` 7 条(`material/autotile.ts:42` TS2420、`material/builder.ts:12` TS2420、`material/builder.ts:91` TS2741、`material/builder.ts:99` TS2420、`material/manager.ts:20` TS2305、`material/manager.ts:38` TS2420、`material/manager.ts:86` TS2741)+ `client-modules` 12 条(`client.ts:84` TS2739、`client.ts:85` TS2741、`render/map/extension/door.ts:36,46` TS2339、`hero.ts:239,364,428` TS2339、`render/map/renderer.ts:234` TS2741、`render/map/renderer.ts:652,653` TS2551、`render/map/renderer.ts:1250` TS2339、`render/map/vertex.ts:546` TS2339)。非 material 180 条(含加载系统重构 16 条:`client-base/src/load/loader.ts` 6 + `client-modules/src/core.ts:6` 1 + `render/ui/load.tsx` 9)与本次改动无关,已在文档中单列。
+
+**口径说明:** 「material / 非 material」的划分为**规划日人工归类**,须用户审阅时确认;门禁只覆盖**错误总行数**。
+
+## 交付文档 A–F 实际行数与核对结论
+
+| 类别 | 实际数量 | 核对结论 |
+|---|---|---|
+| `## 基准变更(4e305e3)` | 移除 **7** 符号 / 改名 **3** 接口 / 新增 `textures` + **4** 处 `ICoreStateExtended` | 逐条覆盖,含现行基准侧锚点与定义源 `data-state/src/types.ts:45-49` |
+| A(已移除符号引用) | **12** 行(7 符号全覆盖) | `getIfBigImage` 7 处调用点 + `bigImageStore` 1 + `getBigImage` 1 + `IBigImageReturn` import 1;`setBigImage`/`isBigImage`/`getBigImageByAlias` 显式登记「调用点 0 处」 |
+| B(改名剩余引用) | 源码面 **0** 命中 | 三个旧名逐个给 0 命中结论 + 原样核对命令;`.planning/graphs/*` 过期标签单列 |
+| C(约束未实现) | **5** 行 | `textures` 1 + 四个实现类缺 `state` 4;带基准侧/实现侧锚点与实测错误码 |
+| D(big-image 残留) | **12** 行 | `MaterialManager` 10 项 + `cache.ts` legacy 概念路径 2 条记录;A/D 互引;`cache.ts` 标注「不走 `ITextureManager`、不导致 material 类型错误」 |
+| E(范围外仅报告) | **8** 行 | 5 处 `src/types/declaration/*` + `packages/` 零命中 + `.planning/graphs/*` 过期标签 + `client-base/package.json` 未声明 `@user/data-state` |
+| F(未从阅读确定) | **6** 条 | 覆盖 `getIfBigImage` 替代 / `textures` 承担者 / `state` 注入 / `cache.ts` 归属 / `setDefaultFrame` 耦合 / `graphs` 重建;均只登记不判定 |
+
+## 机器门禁输出摘要(六道门禁全部转绿)
+
+以等价脚本执行计划内联门禁(逻辑与计划内联脚本逐字一致;PowerShell 对嵌套引号转义有困难,故落为等价脚本,同 04-05 既有惯例):
+
+- **只读门禁**(`git status --porcelain -- packages packages-user src` 与 `## 方法` 记录逐条一致):`OK read-only baseline verbatim entries 0`(exit 0)
+- **类型门禁一致性**(重跑 `pnpm exec vue-tsc --noEmit` 断言错误总行数等于记录值):`OK tsc total unchanged 199 material 19`(exit 0)
+- **类别门禁**(A/C/D/E 行数下限 + 每行双锚点 + 关键词 + B 三个旧名 + F ≥5 + `## 处置`):`OK category gate: A=12 C=5 D=12 E=8 F=6`(exit 0)
+- **B 类 0 命中复验**:`OK zero stale material names in source (git grep status 1)`(exit 0)
+- **既有计划保护 + 计划范围门禁**:`04-05-PLAN.md` / `04-05-SUMMARY.md` porcelain 为空;phase 目录 `-PLAN.md` 仅 `04-01..04-06`;`04-06-SUMMARY.md` 已落盘(exit 0)
+- **CRLF 门禁**:`OK CRLF lines`(交付文档 `\n` 总数 = `\r\n` 总数,无孤立 LF)
+- **静态结构门禁(Task 1)**:11 个二级标题齐备、`只读起始基线` 已记录、`4e305e3` 已登记、基准关键词 15 个齐备、A 行双锚点齐备 —— 全绿
+
+## 人工代码复核(Task 3 步骤 3)
+
+抽查并逐条打开当日源码确认「命中处锚点指向的确实是该成员的引用」「基准侧锚点指向的确实是该接口的现行行」:
+
+1. A 类 `hero.ts` 三处:`hero.ts:239`(`getIfBigImage(nextTile?.identifier ?? block.tile)`)、`:364`(`getIfBigImage(faced?.face ?? image)`)、`:428`(`getIfBigImage(nextFace.identifier)`)—— 均为 `this.renderer.manager.getIfBigImage(...)` 调用,`manager` 类型为 `ITextureManager`(`renderer.ts:217`),基准 `ITextureGetter`(`types.ts:196-226`)确无该成员。**通过**。
+2. A 类 `vertex.ts:546`:`const tile = this.renderer.manager.getIfBigImage(num);` —— 确为引用点。**通过**。
+3. D 类 `manager.ts:552-593` 五个方法:`setBigImage`(`:552-573`)/ `isBigImage`(`:575-577`)/ `getBigImage`(`:579-581`)/ `getBigImageByAlias`(`:583-587`)/ `getIfBigImage`(`:589-593`)逐条确认实现形态与文档锚点一致,且均不在现行 `ITextureManager` 上。**通过**。
+4. D 类 `cache.ts:159-160`:`if (bigImage) { const image = core.material.images.images[bigImage]; ... }` —— 确为经 `core.*` legacy 全局读取,**不走 `ITextureManager`**、不产生 material 类型错误。**通过**。
+
+**复核结论:通过**,未发现偏差,无需修正。
+
+## Deviations from Plan
+
+### Auto-fixed / 执行取舍
+
+**1. [执行取舍 - 非计划偏差] 类型基线两条数字的「机器比对形式」补一行**
+
+- **Found during:** Task 1(`## 类型门禁实测基线` 落笔后跑门禁)
+- **Issue:** 计划 Task 1 步骤 (0b) 要求 bullet **逐字**写 `- **错误总行数**:`,但同一任务的 `` 门禁正则 `错误总行数:(\d+)` 要求 `错误总行数` 与 `:` **连续**(Markdown 粗体 `**` 会打断匹配)——两者对同一行的形态要求冲突。这不是生产代码问题,属文档排版与门禁正则的形态冲突。
+- **Fix:** **保留**逐字 bullet(`- **错误总行数**:199` / `- **material 相关错误行数**:19`),并**追加一行**「机器比对形式」`错误总行数:199;material 相关错误行数:19`,使门禁正则取得同日实测值。未改动门禁口径、未伪造数字。
+- **Files modified:** `.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md`
+- **Verification:** Task 1 / Task 3 类型基线门禁均转绿(`OK tsc total 199 material 19`)。
+- **Committed in:** 本计划提交(单次 `docs(04-06): ...`)
+
+**2. [环境 - 非执行者改动] PowerShell 对嵌套引号转义困难,门禁以内联等价脚本执行**
+
+- **Found during:** Task 1 / 3 执行计划内联 `node -e "..."` 门禁时
+- **Issue:** 计划内联脚本含大量嵌套引号 / 正则,直接在 PowerShell 中调用易被错误解析(04-05 已记录同类困难)。
+- **Fix:** 将计划内联脚本逻辑**逐字**落为等价 `.js` 脚本执行(逻辑一致,`node -e` 与脚本文件等价)。未改门禁口径。
+- **Files modified:** 无(仅执行方式;脚本置于工作区外临时目录,未纳入提交)
+- **Verification:** 六道门禁输出与计划 `` 语义一致。
+- **Committed in:** 无
+
+---
+
+**Total deviations:** 1 执行取舍(文档排版与门禁正则冲突,已双形态兼容)+ 1 环境事项(等价执行入口)。**无计划偏差、无范围蔓延。**
+
+## Issues Encountered
+
+- 无功能性问题。唯一需要处理的是类型基线两条数字的形态冲突(见上)。
+- **运行时不可得(诚实降级):** 本步为只读清点,**无任何运行时行为可观察**(不启动渲染、不改代码、不新增测试)。承载验证为「只读门禁 + 类型门禁一致性 + 静态内容门禁 + B 类 0 命中复验 + 既有计划保护/计划范围门禁 + CRLF 门禁 + 人工代码复核」。**未以任何无法运行的门禁冒充通过。**
+
+## 与规划日的差异说明
+
+- 类型错误总行数与 material 子集:当日 **199 / 19**,规划日 **199 / 19**,**无差异**。
+- 只读起始基线:当日 `packages` / `packages-user` / `src` 的 porcelain 为**空**,与规划日(「(无)」)一致,**无差异**。
+- 工作树 HEAD 为 `58a8e68`(规划日基准提交为 `4e305e3`);`4e305e3` 之后仅有 `.planning/` 计划簿记提交,**生产源码未再改动**,故所有行号锚点与规划日一致。已在清点文档元信息块与 `## 基准变更(4e305e3)` 如实记录。
+
+## Files Created/Modified
+
+- `.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md` — 唯一交付物:material 接口影响只读台账(11 节 / A–F 六类)
+- `.planning/phases/04-render-adaptation/04-06-SUMMARY.md` — 本文件(执行簿记)
+- 生产源码:**0 改动**(`packages/**`、`packages-user/**`、`src/**` 与用户并发改动一律未触碰)
+
+## Task Commits
+
+- 本计划按 PLAN 规定以**单次**提交落盘(文档 + 本 SUMMARY):`docs(04-06): material 接口影响范围清点(只读,D-30/D-31/D-32)`
+- `04-05-PLAN.md` / `04-05-SUMMARY.md` 未被改写 / 覆盖 / 改号
+- `STATE.md` / `ROADMAP.md` 由编排器在返回后统一处理,本 run 不写(与 04-05 惯例一致;PLAN 亦限定只写三份 `04-06` / 交付文档)
+
+## 未实现项清单(显式延后)
+
+- 第二步 **material 接口适应实施**(A / C / D 三类待处置项的修复)——**未规划**,待用户审阅本清点文件后另行规划(D-32)。
+- F 类 6 项待用户裁决(`getIfBigImage` 替代 / `textures` 承担者 / `state` 注入 / `cache.ts` 归属 / `setDefaultFrame` 耦合 / `graphs` 重建)。
+- `core.*` legacy 适配(D-12,第五阶段)、移动控制(D-19)、跟随者(D-22)、`HeroRendering` 语义(D-18)。
+- 移动端 / 桌面端双布局(REND-02)。
+
+## REND-01 / REND-02 状态
+
+REND-01 / REND-02 在本 run 后**仍保持 Pending**:本计划只清点 material 接口的影响面,既未实施 material 适应,也未实施移动端 / 桌面端双布局。两条 FLAGGED ASSUMPTION 的验收边界仍未提供,用户须在推进第二步与其余渲染适配前补齐。
+
+## Next Phase Readiness
+
+- material 接口的一手影响台账已就绪,用户可据此审阅 A / C / D 三类待处置项与 F 类待裁决项,并规划第二步 material 接口适应。
+- 阻塞项:第二步的规划取决于用户对 F 类 6 项的裁决(尤其 `state` 的注入方式与 `textures` 的承担者)。
+
+## Self-Check
+
+- 交付文档存在(`.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md`),11 个二级标题齐备,全文 CRLF。
+- 生产源码零改动(`git status --porcelain -- packages packages-user src` = 空,与起始基线逐条一致)。
+- 本 SUMMARY 与清点文档已随本计划提交落盘。
+
+---
+
+*Phase: 04-render-adaptation*
+*Completed: 2026-09-22*
diff --git a/.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md b/.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md
new file mode 100644
index 0000000..4f04b95
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md
@@ -0,0 +1,261 @@
+# material 接口影响范围清点(只读)
+
+**日期:** 2026-09-22
+**范围:** 被查对象 `packages-user` 全量(含新包 `packages-user/client-system`);`packages/` 不纳入(D-32,理应不受本改动影响);`src/` 命中仅报告、不修改
+**性质:** 只读清点(D-30 / D-31 / D-32 第一步),未修改任何生产代码
+**接口基准:** `packages-user/client-base/src/material/types.ts` 与 `packages-user/client-base/src/types.ts`(`4e305e3` 后),以及 `packages-user/data-state/src/types.ts`(`ICoreStateExtended` 定义源)
+**基准提交:** `4e305e3 refactor(type): material types.`(当日工作树 HEAD `58a8e68`;`4e305e3` 之后仅有 `.planning/` 计划簿记提交,生产源码未再改动)
+**下一步:** 第二步(material 接口适应实施)未规划,待用户审阅本文件后另行规划(D-32)
+
+## 背景
+
+本文件是 Phase 4 第六个增量(用户裁定 2026-09-21 · D-30 / D-31 / D-32 **第一步**)的交付物:对 `4e305e3` material 接口基准在 `packages-user` 全量的**只读影响清点**。
+
+- **D-30**:下一个任务 = 完成 material 相关的接口适应。用户已自行修改接口 `packages-user/client-base/src/types.ts`(提交 `4e305e3 refactor(type): material types.`)并改完受影响的一部分内容;**剩余未适应的实现与消费者**由本任务处理。
+- **D-31**:本次改动的根因:用户**重写了 Texture 的底层管理器,删除 `big-image` 概念**(旧样板概念,新引擎不再需要;旧兼容可**无痛丢弃**)。`4e305e3` 已从接口删除 `IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`,并把 `IMaterialManager`→`ITextureManager`、`IMaterialGetter`→`ITextureGetter`、`IMaterialAliasGetter`→`ITextureAliasGetter`,新增 `textures` 与 `ICoreStateExtended` 约束。**影响面可能很大。**
+- **D-32**:计划分**两步**。**第一步** = 收集影响范围(只读清点,产出本文件);**第二步** = 进行修改,待用户审阅第一步结果后再规划。边界:范围限 `packages-user`;`packages` 不纳入(理应不受本改动影响);以 `packages-user/client-base/src/types.ts`(及 `material/types.ts`)为基准;**若发现其余 big-image 残留,必须在清点文档中报告**;`src/` 或 `packages/` 内若命中,仅报告、不修改。
+
+本步**只清点、不修改、不规划第二步**。
+
+## 方法
+
+**枚举面(两侧):**
+
+- **(a)基准面**:`packages-user/client-base/src/material/types.ts`(`ITextureManager` / `ITextureGetter` / `ITextureAliasGetter` / `IAutotileProcessor` / `IAssetBuilder` / `ITrackedAssetData` / `IMaterialData` / `IMaterialFramedData` 等现行成员)+ `packages-user/client-base/src/types.ts`(`IClientBase.materials`)+ `packages-user/data-state/src/types.ts`(`ICoreStateExtended` 定义源)。
+- **(b)命中面**:`packages-user` 全量(`client-base` / `client-modules` / `client-system` / `entry-*` / `legacy-plugin-*` / `data-fallback`),另对 `packages/` 与 `src/` 做**对照扫描**(只登记、不修改)。
+
+**判定口径(六类固定,一条记录只归一类,跨类拆条并互相引用):**
+
+| 类别 | 含义 | 归属规则 |
+|---|---|---|
+| A | 已移除**接口成员**的**引用点**(消费者侧) | `client-modules` 对 `manager.*` 的调用点 + `manager.ts:20` 的 `IBigImageReturn` import |
+| B | 改名后的旧接口名剩余引用 | 源码面命中数;本次应为 0,须给机器证据 |
+| C | 新增 / 收紧约束在实现类上未实现 | 基准侧 = 接口声明行;命中侧 = 实现类声明行与构造点 |
+| D | **不在接口成员层面**的 big-image 实现 / 概念残留 | `client-base` 内已不属接口的实现成员 + 走 legacy 全局的 `cache.ts` 概念路径 |
+| E | 范围外(`src/`、`packages/`、`.planning/graphs/*`、依赖声明) | 只报告,不修改、不建议 |
+| F | 未能从阅读确定 | 只登记,不得写成错配 / 缺失 / 残留 |
+
+**A 与 D 的分列规则(写死)**:A 记「消费者调用点」(`client-modules` 对 `manager.*` 的调用 + `manager.ts:20` 的类型 import),D 记「`client-base` 内已不属接口的实现残留」与「走 legacy 全局的 `cache.ts` 概念路径」。同一成员两侧都有时两条都记并互相引用。
+
+**证据纪律:** 每行含 `接口/成员名 + 命中处 file:line + 基准侧锚点 file:line + 问题描述 + 影响 + 置信`,**每行至少两个 `file:line` 锚点**;静态阅读不能定论者一律进 F 类,**不得写成错配 / 缺失 / 残留**。
+
+**只读起始基线(packages / packages-user / src)**:Task 1 起始执行 `git status --porcelain -- packages packages-user src`,输出为空,逐行原样记录如下:
+
+(无)
+
+当日工作树内唯一的未提交改动为 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(不在该路径集合内),属用户既有改动,本 run 不回滚 / 不暂存 / 不提交 / 不修改。
+
+**类型门禁命令与口径:** `pnpm exec vue-tsc --noEmit`;「错误总行数」= 输出中正则 `error TS\d+` 的匹配数(与 Task 3 门禁同一正则,保证可逐字比对);「material 相关错误行数」为**规划日人工归类**,执行日按当日实测重新归类并逐行列出,**不由任何门禁断言**、由人工复核保证。
+
+**只读子代理:** 重型整文件读取可按 D-09 委派**只读**子代理(子代理只回传事实表「成员名 + 所属 `file:line` + 形态」,**不得写文件、不得贴大段源码**),最终由主执行者按 `file:line` 切片复核。
+
+## 基准变更(4e305e3)
+
+依据:`git show 4e305e3 -- packages-user/client-base/src/types.ts packages-user/client-base/src/material/types.ts`(当日与工作树逐条比对一致)。该提交共触及 19 个文件(`git show --stat 4e305e3`)。以下三类差异中,每条附**现行基准侧锚点**。
+
+**① 移除 7 个符号**(`4e305e3` 已删除;现行 `material/types.ts` 的成员集合不含):
+
+| 已移除符号 | 原所属 | 现行基准侧锚点(该符号原属接口的现行行) |
+|---|---|---|
+| `IBigImageReturn` | 原定义接口(`material/types.ts`,已整体删除) | `packages-user/client-base/src/material/types.ts:260`(`ITextureManager`,现无该类型;其引用侧见 A 类 `#04-06-A-09` 与 D 类) |
+| `isBigImage` | 原 `IMaterialGetter`(现 `ITextureGetter`) | `packages-user/client-base/src/material/types.ts:196`(`ITextureGetter` 现行成员不含) |
+| `getBigImage` | 原 `IMaterialGetter`(现 `ITextureGetter`) | `packages-user/client-base/src/material/types.ts:196` |
+| `getIfBigImage` | 原 `IMaterialGetter`(现 `ITextureGetter`) | `packages-user/client-base/src/material/types.ts:196` |
+| `getBigImageByAlias` | 原 `IMaterialAliasGetter`(现 `ITextureAliasGetter`) | `packages-user/client-base/src/material/types.ts:228` |
+| `setBigImage` | 原 `IMaterialManager`(现 `ITextureManager`) | `packages-user/client-base/src/material/types.ts:260` |
+| `bigImageStore` | 原 `IMaterialManager`(现 `ITextureManager`) | `packages-user/client-base/src/material/types.ts:260` |
+
+**② 改名 3 个接口**(`4e305e3` 同步改动消费者):
+
+| 旧名 | 新名 | 现行基准侧锚点 | 该提交内同步改名的消费者文件(`git show --stat 4e305e3`) |
+|---|---|---|---|
+| `IMaterialManager` | `ITextureManager` | `packages-user/client-base/src/material/types.ts:260` | `packages-user/client-base/src/{load/loader.ts, material/autotile.ts, material/builder.ts, material/manager.ts, types.ts}`;`packages-user/client-modules/src/{client.ts, fallback/load.ts, render/map/moving.ts, render/map/renderer.ts, render/map/types.ts, types.ts}` |
+| `IMaterialGetter` | `ITextureGetter` | `packages-user/client-base/src/material/types.ts:196` | 同上 |
+| `IMaterialAliasGetter` | `ITextureAliasGetter` | `packages-user/client-base/src/material/types.ts:228` | 同上 |
+
+**③ 新增 / 收紧约束:**
+
+- `ITextureManager` 新增 `readonly textures: ITextureStore`,现行基准侧锚点 `packages-user/client-base/src/material/types.ts:262-263`,jsDoc 原文 `/** 通过加载获取的所有纹理贴图存储,包括图块、普通图片等 */`。未实现于 `MaterialManager`(见 C 类 `#04-06-C-01`)。
+- 四处新增 `extends ICoreStateExtended`:`ITextureManager`(`packages-user/client-base/src/material/types.ts:260`,`extends ITextureGetter, ITextureAliasGetter, ICoreStateExtended`,`extends` 行在 `:261`);`IAutotileProcessor`(`packages-user/client-base/src/material/types.ts:104`);`IAssetBuilder`(`packages-user/client-base/src/material/types.ts:430`);`ITrackedAssetData`(`packages-user/client-base/src/material/types.ts:462-463`)。
+- `ICoreStateExtended` 的**定义源**:`packages-user/data-state/src/types.ts:45-49`(`readonly state: ICoreState;`,`4e305e3` 首次加入该文件)。
+
+**同提交内的相邻变更(登记,不作本步裁定):** `packages-user/client-base/src/types.ts` 新增 `rafExcitation: IExcitation`(`:24`)与 `excitationDivider: IExcitationDivider`(`:26`);新增 `packages-user/client-system` 包(`package.json` / `src/index.ts` / `src/types.ts`,含 `IClientSystem extends IClientBase`);`packages-user/client-modules/package.json` 与 `pnpm-lock.yaml` 有同步改动。
+
+## 类型门禁实测基线
+
+命令:`pnpm exec vue-tsc --noEmit`(整仓非 0 退出属预期)。统计口径:正则 `error TS\d+` 的匹配数。
+
+- **错误总行数**:199
+- **material 相关错误行数**:19
+
+机器比对形式(与本节门禁的同一正则同形,便于逐字比对):错误总行数:199;material 相关错误行数:19。
+
+当日实测(2026-09-22,HEAD `58a8e68`)与规划日(2026-09-21,HEAD `4e305e3`)**完全一致**:199 / 19。
+
+**material 相关错误逐行列出(19 条 = `client-base` 7 条 + `client-modules` 12 条):**
+
+`packages-user/client-base`(7 条):
+
+1. `packages-user/client-base/src/material/autotile.ts:42` `error TS2420`:`Class 'AutotileProcessor' incorrectly implements interface 'IAutotileProcessor'.` / `Property 'state' is missing`
+2. `packages-user/client-base/src/material/builder.ts:12` `error TS2420`:`Class 'AssetBuilder' incorrectly implements interface 'IAssetBuilder'.` / `Property 'state' is missing`
+3. `packages-user/client-base/src/material/builder.ts:91` `error TS2741`:`Property 'state' is missing in type 'TrackedAssetData' but required in type 'ITrackedAssetData'.`
+4. `packages-user/client-base/src/material/builder.ts:99` `error TS2420`:`Class 'TrackedAssetData' incorrectly implements interface 'ITrackedAssetData'.` / `Property 'state' is missing`
+5. `packages-user/client-base/src/material/manager.ts:20` `error TS2305`:`Module '"./types"' has no exported member 'IBigImageReturn'.`
+6. `packages-user/client-base/src/material/manager.ts:38` `error TS2420`:`Class 'MaterialManager' incorrectly implements interface 'ITextureManager'.` / `Type 'MaterialManager' is missing the following properties from type 'ITextureManager': textures, state`
+7. `packages-user/client-base/src/material/manager.ts:86` `error TS2741`:`Property 'state' is missing in type 'AssetBuilder' but required in type 'IAssetBuilder'.`
+
+`packages-user/client-modules`(12 条):
+
+8. `packages-user/client-modules/src/client.ts:84` `error TS2739`:`Type 'MaterialManager' is missing the following properties from type 'ITextureManager': textures, state`
+9. `packages-user/client-modules/src/client.ts:85` `error TS2741`:`Property 'state' is missing in type 'AutotileProcessor' but required in type 'IAutotileProcessor'.`
+10. `packages-user/client-modules/src/render/map/extension/door.ts:36` `error TS2339`:`Property 'getIfBigImage' does not exist on type 'ITextureManager'.`
+11. `packages-user/client-modules/src/render/map/extension/door.ts:46` `error TS2339`:`Property 'getIfBigImage' does not exist on type 'ITextureManager'.`
+12. `packages-user/client-modules/src/render/map/extension/hero.ts:239` `error TS2339`:`Property 'getIfBigImage' does not exist on type 'ITextureManager'.`
+13. `packages-user/client-modules/src/render/map/extension/hero.ts:364` `error TS2339`:`Property 'getIfBigImage' does not exist on type 'ITextureManager'.`
+14. `packages-user/client-modules/src/render/map/extension/hero.ts:428` `error TS2339`:`Property 'getIfBigImage' does not exist on type 'ITextureManager'.`
+15. `packages-user/client-modules/src/render/map/renderer.ts:234` `error TS2741`:`Property 'state' is missing in type 'AutotileProcessor' but required in type 'IAutotileProcessor'.`
+16. `packages-user/client-modules/src/render/map/renderer.ts:652` `error TS2551`:`Property 'bigImageStore' does not exist on type 'ITextureManager'. Did you mean 'imageStore'?`
+17. `packages-user/client-modules/src/render/map/renderer.ts:653` `error TS2551`:`Property 'getBigImage' does not exist on type 'ITextureManager'. Did you mean 'getImage'?`
+18. `packages-user/client-modules/src/render/map/renderer.ts:1250` `error TS2339`:`Property 'getIfBigImage' does not exist on type 'ITextureManager'.`
+19. `packages-user/client-modules/src/render/map/vertex.ts:546` `error TS2339`:`Property 'getIfBigImage' does not exist on type 'ITextureManager'.`
+
+**单列:与本改动无关的既有错误(180 条),不得算作本次 material 改动的后果。**
+
+- `client-base` / `client-modules` 中属**加载系统重构**(提交 `cc434a6`)的 16 条:`packages-user/client-base/src/load/loader.ts` 6 条(`LoadAudioProcessor` / `LoadFontProcessor` / `LoadImageProcessor` / `LoadTextProcessor` / `LoadZipProcessor` / `IMotaDataLoader` 未导出,TS2305)、`packages-user/client-modules/src/core.ts:6` 1 条(TS2554)、`packages-user/client-modules/src/render/ui/load.tsx` 9 条(`IMotaDataLoader` 成员不存在,TS2339)。
+- `data-base` / `data-state` / `data-system` 的测试与寻路相关既有错误(`*.test.ts` / `*.perf.ts` 的属性构造、`DirectionMapper` / `IFaceHandler` 导出、`graph` 可能为 `null`、`createCoreState` 未导出等):合计 164 条,与 material 接口无关。
+
+**口径说明:** 「material / 非 material」的划分为**规划日的人工归类**(material 19 = `client-base` 7 + `client-modules` 12;非 material 180),须用户在审阅时确认。若用户判定其中某条应归 material,执行者按裁定重新归类并同步两个数字与 `## 处置` 的口径说明,不得改动其它事实。此划分**不由任何机器门禁断言**;门禁只覆盖**错误总行数**(Task 3 重跑等于本记录值)。
+
+## A 已移除符号的剩余引用
+
+列序固定:`ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信`。本节登记**消费者侧**对 `4e305e3` 已移除接口成员的引用点(`client-modules` 对 `manager.*` 的调用 + `client-base/src/material/manager.ts:20` 的类型 import);`client-base` 内已不属接口的实现残留见 D 类,两侧对同一成员互相引用。**行内不含任何修复建议或替代写法**(替代是否成立属 F 类)。
+
+| ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信 |
+|---|---|---|---|---|---|---|
+| #04-06-A-01 | `getIfBigImage` | packages-user/client-modules/src/render/map/extension/door.ts:36,46 | packages-user/client-base/src/material/types.ts:196-226 | `openDoor`(`door.ts:36`)/ `closeDoor`(`door.ts:46`)经 `this.renderer.manager.getIfBigImage(num)` 取值并读取 `data.frames` 驱动大图开门帧序;该成员已不在接口上 | 编译报 TS2339(`door.ts:36,46`);若类型断言被绕过则运行时不可达 | 高 |
+| #04-06-A-02 | `getIfBigImage` | packages-user/client-modules/src/render/map/extension/hero.ts:239 | packages-user/client-base/src/material/types.ts:196-226 | 勇士移动取下一图块贴图:`this.renderer.manager.getIfBigImage(nextTile?.identifier ?? block.tile)`;该成员已不在接口上(D-18:不涉 `HeroRendering` 语义裁定) | 编译报 TS2339(`hero.ts:239`) | 高 |
+| #04-06-A-03 | `getIfBigImage` | packages-user/client-modules/src/render/map/extension/hero.ts:364 | packages-user/client-base/src/material/types.ts:196-226 | 勇士贴图朝向取值:`this.renderer.manager.getIfBigImage(faced?.face ?? image)`;该成员已不在接口上 | 编译报 TS2339(`hero.ts:364`) | 高 |
+| #04-06-A-04 | `getIfBigImage` | packages-user/client-modules/src/render/map/extension/hero.ts:428 | packages-user/client-base/src/material/types.ts:196-226 | 勇士移动贴图取值:`this.renderer.manager.getIfBigImage(nextFace.identifier)`;该成员已不在接口上 | 编译报 TS2339(`hero.ts:428`) | 高 |
+| #04-06-A-05 | `getIfBigImage` | packages-user/client-modules/src/render/map/renderer.ts:1250 | packages-user/client-base/src/material/types.ts:196-226 | 背景图块取值:`const tex = this.manager.getIfBigImage(tile)`;该成员已不在接口上 | 编译报 TS2339(`renderer.ts:1250`) | 高 |
+| #04-06-A-06 | `getIfBigImage` | packages-user/client-modules/src/render/map/vertex.ts:546 | packages-user/client-base/src/material/types.ts:196-226 | 顶点数组更新取值:`const tile = this.renderer.manager.getIfBigImage(num)`;该成员已不在接口上 | 编译报 TS2339(`vertex.ts:546`) | 高 |
+| #04-06-A-07 | `bigImageStore` | packages-user/client-modules/src/render/map/renderer.ts:652 | packages-user/client-base/src/material/types.ts:260-271 | `getOffsetPool` 遍历 `this.manager.bigImageStore.keys()` 收集大图偏移;该成员已不在接口上。互引:与 D 类 `#04-06-D-01`(`manager.ts:43` 的实现成员)两侧对同一成员 | 编译报 TS2551(`renderer.ts:652`) | 高 |
+| #04-06-A-08 | `getBigImage` | packages-user/client-modules/src/render/map/renderer.ts:653 | packages-user/client-base/src/material/types.ts:196-226 | `getOffsetPool` 内 `this.manager.getBigImage(identifier)` 取帧数计算偏移;该成员已不在接口上。互引:与 D 类 `#04-06-D-07`(`manager.ts:579-581`) | 编译报 TS2551(`renderer.ts:653`) | 高 |
+| #04-06-A-09 | `IBigImageReturn`(类型 import) | packages-user/client-base/src/material/manager.ts:20 | packages-user/client-base/src/material/types.ts:260 | `material/manager.ts:20` 从 `./types` import `IBigImageReturn`(该符号已不在基准导出中);同文件 `:556,:568` 的类型标注计入 D 类 `#04-06-D-10`。互引:与 D 类 `#04-06-D-10` | 编译报 TS2305(`manager.ts:20`) | 高 |
+| #04-06-A-10 | `setBigImage` | 调用点 0 处(源码面唯一命中为 D 类定义行 packages-user/client-base/src/material/manager.ts:552) | packages-user/client-base/src/material/types.ts:260 | 核对命令 `git grep -n -E "setBigImage\|isBigImage\|getBigImageByAlias" -- packages packages-user src` 仅命中 `manager.ts` 自身定义行;消费者侧**调用点 0 处**(不遗漏、不臆造) | 无消费者调用导致的类型错误 | 高 |
+| #04-06-A-11 | `isBigImage` | 调用点 0 处(源码面唯一命中为 D 类定义行 packages-user/client-base/src/material/manager.ts:575) | packages-user/client-base/src/material/types.ts:196 | 同上核对命令;消费者侧**调用点 0 处** | 无消费者调用导致的类型错误 | 高 |
+| #04-06-A-12 | `getBigImageByAlias` | 调用点 0 处(源码面唯一命中为 D 类定义行 packages-user/client-base/src/material/manager.ts:583) | packages-user/client-base/src/material/types.ts:228 | 同上核对命令;消费者侧**调用点 0 处** | 无消费者调用导致的类型错误 | 高 |
+
+**A 类小结:** 7 个已移除符号全部覆盖;`getIfBigImage` 计 **7 处**调用点(`door.ts:36` / `door.ts:46` / `hero.ts:239` / `hero.ts:364` / `hero.ts:428` / `renderer.ts:1250` / `vertex.ts:546`);`bigImageStore` 1 处(`renderer.ts:652`);`getBigImage` 1 处(`renderer.ts:653`);`IBigImageReturn` 1 处 import(`manager.ts:20`);`setBigImage` / `isBigImage` / `getBigImageByAlias` 消费者侧 0 处。
+
+## B 改名的剩余引用
+
+三个旧接口名在**源码面**(`packages`、`packages-user`、`src`,排除 `node_modules`)的当日命中数:
+
+核对命令(原样执行):
+
+```
+git grep -n -E "IMaterialManager|IMaterialGetter|IMaterialAliasGetter" -- packages packages-user src
+```
+
+原样输出:**(无输出)**,退出码 1(无匹配)。
+
+- `IMaterialManager`:源码面命中 **0** 处。
+- `IMaterialGetter`:源码面命中 **0** 处。
+- `IMaterialAliasGetter`:源码面命中 **0** 处。
+
+**单列:`.planning/graphs/*` 的过期工装态标签**(**不计入源码命中、不作为基准**,归 E 类登记):`.planning/graphs/graph.json` 仍含 `IMaterialGetter`(`:6604` / `:6612` / `:48305` 等)、`IMaterialManager`(`:13359`)、`IBigImageReturn`(`:15400`)、`.getBigImage()`(`:48361` / `:76741`)、`.getBigImageByAlias()`(`:76753` / `:81025`)等标签;`.planning/graphs/.last-build-snapshot.json` 同样残留 `IMaterialManager` / `IMaterialGetter` / `IBigImageReturn`。该快照时间戳为 **2026-09-07T02:50:52.708Z**,早于 `4e305e3` 的 2026-09-21,属过期工装态。
+
+- **结论**:三个旧接口名在源码面命中 0 处(仅工装态过期标签存在,见 E 类)。
+
+## C 新增与收紧约束的未实现
+
+列序固定:`ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信`。每条含**基准侧锚点 + 实现侧锚点 + 当日实测错误码与错误原文摘要**。`state` 的语义来源与注入方式**不在本步裁定**(进 F 类)。
+
+| ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信 |
+|---|---|---|---|---|---|---|
+| #04-06-C-01 | `ITextureManager.textures` 未实现 | packages-user/client-base/src/material/manager.ts:38-43 | packages-user/client-base/src/material/types.ts:262-263 | `MaterialManager implements ITextureManager`(`manager.ts:38`),成员仅 `tileStore`(`:39`)/ `tilesetStore`(`:40`)/ `imageStore`(`:41`)/ `assetStore`(`:42`)/ `bigImageStore`(`:43`),**无 `textures`**;基准新增 `readonly textures: ITextureStore`(`types.ts:262-263`) | 实测 `client-modules/src/client.ts:84` 报 `error TS2739`:`Type 'MaterialManager' is missing the following properties from type 'ITextureManager': textures, state` | 高 |
+| #04-06-C-02 | `ICoreStateExtended`(经 `ITextureManager`)未实现 | packages-user/client-base/src/material/manager.ts:38 | packages-user/client-base/src/material/types.ts:260;定义源 packages-user/data-state/src/types.ts:45-49 | `ITextureManager extends ITextureGetter, ITextureAliasGetter, ICoreStateExtended`(`types.ts:260-261`),实现类 `MaterialManager`(`manager.ts:38`)**无 `state` 成员**;`ICoreStateExtended` 定义源 `data-state/src/types.ts:45-49`(`readonly state: ICoreState`) | 实测 `manager.ts:38` 报 `error TS2420`:`Class 'MaterialManager' incorrectly implements interface 'ITextureManager'.` / `missing ... 'textures', 'state'` | 高 |
+| #04-06-C-03 | `ICoreStateExtended`(经 `IAutotileProcessor`)未实现 | packages-user/client-base/src/material/autotile.ts:42 | packages-user/client-base/src/material/types.ts:104;定义源 packages-user/data-state/src/types.ts:45-49 | `IAutotileProcessor extends ICoreStateExtended`(`types.ts:104`),实现类 `AutotileProcessor`(`autotile.ts:42`,构造器仅参数 `manager`)**无 `state` 成员** | 实测 `autotile.ts:42` 报 `error TS2420`(`Property 'state' is missing`);构造点 `client-modules/src/client.ts:85` 与 `render/map/renderer.ts:234` 报 `error TS2741`:`Property 'state' is missing in type 'AutotileProcessor' but required in type 'IAutotileProcessor'.` | 高 |
+| #04-06-C-04 | `ICoreStateExtended`(经 `IAssetBuilder`)未实现 | packages-user/client-base/src/material/builder.ts:12 | packages-user/client-base/src/material/types.ts:430;定义源 packages-user/data-state/src/types.ts:45-49 | `IAssetBuilder extends ICoreStateExtended`(`types.ts:430`),实现类 `AssetBuilder`(`builder.ts:12`)**无 `state` 成员** | 实测 `builder.ts:12` 报 `error TS2420`(`Property 'state' is missing`);构造点 `material/manager.ts:86`(`new AssetBuilder(this)`)与 `builder.ts:91` 报 `error TS2741` | 高 |
+| #04-06-C-05 | `ICoreStateExtended`(经 `ITrackedAssetData`)未实现 | packages-user/client-base/src/material/builder.ts:99 | packages-user/client-base/src/material/types.ts:462-463;定义源 packages-user/data-state/src/types.ts:45-49 | `ITrackedAssetData extends IDirtyTracker>, ICoreStateExtended`(`types.ts:462-463`),实现类 `TrackedAssetData`(`builder.ts:99`)**无 `state` 成员** | 实测 `builder.ts:99` 报 `error TS2420`(`Property 'state' is missing`);构造点 `builder.ts:91`(TS2741) | 高 |
+
+**构造 / 接线面登记(不裁定 `state` 的注入方式):**
+
+| 构造 / 接线点 | 锚点 |
+|---|---|
+| `new MaterialManager()`(无参) | packages-user/client-modules/src/client.ts:84 |
+| `new AutotileProcessor(this.materials)` | packages-user/client-modules/src/client.ts:85 |
+| `new AutotileProcessor(manager)`(`MapRenderer` 构造内) | packages-user/client-modules/src/render/map/renderer.ts:234 |
+| `new AssetBuilder(this)`(`MaterialManager` 构造内) | packages-user/client-base/src/material/manager.ts:86 |
+| `new TrackedAssetData(materials, this)`(`AssetBuilder` 构造内) | packages-user/client-base/src/material/builder.ts:28 |
+
+背景事实(仅作背景,不裁定):`ClientCore extends CoreState`(packages-user/client-modules/src/client.ts:45);`MaterialManager` 在 `client-modules/src/client.ts:84` 由 `ClientCore` 构造。
+
+## D big-image 残留(实现与消费者)
+
+列序固定:`ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信`。本节登记**不在接口成员层面**的 big-image 实现 / 概念残留:`client-base` 内已不属接口的实现成员(`manager.ts`)与走 legacy 全局的 `cache.ts` 概念路径。每条给「是否仍属某个接口成员」(**否**)与「是否导致类型错误」的判定。**A 类与 D 类对同一成员的两条记录互相引用。**
+
+| ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信 |
+|---|---|---|---|---|---|---|
+| #04-06-D-01 | `bigImageStore`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:43 | packages-user/client-base/src/material/types.ts:260 | `MaterialManager.bigImageStore: ITextureStore = new TextureStore()`;不在现行 `ITextureManager` 上(**是否仍属接口成员:否**)。互引:A 类 `#04-06-A-07`(`renderer.ts:652` 的消费点) | 不直接导致类型错误(`implements` 只要求接口成员;多余成员不报错) | 高 |
+| #04-06-D-02 | `bigImageData`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:56 | packages-user/client-base/src/material/types.ts:260 | `MaterialManager.bigImageData: Map`(大怪物数据);**是否仍属接口成员:否** | 不直接导致类型错误 | 高 |
+| #04-06-D-03 | `bigImageId`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:77 | packages-user/client-base/src/material/types.ts:260 | `private bigImageId: number = 0`(大怪物贴图标识符);**是否仍属接口成员:否** | 不直接导致类型错误 | 高 |
+| #04-06-D-04 | `setDefaultFrame` 对 `bigImageData` 的耦合 | packages-user/client-base/src/material/manager.ts:233-235 | packages-user/client-base/src/material/types.ts:340 | `setDefaultFrame(identifier, defaultFrame)` 除写入 `defaultFrames` 外,还 `this.bigImageData.get(identifier)` 并改其 `defaultFrame`(`:233-235`);`setDefaultFrame` **是**现行接口成员(`types.ts:340`),但其内部对 big-image 数据结构的耦合属残留。**是否仍属接口成员:接口方法在、耦合逻辑不在** | 不直接导致类型错误 | 高 |
+| #04-06-D-05 | `setBigImage`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:552-573 | packages-user/client-base/src/material/types.ts:260 | 实现 `setBigImage(identifier, image, frames): IBigImageReturn`(`:552`),内部 `this.bigImageId++`(`:557`)/ `this.bigImageStore.addTexture`(`:558`)/ `this.bigImageData.set`(`:567`);**是否仍属接口成员:否**。互引:A 类 `#04-06-A-10`(消费者调用点 0 处) | 不直接导致类型错误;其返回类型 `IBigImageReturn` 未导出见 D-10 | 高 |
+| #04-06-D-06 | `isBigImage`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:575-577 | packages-user/client-base/src/material/types.ts:196 | 实现 `isBigImage(identifier): boolean` → `this.bigImageData.has(identifier)`;**是否仍属接口成员:否**。互引:A 类 `#04-06-A-11`(调用点 0 处) | 不直接导致类型错误 | 高 |
+| #04-06-D-07 | `getBigImage`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:579-581 | packages-user/client-base/src/material/types.ts:196 | 实现 `getBigImage(identifier): Readonly \| null` → `this.bigImageData.get(identifier) ?? null`;**是否仍属接口成员:否**。互引:A 类 `#04-06-A-08`(`renderer.ts:653`) | 不直接导致类型错误 | 高 |
+| #04-06-D-08 | `getBigImageByAlias`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:583-587 | packages-user/client-base/src/material/types.ts:228 | 实现 `getBigImageByAlias(alias)` → 经 `idNumMap` 取 identifier 后 `this.bigImageData.get`;**是否仍属接口成员:否**。互引:A 类 `#04-06-A-12`(调用点 0 处) | 不直接导致类型错误 | 高 |
+| #04-06-D-09 | `getIfBigImage`(实现,非接口成员) | packages-user/client-base/src/material/manager.ts:589-593 | packages-user/client-base/src/material/types.ts:196 | 实现 `getIfBigImage(identifier)` → 先查 `this.bigImageData`,无则 `this.getTile(identifier)`;**是否仍属接口成员:否**。互引:A 类 `#04-06-A-01`..`#04-06-A-06`(7 处消费者调用点) | 不直接导致类型错误(实现类多余成员) | 高 |
+| #04-06-D-10 | `IBigImageReturn` 类型引用 | packages-user/client-base/src/material/manager.ts:20 | packages-user/client-base/src/material/types.ts:260 | import `IBigImageReturn`(`:20`)与类型标注 `): IBigImageReturn {`(`:556`)、`const data: IBigImageReturn = {`(`:568`);该类型已不在基准导出中。互引:A 类 `#04-06-A-09`(import 侧) | **导致类型错误**:`manager.ts:20` 报 `error TS2305`(`Module '"./types"' has no exported member 'IBigImageReturn'.`) | 高 |
+| #04-06-D-11 | `RenderableDataBase.bigImage`(legacy 概念路径) | packages-user/client-modules/src/render/elements/cache.ts:36 | src/types/declaration/map.d.ts:354 | `RenderableDataBase` 接口声明 `bigImage: boolean`(`cache.ts:36`),并在 `:48`(`AutotileRenderable`)/ `:144` / `:190` / `:215` / `:243` / `:276` 赋值。**不走 `ITextureManager`**(走 legacy 全局 `core.material.*`);**是否仍属接口成员:否** | **不导致 material 类型错误**(走 `core.*` legacy 全局,`interface` 定义在本文件内) | 高 |
+| #04-06-D-12 | `cache.ts` legacy bigImage 读取(`enemys[id].bigImage` 与 `core.material.images.images[bigImage]`) | packages-user/client-modules/src/render/elements/cache.ts:153,157,159-160 | src/types/declaration/map.d.ts:354 | `let { faceIds, bigImage } = data;`(`:153`)与 `({ bigImage, faceIds } = enemys[id as EnemyIds]);`(`:157`)取自 legacy `maps_*` / `enemys_*` 全局;`if (bigImage) { const image = core.material.images.images[bigImage]; ... }`(`:159-160`)经 `core.material.*` 读旧全局。**不走 `ITextureManager`**;**是否仍属接口成员:否** | **不导致 material 类型错误**(走 `core.*` legacy 全局) | 高 |
+
+**D 类小结:** `MaterialManager` 残留 **10** 项(`#04-06-D-01`..`#04-06-D-10`)+ `cache.ts` legacy 概念路径 2 条记录(`#04-06-D-11` / `#04-06-D-12`,同一 `cache.ts` 文件)。`cache.ts` 明确标注「不走 `ITextureManager`、不导致 material 类型错误」。全部 `bigImage*` 源码命中已由 `git grep -n -i "bigimage" -- packages-user` 核对无遗漏(命中集仅 `manager.ts` / `cache.ts` / `door.ts` / `hero.ts` / `renderer.ts` / `vertex.ts`)。
+
+## E 范围外命中(仅报告)
+
+**本节仅报告:不在本阶段修改,也不提出修改建议。**
+
+列序固定:`ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信`。本节只登记事实与核对命令。
+
+| ID | 接口/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信 |
+|---|---|---|---|---|---|---|
+| #04-06-E-01 | `bigImage` 数据字段 | src/types/declaration/enemy.d.ts:65 | packages-user/client-base/src/material/types.ts:196 | `enemy.d.ts:65` 声明 `bigImage?: ImageIds;`(`src/` 内旧数据字段声明),与基准 `ITextureGetter` 已无 big-image 成员形成对照 | 仅报告;`src/` 不在本阶段范围(D-32) | 高 |
+| #04-06-E-02 | `bigImage` 数据字段 | src/types/declaration/core.d.ts:1286 | packages-user/client-base/src/material/types.ts:196 | `core.d.ts:1286` 声明 `bigImage?: ImageIds;`(同形旧数据字段声明) | 仅报告;`src/` 不在本阶段范围 | 高 |
+| #04-06-E-03 | `bigImage` 数据字段 | src/types/declaration/map.d.ts:354 | packages-user/client-base/src/material/types.ts:196 | `map.d.ts:354` 声明 `bigImage: HTMLImageElement;`(旧数据字段声明) | 仅报告;`src/` 不在本阶段范围 | 高 |
+| #04-06-E-04 | `_getBigImageInfo` | src/types/declaration/map.d.ts:1443 | packages-user/client-base/src/material/types.ts:196 | `map.d.ts:1443` 声明 `_getBigImageInfo(bigImage: HTMLImageElement, face: Dir, posX: number): any;`(legacy API 声明) | 仅报告;`src/` 不在本阶段范围 | 高 |
+| #04-06-E-05 | `bigImage` 数据字段 | src/types/declaration/status.d.ts:391 | packages-user/client-base/src/material/types.ts:196 | `status.d.ts:391` 声明 `bigImage: HTMLImageElement;`(在 `BigImageBoxAnimate`,`status.d.ts:387`) | 仅报告;`src/` 不在本阶段范围 | 高 |
+| #04-06-E-06 | `bigImage`(对照扫描零命中) | packages/(零命中;对照扫描无任何 file:line 命中) packages-user/client-base/src/types.ts:18 | packages-user/client-base/src/material/types.ts:196 | 核对命令 `git grep -n -i bigImage -- packages` 输出为**空**(退出码 1,无匹配);与 D-32「`packages` 不纳入、理应不受影响」一致 | 仅报告;`packages/` 零命中,无需处置 | 高 |
+| #04-06-E-07 | 过期工装态标签 | .planning/graphs/graph.json(`IMaterialManager` 等过期标签) packages-user/client-base/src/material/types.ts:260 | packages-user/client-base/src/types.ts:18 | `.planning/graphs/graph.json` 与 `.planning/graphs/.last-build-snapshot.json` 仍标注 `IMaterialManager` / `IMaterialGetter` / `IBigImageReturn` / `.getBigImage()` 等已被删除的符号;快照时间戳 `2026-09-07T02:50:52.708Z`,早于 `4e305e3`(2026-09-21) | 仅报告;不得作为基准或命中依据(是否重建由用户决定,见 F 类) | 高 |
+| #04-06-E-08 | `@user/data-state` 依赖声明 | packages-user/client-base/package.json:3-8 packages-user/client-base/src/material/types.ts:9 | packages-user/client-base/src/types.ts:3 | `client-base/package.json` 的 `dependencies` 仅声明 `@motajs/audio` / `@motajs/render` / `@motajs/client-base` / `@user/data-base`,**未声明** `@user/data-state`;而 `material/types.ts:9`(`import { ICoreStateExtended } from '@user/data-state';`)与 `client-base/src/types.ts:3`(`import { ICoreState } from '@user/data-state';`)已 import 该包 | 仅报告事实;是否算问题**交由用户判断**(本步不判定) | 高 |
+
+**E 类小结:** `src/types/declaration/*` 命中 **5** 处(`enemy.d.ts:65` / `core.d.ts:1286` / `map.d.ts:354` / `map.d.ts:1443` / `status.d.ts:391`);`packages/` **0** 命中;`.planning/graphs/*` 过期标签;`client-base/package.json` 未声明 `@user/data-state` 的事实。全节只登记事实与核对命令,**不写成待修改项、不提出修改建议**。
+
+## F 未能从阅读确定(未猜测)
+
+条目形如 `- **#04-06-F-NN**:现象 + 为什么读不出来 + 需要用户裁决的点`。**只登记,不判定**;不得写成错配 / 缺失 / 残留。
+
+- **#04-06-F-01**:`getIfBigImage` 的语义替代是否成立。现象:`getIfBigImage` 已被删除,新基准 `ITextureGetter` 只提供 `getTile`(`packages-user/client-base/src/material/types.ts:201`)、`getImage`(`:225`)、`ITextureManager.getRenderable`(`:397`)。读不出来的原因:`door.ts` 的大图开门帧数语义(`:36,:46` 读 `data.frames`)、`hero.ts` 的贴图取值(`:239,:364,:428`)、`vertex.ts` 的顶点裁剪(`:546`)所依赖的「大图帧数据」是否可由 `getTile` 的返回(`Readonly`)等价承接,取决于这些调用点对「大图特殊帧序 / 偏移」的实际期望,超出静态阅读可定论的范围。需要用户裁决:**大图帧序语义由哪个成员承接**。
+
+- **#04-06-F-02**:`ITextureManager.textures`(`packages-user/client-base/src/material/types.ts:262`)应由哪个现有 store 承担。现象:基准新增 `readonly textures: ITextureStore`(jsDoc:「通过加载获取的所有纹理贴图存储,包括图块、普通图片等」),实现类 `MaterialManager`(`packages-user/client-base/src/material/manager.ts:38-43`)现只有 `tileStore` / `tilesetStore` / `imageStore` / `assetStore` / `bigImageStore`。读不出来的原因:`textures` 的语义是「所有纹理的合集」还是某一个已有的 store,抑或需要新建,取决于新引擎对「加载所得纹理统一入口」的定位。需要用户裁决:**`textures` 由 `tileStore` 承担,还是新建 / 复用其它 store**。
+
+- **#04-06-F-03**:四个实现类缺失的 `state` 从何处注入。现象:`MaterialManager`(`manager.ts:38`)、`AutotileProcessor`(`autotile.ts:42`)、`AssetBuilder`(`builder.ts:12`)、`TrackedAssetData`(`builder.ts:99`)均无 `state` 成员,而基准四处 `extends ICoreStateExtended`(定义源 `packages-user/data-state/src/types.ts:45-49`:`readonly state: ICoreState`)。读不出来的原因:`ClientCore extends CoreState`(`packages-user/client-modules/src/client.ts:45`),构造点 `client.ts:84`(`new MaterialManager()`)、`client.ts:85`、`render/map/renderer.ts:234`、`manager.ts:86`、`builder.ts:28` 均在 `client-modules` 侧;但 `client-base` 是否应依赖 `@user/data-state`(其 `package.json` 未声明该依赖,见 E-08)以及 `state` 是构造注入还是属性赋值,超出静态阅读可定论范围。需要用户裁决:**`state` 的来源与注入方式**(D-23 亦约束新增代码不得取全局单例)。
+
+- **#04-06-F-04**:`cache.ts` 的 legacy bigImage 路径属本 material 增量还是 Phase 5 legacy 清退边界。现象:`packages-user/client-modules/src/render/elements/cache.ts` 经 `core.material.images.images[bigImage]`(`:160`)与 `enemys[id].bigImage`(`:157`)读旧全局,**不走 `ITextureManager`**、且不产生 material 类型错误。读不出来的原因:`core.*` 属旧引擎适配(D-12,第五阶段),而「big-image 概念删除后的 conceptual 残留」又落在本 material 增量的语义范围内,二者边界需要裁决。需要用户裁决:**`cache.ts` 的 legacy bigImage 路径归属哪个阶段**。
+
+- **#04-06-F-05**:`setDefaultFrame` 与 `bigImageData` 的耦合在新概念下是否有对位。现象:`setDefaultFrame`(`packages-user/client-base/src/material/types.ts:340`,接口成员)内部耦合 `this.bigImageData`(`manager.ts:233-235`);`bigImageData` 已不属接口成员。读不出来的原因:新引擎中「按 identifier 设置默认帧」是否仍需同步一份「大图帧数据」,还是会随 big-image 概念一并消失,取决于新引擎的帧数据模型。需要用户裁决:**`setDefaultFrame` 在新概念下是否需要保留对某帧数据结构的同步**。
+
+- **#04-06-F-06**:`.planning/graphs/*` 是否需要重建。现象:`.planning/graphs/{graph.json,.last-build-snapshot.json}` 仍标注已被删除的 `IMaterialManager` / `IMaterialGetter` / `IBigImageReturn` / `getBigImage` 等标签(快照 `2026-09-07`)。读不出来的原因:工装态重建属流程决策,非代码事实。需要用户裁决:**是否在本阶段或后续重建 `.planning/graphs/*`**。
+
+## 处置
+
+- 本文件是 material 接口适应(D-30 / D-31)**第一步(只读清点)**的交付物;本步**未修改任何生产代码**,`files_modified` 仅本文件。
+- 相对本文件 `## 方法` 记录的**只读起始基线**(`git status --porcelain -- packages packages-user src` = 空)**零变化**;生产源码自 `4e305e3` 起未再改动,类型错误总行数重跑一致(199)。
+- **第二步(material 接口适应实施)未规划**,待用户审阅本文件后另行规划(D-32)。本文件不含修复方案、不含代码改动建议、未产出任何 `04-07+` 或新 `*-PLAN.md`。
+- 范围限 `packages-user`,`packages/` 不纳入、`src/` 命中仅报告(D-32)。
+- A / B / C / D / E / F 六类中:只有 **A / C / D** 是 `packages-user` 内的待处置项;**B** 类已清零;**E** 类仅报告;**F** 类须用户裁决。
+- `state` 的注入方式、`textures` 的承担者、`getIfBigImage` 的替代一律由**用户裁决**(不得由 AI 裁定;D-23 亦约束新增代码不得取全局单例)。
+- `core.*` legacy 适配(D-12)与移动控制(D-19)、跟随者(D-22)、`HeroRendering` 语义(D-18)不属本步。
+- `REND-01` / `REND-02` 保持 **Pending**。
+- 当日实测数值(199 / 19)与规划日(199 / 19)一致,无差异需要说明;「material / 非 material」的划分口径为规划日人工归类,**须用户确认**(见 `## 类型门禁实测基线`)。
+
From ac69d31cbb798a328f0190f976230827ebac476e Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 10:29:20 +0800
Subject: [PATCH 064/173] docs(04-06): complete material impact study; sync
state
---
.planning/ROADMAP.md | 4 ++--
.planning/STATE.md | 8 ++++----
.planning/phases/04-render-adaptation/04-CONTEXT.md | 1 +
.planning/state.json | 2 +-
4 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 8321138..5683a22 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 5 of 6 executed(04-01..04-05)+ 04-06 planned(material 接口影响范围清点·只读,未执行)
+**Plans**: 6 of 6 executed(04-01..04-06;04-06 = material 接口影响范围清点·只读,已执行)。material 接口适应第二步(实施)待用户审阅清点结果后另行规划;渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
**Plan register note (2026-09-21):** 第五个增量 `04-05`(收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性改经 `client.hero.attribute.getFinalAttribute(...)` 读取 + 逐图层钩子类 `RendererLayerHook`,2026-09-20 执行)此前未登记进本路线图,本次一并补登;第六个增量 `04-06` = material 接口适应(D-30/D-31/D-32)**第一步·只读影响清点**,产出 `04-MATERIAL-INTERFACE-IMPACT.md`,第二步(实施)待用户审阅后另行规划。
Plans:
@@ -399,7 +399,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 5/6 | In Progress| |
+| 4. 渲染适配与双布局 | 6/6 | In Progress| |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 34b790b..e919353 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -5,15 +5,15 @@ current_phase: 04
current_phase_name: 渲染适配与双布局
status: executing
stopped_at: Completed 04-04-PLAN.md(勇士渲染修正:注入 IFaceManager、拆钩子类、补 AnimDir、换 @motajs/animate,三文件机器门禁全绿;REND-01/REND-02 保持 Pending)
-last_updated: "2026-09-21T15:16:11.920Z"
+last_updated: "2026-09-22T02:28:19.679Z"
last_activity: 2026-09-21
last_activity_desc: Phase 04 execution started
-state_head: 3fadf34fff51c15c9f105806a278e6a8478fb6af
+state_head: 85f65b8f551e700992664b4f14700cd3cf0f4074
progress:
total_phases: 7
completed_phases: 0
total_plans: 77
- completed_plans: 74
+ completed_plans: 76
milestone_name: milestone
---
@@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
-Plan: 5 of 6
+Plan: 6 of 6
Status: Ready to execute
Last activity: 2026-09-21 — Completed quick task 260921-lwa: Upgrade toolchain to pnpm 12.5.1 and Node 24
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
index ad7061e..e65d059 100644
--- a/.planning/phases/04-render-adaptation/04-CONTEXT.md
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -69,6 +69,7 @@
- **D-31:** 本次改动的根因:用户**重写了 Texture 的底层管理器,删除 `big-image` 概念**(旧样板概念,新引擎不再需要;旧兼容可**无痛丢弃**)。`4e305e3` 已从接口删除 `IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`,并把 `IMaterialManager`→`ITextureManager`、`IMaterialGetter`→`ITextureGetter`、`IMaterialAliasGetter`→`ITextureAliasGetter`,新增 `textures` 与 `ICoreStateExtended` 约束。**影响面可能很大。**
- **D-32:** 计划分**两步**:**第一步**收集影响范围(只读清点);**第二步**进行修改。第二步骤第一步结果与用户审阅后再规划。
- **边界确认(2026-09-21):** 范围限 `packages-user`;`packages` 不纳入(理应不受本改动影响)。**以 `packages-user/client-base/src/types.ts`(及 `material/types.ts`)为基准**。**若发现其余 big-image 残留,必须在清点文档中报告。** `src/` 或 `packages/` 内若命中,仅报告、不修改。第一步产出 `.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md`(只读,允许派只读子代理);第二步待用户审阅第一步结果后再规划。
+- **D-33:** **范围外一律不改**;**不追求解决全部类型错误**(整仓 199 条中大量为既有/无关),**只修复与本次 material 接口适应相关的项**。第二步实施时严格按此约束。
### the agent's Discretion
- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
diff --git a/.planning/state.json b/.planning/state.json
index 602c80e..f8f3805 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -44,5 +44,5 @@
"label": "Advance to the next step",
"reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-21T15:16:12.042Z"
+ "updated_at": "2026-09-22T02:28:19.843Z"
}
From bb1364b4955102f99f1d727dd02239c0dc0a6ecf Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 11:07:18 +0800
Subject: [PATCH 065/173] docs(04-07): create material interface adaptation
plan (D-33..D-37)
---
.planning/ROADMAP.md | 6 +-
.../phases/04-render-adaptation/04-07-PLAN.md | 575 ++++++++++++++++++
2 files changed, 580 insertions(+), 1 deletion(-)
create mode 100644 .planning/phases/04-render-adaptation/04-07-PLAN.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 5683a22..cd7363b 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 6 of 6 executed(04-01..04-06;04-06 = material 接口影响范围清点·只读,已执行)。material 接口适应第二步(实施)待用户审阅清点结果后另行规划;渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
+**Plans**: 7 plans — 04-01..04-06 已执行(04-06 = material 接口影响范围清点·只读);**04-07(material 接口适应实施·第二步)已规划待执行**;渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
**Plan register note (2026-09-21):** 第五个增量 `04-05`(收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性改经 `client.hero.attribute.getFinalAttribute(...)` 读取 + 逐图层钩子类 `RendererLayerHook`,2026-09-20 执行)此前未登记进本路线图,本次一并补登;第六个增量 `04-06` = material 接口适应(D-30/D-31/D-32)**第一步·只读影响清点**,产出 `04-MATERIAL-INTERFACE-IMPACT.md`,第二步(实施)待用户审阅后另行规划。
Plans:
@@ -206,6 +206,10 @@ Plans:
- [x] 04-05-PLAN.md — 收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性从失效的 `getHeroStatusOn` 改经 `client.hero.attribute.getFinalAttribute(...)` 读取(并移除 `main.tsx:28` 失效的 `@ts-expect-error`);新增模块内不导出类 `RendererLayerHook`(`Partial`)把逐图层 `onUpdateArea` / `onUpdateBlock` 转发到 `MapRenderer.updateLayerArea` / `updateLayerBlock`,并在 `setLayerState` / `updateLayerList` 两条路径注册与注销(M-06 使用 `client` 单例经用户裁定为 D-23 的显式例外)— **2026-09-20 已执行**(本次补登)
- [ ] 04-06-PLAN.md — material 接口影响范围清点(**只读**,D-30/D-31/D-32 第一步):以 `4e305e3` 后的 `client-base/src/{types.ts,material/types.ts}` 为基准,清点 `packages-user` 内对已移除 7 符号(`IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`)、3 个改名接口、新增 `textures` 与 4 处 `ICoreStateExtended` 约束未实现、以及所有 big-image 残留(含 `material/manager.ts` 与 `render/elements/cache.ts`)的引用,按 A 已移除 / B 改名 / C 约束未实现 / D big-image 残留 / E 范围外命中(`src/`、`packages/` 仅报告)/ F 未确定 六类分组,每行精确到符号 + `file:line`;产出 `04-MATERIAL-INTERFACE-IMPACT.md`,生产代码零改动,第二步实施待用户审阅后另行规划
+**Wave 6** *(blocked on Wave 5 completion)*
+
+- [ ] 04-07-PLAN.md — material 接口适应实施(**第二步·代码改动**,D-30/D-31/D-32 第二步 + D-33..D-37):按 04-06 清点的 A/C/D/E 四类落地——A 类(D-34)删除全部 big-image 引用并把 `getIfBigImage` 的 7 处消费者(`door.ts` 2 / `hero.ts` 3 / `renderer.ts` 1 / `vertex.ts` 1)改为行为等价的 `getTile`、删除 `renderer.ts` `getOffsetPool` 的 big-image 偏移收集;C 类(D-35)为 `MaterialManager` 补 `textures`、为 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 补构造器注入的 `state`(不取全局单例,D-23;`renderer.ts` 从 `manager.state` 取值);D 类(D-36)删除 `MaterialManager` 的 big-image 实现残留(`bigImageStore` / `bigImageData` / `bigImageId` / 五个方法 / `setDefaultFrame` 耦合)与 `render/elements/cache.ts` 的 legacy big-image 路径;E 类(D-37)为 `client-base/package.json` 补 `@user/data-state` 声明(不安装、不同步 lockfile)。10 个文件原子提交;门禁 = material 归属诊断归零 + 在范围文件零诊断 + 总行数不增加(规划日 199 / 19 → ≤ 180)+ `packages-user` 内 big-image 记号归零 + eslint / CRLF / 范围(基线感知)/ 并发基线;运行时 UAT 不可得(渲染端无测试设施)如实声明
+
**UI hint**: yes
### Phase 5: Legacy 移植
diff --git a/.planning/phases/04-render-adaptation/04-07-PLAN.md b/.planning/phases/04-render-adaptation/04-07-PLAN.md
new file mode 100644
index 0000000..dddce7e
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-07-PLAN.md
@@ -0,0 +1,575 @@
+---
+phase: 04-render-adaptation
+plan: 7
+type: execute
+wave: 6
+depends_on:
+ - 04-01
+ - 04-02
+ - 04-03
+ - 04-04
+ - 04-05
+ - 04-06
+files_modified:
+ - packages-user/client-base/package.json
+ - packages-user/client-base/src/material/manager.ts
+ - packages-user/client-base/src/material/autotile.ts
+ - packages-user/client-base/src/material/builder.ts
+ - packages-user/client-modules/src/client.ts
+ - packages-user/client-modules/src/render/map/renderer.ts
+ - packages-user/client-modules/src/render/map/vertex.ts
+ - packages-user/client-modules/src/render/map/extension/door.ts
+ - packages-user/client-modules/src/render/map/extension/hero.ts
+ - packages-user/client-modules/src/render/elements/cache.ts
+autonomous: false
+requirements:
+ - REND-01
+ - REND-02
+estimate:
+ tokens: 82000
+ raw_tokens: 82000
+ tasks: 4
+ confidence: low
+must_haves:
+ truths:
+ - "**本计划的第一个 block 点**是 Task 0 的 `blocking-human` 汇报关卡:先向用户汇报「要解决的问题(`4e305e3` 的 material 接口基准下,`packages-user` 内仍有 19 条 material 归属类型诊断 + A/C/D/E 四类待处置项,见 `04-MATERIAL-INTERFACE-IMPACT.md`)、大致解决方案(按用户裁定 D-34 / D-35 / D-36 / D-37 逐类落地:A 类 big-image 引用全部删除或合理改写、C 类补实现、D 类残留全部删除、E 类补依赖声明)、**逐处替换映射表与成员命名清单**、**三个已导出类的公共构造器签名变更单独列为待批准项**、`textures` 的承担 store 这一未决项(F-02)的候选与建议默认、`cache.ts` legacy big-image 路径的删除范围与行为影响、`setDefaultFrame` 耦合删除、`pnpm-lock.yaml` 是否同步的处置、范围边界与禁令、验证方式与运行时不可得的诚实降级」,然后必须**停下等待**用户明确回复「可以执行」四个字;收到该回复前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
+ - "**D-34(A 类)落地**:`packages-user` 内不再出现任何 big-image 相关记号(`getIfBigImage` / `getBigImage` / `getBigImageByAlias` / `isBigImage` / `setBigImage` / `bigImageStore` / `bigImageData` / `bigImageId` / `IBigImageReturn` / `bigImage`,大小写不敏感);`MaterialManager` 的 `IBigImageReturn` import、`bigImageStore` / `bigImageData` / `bigImageId` 字段、`setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` 五个方法、以及 `setDefaultFrame` 内对 `bigImageData` 的耦合全部删除;7 处消费者调用点(`door.ts:36,46`、`hero.ts:239,364,428`、`renderer.ts:1250`、`vertex.ts:546`)改为 `getTile(...)`;`renderer.ts` 的 `getOffsetPool` 中遍历 `bigImageStore.keys()` 取 `getBigImage(identifier)` 的整段(注释 + 循环)删除。**改写为 `getTile` 是行为保持的**:`getIfBigImage(x)` 的实现是「`bigImageData.get(x) ?? getTile(x)`」,而 `bigImageData` 的唯一写入者是 `setBigImage`,其在全仓库**消费者调用点为 0**(清点文档 `#04-06-A-10` / `#04-06-A-11` / `#04-06-A-12`),故今日 `getIfBigImage ≡ getTile`"
+ - "**D-35(C 类)落地**:`ITextureManager.textures` 与 `ICoreStateExtended`(`readonly state: ICoreState`)在四个实现类上补齐——`MaterialManager` 新增 `readonly textures: ITextureStore`(承担 store 由用户在 Task 0 裁定,默认新建一个专用 `TextureStore` 实例)与构造器注入的 `state`;`AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 各新增构造器注入的 `state`。**`state` 一律经构造器传入,不在新代码里取全局单例**(D-23 / D-35):`client.ts:84` → `new MaterialManager(this)`、`client.ts:85` → `new AutotileProcessor(this.materials, this)`、`renderer.ts:234` → `new AutotileProcessor(manager, manager.state)`、`manager.ts:86` → `new AssetBuilder(this, state)`、`builder.ts:28` → `new TrackedAssetData(materials, this, state)`。注入源是 `ClientCore extends CoreState implements IClientCore`(`IClientCore → IClientSystem → IClientBase → ICoreState`),`this` 即 `ICoreState`;`renderer.ts` 侧从它已持有的 `ITextureManager` 的 `state` 取值,不引入任何全局单例(D-23)"
+ - "**D-36(D 类)落地**:`MaterialManager` 的 big-image 实现残留(见 D-34 条)与 `client-modules/src/render/elements/cache.ts` 的 legacy big-image 路径全部删除——`RenderableDataBase.bigImage` 字段、`AutotileRenderable` 的 `bigImage: false`、5 处对象字面量的 `bigImage: false/true`、`let { faceIds, bigImage } = data;`、`({ bigImage, faceIds } = enemys[...]);`、整个 `if (bigImage) { ... core.material.images.images[bigImage] ... }` 分支、以及因该分支删除而变为未使用的 `const enemys = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;` 声明。删除后 `packages-user` 内 `bigImage` 记号(大小写不敏感)计数为 0;`RenderableData` / `AutotileRenderable` 的消费者(`elements/misc.ts`、`render/components/textboxTyper.tsx`)**均不读取 `.bigImage`**,故字段删除不产生新诊断"
+ - "**D-37(E 类)落地**:`packages-user/client-base/package.json` 的 `dependencies` 补上 `\"@user/data-state\": \"workspace:*\"`(其源码 `material/types.ts:9` 与 `types.ts:3` 早已 import 该包);**不执行任何依赖安装、不修改 `pnpm-lock.yaml`**——Task 0 必须把「仅补声明 / 不同步 lockfile」这一处置原样告知用户并请其确认(若用户要求同步 lockfile,退出本计划、修订 `04-07-PLAN.md` 后重新汇报,不得自行改道)"
+ - "**D-33(范围硬边界)落地**:只修复 material 归属的诊断,**不追求解决全部类型错误**。执行前先记录当日 `pnpm exec vue-tsc --noEmit` 的基线(本次规划日实测:错误总行数 **199**、material 归属 **19**,与 `04-MATERIAL-INTERFACE-IMPACT.md` 记录一致);完工后要求 ① material 归属诊断为 **0**;② 错误总行数 **不超过基线总行数减 material 条数(≤ 180)**;③ 十个在范围文件内**不再有任何诊断**。`src/types/declaration/*`(E 类只报告)与 `packages/**` 一律不修改;其余约 180 条既有/无关诊断(加载系统重构 16 条 + 数据端测试与寻路 164 条)**一条都不追**"
+ - "**契约面与命名仅两处经批准的变更**:① `MaterialManager` / `AutotileProcessor` / `AssetBuilder` 三个**已导出类**的构造器各新增一个 `state: ICoreState` 形参(Task 0 单独列为待批准项);② `MaterialManager` 新增 `readonly textures: ITextureStore` 成员。`IMapHeroRenderer` / `IMapExtensionManager` / `IAutotileProcessor` / `IAssetBuilder` / `ITrackedAssetData` / `ITextureManager` 的**成员集合与既有 jsDoc 一字不改**(`textures` / `state` 本就是 `4e305e3` 已声明的接口成员,本次只是补实现);不新增 / 删除任何接口成员,不新增任何方法名"
+ - "**既有注释零改写**:不修改任何既有 jsDoc / 注释文本(AGENTS.md);被 D-34 / D-36 删除的成员按其定义**连注释整体删除**(「删除 / 移动成员及其 jsDoc」属 AGENTS.md 预授权范围),不新增任何无价值注释;`renderer.ts` `getOffsetPool` 中随 big-image 循环一起删除的行内注释属同一次删除"
+ - "**范围外零改动**:`action/move.ts` 与全部移动控制 API(D-19)、`packages/**`、`src/**`、所有数据端包(`data-common` / `data-base` / `data-system` / `data-state`)、贴图与不透明度来源(D-18)、跟随者族(D-22)、`core.*` / `client.*` legacy(D-12)、存量 `state` 单例使用(D-28)、`.planning/graphs/*`(E 类只报告,F-06 待用户裁决)一律不动;`hero.ts` 的 `state.roleFace.getFaceOf(...)` 三处与 `import { state } from '@user/data-state'` 逐字保留(D-28)"
+ - "**验证可运行且不臆造通过**:改动前先记录当日 `pnpm exec vue-tsc --noEmit` 的基线(总行数 + material 归属行数,逐行原样入 SUMMARY)与并发改动基线(porcelain + 内容 `sha256` 摘要);改动后要求 material 归属诊断为 0、十个在范围文件诊断为 0、总行数 ≤ 基线总行数减 material 条数;`pnpm exec eslint` 对全部被改 TS 文件 0 错误;静态门禁(`packages-user` 内 `bigImage` 记号归零、`textures` 声明就位、四个实现类 `state` 就位、7 处 `getTile(` 就位、`getOffsetPool` 已清理、`@user/data-state` 在 `client-base/package.json`)、CRLF 门禁、基线感知范围门禁与并发改动基线门禁全部转绿。渲染端**无测试设施**,本增量虽在运行时路径上(构造器注入)但**无可运行的渲染或 UAT 设施**,故实质验证为「scoped 类型 + 静态 + eslint / CRLF + 范围 / 并发基线」机器门禁加人工代码复核;**不得**以任何无法运行的门禁冒充通过"
+ - "用户并发的未提交改动(以 Task 1 起始 `git status --porcelain` 当日实测为准;规划日实测工作树内唯一未提交项为 ` M .planning/phases/04-render-adaptation/04-CONTEXT.md`)在 Task 1 起始记录基线(porcelain 条目 + 各文件内容 `sha256` 摘要),Task 3 复验逐条与摘要未变;**不回滚 / 不暂存 / 不提交 / 不修改**它们。本次基线**不沿用** 04-04 / 04-06 的旧列表,一律以 Task 1 当日实测为准"
+ - "REND-01 / REND-02 在本 run 后仍保持 Pending:本计划只落地「material 接口适应实施(第二步)」一个子切片,未实施其余渲染适配与移动端 / 桌面端双布局(见 assumptions 的两条 FLAGGED ASSUMPTION)"
+ artifacts:
+ - path: packages-user/client-base/src/material/manager.ts
+ provides: "`MaterialManager` 上的 `ICoreStateExtended.state`(构造器注入的 `readonly state: ICoreState`)与 `ITextureManager.textures`(`readonly textures: ITextureStore`,承担 store 由用户裁定);big-image 实现(`IBigImageReturn` import、`bigImageStore` / `bigImageData` / `bigImageId`、`setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage`、`setDefaultFrame` 内的 `bigImageData` 耦合)全部删除;`new AssetBuilder(this, state)`"
+ - path: packages-user/client-base/src/material/builder.ts
+ provides: "`AssetBuilder` 与 `TrackedAssetData` 上的 `ICoreStateExtended.state`(构造器注入);`new TrackedAssetData(materials, this, state)`"
+ - path: packages-user/client-base/src/material/autotile.ts
+ provides: "`AutotileProcessor` 上的 `ICoreStateExtended.state`(构造器注入,保留既有 `manager` 形参)"
+ - path: packages-user/client-base/package.json
+ provides: "`dependencies` 新增 `\"@user/data-state\": \"workspace:*\"`(D-37;不含任何安装动作、不改 `pnpm-lock.yaml`)"
+ - path: packages-user/client-modules/src/client.ts
+ provides: "`new MaterialManager(this)` 与 `new AutotileProcessor(this.materials, this)`(构造器注入 `state`,D-35 / D-23)"
+ - path: packages-user/client-modules/src/render/map/renderer.ts
+ provides: "`new AutotileProcessor(manager, manager.state)`;`getOffsetPool` 删除 big-image 偏移收集循环(D-34);背景图块取值改 `this.manager.getTile(tile)`"
+ - path: packages-user/client-modules/src/render/map/vertex.ts
+ provides: "顶点数组更新取值改 `this.renderer.manager.getTile(num)`(D-34)"
+ - path: packages-user/client-modules/src/render/map/extension/door.ts
+ provides: "`openDoor` / `closeDoor` 取值改 `this.renderer.manager.getTile(num)`(D-34)"
+ - path: packages-user/client-modules/src/render/map/extension/hero.ts
+ provides: "勇士移动 / 朝向 / 跟随者三处取值改 `this.renderer.manager.getTile(...)`(D-34);`state.roleFace.getFaceOf` 与 `state` import 逐字保留(D-28)"
+ - path: packages-user/client-modules/src/render/elements/cache.ts
+ provides: "legacy big-image 路径整体删除:`RenderableDataBase.bigImage` 字段、`AutotileRenderable` 的 `bigImage: false`、对象字面量的 `bigImage` 属性、`faceIds` / `bigImage` 解构与 `enemys` 特判、`if (bigImage) { ... core.material.images.images[bigImage] ... }` 分支、以及随之未使用的 `enemys` 声明(D-36)"
+ key_links:
+ - "`ICoreStateExtended { readonly state: ICoreState }`(`packages-user/data-state/src/types.ts:45-49`)↔ `material/types.ts:9` 的 import ↔ 四处 `extends ICoreStateExtended`(`types.ts:104` / `:260-261` / `:430` / `:462-463`)↔ 四个实现类的 `state` 形参与构造点(`client.ts:84,85` / `renderer.ts:234` / `manager.ts:86` / `builder.ts:28`)"
+ - "`ITextureManager.textures: ITextureStore`(`material/types.ts:262-263`)↔ `MaterialManager.textures`(本计划补实现)↔ `ITextureStore` / `TextureStore`(`packages/render/src/assets/{types.ts:140,store.ts:5}`,`manager.ts:11` 已 import)"
+ - "`ITextureGetter.getTile(identifier): Readonly | null`(`material/types.ts:201`)↔ 7 处消费者调用点(`door.ts:36,46` / `hero.ts:239,364,428` / `renderer.ts:1250` / `vertex.ts:546`)——原 `getIfBigImage` 的返回类型与 `getTile` 一致,故替换不改变调用点后续用法(`block.setTexture(...)` / `tile.texture.render()` / `tile.frames` / `tile.cls`)"
+ - "`MaterialManager.setBigImage` 在全仓库消费者调用点为 0(`04-MATERIAL-INTERFACE-IMPACT.md` `#04-06-A-10..A-12`)↔ `bigImageData` 恒空 ↔ `getIfBigImage(x) ≡ getTile(x)` ↔ D-34 的「合理修改」依据"
+ - "`ClientCore extends CoreState`(`client-modules/src/client.ts:45`,`CoreState implements ICoreState`(`data-state/src/core.ts:77`))↔ `IClientCore → IClientSystem → IClientBase → ICoreState`(`client-modules/src/types.ts:9`、`client-system/src/types.ts`、`client-base/src/types.ts:8`)↔ `new MaterialManager(this)` / `new AutotileProcessor(this.materials, this)` 的注入合法性"
+ - "`MapRenderer.manager: ITextureManager`(`render/map/renderer.ts:217`)↔ `ITextureManager extends ICoreStateExtended` ↔ `renderer.ts:234` 的 `new AutotileProcessor(manager, manager.state)`(不再取全局单例,D-23)"
+ - "`material/types.ts:260-271` 的现行 store 成员集合(`textures` / `tileStore` / `tilesetStore` / `imageStore` / `assetStore`,已无 `bigImageStore`)↔ `MaterialManager` 字段集合(删除 `bigImageStore` 后与之对齐)"
+ - "`pnpm exec vue-tsc --noEmit` 的基线(总行数 + material 归属行数,Task 1 当日实测,逐行入 SUMMARY)↔ Task 1 / 2 / 3 门禁的一致断言;material 归属由固定谓词判定(`client-base/src/material/` 路径 **或** 诊断文本命中 `ITextureManager` / `IAutotileProcessor` / `IAssetBuilder` / `ITrackedAssetData` / `IBigImageReturn` / `getIfBigImage` / `getBigImage` / `isBigImage` / `setBigImage` / `getBigImageByAlias` / `bigImageStore` / `IMaterialManager` / `IMaterialGetter` / `IMaterialAliasGetter`),规划日实测该谓词恰得 **19**(= `client-base` 7 + `client-modules` 12),故本门禁为机器判定、不依赖人工归类"
+ assumptions:
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付 ledger A / C / D / E 四类的实施切片,判据取自 04-CONTEXT 的 D-33..D-37 与 `04-MATERIAL-INTERFACE-IMPACT.md`。用户须在推进其余渲染适配前补齐 REND-01 的边界判据。"
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局实现,也不发明其验收阈值。"
+ - "**F-02 未决项(`textures` 的承担 store):** 清点文档把「`textures` 由 `tileStore` 承担、还是新建 / 复用其它 store」列为待用户裁决(`#04-06-F-02`),用户 D-35 只裁定「补充实现」而未指定承担者。本计划的**建议默认** = 新建一个专用 `TextureStore` 实例(`readonly textures: ITextureStore = new TextureStore();`,与同文件其它 store 字段形态一致、不叠加语义到既有 store);备选 (b) = 指向 `this.tileStore`;备选 (c) = 让 `addGrid` / `addRowAnimate` / `addTileset` / `addImage` 同时写入该专用 store(行为与内存面更大,本计划**不建议**在本增量做,属后续引擎步骤)。该选择须在 Task 0 获批;获批后按用户指定落地并把结论写入 SUMMARY。"
+ - "**执行前必须复核(未确定项,不猜):** Task 1 步骤 (0a) 必须在动手前打开当日 HEAD 逐条核对:① `MaterialManager` 仍 `implements ITextureManager` 且缺 `textures` / `state`(`manager.ts:38-43`);② 四个实现类仍缺 `state`(`manager.ts:38` / `autotile.ts:42` / `builder.ts:12` / `builder.ts:99`);③ `ICoreStateExtended` 定义源仍为 `data-state/src/types.ts:45-49`;④ 四处 `extends ICoreStateExtended` 仍在(`material/types.ts:104` / `:260-261` / `:430` / `:462-463`),`readonly textures: ITextureStore` 仍在 `:262-263`;⑤ 构造 / 接线面五处仍为 `client.ts:84` / `client.ts:85` / `render/map/renderer.ts:234` / `manager.ts:86` / `builder.ts:28`;⑥ `ClientCore extends CoreState implements IClientCore`(`client-modules/src/client.ts:45`)且 `CoreState implements ICoreState`(`data-state/src/core.ts:77`),`IClientCore` 链上确有 `ICoreState`;⑦ `ICoreState` 由 `@user/data-state` barrel 导出(`data-state/src/index.ts` 含 `export * from './types';`),且 `@user/data-state` 未出现在 `client-base/package.json`;⑧ `getTile` 在 `ITextureGetter` 上(`material/types.ts:201`)且返回类型与 `getIfBigImage` 一致;⑨ `setBigImage` / `isBigImage` / `getBigImageByAlias` 在 `packages-user` 内消费者调用点仍为 0(`git grep`);⑩ `cache.ts` 的 11 处 `bigImage` 命中行号与 `enemys` 声明(`cache.ts:127`)仍在;⑪ `@motajs/render` barrel 仍导出 `TextureStore`(`manager.ts:11` 已 import)。任一不同即**停下汇报并修订本计划**,不得按记忆改代码。"
+ - "**运行时 UAT 不可得(诚实降级):** 渲染端无测试设施(`client-modules` 无 `*.test.*`),且本增量的改动(构造器注入、材质取值改写)只在浏览器渲染流程里才可观察——本计划**不启动渲染、不新增测试、不接线任何被注释的接线点**。故验收依赖「scoped 类型门禁 + 静态门禁 + eslint / CRLF + 基线感知范围 / 并发基线门禁 + 人工代码复核」,运行时渲染 UAT 顺延到具备渲染验证设施的增量。**不伪造绿。**"
+ - "**数据端实现可能被用户并发修改(D-05):** 接口签名视为稳定、实现可变;本计划只读引用数据端接口(`ICoreState` / `ICoreStateExtended` 类型),不修改任何数据端文件。若执行当日接口签名与规划基线不一致,按 Task 1 步骤 (0a) 停下汇报,不按旧形状适配。"
+ - "**成员命名需用户同意:** 本计划拟新增 / 变更的成员名——四个构造器的新形参 `state`(`MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData`)、`MaterialManager` 的新成员 `textures`(名由 `ITextureManager` 固定)、以及**三个已导出类构造器签名的变更**(`MaterialManager` / `AutotileProcessor` / `AssetBuilder`)须在 Task 0 汇报中获得用户同意后才可落地(AGENTS.md「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」);`textures` 的承担 store 亦须在同一关卡获批。用户未同意即改名为用户指定的写法,或停下修订计划。"
+ - "**`textures` 默认实现可能是一个当前无写入者的 store(须用户知悉):** 若用户采纳建议默认(新建专用 `TextureStore`),在本次改动范围内**没有任何代码向它写入**(写入属新引擎后续步骤,且 loader 不在 D-33 范围内);本计划会**如实**在 Task 0 与 SUMMARY 中声明这一点,绝不把它描述成「已可用的统一纹理入口」。"
+ - "**`cache.ts` 删除的行为影响(须用户知悉):** `cache.ts` 的 `if (bigImage)` 分支(读 legacy `enemys_*.bigImage` 与 `core.material.images.images[bigImage]`)删除后,原本走该分支的 legacy 大图怪物会**落到后续分支**(`enemy48` / `npc48` 分支或普通图块分支)渲染。这与 D-31「big-image 概念删除、旧兼容可痛丢弃」一致,但确是一次运行时可见的变化——本计划**不在运行时验证它**(无渲染设施),须在 Task 0 原样告知用户。"
+ - "**`pnpm-lock.yaml` 不同步(须用户确认):** D-37 只裁定「补依赖声明」,本计划据此**只改 `client-base/package.json`、不动 `pnpm-lock.yaml`**(不执行任何安装、`--lockfile-only` 亦不执行)。`pnpm install --frozen-lockfile` 会因 importer 段未更新而失败——此事实须在 Task 0 原样告知,由用户决定是否需要另行同步(若要同步,退出本计划并修订后重新汇报)。"
+ prohibitions:
+ - "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理(AGENTS.md 的第一个 block 点约束;不因触发方式或 `yolo` / `auto_advance` 模式而放宽)。"
+ - "不得修改 `packages/**`、`src/**`(含 `src/types/declaration/*` 的 E 类命中)与任何数据端包(`packages-user/data-common` / `data-base` / `data-system` / `data-state`);E 类只报告、不修改。"
+ - "不得修改 `packages-user/client-base/src/material/types.ts` 与 `packages-user/client-base/src/types.ts`——它们是用户以 `4e305e3` 定下的接口基准(`ITextureManager.textures`、四处 `extends ICoreStateExtended`),本计划只补实现、不动接口;若编译链要求改动这两个文件,必须停下汇报并修订本计划(不得自行扩面)。"
+ - "不得新增 / 删除任何公共接口成员,不得新增任何接口方法名;本次契约面只允许「三个已导出类构造器新增 `state` 形参」与「`MaterialManager` 新增 `textures` 成员」两处(均须 Task 0 获批)。"
+ - "不得追求解决无关类型错误:`client-base/src/load/loader.ts`(6 条)、`client-modules/src/core.ts`(1 条)、`render/ui/load.tsx`(9 条)与数据端测试 / 寻路(约 164 条)一律不动、不修、不「顺手」处理(D-33)。"
+ - "不得修改 `packages-user/client-base/src/load/**`(加载系统;其既有诊断与本增量无关,且 loader 改动属另一个增量)。"
+ - "不得删除 / 替换 / 阻塞 `hero.ts` 的存量 `state` 使用与 `import { state } from '@user/data-state'`(D-28);不得让新代码取 `client` / `state` 全局单例(D-23)——`state` 一律经构造器注入。"
+ - "不得适配贴图 / 不透明度来源(D-18)与跟随者位置 / 动画归属(D-22);不得改动 `action/move.ts` 与任何移动控制 API(D-19);不得引入 `core.*` / `client.*` legacy 适配(D-12)。"
+ - "不得修改任何既有 jsDoc / 注释文本(AGENTS.md);被 D-34 / D-36 删除的成员连其 jsDoc 整体删除属预授权范围,但**不得**顺手改写邻近未删除成员的注释。"
+ - "不得引入 `as` 类型断言(含连续 `as`)、不得引入 `import type`(dev.md)、不得使用 `?.` 于非建议场景、不得新增 `_` 前缀参数名或未使用变量(eslint `args: 'all'` 为 error)。"
+ - "不得新增依赖、不得执行任何安装命令、不得修改 `pnpm-lock.yaml` / `tsconfig.json` / `eslint.config.js`(D-37 仅补 `client-base/package.json` 的一行声明)。"
+ - "不得修改 `files_modified` 之外的源码;不得回滚 / 暂存 / 提交用户的并发未提交改动。"
+ - "不得新增、删除或修改任何测试文件(渲染端无测试设施,本计划不建测试)。"
+ - "不得以「无测试设施」为由弱化验证:每条 `` 必须有 ``;确实无法运行的门禁必须显式声明为不可得并降级为静态 + 人工,不得伪造绿。"
+---
+
+
+Phase 4 第七个增量(D-32 **第二步**)—— **material 接口适应实施**。按用户对 `04-MATERIAL-INTERFACE-IMPACT.md` 的 A / C / D / E 四类裁定(D-34 / D-35 / D-36 / D-37),把 `4e305e3` material 接口基准在 `packages-user` 内**剩余的 material 归属类型诊断(19 条)与四类待处置项**一次落地。**本计划是代码改动计划**,改动面限定为 10 个文件(`client-base` 4 个 + `client-modules` 6 个)。
+
+**要解决的问题(四类,全部来自 `04-MATERIAL-INTERFACE-IMPACT.md`;D-30 / D-31 为根因):**
+
+1. **A 类(已移除符号的剩余引用,7 个符号)** —— `4e305e3` 删除了 `IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`,但 `client-modules` 侧仍有 9 处引用与 1 处 import:`getIfBigImage` × 7(`door.ts:36,46`、`hero.ts:239,364,428`、`renderer.ts:1250`、`vertex.ts:546`)、`bigImageStore` × 1(`renderer.ts:652`)、`getBigImage` × 1(`renderer.ts:653`)、`IBigImageReturn` import × 1(`manager.ts:20`)。用户裁定 **D-34**:跟 big-image 有关的**全部删除**;依赖它的方法 / 函数做**合理修改或删除**。
+2. **C 类(新增 / 收紧约束未实现,5 项)** —— `ITextureManager.textures` 未实现于 `MaterialManager`;四处 `extends ICoreStateExtended` 在 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 上均缺 `state`。用户裁定 **D-35**:**补充实现**,且 `state` **统一通过构造器传入**。
+3. **D 类(不在接口成员层面的 big-image 残留)** —— `MaterialManager` 的 `bigImageStore` / `bigImageData` / `bigImageId` / `setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` / `setDefaultFrame` 耦合,以及 `render/elements/cache.ts` 的 legacy big-image 路径。用户裁定 **D-36**:**全部删除**。
+4. **E 类(依赖声明)** —— `client-base/package.json` 未声明 `@user/data-state`,而其源码已 import 该包。用户裁定 **D-37**:**补充依赖声明**。
+
+**大致解决方案(逐处替换映射表):**
+
+| 位置 | 旧 | 新 | 等价性 |
+|---|---|---|---|
+| `door.ts:36` | `this.renderer.manager.getIfBigImage(num)` | `this.renderer.manager.getTile(num)` | **行为保持**:`getIfBigImage ≡ getTile`(`bigImageData` 无写入者) |
+| `door.ts:46` | 同上 | 同上 | 同上 |
+| `hero.ts:239` | `getIfBigImage(nextTile?.identifier ?? block.tile)` | `getTile(nextTile?.identifier ?? block.tile)` | 同上 |
+| `hero.ts:364` | `getIfBigImage(faced?.face ?? image)` | `getTile(faced?.face ?? image)` | 同上 |
+| `hero.ts:428` | `getIfBigImage(nextFace.identifier)` | `getTile(nextFace.identifier)` | 同上 |
+| `renderer.ts:1250` | `this.manager.getIfBigImage(tile)` | `this.manager.getTile(tile)` | 同上 |
+| `vertex.ts:546` | `this.renderer.manager.getIfBigImage(num)` | `this.renderer.manager.getTile(num)` | 同上 |
+| `renderer.ts:651-657` | `for (const identifier of this.manager.bigImageStore.keys()) { const data = this.manager.getBigImage(identifier); ... }` | **整段删除**(含其上注释) | big-image 专属偏移收集;big-image 概念删除后不再存在此类偏移,池中其余来源(`new Set([32])` 与 `imageStore.values()` 循环)保留 |
+| `manager.ts:20` | import `IBigImageReturn` | 从 import 列表删除该名 | 符号已不存在 |
+| `manager.ts:43` / `:56` / `:77` | `bigImageStore` / `bigImageData` / `bigImageId` 字段 | **删除** | 非接口成员、无消费者 |
+| `manager.ts:233-235` | `setDefaultFrame` 内 `bigImageData` 耦合 | **删除该耦合**,方法只保留 `defaultFrames.set` | 接口方法保留、耦合属残留(`#04-06-D-04` / F-05) |
+| `manager.ts:552-593` | `setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` | **五个方法整体删除** | 非接口成员;消费者为 0(`setBigImage` / `isBigImage` / `getBigImageByAlias`)或已被 7 处 `getTile` 取代(`getIfBigImage`) |
+| `cache.ts` 11 处 | `RenderableDataBase.bigImage`、`AutotileRenderable.bigImage`、5 处字面量、`faceIds` / `bigImage` 解构 + `enemys` 特判、`if (bigImage) {...}` 分支 | **整体删除**(含随之未使用的 `enemys` 声明) | legacy 概念路径(走 `core.material.*`),big-image 概念删除(D-31)后旧兼容可痛丢弃 |
+| `client-base/package.json` | 无 `@user/data-state` | `"@user/data-state": "workspace:*"` | 声明补全(源码已 import;不安装、不同步 lockfile) |
+
+- **D-35 注入链(C 类)**:`ClientCore extends CoreState`(`client.ts:45`)→ `new MaterialManager(this)`(`:84`)→ `MaterialManager.constructor(readonly state: ICoreState)` → `new AssetBuilder(this, state)`(`manager.ts:86`)→ `AssetBuilder.constructor(readonly materials, readonly state)` → `new TrackedAssetData(materials, this, state)`(`builder.ts:28`)→ `TrackedAssetData.constructor(readonly materials, readonly builder, readonly state) { super(0); }`;另有 `new AutotileProcessor(this.materials, this)`(`client.ts:85`)与 `new AutotileProcessor(manager, manager.state)`(`renderer.ts:234`,从已注入的 `ITextureManager` 取 `state`,不取全局单例,D-23)。`MaterialManager` 同时补 `readonly textures: ITextureStore`(承担 store 由 Task 0 裁定,建议默认新建专用 `TextureStore`)。
+- **范围硬边界(D-33)**:只修 material 归属诊断;约 180 条既有/无关诊断一条不追;`src/**`、`packages/**`、数据端只读;D-18 / D-19 / D-22 / D-23 / D-28 边界照旧。
+
+**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「要解决的问题 / 大致解决方案与逐处替换映射表 / 成员命名清单 / 三个已导出类构造器签名变更 / `textures` 承担 store 的未决项与建议默认 / `cache.ts` 删除范围与行为影响 / `pnpm-lock.yaml` 处置 / 范围边界与禁令 / 验证方式的诚实降级」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式或配置模式如何均不得自动放行。故本计划 `autonomous: false`。
+
+**显式延后(本计划不涉及、不实施):** `textures` 的写入侧(让 loader / `add*` 方法填充统一 store,属后续引擎步骤);`.planning/graphs/*` 重建(F-06,待用户裁决);其余渲染适配与移动端 / 桌面端双布局(REND-01 / REND-02);`core.*` legacy(D-12,第五阶段);移动控制(D-19);跟随者(D-22);贴图 / 不透明度来源(D-18);加载系统重构遗留的 16 条诊断(D-33)。
+
+Purpose: `4e305e3` 把 material 接口改成了新基准(无 big-image、纹理管理器带 `textures` 与 Layer 3 `state` 约束),但实现与消费者还停在旧形状上,编译面上留下 19 条 material 归属诊断、概念面留下成片的 dead big-image 代码。本计划把四类待处置项一次清爽落地,让新引擎的材质系统在编译与概念上都自洽,为后续渲染适配与双布局建立干净地基。
+
+Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑的 scoped 类型门禁(material 归属归零、在范围文件零诊断、总行数不增加)、静态门禁(big-image 记号归零、`textures` / `state` 就位、7 处 `getTile` 就位、`getOffsetPool` 已清理、`@user/data-state` 声明就位)、eslint 0 错误、CRLF 门禁、基线感知范围门禁与并发改动基线门禁结果 + `.planning/phases/04-render-adaptation/04-07-SUMMARY.md`。Task 0 为只读汇报关卡,不写任何文件。
+
+## Source Audit(多源覆盖审计)
+
+| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
+|--------|----|---------------------|------|--------|-------|
+| GOAL | — | Phase 4:「渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局」 | 04-07 | COVERED(增量·material 接口适应实施子切片) | 本 run 只落地 ledger A / C / D / E 四类;其余渲染适配与双布局属后续增量(REND-02 见 assumptions) |
+| REQ | REND-01 | 渲染端适配新数据层接口 | 04-07 | COVERED(子切片;需求保持 Pending) | C 类把渲染端系统层实现补到新接口(`textures` / `state`),A / D 类删除旧 big-image 概念;验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
+| REQ | REND-02 | 渲染端同时支持移动端与桌面端布局 | 04-07 | COVERED(不涉及) | 本 run 范围不含双布局;REND-02 验收边界未提供 → 见 assumptions 的 FLAGGED ASSUMPTION |
+| RESEARCH | — | (本阶段无 RESEARCH.md) | — | N/A | Phase 4 未产出 RESEARCH.md;等价输入为 04-01 / 04-02 账本与 04-06 的 `4e305e3` material 影响清点 |
+| CONTEXT | D-30 | material 接口适应 = 下一个任务;剩余未适应的实现与消费者由本任务处理 | 04-07 | COVERED | 本计划即 D-32 的第二步(实施);基准为 `4e305e3` |
+| CONTEXT | D-31 | 根因:Texture 底层管理器重写、删除 big-image 概念;旧兼容可痛丢弃 | 04-07 | COVERED | Task 2 / Task 3 的删除依据(big-image 无消费者 / 无写入者) |
+| CONTEXT | D-32 | 两步走,第二步 = 修改;范围限 `packages-user`,`packages` / `src` 只报告 | 04-07 | COVERED | 本计划 = 第二步;`files_modified` 全部在 `packages-user` 内 |
+| CONTEXT | D-33 | 范围外一律不改;不追求解决全部类型错误,只修 material 相关项 | 04-07 | COVERED | 门禁以「material 归属归零 + 总行数不增加 + 在范围文件零诊断」三条件界定;prohibitions 明列不得追无关错误 |
+| CONTEXT | D-34 | A 类:big-image 相关的全部删除;依赖它的方法做合理修改或删除 | 04-07 | COVERED | Task 2:`MaterialManager` big-image 实现删除 + 7 处消费者改 `getTile` + `getOffsetPool` 循环删除 |
+| CONTEXT | D-35 | C 类:补实现 `ITextureManager.textures` 与 `ICoreStateExtended.state`;`state` 统一经构造器传入 | 04-07 | COVERED | Task 1(tracer):四个实现类 + 五处构造点;`textures` 承担 store 由 Task 0 裁定 |
+| CONTEXT | D-36 | D 类:全部删除 `MaterialManager` big-image 残留与 `cache.ts` legacy big-image 路径 | 04-07 | COVERED | Task 2(`MaterialManager`)+ Task 3(`cache.ts`) |
+| CONTEXT | D-37 | E 类:为 `client-base/package.json` 补 `@user/data-state` 依赖 | 04-07 | COVERED | Task 1:仅补声明,不安装、不同步 lockfile(Task 0 告知) |
+| CONTEXT | D-18 | `HeroRendering` 贴图 / 不透明度暂不处理 | 04-07 | COVERED(排除) | `hero.ts` 只改取值成员(`getIfBigImage` → `getTile`),不动贴图来源与不透明度语义 |
+| CONTEXT | D-19 | 移动控制由用户手动修改 | 04-07 | COVERED(排除) | `action/move.ts` 不在 `files_modified` |
+| CONTEXT | D-22 | 跟随者暂缓 | 04-07 | COVERED(排除) | 跟随者族成员与钩子方法一律不动(`hero.ts:428` 只换取值成员) |
+| CONTEXT | D-23 | 不使用 `client` / `state` 全局单例(约束新增 / 后续代码) | 04-07 | COVERED | 新增代码只经构造器注入 `state`(`client.ts` 传 `this`、`renderer.ts` 传 `manager.state`),不取全局单例 |
+| CONTEXT | D-28 | 存量 `state` / `client` 使用暂不处理 | 04-07 | COVERED(保留) | `hero.ts` 的 `state.roleFace.getFaceOf` 三处与 `state` import 逐字保留 |
+| CONTEXT | D-12 / D-11 | `core.*` 属第五阶段;`getHeroStatusOn` 类裸函数待删 | 04-07 | COVERED(排除) | prohibitions 明列不得引入 `core.*` 适配 |
+| CONTEXT | D-05 | 以接口为准,不受数据端实现变动影响 | 04-07 | COVERED | Task 1 步骤 (0a) 复核;签名若变则停下汇报 |
+| CONTEXT | D-01..D-29 | 增量方式 / 对账 / 探索 / 勇士适配与修正 | 04-01..04-06 | COVERED(前序计划) | 由前序计划落地;本计划是 04-06 清点的实施 |
+| CONTEXT | D-13 | 缺失接口待改时逐步确认 | 04-07 | COVERED(边界) | 本计划不做新的缺失接口裁定 |
+| CONTEXT | D-14..D-16 | hook / loading / 存档 UI / 结构化重构 | 后续阶段 | DEFERRED(CONTEXT 未纳入本阶段) | 非本增量范围 |
+
+## Estimate Basis(估算依据)
+
+`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。按「执行者实际触及量」估算(`estimate.tasks: 4` = Task 0 汇报关卡 + 三个工作任务):Task 0 只读汇报并等待用户确认(约 5k,不写文件);Task 1(tracer)起始复核 + 构造器注入链 + `textures` + `package.json` + scoped 类型门禁(约 26k,含 `manager.ts` 602 行与 `builder.ts` / `autotile.ts` 的定向读取);Task 2 `MaterialManager` big-image 删除 + 5 个消费者文件改写 + `getOffsetPool` 清理 + 门禁(约 26k);Task 3 `cache.ts` 删除 + 原子提交 + 全部门禁 + 人工复核 + SUMMARY(约 20k);固定开销约 5k;合计约 82k。
+
+**为何不拆分:** D-33 明令不追无关错误,A 类的删除与其消费者改写**不可分离编译**(删掉 `getIfBigImage` 后 7 处调用点立即报错),故本增量是一个不可再切的最小整体;本计划的四类改动落在同一条「material 接口适应」纵切面上,拆成多计划只会制造中间不可编译状态。按 context-budget 的 Phase Sizing 规则,本计划列为一个计划(3 个工作任务),估算超 smart-zone 预算属**建议性**提示,不阻塞。
+
+
+
+
+@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
+@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/REQUIREMENTS.md
+@.planning/phases/04-render-adaptation/04-CONTEXT.md
+@.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md
+@.planning/phases/04-render-adaptation/04-06-SUMMARY.md
+@.planning/phases/04-render-adaptation/04-04-PLAN.md
+@AGENTS.md
+@dev.md
+
+
+
+
+
+ Task 0: 预执行汇报关卡 —— 汇报 material 接口适应实施(D-34..D-37)的逐处替换映射表、成员命名清单、三个已导出类构造器签名变更、`textures` 承担 store 的未决项与建议默认、`cache.ts` 删除范围与行为影响,等待用户回复「可以执行」
+ 用户对 material 接口适应第二步的裁定 D-33 / D-34 / D-35 / D-36 / D-37 已写入 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(以文件内实际存在 D-33..D-37 为准),且 `04-MATERIAL-INTERFACE-IMPACT.md` 已落盘(A–F 六类清点结果存在)
+ (只读汇报,不修改任何文件)
+
+ - AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行;「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈,我同意后才可以使用」)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-30..D-37 裁定原文;D-18 / D-19 / D-22 / D-23 / D-28 边界)
+ - .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md(A / C / D / E 四类待处置项与 F 类未决项;`## 类型门禁实测基线` 的 199 / 19)
+ - .planning/phases/04-render-adaptation/04-06-SUMMARY.md(清点的执行基线:错误总行数 199、material 19;八个执行前核对项的结论)
+ - .planning/phases/04-render-adaptation/04-07-PLAN.md(本计划全文:objective 的替换映射表 / tasks / 禁令 / 门禁)
+ - packages-user/client-base/src/material/types.ts(基准:`:104` / `:196-258` / `:260-271` / `:430-460` / `:462-483`)
+ - packages-user/client-base/src/material/manager.ts(现状 big-image 实现与 `implements ITextureManager`)
+ - packages-user/client-base/src/material/{autotile.ts,builder.ts}(现状构造器与缺 `state` 的两处 / 一处)
+ - packages-user/client-base/package.json(现状 `dependencies`)
+ - packages-user/client-modules/src/{client.ts,render/map/renderer.ts,render/map/vertex.ts,render/map/extension/door.ts,render/map/extension/hero.ts,render/elements/cache.ts}(9 处消费者调用点与 `cache.ts` 的 11 处 big-image 命中)
+ - packages-user/data-state/src/types.ts:45-49(`ICoreStateExtended` 定义源)
+
+
+ **停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
+
+ ① **要解决的问题(四类,来自 `04-MATERIAL-INTERFACE-IMPACT.md`;根因 D-30 / D-31)**:
+ - **A 类(已移除符号的剩余引用)**:`4e305e3` 删除 7 个符号后,`packages-user` 内仍有 10 处引用——`getIfBigImage` 7 处(`render/map/extension/door.ts:36,46`、`render/map/extension/hero.ts:239,364,428`、`render/map/renderer.ts:1250`、`render/map/vertex.ts:546`)、`bigImageStore` 1 处(`render/map/renderer.ts:652`)、`getBigImage` 1 处(`render/map/renderer.ts:653`)、`IBigImageReturn` import 1 处(`client-base/src/material/manager.ts:20`);`setBigImage` / `isBigImage` / `getBigImageByAlias` 消费者调用点 0 处。
+ - **C 类(新增 / 收紧约束未实现)**:`ITextureManager.textures` 未实现于 `MaterialManager`;`ICoreStateExtended.state` 在 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 上均缺。当日实测对应 9 条诊断(`manager.ts:38,86`、`autotile.ts:42`、`builder.ts:12,91,99`、`client.ts:84,85`、`renderer.ts:234`)。
+ - **D 类(big-image 实现与概念残留)**:`MaterialManager` 的 `bigImageStore` / `bigImageData` / `bigImageId` / `setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` / `setDefaultFrame` 耦合,以及 `render/elements/cache.ts` 的 legacy big-image 路径(11 处命中)。
+ - **E 类(依赖声明)**:`client-base/package.json` 未声明 `@user/data-state`,而 `material/types.ts:9` 与 `client-base/src/types.ts:3` 已 import 该包。
+ - **总账**:当日 `pnpm exec vue-tsc --noEmit` 实测 **199** 条诊断,其中 material 归属 **19** 条(按固定谓词机器判定,见 ⑤)。
+
+ ② **大致解决方案(按用户裁定逐类落地)**:见 objective 的逐处替换映射表,逐行汇报。要点:
+ - **D-34(A 类)**:`getIfBigImage` 的 7 处调用改为 **`getTile(...)`**——理由:`getIfBigImage(x)` 的实现是「`bigImageData.get(x) ?? getTile(x)`」,而 `bigImageData` 的唯一写入者 `setBigImage` 在**全仓库消费者调用点为 0**(`#04-06-A-10` / A-11 / A-12),故今日 **`getIfBigImage ≡ getTile`**,改为 `getTile` 是**行为保持**的「合理修改」;`renderer.ts` 的 `getOffsetPool` 中遍历 `bigImageStore.keys()` + `getBigImage(identifier)` 的整段(含其上注释)**删除**(big-image 专属偏移,概念删除后不存在);`MaterialManager` 的 big-image 实现(import / 3 字段 / 5 方法 / `setDefaultFrame` 耦合)**全部删除**。
+ - **D-35(C 类)**:四个实现类补 `state`,**一律经构造器传入**;`MaterialManager` 另补 `readonly textures: ITextureStore`。注入链:`new MaterialManager(this)`(`client.ts:84`,`ClientCore extends CoreState implements IClientCore`,`this` 即 `ICoreState`)→ `new AssetBuilder(this, state)`(`manager.ts:86`)→ `new TrackedAssetData(materials, this, state)`(`builder.ts:28`);`new AutotileProcessor(this.materials, this)`(`client.ts:85`)与 `new AutotileProcessor(manager, manager.state)`(`renderer.ts:234`,从已注入的 `ITextureManager` 取 `state`)。**不取任何全局单例**(D-23)。
+ - **D-36(D 类)**:`MaterialManager` 残留见 D-34;`cache.ts` 的 `RenderableDataBase.bigImage` 字段、`AutotileRenderable` 的 `bigImage: false`、5 处对象字面量 `bigImage`、`faceIds` / `bigImage` 解构与 `enemys` 特判、整个 `if (bigImage) { ... core.material.images.images[bigImage] ... }` 分支、以及随之变为未使用的 `const enemys = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;` 声明**全部删除**。
+ - **D-37(E 类)**:`client-base/package.json` 的 `dependencies` 追加 `"@user/data-state": "workspace:*"`;**不执行任何安装、不修改 `pnpm-lock.yaml`**(见 ④)。
+ - **F 类对应关系(原清点文档的待裁决项,逐条说明本计划如何对上用户裁定)**:F-01(`getIfBigImage` 的替代)→ 由 **D-34** 裁定「合理修改」= `getTile`(行为保持论据见上);F-02(`textures` 承担者)→ **D-35 只裁「补实现」未指定承担者**,见 ③ 的未决项;F-03(`state` 从何注入)→ 由 **D-35** 裁定「统一经构造器传入」,本计划据此定注入链;F-04(`cache.ts` legacy 路径归属)→ 由 **D-36** 裁定归本 material 增量并**删除**;F-05(`setDefaultFrame` 与 `bigImageData` 的耦合)→ 由 **D-36** 裁定删除耦合、保留接口方法本体;F-06(`.planning/graphs/*` 重建)→ 不在本计划(E 类只报告,仍待用户裁决)。
+
+ ③ **成员命名清单与待批准项(AGENTS.md 命名规则)**:
+ - **构造器新形参 `state`(4 处,须批准)**:`MaterialManager.constructor(readonly state: ICoreState)`;`AutotileProcessor.constructor(readonly manager: ITextureManager, readonly state: ICoreState)`(保留既有 `manager` 形参在前);`AssetBuilder.constructor(readonly materials: ITextureGetter, readonly state: ICoreState)`;`TrackedAssetData.constructor(readonly materials: ITextureGetter, readonly builder: AssetBuilder, readonly state: ICoreState)`(`super(0)` 仍为构造体第一条语句)。均按构造器属性声明语法书写(`ICoreStateExtended.state` 是 implements 继承的成员,按 dev.md:75 不重复添加 jsDoc,故该语法可用)。
+ - **新增成员 `textures`(1 处,名由接口固定)**:`MaterialManager` 上的 `readonly textures: ITextureStore = <承担 store>`;与同文件 `tileStore` / `tilesetStore` / `imageStore` / `assetStore` 一致**不写注释**(接口 `material/types.ts:262-263` 已有 jsDoc,按 dev.md:75 不重复)。
+ - **三个已导出类的公共构造器签名变更(单独列为待批准项)**:`MaterialManager`(`packages-user/client-base` 导出)、`AutotileProcessor`(同)、`AssetBuilder`(同)各新增一个必填 `state` 形参。三者在 `packages-user` 内的构造点各只有计划内已列的那几处(`client.ts:84` / `client.ts:85` + `renderer.ts:234` / `manager.ts:86`),**无其它调用者**,但属公共面变更,请用户明确批准。
+ - **删除的成员(名字消失,须批准)**:`MaterialManager.setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage`;字段 `bigImageStore` / `bigImageData` / `bigImageId`;`cache.ts` 的 `RenderableDataBase.bigImage` 字段(含其字面量赋值);`renderer.ts` 的 `getOffsetPool` 局部逻辑删除**但不改方法名**。
+ - **无新增方法名**;`IMapHeroRenderer` / `IMapExtensionManager` / `ITextureManager` / `IAutotileProcessor` / `IAssetBuilder` / `ITrackedAssetData` 的成员集合与既有 jsDoc **一字不改**(`textures` / `state` 本就是 `4e305e3` 已声明的接口成员)。
+ - **未决项(须用户当场裁定):`textures` 的承担 store**(清点文档 `#04-06-F-02`,D-35 未指定)。三种候选:
+ (a) **【建议默认】** 新建专用实例 `readonly textures: ITextureStore = new TextureStore();`——与同文件其它 store 字段形态一致,不把「全部纹理」的语义叠加到既有任一 store 上;**须知悉:本次改动范围内没有任何代码向它写入**(写入属新引擎后续步骤,loader 不在 D-33 范围内)。
+ (b) 指向既有 store `readonly textures: ITextureStore = this.tileStore;`——零新增状态,但语义变成「仅图块」。
+ (c) 让 `addGrid` / `addRowAnimate` / `addTileset` / `addImage` 同时写入该专用 store——语义最贴近 jsDoc(「所有加载所得纹理,含图块与普通图片」),但属**行为 / 内存面扩大**,本计划**不建议**在本增量做,建议留作后续引擎步骤。
+ 请用户在 (a) / (b) / (c) 中指定其一;未指定即按 (a) 落地。
+
+ ④ **`pnpm-lock.yaml` 处置(须原样告知并请确认)**:D-37 只裁「补充依赖声明」,故本计划**只改 `client-base/package.json`,不修改 `pnpm-lock.yaml`、不执行任何安装命令**(`pnpm install --lockfile-only` 亦不执行)。事实:`pnpm-lock.yaml` 的 `packages-user/client-base` importer 段当前不含 `@user/data-state`,因此加声明后 `pnpm install --frozen-lockfile` 会失败,需要用户自行同步 lockfile。若用户要求本计划一并同步 lockfile,退出本计划、修订 `04-07-PLAN.md` 后重新汇报,不得自行改道。
+
+ ⑤ **验证方式与诚实降级(须原样告知)**:
+ - 渲染端**无测试设施**(`client-modules` 无 `*.test.*`),本增量的改动(构造器注入、材质取值改写)只在浏览器渲染流程里可观察,而本计划**不启动渲染、不新增测试、不接线任何被注释的接线点**,故运行时渲染 UAT **不可得**。
+ - 本计划的实质验证 = **scoped 类型门禁**(material 归属诊断为 0;十个在范围文件诊断为 0;错误总行数 ≤ 基线总行数减 material 条数;基线总行数与 material 条数在 Task 1 起始当日实测并逐行入 SUMMARY,规划日实测为 **199 / 19**;material 归属由固定谓词**机器判定**,不依赖人工归类)+ **静态门禁**(`packages-user` 内 `bigImage` 记号大小写不敏感计数为 0;`MaterialManager` 的 `textures` 声明就位;四个实现类的 `state` 就位;7 处 `getTile(` 就位;`getOffsetPool` 已清理;`@user/data-state` 在 `client-base/package.json`)+ **eslint 0 错误 + CRLF 门禁** + **基线感知范围门禁与并发改动基线门禁** + **人工代码复核**。
+ - **不得**以任何无法运行的门禁冒充通过;「编译通过」不等于「运行时可用」——本增量的运行时表现(含 `cache.ts` 大图怪物落回普通分支的变化)**未经运行验证**。
+ - **`cache.ts` 删除的行为影响须原样告知**:`if (bigImage)` 分支删除后,原本走该分支的 legacy 大图怪物会落到后续分支(`enemy48` / `npc48` 分支或普通图块分支)渲染;这与 D-31「big-image 概念删除、旧兼容可痛丢弃」一致,但确是一次运行时可见的变化,本计划不在运行时验证它。
+
+ ⑥ **范围边界与禁令**:只改 10 个文件(`client-base` 的 `package.json` + `material/{manager,autotile,builder}.ts`,`client-modules` 的 `client.ts` + `render/map/{renderer.ts,vertex.ts,extension/door.ts,extension/hero.ts}` + `render/elements/cache.ts`);`material/types.ts` 与 `client-base/src/types.ts` **一字不动**(用户以 `4e305e3` 定下的基准);`src/**`、`packages/**`、数据端一律不动;约 180 条既有/无关诊断一条不追(D-33);不新增 / 删除任何公共接口成员;`state` 一律经构造器注入、不取全局单例(D-23);`hero.ts` 存量 `state.roleFace.getFaceOf` 保留(D-28);不新增依赖、不安装、不同步 lockfile(D-37);不改 `pnpm-lock.yaml` / `tsconfig.json` / `eslint.config.js`;不回滚 / 不暂存 / 不提交用户并发未提交改动。
+
+ ⑦ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
+
+ ⑧ **失败语义(等同硬门禁,非自动命令)**:用户未回复「可以执行」即继续,或回复的是其它任何内容(含修订意见、沉默、`yolo` / `auto_advance` 模式下的自动推进、或子代理/执行器的自行放行),一律视为**未通过本关卡**:必须立即停止执行,不派发任何执行子代理,保持只读、不写任何文件,直到用户在对话中明确回复「可以执行」四个字后方可继续。
+
+ ⑨ **若用户给出修订意见**(含不同意命名 / 不同意构造器签名变更 / 不同意 `getIfBigImage` → `getTile` 的改写 / 不同意 `cache.ts` 的删除范围 / 不同意 lockfile 处置 / 指定其它 `textures` 承担者 / 要求换方案):退出本计划、修订 `04-07-PLAN.md` 后重新汇报,不得自行改道执行。
+
+ 是否按本计划(10 个文件的 material 接口适应实施:D-34 删除全部 big-image 引用并把 7 处消费者改为 `getTile`、D-35 为四个实现类补 `state` 与 `MaterialManager.textures` 且 `state` 经构造器注入、D-36 删除 `cache.ts` legacy big-image 路径、D-37 为 `client-base/package.json` 补 `@user/data-state` 声明)开始执行;并确认 ③ 的成员命名与三个构造器签名变更、③ 的 `textures` 承担 store 选择、④ 的 `pnpm-lock.yaml` 处置
+
+ AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认,且任何新增 / 改写的成员命名必须先经用户同意。本计划改的是渲染端系统层的实现与消费者,涉及三个已导出类的公共构造器签名与一个新成员(`textures`)的语义,属「接口 / 依赖事实归用户」的边界;`.planning/config.json` 为 `mode: yolo`,因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`。`textures` 的承担 store 若不先裁决,执行者会在「新建空 store」与「复用 `tileStore`」之间自行猜测;`pnpm-lock.yaml` 是否同步若不先澄清,执行者可能擅自扩大改动面。
+
+
+
+
+
+
+ 回复「可以执行」四个字(如需指定 `textures` 承担者 / 改名 / 改删除范围 / 要求同步 lockfile,请在回复中一并给出),或给出修订意见
+
+ 用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无可运行的 `` 命令,故不携带 ``;失败语义完整写在 Task 0 的 action ⑧ 与 acceptance_criteria 中)
+
+
+ - 用户回复中明确包含「可以执行」四个字
+ - 汇报内容涵盖:要解决的问题(A / C / D / E 四类 + 199 / 19 总账)、逐处替换映射表(含 `getIfBigImage ≡ getTile` 的行为保持论据与 `renderer.ts` 偏移循环的删除)、成员命名清单(四个 `state` 形参 + `textures` + 三个已导出类构造器签名变更单独列为待批准项 + 被删除成员名)、`textures` 承担 store 的三种候选与建议默认 (a)、`cache.ts` 删除范围与行为影响、`pnpm-lock.yaml` 处置、范围边界与禁令、验证方式与运行时不可得的诚实降级
+ - 汇报中已原样告知关卡性质:执行者/子代理必须停下等待,不因触发方式或 yolo / auto 模式自动放行
+ - 汇报中已原样告知 F 类六项与 D-34..D-37 的对应关系(含 F-02 的承担 store 仍未由 D-35 指定)
+
+ 用户明确回复「可以执行」(如需指定 `textures` 承担者 / 改名 / 改删除范围 / 同步 lockfile,已在回复中给出);在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
+
+
+
+ Task 1(tracer):D-35 / D-37 端到端最小切片 —— `ClientCore` 经构造器把 `state` 注入 `MaterialManager` → `AssetBuilder` → `TrackedAssetData`,并注入 `AutotileProcessor`;`MaterialManager` 补 `textures`;`client-base/package.json` 补 `@user/data-state` 声明
+ packages-user/client-base/src/material/manager.ts, packages-user/client-base/src/material/autotile.ts, packages-user/client-base/src/material/builder.ts, packages-user/client-base/package.json, packages-user/client-modules/src/client.ts, packages-user/client-modules/src/render/map/renderer.ts, .planning/phases/04-render-adaptation/04-07-SUMMARY.md
+
+ - AGENTS.md(命名规则 / 注释规则 / 第一个 block 点)与 dev.md(双端分离;注释规范;`CRLF`;构造器属性声明语法;无 `import type`;无 `as`)
+ - packages-user/client-base/src/material/types.ts:96-104,190-271,428-483(`IAutotileProcessor extends ICoreStateExtended` `:104`、`ITextureGetter.getTile` `:201`、`ITextureManager` 的 `textures` / 四个 store / `assetDataStore` / `trackedAsset` `:260-279`、`IAssetBuilder extends ICoreStateExtended` `:430`、`ITrackedAssetData extends IDirtyTracker>, ICoreStateExtended` `:462-463`)
+ - packages-user/client-base/src/material/manager.ts(整文件 602 行:import `:1-29`;`implements ITextureManager` `:38`;四个 store 字段 `:39-42`;`assetBuilder` 字段与构造器 `:60,85-89`)
+ - packages-user/client-base/src/material/builder.ts(整文件 159 行:`AssetBuilder` `:12`、构造器 `:27-29`、`TrackedAssetData` `:99-115`)
+ - packages-user/client-base/src/material/autotile.ts:42-48(`implements IAutotileProcessor` 与 `constructor(readonly manager: ITextureManager) {}`)
+ - packages-user/client-base/package.json(现状 `dependencies` 四项)
+ - packages-user/data-state/src/types.ts:26-48(`ICoreState` / `ICoreStateExtended { readonly state: ICoreState }`)
+ - packages-user/data-state/src/index.ts(barrel:`export * from './types';`)
+ - packages-user/client-modules/src/client.ts:1-2,45-51,63-93(`CoreState` import、`ClientCore extends CoreState implements IClientCore`、`materials` / `autotile` 字段与构造点 `:84-85`)
+ - packages-user/client-modules/src/render/map/renderer.ts:1-15,217,234(`ITextureManager` import、`constructor(readonly manager: ITextureManager)`、`new AutotileProcessor(manager)`)
+ - packages/render/src/assets/types.ts:140-199 与 packages/render/src/assets/store.ts:5-36(`ITextureStore` / `TextureStore` 形态,确认 `textures` 可声明为 `ITextureStore`)
+ - .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md 的 `## C 新增与收紧约束的未实现` 与 `## 构造 / 接线面登记`(本任务的两张事实表)
+
+
+ (0) **执行前核对与基线(只读前置,必须先做)**:
+ (0a) **接口与接线面复核(消费未确定项,不猜)**:打开当日 HEAD 逐条确认,并把结论(每条一行:结论 + `file:line`)写入 `04-07-SUMMARY.md` 的「执行前核对」段:① `MaterialManager` 仍 `implements ITextureManager` 且字段仅 `tileStore` / `tilesetStore` / `imageStore` / `assetStore` / `bigImageStore`,无 `textures`、无 `state`(`manager.ts:38-43`);② 四个实现类仍缺 `state`(`manager.ts:38`、`autotile.ts:42`、`builder.ts:12`、`builder.ts:99`);③ `ICoreStateExtended` 定义源仍为 `readonly state: ICoreState`(`data-state/src/types.ts:45-49`),且 `ICoreState` 由 `@user/data-state` barrel 导出;④ 四处 `extends ICoreStateExtended` 与 `readonly textures: ITextureStore` 仍在基准侧(`material/types.ts:104`、`:260-263`、`:430`、`:462-463`);⑤ 构造 / 接线面五处仍为 `client.ts:84` / `client.ts:85` / `render/map/renderer.ts:234` / `manager.ts:86` / `builder.ts:28`,且 `git grep -n "new MaterialManager\|new AutotileProcessor\|new AssetBuilder\|new TrackedAssetData" -- packages-user packages src` 不出现第六处;⑥ `ClientCore extends CoreState implements IClientCore`(`client-modules/src/client.ts:45`)且 `CoreState implements ICoreState`(`data-state/src/core.ts:77`),`IClientCore → IClientSystem → IClientBase → ICoreState` 链成立;⑦ `render/map/renderer.ts:217` 的 `manager` 类型仍为 `ITextureManager`;⑧ `@user/data-state` 仍**不在** `client-base/package.json` 的 `dependencies`,而 `material/types.ts:9` 与 `client-base/src/types.ts:3` 已 import 该包;⑨ `ITextureGetter.getTile(identifier): Readonly | null`(`material/types.ts:201`)且返回类型与 `getIfBigImage` 一致;⑩ `git grep -n -E "setBigImage|isBigImage|getBigImageByAlias" -- packages-user` 在消费者侧仍为 0(仅 `manager.ts` 自身定义行)。**任一不同:立即停止、不写任何代码,向用户汇报差异并修订本计划**(不得按记忆继续)。
+ (0b) **类型门禁基线**:执行 `pnpm exec vue-tsc --noEmit`(整仓非 0 退出属预期),用固定谓词统计并把两个数字写入 SUMMARY,格式固定为:
+ ```
+
+ 错误总行数:;material 归属行数:
+
+
+ ```
+ **material 归属谓词(写死、机器判定)**:诊断行的文件路径含 `client-base/src/material/`,**或**诊断文本命中 `ITextureManager` / `IAutotileProcessor` / `IAssetBuilder` / `ITrackedAssetData` / `IBigImageReturn` / `getIfBigImage` / `getBigImage` / `isBigImage` / `setBigImage` / `getBigImageByAlias` / `bigImageStore` / `IMaterialManager` / `IMaterialGetter` / `IMaterialAliasGetter` 之一。规划日实测:`N = 199`、`M = 19`(= `client-base` 7 + `client-modules` 12)。**若当日实测与此不同,以当日实测为准写入 SUMMARY 并说明差异**(不得静默沿用旧值)。
+ (0c) **并发改动基线**:执行 `node -e "const fs=require('fs'),c=require('crypto'),{execSync}=require('child_process');const keep=l=>!/packages-user\/client-base\/(package\.json|src\/material\/)/.test(l)&&!/packages-user\/client-modules\/src\/(client\.ts|render\/map\/renderer\.ts)/.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,格式固定为:
+ ```
+
+
+
+ 基线摘要 sha256=<64hex>
+ ```
+ **不得沿用 04-04 / 04-06 SUMMARY 的旧列表**;一律以本次实测为准。**不得回滚 / 暂存 / 提交 / 修改**其中任何条目。
+ (0d) 建立 `04-07-SUMMARY.md` 骨架(含上面两段 + 后续任务要填的空节),保持 CRLF。
+ (1) **`manager.ts`(C 类:`state` + `textures`)**:在 `./types` 的 import 之后新增 `import { ICoreState } from '@user/data-state';`(**必须**与 `material/types.ts:9` 同源同写法;不得写 `import type`——dev.md:54)。在既有 store 字段之后新增 `readonly textures: ITextureStore = new TextureStore();`(**不写注释**,与 `tileStore` / `tilesetStore` / `imageStore` / `assetStore` 形态一致;若 Task 0 用户指定承担者为既有 store,则按用户指定的赋值改写这一行,并在 SUMMARY 记录裁定)。把构造器改为 `constructor(readonly state: ICoreState) {`,并把 `this.assetBuilder = new AssetBuilder(this);` 改为 `this.assetBuilder = new AssetBuilder(this, state);`(用形参 `state` 而非 `this.state`,避免任何字段初始化时序疑问)。不改 `pipe(this.assetStore)` 与 `tracked()` 两行、不改其它字段与方法。
+ (2) **`builder.ts`(C 类:`state` ×2)**:新增 `import { ICoreState } from '@user/data-state';`。`AssetBuilder`:构造器改为 `constructor(readonly materials: ITextureGetter, readonly state: ICoreState) {`,并把 `new TrackedAssetData(materials, this);` 改为 `new TrackedAssetData(materials, this, state);`。`TrackedAssetData`:构造器改为三形参 `constructor(readonly materials: ITextureGetter, readonly builder: AssetBuilder, readonly state: ICoreState) { super(0); }`(`super(0)` 必须仍是构造体第一条语句)。不改 `markDirty` / `updateSource` / `then` / `close`。
+ (3) **`autotile.ts`(C 类:`state`)**:新增 `import { ICoreState } from '@user/data-state';`;构造器改为 `constructor(readonly manager: ITextureManager, readonly state: ICoreState) {}`(保留既有 `manager` 形参在前)。不改任何方法。
+ (4) **`client.ts`(构造点注入)**:`this.materials = new MaterialManager();` → `this.materials = new MaterialManager(this);`;`this.autotile = new AutotileProcessor(this.materials);` → `this.autotile = new AutotileProcessor(this.materials, this);`。两行均在 80 列内,保持单行(若 prettier 仍要换行,按仓库既有惯例加 `// prettier-ignore` 并把该取舍记入 SUMMARY 的 Deviations)。**不改** `ClientCore` 的 `extends` / `implements`、字段声明、音频 / 渲染 / 加载各段。
+ (5) **`renderer.ts`(构造点注入)**:`this.autotile = new AutotileProcessor(manager);` → `this.autotile = new AutotileProcessor(manager, manager.state);`(`manager` 已是注入的 `ITextureManager`,其 `state` 来自 `ICoreStateExtended`;**不**引入任何全局单例,D-23)。本任务**只改这一行**,不动 `getOffsetPool`(Task 2 处理)。
+ (6) **`client-base/package.json`(D-37)**:在 `dependencies` 的 `"@user/data-base": "workspace:*"` 之后追加 `"@user/data-state": "workspace:*"`(保持 `@motajs/*` 在前、`@user/*` 在后的既有次序;`@user/data-base` 行末补逗号)。**只改这一处**:不执行任何安装命令、不修改 `pnpm-lock.yaml`、不改 `name`、不改缩进风格。
+ (7) **提交**:本任务改动是整体修正的中间态(A 类诊断仍在),**不单独提交**;Task 3 完成全部改动后再一次性原子提交。若执行环境强制每任务提交,提交信息用 `refactor(04-07): inject state into material manager chain`,并与后续任务的提交在本次执行内连续完成。
+ (8) 全程保持被改文件 CRLF;改完执行 `pnpm exec eslint --fix` 对这 5 个 TS 文件,再 `pnpm exec eslint` 必须 0 错误。
+
+ 本任务把 `state` 的注入方式钉进 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` 三个**已导出类**的公共构造器签名,并决定 `MaterialManager.textures` 的承担 store——这是公共契约面与依赖声明面的选择(Task 0 已获批)。回退需同时还原三个构造器、五处构造点与 `package.json`,且 `textures` 的语义选择被后续消费者依赖后不易更改,故记为 `costly`(Task 0 已在 `blocking-human` 关卡取得用户批准)。
+
+ node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,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 MRE=/ITextureManager|IAutotileProcessor|IAssetBuilder|ITrackedAssetData|IBigImageReturn|getIfBigImage|getBigImage|isBigImage|setBigImage|getBigImageByAlias|bigImageStore|IMaterialManager|IMaterialGetter|IMaterialAliasGetter/;const isMat=l=>/client-base[\\/]src[\\/]material[\\/]/.test(l)||MRE.test(l);let t='';try{t=fs.readFileSync('.planning/phases/04-render-adaptation/04-07-SUMMARY.md','utf8')}catch(e){t=''}const bm=t.match(/([\s\S]*?)/);if(!bm){console.error('NO TYPE BASELINE');process.exit(1)}const dm=bm[1].match(/错误总行数:(\d+);material 归属行数:(\d+)/);if(!dm){console.error('NO BASELINE NUMBERS');process.exit(1)}const N=+dm[1],M=+dm[2];const mat=lines.filter(isMat);const imp=lines.filter(l=>/incorrectly implements interface/.test(l));const miss=lines.filter(l=>/Property 'state' is missing/.test(l));const tex=lines.filter(l=>/missing the following properties from type 'ITextureManager'/.test(l));if(imp.length||miss.length||tex.length){console.error('C-CLASS DIAGNOSTICS REMAIN implements='+imp.length+' stateMissing='+miss.length+' texturesMissing='+tex.length+'\n'+imp.concat(miss,tex).join('\n'));process.exit(1)}const notBig=mat.filter(l=>!/bigimage/i.test(l));if(notBig.length){console.error('MATERIAL DIAGNOSTICS NOT ONLY BIG-IMAGE '+notBig.length+'\n'+notBig.join('\n'));process.exit(1)}if(mat.length!==M-9){console.error('MATERIAL COUNT MISMATCH expected '+(M-9)+' got '+mat.length+'\n'+mat.join('\n'));process.exit(1)}if(lines.length>N-9){console.error('TOTAL INCREASED total='+lines.length+' baseline '+N);process.exit(1)}console.log('OK tracer: C-class 0; material '+mat.length+'==M-9; total '+lines.length+'<='+(N-9))"
+ non-zero exit, 或 stderr 含 "NO TYPE BASELINE" / "NO BASELINE NUMBERS"(SUMMARY 未按固定格式记录类型基线)/ "C-CLASS DIAGNOSTICS REMAIN"(`incorrectly implements interface` / `Property 'state' is missing` / `missing the following properties from type 'ITextureManager'` 仍有残留)/ "MATERIAL DIAGNOSTICS NOT ONLY BIG-IMAGE"(material 归属诊断里出现了非 big-image 的条目,说明本次注入引入了新问题)/ "MATERIAL COUNT MISMATCH"(material 归属数量不是基线减 9,C 类未全部消除或多消)/ "TOTAL INCREASED"(错误总行数未从基线下降 9 条,说明改动引入了新诊断)
+ node -e "const fs=require('fs');const R=f=>fs.readFileSync(f,'utf8');const mg=R('packages-user/client-base/src/material/manager.ts'),au=R('packages-user/client-base/src/material/autotile.ts'),bu=R('packages-user/client-base/src/material/builder.ts'),cl=R('packages-user/client-modules/src/client.ts'),re=R('packages-user/client-modules/src/render/map/renderer.ts');const checks=[['manager.textures',/readonly textures:\s*ITextureStore/,mg],['manager.state',/constructor\(readonly state:\s*ICoreState\)/,mg],['manager.AssetBuilder.arg',/new AssetBuilder\(this,\s*state\)/,mg],['autotile.state',/readonly manager:\s*ITextureManager,\s*readonly state:\s*ICoreState/,au],['builder.state',/state:\s*ICoreState/,bu],['builder.TrackedAssetData.arg',/new TrackedAssetData\(materials,\s*this,\s*state\)/,bu],['client.MaterialManager',/new MaterialManager\(this\)/,cl],['client.AutotileProcessor',/new AutotileProcessor\(this\.materials,\s*this\)/,cl],['renderer.AutotileProcessor',/new AutotileProcessor\(manager,\s*manager\.state\)/,re]];const miss=checks.filter(c=>!c[1].test(c[2])).map(c=>c[0]);const all=mg+au+bu+cl+re;const bad=[];if(/import type/.test(all))bad.push('import type');if(/import \{ state \}/.test(all))bad.push('state singleton import');if(/from '\.\.?\/core';/.test(all))bad.push('core singleton import');const ds=[['manager',mg],['autotile',au],['builder',bu]].filter(p=>!(p[1].indexOf(\"import { ICoreState } from '@user/data-state';\")>=0)).map(p=>p[0]);const pkg=JSON.parse(R('packages-user/client-base/package.json'));if(miss.length){console.error('D35 BINDINGS MISSING '+miss.join(','));process.exit(1)}if(bad.length){console.error('D23 D37 VIOLATION '+bad.join(','));process.exit(1)}if(ds.length){console.error('CLIENT-BASE data-state IMPORT NOT EXACTLY ICoreState '+ds.join(','));process.exit(1)}if(!pkg.dependencies||pkg.dependencies['@user/data-state']!=='workspace:*'){console.error('D37 DEPENDENCY MISSING');process.exit(1)}console.log('OK tracer bindings: textures+state+5 construction sites+dep')"
+ non-zero exit, 或 stderr 含 "D35 BINDINGS MISSING"(`textures` 声明 / 四个实现类的 `state` / 五处构造点任一未就位)/ "D23 D37 VIOLATION"(出现 `import type`、取了 `state` 全局单例、或从 core 模块引入单例)/ "CLIENT-BASE data-state IMPORT NOT EXACTLY ICoreState"(三个 client-base 文件的 `@user/data-state` import 不是只引入 `ICoreState` 类型)/ "D37 DEPENDENCY MISSING"(`client-base/package.json` 未声明 `@user/data-state: workspace:*`)
+ pnpm exec eslint packages-user/client-base/src/material/manager.ts packages-user/client-base/src/material/autotile.ts packages-user/client-base/src/material/builder.ts packages-user/client-modules/src/client.ts packages-user/client-modules/src/render/map/renderer.ts
+ non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用变量 / 未使用 import / 行宽超限)
+ node -e "const fs=require('fs');const F=['packages-user/client-base/src/material/manager.ts','packages-user/client-base/src/material/autotile.ts','packages-user/client-base/src/material/builder.ts','packages-user/client-modules/src/client.ts','packages-user/client-modules/src/render/map/renderer.ts'];for(const f of F){const s=fs.readFileSync(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')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」)
+
+
+ - `manager.ts` 含 `import { ICoreState } from '@user/data-state';`、`readonly textures: ITextureStore = <承担 store>;`(默认 `new TextureStore()`)、`constructor(readonly state: ICoreState)` 与 `new AssetBuilder(this, state)`;`tileStore` / `tilesetStore` / `imageStore` / `assetStore` 等既有成员与 `pipe` / `tracked` 调用未动
+ - `builder.ts` 的 `AssetBuilder` 与 `TrackedAssetData` 各含 `readonly state: ICoreState` 形参,`new TrackedAssetData(materials, this, state)` 就位,`super(0)` 仍是 `TrackedAssetData` 构造体第一条语句
+ - `autotile.ts` 构造器为 `(readonly manager: ITextureManager, readonly state: ICoreState)`
+ - `client.ts:84` 为 `new MaterialManager(this)`、`client.ts:85` 为 `new AutotileProcessor(this.materials, this)`;`renderer.ts:234` 为 `new AutotileProcessor(manager, manager.state)`;三处均为单行或已按仓库惯例加 `// prettier-ignore`(并在 SUMMARY 记录取舍)
+ - `client-base/package.json` 的 `dependencies` 含 `"@user/data-state": "workspace:*"`;`pnpm-lock.yaml` 未被改动、未执行任何安装
+ - 三个 client-base 文件对 `@user/data-state` 只引入 `ICoreState`(无 `import type`、无 `state` 全局单例 import);本任务未引入 `as`、未取全局单例(D-23)
+ - 五条 `` 全部转绿(tracer 类型门禁:C 类归零 / material 归属 = 基线减 9 / 总行数 ≤ 基线减 9;绑定门禁;eslint;CRLF)
+ - 本任务**未**触碰 `getOffsetPool`、`cache.ts`、`hero.ts` / `door.ts` / `vertex.ts`、`material/types.ts`、`client-base/src/types.ts`;未提交任何改动
+
+ `state` 经构造器从 `ClientCore` 注入 `MaterialManager` → `AssetBuilder` → `TrackedAssetData` 并注入 `AutotileProcessor`(渲染端侧从 `manager.state` 取值),`MaterialManager.textures` 就位,`client-base/package.json` 补上 `@user/data-state` 声明,C 类 9 条诊断消除且总行数下降 9 条,五条机器门禁转绿
+
+
+
+ Task 2:D-34(A 类)—— 删除 `MaterialManager` 的 big-image 实现面,把 7 处消费者调用改为 `getTile`,删除 `getOffsetPool` 的 big-image 偏移收集
+ packages-user/client-base/src/material/manager.ts, packages-user/client-modules/src/render/map/renderer.ts, packages-user/client-modules/src/render/map/vertex.ts, packages-user/client-modules/src/render/map/extension/door.ts, packages-user/client-modules/src/render/map/extension/hero.ts
+
+ - packages-user/client-base/src/material/manager.ts(Task 1 产出的现状:import `:1-29`、字段 `:38-83`、`setDefaultFrame` `:231-237`、`setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` 五个方法 `:552-593`)
+ - packages-user/client-base/src/material/types.ts:196-226(`ITextureGetter` 现行成员集合,确认 `getTile` 是唯一对位)、`:260-271`(`ITextureManager` 现行成员集合,确认无 big-image 成员)
+ - packages-user/client-modules/src/render/map/extension/door.ts(整文件 73 行:`openDoor` `:30-43`、`closeDoor` `:45-56`)
+ - packages-user/client-modules/src/render/map/extension/hero.ts:225-251,350-390,415-438(`moveEntity` / `addFollower` / 跟随者循环三处取值点;`:238` / `:363` / `:423` 的 `state.roleFace.getFaceOf` 属 D-28 保留)
+ - packages-user/client-modules/src/render/map/renderer.ts:640-690(`getOffsetPool` 现行实现,`:651-657` 为待删的 big-image 循环)、`:1238-1278`(`useTileBackground`,`:1250` 为待改取值点)
+ - packages-user/client-modules/src/render/map/vertex.ts:530-600(`updateVertexArray`,`:546` 为待改取值点;其后 `tile.cls` / `tile.texture.render()` / `tile.frames` / `tile.offset` 的用法必须保持不变)
+ - .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md 的 `## A 已移除符号的剩余引用` 与 `## D big-image 残留(实现与消费者)`(本任务的两张事实表)
+ - packages-user/data-common/src/common/mover.ts(若需确认 `MovingBlock.setTexture` 的形参类型——通过 `render/map/moving.ts` 的 `IMovingBlock`)
+
+
+ (1) **`manager.ts` 的 big-image 删除(逐处,按删除前行号)**:
+ (1a) `:20` 从 `./types` 的 import 列表中删除 `IBigImageReturn,` 一行(其余导入名全部保留)。
+ (1b) `:43` 删除 `readonly bigImageStore: ITextureStore = new TextureStore();`(连其前后空行按上下文处理,保持文件中 store 字段块整齐)。
+ (1c) `:55-56` 删除 `/** 大怪物数据 */` 注释与其下 `readonly bigImageData: Map = new Map();`(成员连注释整体删除)。
+ (1d) `:76-77` 删除 `/** 大怪物贴图的标识符 */` 注释与其下 `private bigImageId: number = 0;`(同上)。
+ (1e) `setDefaultFrame`:删除 `const bigImageData = this.bigImageData.get(identifier); if (bigImageData) { bigImageData.defaultFrame = defaultFrame; }` 三行,方法只保留 `this.defaultFrames.set(identifier, defaultFrame);`(接口方法本体保留,耦合属 `#04-06-D-04` 残留)。**不得**改写方法签名与其既有 jsDoc。
+ (1f) `:552-593` 删除五个方法整体(含各自 jsDoc 与空行):`setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage`。**不得**删除或改写其后的 `assetContainsTexture` / `getTextureAsset`。
+ (1g) 删除后自查:`manager.ts` 内不得再出现任何 `bigImage` 记号;`IMaterialFramedData` / `ITexture` / `BlockCls` / `TextureStore` / `ITextureStore` 仍被其它成员使用(不得顺手删除这些 import);`getTile` / `getTileByAlias` / `getTileset` / `getImage` 等取值方法**一字不动**。
+ (2) **7 处消费者调用改为 `getTile(...)`(成员名替换,其余一字不动)**:
+ (2a) `render/map/extension/door.ts:36`:`const data = this.renderer.manager.getIfBigImage(num);` → `const data = this.renderer.manager.getTile(num);`(`openDoor`)。
+ (2b) `render/map/extension/door.ts:46`:同上改写(`closeDoor`)。
+ (2c) `render/map/extension/hero.ts:239-241`:`this.renderer.manager.getIfBigImage(nextTile?.identifier ?? block.tile)` → `this.renderer.manager.getTile(nextTile?.identifier ?? block.tile)`(`moveEntity`;保持现有换行形态,仅换成员名)。
+ (2d) `render/map/extension/hero.ts:364`:`getIfBigImage(faced?.face ?? image)` → `getTile(faced?.face ?? image)`(`addFollower`)。
+ (2e) `render/map/extension/hero.ts:428-430`:`getIfBigImage(nextFace.identifier)` → `getTile(nextFace.identifier)`(跟随者循环)。
+ (2f) `render/map/renderer.ts:1250`:`const tex = this.manager.getIfBigImage(tile);` → `const tex = this.manager.getTile(tile);`(`useTileBackground`)。
+ (2g) `render/map/vertex.ts:546`:`const tile = this.renderer.manager.getIfBigImage(num);` → `const tile = this.renderer.manager.getTile(num);`(`updateVertexArray`)。
+ **不得**改动这 7 处周围的分支结构、变量名、注释、`logger` 调用与后续用法(`status.useSpecifiedFrame` / `moving.useSpecifiedFrame` / `block.setTexture` / `tex.frames` / `tile.texture.render()` / `tile.offset` 等一律保持)。**不得**改动 `hero.ts:238` / `:363` / `:423` 的 `state.roleFace.getFaceOf(...)` 与 `import { state } from '@user/data-state'`(D-28)。
+ (3) **`renderer.ts` 的 `getOffsetPool` 清理**:删除 `:651-657` 的整段(`:651` 的行内注释「其他的都是 bigImage 了,直接遍历获取」+ `for (const identifier of this.manager.bigImageStore.keys()) { const data = this.manager.getBigImage(identifier); if (!data) continue; const offset = data.texture.width / data.frames; pool.add(offset); }`)。**保留** `const pool = new Set([32]);`、其后的「还有勇士图片」注释段与 `imageStore.values()` 循环、`pool.size > 64` 的 DEV 告警、`MAX_VERTEX_UNIFORM_VECTORS` 的错误分支与 `return [...pool];`。**不得**改方法名、签名与其它逻辑。
+ (4) **不做的事**:不碰 `cache.ts`(Task 3)、不碰 `material/types.ts` 与 `client-base/src/types.ts`、不碰 `action/move.ts`、不碰任何数据端文件与 `packages/**`、不改 `getOffsetPool` 之外的 renderer 逻辑、不新增注释、不新增 `as` 断言。
+ (5) **提交**:不单独提交(Task 3 整体原子提交)。若执行环境强制每任务提交,提交信息用 `refactor(04-07): drop big-image path from material manager`。
+ (6) 全程保持被改文件 CRLF;改完执行 `pnpm exec eslint --fix` 对这 5 个文件,再 `pnpm exec eslint` 必须 0 错误。
+
+ 本任务删除的是无写入者 / 无消费者的 dead big-image 实现面,并把 7 处调用换成与今日行为等价的 `getTile`(`getIfBigImage ≡ getTile`,因 `bigImageData` 的唯一写入者 `setBigImage` 消费者调用点为 0);`getOffsetPool` 只是不再收集已不存在的 big-image 偏移。回退只需还原 5 个文件的这一次删除,故为 `reversible`。
+
+ node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,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 MRE=/ITextureManager|IAutotileProcessor|IAssetBuilder|ITrackedAssetData|IBigImageReturn|getIfBigImage|getBigImage|isBigImage|setBigImage|getBigImageByAlias|bigImageStore|IMaterialManager|IMaterialGetter|IMaterialAliasGetter/;const isMat=l=>/client-base[\\/]src[\\/]material[\\/]/.test(l)||MRE.test(l);let t='';try{t=fs.readFileSync('.planning/phases/04-render-adaptation/04-07-SUMMARY.md','utf8')}catch(e){t=''}const bm=t.match(/([\s\S]*?)/);if(!bm){console.error('NO TYPE BASELINE');process.exit(1)}const dm=bm[1].match(/错误总行数:(\d+);material 归属行数:(\d+)/);if(!dm){console.error('NO BASELINE NUMBERS');process.exit(1)}const N=+dm[1],M=+dm[2];const mat=lines.filter(isMat);if(mat.length){console.error('MATERIAL DIAGNOSTICS REMAIN '+mat.length+'\n'+mat.join('\n'));process.exit(1)}const touched=['packages-user/client-base/src/material/manager.ts','packages-user/client-modules/src/render/map/renderer.ts','packages-user/client-modules/src/render/map/vertex.ts','packages-user/client-modules/src/render/map/extension/door.ts','packages-user/client-modules/src/render/map/extension/hero.ts'];const hit=lines.filter(l=>touched.some(p=>l.indexOf(p.replace(/\//g,'/'))>=0||l.indexOf(p)>=0));if(hit.length){console.error('DIAGNOSTICS IN TOUCHED FILES '+hit.length+'\n'+hit.join('\n'));process.exit(1)}if(lines.length>N-M){console.error('TOTAL INCREASED total='+lines.length+' allowed '+(N-M));process.exit(1)}console.log('OK A-class: material 0; touched 0; total '+lines.length+'<='+(N-M))"
+ non-zero exit, 或 stderr 含 "NO TYPE BASELINE" / "NO BASELINE NUMBERS"(SUMMARY 未按固定格式记录类型基线)/ "MATERIAL DIAGNOSTICS REMAIN"(material 归属诊断未清零)/ "DIAGNOSTICS IN TOUCHED FILES"(五个在范围文件仍有诊断,说明删除或改写引入了新问题)/ "TOTAL INCREASED"(错误总行数超过「基线总行数 - material 条数」)
+ node -e "const fs=require('fs');const F={man:'packages-user/client-base/src/material/manager.ts',ren:'packages-user/client-modules/src/render/map/renderer.ts',ver:'packages-user/client-modules/src/render/map/vertex.ts',doo:'packages-user/client-modules/src/render/map/extension/door.ts',her:'packages-user/client-modules/src/render/map/extension/hero.ts'};const S={};for(const k of Object.keys(F))S[k]=fs.readFileSync(F[k],'utf8');const left=Object.keys(S).filter(k=>/bigimage/i.test(S[k]));if(left.length){console.error('BIGIMAGE TOKENS REMAIN IN '+left.join(','));process.exit(1)}const cnt=(s,t)=>(s.match(new RegExp('getTile\\(','g'))||[]).length;if(cnt(S.doo)!==2||cnt(S.her)!==3||cnt(S.ren)!==1||cnt(S.ver)!==1){console.error('getTile( COUNT WRONG door='+cnt(S.doo)+' hero='+cnt(S.her)+' renderer='+cnt(S.ren)+' vertex='+cnt(S.ver));process.exit(1)}if(!/const pool = new Set\(\[32\]\);/.test(S.ren)||!/imageStore\.values\(\)/.test(S.ren)||!/return \[\.\.\.pool\];/.test(S.ren)){console.error('getOffsetPool TAIL LOGIC LOST');process.exit(1)}const sd=S.man.indexOf('setDefaultFrame(');if(sd<0){console.error('setDefaultFrame MISSING');process.exit(1)}const sdBody=S.man.slice(sd,S.man.indexOf('}',sd)+1);if(!/defaultFrames\.set\(identifier, defaultFrame\)/.test(sdBody)){console.error('setDefaultFrame BODY WRONG');process.exit(1)}const h=S.her;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 D34 deletions + 7 getTile + getOffsetPool cleaned')"
+ non-zero exit, 或 stderr 含 "BIGIMAGE TOKENS REMAIN IN"(五个在范围文件仍含 `bigimage` 记号)/ "getTile( COUNT WRONG"(7 处替换的分布不是 door 2 / hero 3 / renderer 1 / vertex 1)/ "getOffsetPool TAIL LOGIC LOST"(`getOffsetPool` 的其余偏移来源或返回被误删)/ "setDefaultFrame MISSING" / "setDefaultFrame BODY WRONG"(`setDefaultFrame` 被误删或其剩余本体不正确)/ "D28 STATE USAGE LOST"(存量 `state.roleFace` 用法被误删)
+ pnpm exec eslint packages-user/client-base/src/material/manager.ts packages-user/client-modules/src/render/map/renderer.ts packages-user/client-modules/src/render/map/vertex.ts packages-user/client-modules/src/render/map/extension/door.ts packages-user/client-modules/src/render/map/extension/hero.ts
+ non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用变量 / 未使用 import——务必确认删除 big-image 后无 import 变为未使用)
+ node -e "const fs=require('fs');const F=['packages-user/client-base/src/material/manager.ts','packages-user/client-modules/src/render/map/renderer.ts','packages-user/client-modules/src/render/map/vertex.ts','packages-user/client-modules/src/render/map/extension/door.ts','packages-user/client-modules/src/render/map/extension/hero.ts'];for(const f of F){const s=fs.readFileSync(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')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF)
+
+
+ - `manager.ts` 不再含 `IBigImageReturn` / `bigImageStore` / `bigImageData` / `bigImageId` / `setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage`(词边界判定),`setDefaultFrame` 本体只剩 `defaultFrames.set(identifier, defaultFrame)`;`getTile` 等取值方法与其既有 jsDoc 未动
+ - `door.ts` / `hero.ts` 的 7 处取值全部改为 `getTile(...)`(door 2 / hero 3),`renderer.ts:1250` 与 `vertex.ts:546` 同样改为 `getTile(...)`;这些调用点周围的分支、变量与后续用法未变
+ - `renderer.ts` 的 `getOffsetPool` 已删除 big-image 循环,保留 `new Set([32])`、`imageStore.values()` 循环、DEV 告警与 `return [...pool]`
+ - `hero.ts` 的 `state.roleFace.getFaceOf` 三处与 `import { state } from '@user/data-state'` 逐字保留(D-28);`action/move.ts`、`cache.ts`、`material/types.ts`、`client-base/src/types.ts`、任何数据端文件均未被本任务触碰
+ - 四条 `` 全部转绿:scoped 类型门禁(material 归属 0、五个在范围文件 0、总行数 ≤ 基线减 material 条数)、删除 / 替换门禁(`bigimage` 记号归零、`getTile(` 分布正确、`getOffsetPool` 尾部逻辑在、`setDefaultFrame` 正确、D-28 保留)、eslint 0 错误、CRLF
+ - 本任务**未**提交任何改动
+
+ `MaterialManager` 的 big-image 实现面全部删除、7 处消费者取值改为行为等价的 `getTile`、`getOffsetPool` 的 big-image 偏移收集删除,material 归属诊断清零且总行数降至 ≤ 基线减 19,D-28 保留项未动,四条机器门禁转绿
+
+
+
+ Task 3:D-36 —— 删除 `cache.ts` 的 legacy big-image 路径;原子提交全部 10 个文件;终验门禁(类型 / 静态 / 范围 / 并发基线 / eslint / CRLF)+ 人工复核 + `04-07-SUMMARY.md` 定稿
+ packages-user/client-modules/src/render/elements/cache.ts, .planning/phases/04-render-adaptation/04-07-SUMMARY.md
+
+ - packages-user/client-modules/src/render/elements/cache.ts(整文件 553 行:`ImageMapKeys` / `i()` `:7-12`;`RenderableDataBase` `:30-38`;`RenderableData` `:40-43`;`AutotileRenderable` `:45-49`;`calRenderableByNum` `:119-286` 内含待删的 `enemys` 声明 `:127`、`faceIds` / `bigImage` 解构 `:153`、`enemys` 特判 `:155-158`、`if (bigImage)` 分支 `:159-194`、`bigImage` 字面量 `:215` / `:243` / `:276`;`splitAutotiles` 与 `createCache` `:435-553`)
+ - packages-user/client-modules/src/render/elements/misc.ts:26,125 与 packages-user/client-modules/src/render/components/textboxTyper.ts:172(`RenderableData | AutotileRenderable` 的消费者,确认二者均**不读取** `.bigImage`,字段删除不产生新诊断)
+ - packages-user/client-modules/src/render/elements/index.ts(barrel:`export * from './cache';`)
+ - .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md 的 `## D big-image 残留` 的 `#04-06-D-11` / `#04-06-D-12`(`cache.ts` 的两条记录)
+ - .planning/phases/04-render-adaptation/04-07-SUMMARY.md(Task 1 写入的执行前核对 / `CONCURRENT-BASELINE` / `TYPE-BASELINE` 三段)
+ - .planning/phases/04-render-adaptation/04-07-PLAN.md(本任务的全部门禁定义与成功标准)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-31 / D-33 / D-34 / D-35 / D-36 / D-37 的裁定原文,用于 SUMMARY 的决策段)
+
+
+ (1) **`cache.ts` 的 legacy big-image 路径整体删除(逐处,按删除前行号)**:
+ (1a) `RenderableDataBase`:删除 `:35-36` 的 `/** 是否是大怪物 */` 注释与 `bigImage: boolean;`(成员连注释整体删除)。
+ (1b) `AutotileRenderable`:删除 `:48` 的 `bigImage: false;`。
+ (1c) `calRenderableByNum` 的额外素材分支(`:138-145`):删除 `:143-144` 的 `bigImage: false` 属性行(保留 `autotile: false` 与其前各项)。
+ (1d) 删除 `:127` 的 `const enemys = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;`(该局部变量只服务于 `:157` 的 big-image 特判,删除后必为未使用,eslint `no-unused-vars` 为 error)。
+ (1e) 删除 `:153` 的 `let { faceIds, bigImage } = data;`(保留 `:154` 的 `const { cls, id, animate } = data;`)。
+ (1f) 删除 `:155-158` 的 `if (cls === 'enemys' || cls === 'enemy48') { // 怪物需要特殊处理,因为它的大怪物信息不在 maps 里面 ({ bigImage, faceIds } = enemys[id as EnemyIds]); }` 整块(含其行内注释与前后空行)。
+ (1g) 删除 `:159-194` 的整个 `if (bigImage) { const image = core.material.images.images[bigImage]; ... this.renderable.set(num, data); return data; }` 分支(含 `logger.warn(10, id)`、`faceIds` 行序计算、`RenderableData` 构造与 `return`)。
+ (1h) enemy48 / npc48 分支(`:195-219`):只删除 `:215` 的 `bigImage: true` 属性行(**保留**该分支的既有行内注释、`icons[cls][id]` 计算、`RenderableData` 构造与 `return`——它仍负责 48 尺寸怪物的渲染,不属于 big-image 数据路径)。
+ (1i) 自动元件分支(`:221-247`):只删除 `:243` 的 `bigImage: false,` 属性行。
+ (1j) 普通分支(`:248-281`):只删除 `:276` 的 `bigImage: false,` 属性行。
+ (1k) **自查**:`cache.ts` 内不得再出现任何 `bigimage` 记号(大小写不敏感);删除后 `map` / `icons` / `data` / `cls` / `id` / `animate` / `logger` / `TextureCache` 其余成员与 `splitAutotiles` / `createCache` / `getAutotileIndices` / `getRepeatMap` 一律保持原样;**不得**改写任何既有注释文本(除随待删成员一起删除者)、不得新增注释、不得新增 / 删除其它成员。
+ (1l) **行为影响须写入 SUMMARY**:删除 `if (bigImage)` 分支后,原本走该分支的 legacy 大图怪物会落到后续分支(`enemy48` / `npc48` 分支或普通图块分支)渲染(D-31「big-image 概念删除、旧兼容可痛丢弃」);此变化**未经运行验证**(无渲染设施),须如实记录。
+ (2) **原子提交**:`git add` 仅本计划的 10 个文件——`packages-user/client-base/package.json`、`packages-user/client-base/src/material/{manager,autotile,builder}.ts`、`packages-user/client-modules/src/client.ts`、`packages-user/client-modules/src/render/map/{renderer.ts,vertex.ts}`、`packages-user/client-modules/src/render/map/extension/{door.ts,hero.ts}`、`packages-user/client-modules/src/render/elements/cache.ts`;提交信息 `refactor(04-07): adapt material interfaces (drop big-image, inject state)`。**不得**把用户的并发未提交改动纳入提交、不得把 `.planning/**` 混入该提交。
+ (3) **范围门禁(基线感知,先跑,防止越界改动)**:把当前 `git status --porcelain` 的**生产路径**(`^packages(-user)?/`)与 SUMMARY 中 `CONCURRENT-BASELINE` 段记录的基线条目做差集;只有**基线之外**、且不属于上述 10 个在范围文件的生产改动才算越界。特别断言 `action/move.ts`(D-19)、`packages-user/client-base/src/load/**`、`client-base/src/material/types.ts`、`client-base/src/types.ts`、`data-common` / `data-base` / `data-system` / `data-state`、`packages/**`、`src/**` 均无基线之外的新改动。若出现基线之外的第九 / 第十一个生产文件改动:停止、汇报、修订计划(不得自行扩面)。
+ (4) **并发改动基线门禁**:用与 Task 1 步骤 (0c) 完全相同的固定命令重算(排除本计划在范围文件的四类前缀与 `.planning/phases/04-render-adaptation/`),断言 porcelain 条目集合与内容摘要 `sha256` 均与 SUMMARY 的 `CONCURRENT-BASELINE` 段记录一致。
+ (5) **复跑全部机器门禁**:scoped 类型门禁(material 归属 0、10 个在范围文件诊断 0、总行数 ≤ 基线总行数减 material 条数)、静态门禁 A(D-34 / D-35 / D-36 / D-37 全量)、静态门禁 B(D-28 保留 + `cache.ts` 删除面 + `getOffsetPool` 保留面)、`pnpm exec eslint` 对 9 个被改 TS 文件 0 错误、CRLF 门禁。
+ (6) **人工复核(写进 SUMMARY 的「人工复核」段,逐条给结论)**:① D-34 的 7 处调用逐条对位(旧 `getIfBigImage` → 新 `getTile`)并复核「行为保持」论据(`bigImageData` 的唯一写入者 `setBigImage` 消费者调用点为 0,现场用 `git grep` 复验);② `getOffsetPool` 删除后池的来源只剩 `ImageStore` 与常量 32,说明该变化对 shader uniform 池的语义(不再有 big-image 偏移);③ `MaterialManager` 的五个 big-image 方法与三个字段确实全部删除、`setDefaultFrame` 本体与签名未变;④ D-35 的注入链逐环节对位(`client.ts:84` 的 `this` → `manager.ts` 构造器 → `manager.ts:86` → `builder.ts:28`;`client.ts:85`;`renderer.ts:234` 的 `manager.state`)并说明**未引入任何全局单例**(D-23);⑤ `textures` 的承担 store 与 Task 0 用户裁定一致(并在 SUMMARY 明写「本次范围内无写入者」的事实);⑥ D-36 的 `cache.ts` 11 处命中逐条对位(字段 / 字面量 / 解构 / 特判 / 分支 / 未使用声明),并记录其行为影响(大图怪物落回后续分支)与「未经运行验证」;⑦ D-37 的声明就位、`pnpm-lock.yaml` 未动、未执行安装;⑧ D-33 范围核对:`git diff --stat` 只含 10 个文件、其余约 180 条诊断未被触碰;⑨ D-28 保留:`hero.ts` 的 `state.roleFace.getFaceOf` 三处与 `state` import 未动;⑩ 运行时 UAT 不可得的理由已写明(无测试设施、不启动渲染)。
+ (7) **SUMMARY 定稿**:写 `.planning/phases/04-render-adaptation/04-07-SUMMARY.md`,含:10 个文件的改动清单与职责;D-33..D-37 的逐条处置对照表(含逐处替换映射与「`getIfBigImage ≡ getTile`」论据、`textures` 承担 store 的用户裁定、`cache.ts` 的行为影响);执行前核对结论(带 `file:line`);`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`;`TYPE-BASELINE` 段(含两个基线数字与 material 归属诊断逐行);完工后的类型门禁实测值(material 归属 0、总行数实测值、10 个在范围文件 0);全部机器门禁的实际输出摘要;人工复核十条;未实现 / 显式延后项清单;REND-01 / REND-02 仍为 Pending 与运行时 UAT 不可得的明确声明。全文 CRLF。
+ (8) **提交 SUMMARY**:`docs(04-07): record execution state`,仅含 `04-07-SUMMARY.md`。
+ (9) **不做的事**:不接线任何被注释的接线点、不改 `action/move.ts`、不新增测试文件、不改任何数据端文件与 `packages/**`、不修任何无关诊断、不重建 `.planning/graphs/*`、不修改 `04-01..04-06` 的任何产物。
+
+ 本任务删除 `cache.ts` 的 legacy big-image 渲染路径——这是一处**运行时可见**的行为变化(原本走该分支的 legacy 大图怪物会落回 `enemy48` / `npc48` 或普通图块分支渲染)。删除依据是用户 D-36 的明确裁定与 D-31「旧兼容可痛丢弃」,但该语义变化一旦被后续消费者依赖便不易收回,且本计划无运行时验证设施,故记为 `costly`。
+
+ node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,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 MRE=/ITextureManager|IAutotileProcessor|IAssetBuilder|ITrackedAssetData|IBigImageReturn|getIfBigImage|getBigImage|isBigImage|setBigImage|getBigImageByAlias|bigImageStore|IMaterialManager|IMaterialGetter|IMaterialAliasGetter/;const isMat=l=>/client-base[\\/]src[\\/]material[\\/]/.test(l)||MRE.test(l);let t='';try{t=fs.readFileSync('.planning/phases/04-render-adaptation/04-07-SUMMARY.md','utf8')}catch(e){t=''}const bm=t.match(/([\s\S]*?)/);if(!bm){console.error('NO TYPE BASELINE');process.exit(1)}const dm=bm[1].match(/错误总行数:(\d+);material 归属行数:(\d+)/);if(!dm){console.error('NO BASELINE NUMBERS');process.exit(1)}const N=+dm[1],M=+dm[2];const mat=lines.filter(isMat);if(mat.length){console.error('MATERIAL DIAGNOSTICS REMAIN '+mat.length+'\n'+mat.join('\n'));process.exit(1)}const touched=['packages-user/client-base/src/material/manager.ts','packages-user/client-base/src/material/autotile.ts','packages-user/client-base/src/material/builder.ts','packages-user/client-modules/src/client.ts','packages-user/client-modules/src/render/map/renderer.ts','packages-user/client-modules/src/render/map/vertex.ts','packages-user/client-modules/src/render/map/extension/door.ts','packages-user/client-modules/src/render/map/extension/hero.ts','packages-user/client-modules/src/render/elements/cache.ts'];const hit=lines.filter(l=>touched.some(p=>l.indexOf(p)>=0));if(hit.length){console.error('DIAGNOSTICS IN TOUCHED FILES '+hit.length+'\n'+hit.join('\n'));process.exit(1)}if(lines.length>N-M){console.error('TOTAL INCREASED total='+lines.length+' allowed '+(N-M));process.exit(1)}console.log('OK final type gate: material 0; touched 0; total '+lines.length+'<='+(N-M)+' (baseline '+N+'/'+M+')')"
+ non-zero exit, 或 stderr 含 "NO TYPE BASELINE" / "NO BASELINE NUMBERS" / "MATERIAL DIAGNOSTICS REMAIN" / "DIAGNOSTICS IN TOUCHED FILES" / "TOTAL INCREASED"(终验类型门禁未过)
+ node -e "const fs=require('fs'),{execSync}=require('child_process');let g='';try{g=execSync('git grep -i bigimage -- packages-user',{encoding:'utf8'})}catch(e){g=''}if(g.trim().length){console.error('BIGIMAGE TOKENS REMAIN IN packages-user\n'+g);process.exit(1)}const rd=f=>fs.readFileSync(f,'utf8');const mg=rd('packages-user/client-base/src/material/manager.ts'),au=rd('packages-user/client-base/src/material/autotile.ts'),bu=rd('packages-user/client-base/src/material/builder.ts'),ca=rd('packages-user/client-modules/src/render/elements/cache.ts'),ren=rd('packages-user/client-modules/src/render/map/renderer.ts'),her=rd('packages-user/client-modules/src/render/map/extension/hero.ts');const need=[['textures',/readonly textures:\s*ITextureStore/,mg],['MaterialManager state',/constructor\(readonly state:\s*ICoreState\)/,mg],['AutotileProcessor state',/readonly state:\s*ICoreState/,au],['AssetBuilder state',/readonly state:\s*ICoreState/,bu],['TrackedAssetData state',/readonly state:\s*ICoreState/,bu],['getOffsetPool base',/const pool = new Set\(\[32\]\);/,ren]].filter(n=>!n[1].test(n[2])).map(n=>n[0]);if(need.length){console.error('REQUIRED BINDINGS MISSING '+need.join(','));process.exit(1)}if(!/interface RenderableDataBase/.test(ca)||/bigImage/.test(ca)){console.error('CACHE CLEANUP INCOMPLETE');process.exit(1)}if(/enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80/.test(ca)){console.error('UNUSED enemys DECLARATION REMAINS');process.exit(1)}const gf=(her.match(/state\.roleFace\.getFaceOf/g)||[]).length;if(gf<3){console.error('D28 STATE USAGE LOST getFaceOf='+gf);process.exit(1)}const pkg=JSON.parse(fs.readFileSync('packages-user/client-base/package.json','utf8'));if(!pkg.dependencies||pkg.dependencies['@user/data-state']!=='workspace:*'){console.error('D37 DEPENDENCY MISSING');process.exit(1)}console.log('OK final static gates D34 D35 D36 D37')"
+ non-zero exit, 或 stderr 含 "BIGIMAGE TOKENS REMAIN IN packages-user"(`packages-user` 内仍有 big-image 记号)/ "REQUIRED BINDINGS MISSING"(`textures` / 四个 `state` / `getOffsetPool` 基础逻辑有缺)/ "CACHE CLEANUP INCOMPLETE" / "UNUSED enemys DECLARATION REMAINS"(`cache.ts` 的 legacy 路径未删净或遗留未使用声明)/ "D28 STATE USAGE LOST" / "D37 DEPENDENCY MISSING"
+ 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-base/package.json','packages-user/client-base/src/material/manager.ts','packages-user/client-base/src/material/autotile.ts','packages-user/client-base/src/material/builder.ts','packages-user/client-modules/src/client.ts','packages-user/client-modules/src/render/map/renderer.ts','packages-user/client-modules/src/render/map/vertex.ts','packages-user/client-modules/src/render/map/extension/door.ts','packages-user/client-modules/src/render/map/extension/hero.ts','packages-user/client-modules/src/render/elements/cache.ts'];let t='';try{t=fs.readFileSync('.planning/phases/04-render-adaptation/04-07-SUMMARY.md','utf8')}catch(e){t=''}const bm=t.match(/([\s\S]*?)/);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')"
+ non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 10 个在范围文件之外、且不在并发基线内的生产文件改动,含 `action/move.ts`、`client-base/src/load/**`、`material/types.ts`、`client-base/src/types.ts`、任何数据端包或 `packages/**` 的新改动;基线内的用户并发改动不触发)/ "GIT STATUS FAILED" / "NO CONCURRENT BASELINE"
+ node -e "const fs=require('fs'),c=require('crypto'),{execSync}=require('child_process');const keep=l=>!/packages-user\/client-base\/(package\.json|src\/material\/)/.test(l)&&!/packages-user\/client-modules\/src\/(client\.ts|render\/map\/renderer\.ts)/.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');let t='';try{t=fs.readFileSync('.planning/phases/04-render-adaptation/04-07-SUMMARY.md','utf8')}catch(e){t=''}const bm=t.match(/([\s\S]*?)/);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)"
+ non-zero exit, 或 stderr 含 "NO BASELINE DIGEST" / "BASELINE ENTRY SET CHANGED" / "BASELINE DIGEST CHANGED"(用户并发的未提交改动被本计划回滚 / 暂存 / 提交 / 修改)
+ pnpm exec eslint packages-user/client-base/src/material/manager.ts packages-user/client-base/src/material/autotile.ts packages-user/client-base/src/material/builder.ts packages-user/client-modules/src/client.ts packages-user/client-modules/src/render/map/renderer.ts packages-user/client-modules/src/render/map/vertex.ts packages-user/client-modules/src/render/map/extension/door.ts packages-user/client-modules/src/render/map/extension/hero.ts packages-user/client-modules/src/render/elements/cache.ts
+ non-zero exit 或输出含 "error"(ESLint / Prettier 违规;`cache.ts` 删除后若有 import / 局部变量变为未使用即为此类失败)
+ node -e "const fs=require('fs');const F=['packages-user/client-base/src/material/manager.ts','packages-user/client-base/src/material/autotile.ts','packages-user/client-base/src/material/builder.ts','packages-user/client-modules/src/client.ts','packages-user/client-modules/src/render/map/renderer.ts','packages-user/client-modules/src/render/map/vertex.ts','packages-user/client-modules/src/render/map/extension/door.ts','packages-user/client-modules/src/render/map/extension/hero.ts','packages-user/client-modules/src/render/elements/cache.ts'];for(const f of F){const s=fs.readFileSync(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')"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」;SUMMARY 不在本机器门禁范围——工具写出的 markdown 不施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)
+ node -e "const fs=require('fs');let t='';try{t=fs.readFileSync('.planning/phases/04-render-adaptation/04-07-SUMMARY.md','utf8')}catch(e){t=''}const need=['执行前核对','CONCURRENT-BASELINE:START','TYPE-BASELINE:START','人工复核','REND-01','REND-02','Pending','textures','ICoreState','getIfBigImage','getTile','getOffsetPool','cache.ts','@user/data-state','D-34','D-35','D-36','D-37','基线摘要'];const miss=need.filter(x=>!t.includes(x));if(miss.length){console.error('SUMMARY MISSING SECTIONS '+miss.join(','));process.exit(1)}const dir='.planning/phases/04-render-adaptation';const plans=fs.readdirSync(dir).filter(f=>/-PLAN\.md$/.test(f)).sort();const extra=plans.filter(p=>!/^04-0[1-7]-PLAN\.md$/.test(p));if(extra.length){console.error('UNEXPECTED PLAN FILES '+extra.join(','));process.exit(1)}console.log('OK summary completeness; plans '+plans.join(','))"
+ non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线段 / 人工复核 / 需求 Pending 声明 / D-33..D-37 关键项 / 运行时不可得说明)/ "UNEXPECTED PLAN FILES"(phase 目录内出现了预期之外的 `-PLAN.md`)
+
+
+ - `cache.ts` 不再含 `bigImage`(大小写不敏感):`RenderableDataBase.bigImage`、`AutotileRenderable` 的 `bigImage: false`、5 处对象字面量、`faceIds` / `bigImage` 解构与 `enemys` 特判、`if (bigImage)` 分支、`enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80` 声明全部消失;`splitAutotiles` / `createCache` / `TextureCache` 其余成员与既有注释未被误改
+ - `enemy48` / `npc48` 分支与普通分支、自动元件分支的渲染逻辑保留(只去掉 `bigImage` 属性行),消费者 `elements/misc.ts` / `render/components/textboxTyper.tsx` 无需改动且未产生新诊断
+ - 10 个在范围文件已一次原子提交(`refactor(04-07)`),提交不含用户并发改动、不含 `.planning/**`
+ - 终验类型门禁通过:material 归属 0、9 个在范围 TS 文件诊断 0、错误总行数 ≤ 基线总行数减 material 条数(规划日 199 / 19 → 期望 ≤ 180)
+ - 静态门禁(D-34 / D-35 / D-36 / D-37 全量)、范围门禁(基线感知)、并发改动基线门禁、eslint 0 错误、CRLF 门禁全部转绿
+ - `04-07-SUMMARY.md` 含执行前核对结论(带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段(含两个基线数字与 material 归属诊断逐行)、D-33..D-37 处置对照、`textures` 承担 store 的用户裁定、`cache.ts` 行为影响与「未经运行验证」、门禁输出摘要、人工复核十条、未实现 / 延后项清单、REND-01 / REND-02 仍 Pending 与运行时 UAT 不可得的明确说明;并以 `docs(04-07)` 提交(仅含该 SUMMARY)
+
+ `cache.ts` 的 legacy big-image 路径删净、10 个文件原子提交、material 归属诊断清零且总行数不增加、多类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(`textures` 写入侧、`.planning/graphs/*` 重建、其余渲染适配与 REND-02 双布局)
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| 渲染端系统层契约面 → 下游调用方 | `MaterialManager` / `AutotileProcessor` / `AssetBuilder` 是 `@user/client-base` 的**已导出类**,其构造器签名与 `MaterialManager` 的新成员 `textures` 属公共面;本计划在 Task 0 获批后各增一个 `state` 形参并新增 `textures`,误改其它成员或误删既有成员会直接扩大破坏面 |
+| 数据端 Layer 3 → 渲染端系统层 | 渲染端系统层新增对 `ICoreState` 的类型依赖(`@user/data-state`);接口签名视为稳定、实现可变(D-05),一旦被用户并发改动,本计划即按错误形状落地 |
+| 全局单例 → 新增代码 | D-23 禁止新增 / 后续代码取 `client` / `state` 全局单例;本计划的 `state` 只能经构造器注入(`ClientCore` 的 `this`、`renderer.ts` 的 `manager.state`),若执行者改用单例即违反用户裁定 |
+| 材质取值语义 → 运行时渲染 | 7 处 `getIfBigImage` → `getTile` 与 `cache.ts` 大图分支的删除都改变运行时取值来源;无渲染验证设施时,「编译通过」易被误读为「渲染行为不变」 |
+| 范围 → 仓库既有问题 | 仓库有约 180 条与 material 无关的既有诊断;D-33 明令不追,若执行者「顺手」处理即越界并破坏与用户既有工作的隔离 |
+| 执行者 → 仓库工作树 | 用户会并发手工重构数据端与加载系统;本计划的 10 个文件之外不得有任何生产改动,且不得触碰用户的未提交改动 |
+| 依赖声明 → lockfile | 加 `client-base/package.json` 声明会影响 `pnpm-lock.yaml` 的一致性;本计划按 D-37 只补声明、不安装、不同步 lockfile,须显式声明该事实 |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-04-07-01 | Tampering | `MaterialManager` / `AutotileProcessor` / `AssetBuilder` 公共构造器与 `MaterialManager.textures` | high | mitigate | 三处构造器签名变更与 `textures` 承担 store 在 Task 0 `blocking-human` 关卡单独列为待批准项;`files_modified` 不含 `material/types.ts` / `client-base/src/types.ts`;Task 1 静态门禁逐一断言 `textures` / 四个 `state` / 五处构造点就位,prohibitions 明列不得新增 / 删除接口成员且不得自行扩面到接口文件 |
+| T-04-07-02 | Elevation of Privilege | 执行前置关卡(AGENTS.md 要求的第一处 block 点) | high | mitigate | Task 0 为 `checkpoint:decision` + `gate="blocking-human"` 且本计划 `autonomous: false`;Task 0 的 action ⑦/⑧ 与 acceptance_criteria 明示未收到「可以执行」四个字即必须停止、不派发执行子代理;`yolo` / `auto_advance` 不构成放行理由 |
+| T-04-07-03 | Tampering | D-23(不得新增全局单例) | high | mitigate | Task 1 静态门禁断言三个 client-base 文件对 `@user/data-state` 只引入 `ICoreState`、无 `state` 全局单例 import、无 `from '../core'` 类单例引入;`renderer.ts` 侧断言使用 `manager.state`;Task 0 汇报中明示注入链 |
+| T-04-07-04 | Repudiation | 删除面 / 改写语义的可追溯性 | medium | mitigate | 逐处替换映射表(含「`getIfBigImage ≡ getTile`」的行为保持论据)在 Task 0 汇报并经用户确认;Task 2 静态门禁断言 `bigimage` 记号在五个文件内归零、`getTile(` 分布为 door 2 / hero 3 / renderer 1 / vertex 1、`getOffsetPool` 尾部逻辑保留、`setDefaultFrame` 本体正确;Task 3 断言 `packages-user` 全量归零并把行为影响写入 SUMMARY 人工复核段 |
+| T-04-07-05 | Spoofing | 「编译通过」被当作「运行时可用 / 行为不变」 | high | mitigate | truths / assumptions 明写运行时 UAT 不可得(无测试设施、不启动渲染);`cache.ts` 大图怪物落回后续分支与 `getOffsetPool` 池内容变化被列为**未经运行验证**的行为变化;SUMMARY 必须含运行时不可得说明(Task 3 门禁断言 `Pending` / `cache.ts` 等关键词);严禁以不可运行的门禁冒充通过 |
+| T-04-07-06 | Elevation of Privilege | 范围蔓延(追无关诊断、改接口文件、动 `action/move.ts` / `load/**` / 数据端 / `packages/**` / `src/**`、重建 `.planning/graphs/*`) | high | mitigate | 范围门禁(基线感知)只放行 10 个在范围文件 + 基线内并发改动;Task 3 类型门禁要求总行数 ≤ 基线减 material 条数(迫使无关诊断保持原状);prohibitions 逐条列明;需要第十一个文件时强制停下汇报并修订计划 |
+| T-04-07-07 | Tampering | 用户并发的未提交改动 | high | mitigate | Task 1 步骤 (0c) 记录 porcelain 条目 + 文件内容 `sha256` 摘要(写入 SUMMARY 的 `CONCURRENT-BASELINE` 段);Task 3 用同一固定命令复算并断言集合与摘要未变;prohibitions 明确不得回滚 / 暂存 / 提交 / 修改 |
+| T-04-07-08 | Tampering | `pnpm-lock.yaml` 一致性 | medium | accept | D-37 只裁「补依赖声明」,本计划据此只改 `client-base/package.json` 并**显式声明** lockfile 未同步(`pnpm install --frozen-lockfile` 会失败,需用户自行同步);该事实在 Task 0 与 SUMMARY 中原样告知,不静默制造不可复现的安装状态 |
+| T-04-07-09 | Information Disclosure | 改动引用的接口名、文件行号与类型签名 | low | accept | 改动只含渲染端源码引用,不含凭据 / 密钥 / 用户数据;无需额外缓解 |
+| T-04-07-SC | Tampering | npm / pnpm 依赖安装 | high | accept | 本计划**零安装**、不改 `pnpm-lock.yaml`(只在 `client-base/package.json` 补一条 workspace 依赖声明,该包已是 workspace 成员且现存的 import 已可解析);若执行中出现安装需求,立即停止并汇报(不擅自安装、不评估包合法性) |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题(A / C / D / E 四类 + 199 / 19 总账)/ 大致解决方案与逐处替换映射表 / 成员命名清单 / 三个已导出类构造器签名变更 / `textures` 承担 store 的候选与建议默认 / `cache.ts` 删除范围与行为影响 / `pnpm-lock.yaml` 处置 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
+1. 执行前核对与基线(Task 1 步骤 0):十项接口 / 构造面复核结论(带 `file:line`)、类型门禁基线(`TYPE-BASELINE` 段:错误总行数与 material 归属行数两个数字 + material 归属诊断逐行)、并发改动基线(`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`)写入 SUMMARY;任一与规划不一致即停止并修订计划;**不得沿用 04-04 / 04-06 SUMMARY 的旧并发列表**。
+2. scoped 类型门禁(Task 1 / 2 / 3):`pnpm exec vue-tsc --noEmit` 过滤后要求——Task 1 完工时 C 类三形态诊断(`incorrectly implements interface` / `Property 'state' is missing` / `missing the following properties from type 'ITextureManager'`)为 0、material 归属数 == 基线减 9、且余下 material 归属全部命中 `bigimage`(大小写不敏感)、总行数 ≤ 基线减 9;Task 2 / Task 3 完工时 material 归属数为 0、在范围文件诊断为 0、总行数 ≤ 基线总行数减 material 条数(规划日 199 / 19 → 期望 ≤ 180)。整仓其余既有诊断不属本计划,不得以「整仓非 0 退出」判定失败,也不得漏判在范围文件的新诊断。
+3. 静态门禁(Task 1 / 2 / 3):① `MaterialManager` 含 `readonly textures: ITextureStore`;② 四个实现类各含 `readonly state: ICoreState`(或构造器形参形式);③ 五处构造点就位(`new MaterialManager(this)` / `new AutotileProcessor(this.materials, this)` / `new AutotileProcessor(manager, manager.state)` / `new AssetBuilder(this, state)` / `new TrackedAssetData(materials, this, state)`);④ 三个 client-base 文件的 `@user/data-state` import 只引入 `ICoreState`,无 `import type`、无 `state` 单例 import;⑤ `client-base/package.json` 的 `dependencies['@user/data-state'] === 'workspace:*'`;⑥ `git grep -i bigimage -- packages-user` 无输出;⑦ 7 处 `getTile(` 分布为 door 2 / hero 3 / renderer 1 / vertex 1;⑧ `getOffsetPool` 保留 `const pool = new Set([32]);`、`imageStore.values()` 循环与 `return [...pool];`;⑨ `setDefaultFrame` 本体只剩 `defaultFrames.set(...)`;⑩ `cache.ts` 保留 `interface RenderableDataBase` 且无 big-image 记号、`enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80` 声明已消失。
+4. D-28 保留门禁(Task 2 / 3):`hero.ts` 的 `state.roleFace.getFaceOf` 计数 ≥ 3 且 `from '@user/data-state'` 仍在(这是**正向**门禁:D-28 要求保留存量用法)。
+5. 范围门禁(Task 3,基线感知):将当前 `git status --porcelain` 的生产路径(`^packages(-user)?/`)与 `CONCURRENT-BASELINE` 段做差集;只有基线之外、且不属于 10 个在范围文件的生产改动才判越界;`action/move.ts`、`client-base/src/load/**`、`client-base/src/material/types.ts`、`client-base/src/types.ts`、数据端各包、`packages/**`、`src/**` 均无基线之外的新改动。
+6. 并发改动基线门禁(Task 3):排除本计划在范围文件的四类前缀与 `.planning/phases/04-render-adaptation/` 后,porcelain 条目集合与内容 `sha256` 与 SUMMARY 的 `CONCURRENT-BASELINE` 段记录一致(用户改动未被回滚 / 暂存 / 提交 / 修改)。
+7. ESLint / CRLF 门禁(Task 1 / 2 / 3):`pnpm exec eslint` 对全部被改 TS 文件(Task 1 五个、Task 2 五个、Task 3 九个)0 错误;9 个源文件无孤立 LF(dev.md「换行使用 CRLF 格式」)。SUMMARY 不施加该硬门禁(工具写出的 markdown),改由人工复核遵循仓库 markdown 惯例。
+8. 提交门禁(Task 3):`refactor(04-07)` 仅含 10 个在范围文件、不含 `.planning/**` 与用户并发改动;`docs(04-07)` 仅含 SUMMARY;`04-01..04-06` 的任何产物未被改写 / 覆盖 / 改号。
+9. 人工复核(Task 3):十条逐条给结论(D-34 的 7 处对位与「行为保持」论据现场复验、`getOffsetPool` 池来源变化、`MaterialManager` 删除面完整性、D-35 注入链与「无全局单例」、`textures` 承担 store 与「本次无写入者」事实、D-36 的 `cache.ts` 11 处对位与行为影响、D-37 声明与 lockfile 未动、D-33 范围核对、D-28 保留、运行时 UAT 不可得理由)。
+10. 运行时验证(诚实降级):渲染端无测试设施,本计划不启动渲染、不新增测试、不接线被注释的接线点,故**无运行时门禁可跑**;本计划的「运行结果」仅由上述编译 / 静态 / 格式 / 范围 / 并发基线机器门禁与人工复核承载。任何以「编译通过」代替运行时结论的表述都视为失败。
+
+
+
+- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报计划内容、逐处替换映射表、成员命名清单、三个已导出类构造器签名变更、`textures` 承担 store 的候选与建议默认、`cache.ts` 删除范围与行为影响、`pnpm-lock.yaml` 处置后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行。
+- **D-34(A 类):** `packages-user` 内 big-image 记号全部清零;`MaterialManager` 的 `IBigImageReturn` import、`bigImageStore` / `bigImageData` / `bigImageId` 三个字段、`setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` 五个方法、`setDefaultFrame` 的 big-image 耦合全部删除;7 处消费者调用点改为行为等价的 `getTile(...)`;`getOffsetPool` 的 big-image 偏移收集删除、其余偏移来源与返回保留。
+- **D-35(C 类):** `MaterialManager` 补齐 `textures` 与 `state`,`AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 各补齐 `state`;`state` 全部经构造器注入(`ClientCore` 传 `this`、`renderer.ts` 传 `manager.state`),**未引入任何全局单例**(D-23);C 类 9 条诊断消除。
+- **D-36(D 类):** `cache.ts` 的 legacy big-image 路径(字段 / 字面量 / 解构 / `enemys` 特判 / 大图分支 / 随之未使用的 `enemys` 声明)全部删除;`enemy48` / `npc48` 分支与其余渲染分支保留;其行为影响(大图怪物落回后续分支)如实记录为**未经运行验证**。
+- **D-37(E 类):** `client-base/package.json` 补上 `@user/data-state: workspace:*`;未执行任何安装、未修改 `pnpm-lock.yaml`(该事实在 Task 0 与 SUMMARY 中原样声明)。
+- **D-33(范围与门槛):** 只修 material 归属的诊断;完工后 material 归属诊断为 0、10 个在范围文件诊断为 0、错误总行数 ≤ 基线总行数减 material 条数(规划日 199 / 19 → 期望 ≤ 180);约 180 条无关诊断一条未动;`src/**`、`packages/**`、数据端、`material/types.ts`、`client-base/src/types.ts` 零改动。
+- **契约面与范围:** 只有经批准的两处契约面变更(三个已导出类构造器新增 `state` 形参、`MaterialManager` 新增 `textures`),无接口成员增删;D-18 / D-19 / D-22 / D-28 边界一律未破;用户并发未提交改动未被回滚 / 暂存 / 提交 / 修改。
+- **验证诚实且可运行:** scoped 类型门禁、静态门禁(D-34 / D-35 / D-36 / D-37 全量)、D-28 保留门禁、范围门禁(基线感知)、并发改动基线门禁、eslint / CRLF 门禁、提交门禁全部转绿且有实际输出;运行时渲染 UAT 不可得的原因(无测试设施、不启动渲染)在摘要中明写,未以任何不可运行的门禁冒充通过。
+- REND-01 / REND-02 保持 Pending(两条 FLAGGED ASSUMPTION);用户可据此继续裁决后续增量(`textures` 的写入侧、`.planning/graphs/*` 重建、其余渲染适配与移动端 / 桌面端双布局)。
+
+## Artifacts this phase produces
+
+| 类型 | 路径 | 内容 |
+|------|------|------|
+| 生产源码(修改) | `packages-user/client-base/src/material/manager.ts` | `MaterialManager` 补齐 `readonly textures: ITextureStore`(承担 store 由用户裁定)与构造器注入的 `state`;big-image 实现面(import / 三字段 / 五方法 / `setDefaultFrame` 耦合)全部删除;`new AssetBuilder(this, state)` |
+| 生产源码(修改) | `packages-user/client-base/src/material/builder.ts` | `AssetBuilder` 与 `TrackedAssetData` 各新增构造器注入的 `state`;`new TrackedAssetData(materials, this, state)` |
+| 生产源码(修改) | `packages-user/client-base/src/material/autotile.ts` | `AutotileProcessor` 新增构造器注入的 `state`(保留既有 `manager` 形参) |
+| 配置(修改) | `packages-user/client-base/package.json` | `dependencies` 新增 `"@user/data-state": "workspace:*"`(D-37;不含安装、不改 lockfile) |
+| 生产源码(修改) | `packages-user/client-modules/src/client.ts` | `new MaterialManager(this)`、`new AutotileProcessor(this.materials, this)`(构造器注入 `state`) |
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/renderer.ts` | `new AutotileProcessor(manager, manager.state)`;`getOffsetPool` 删除 big-image 偏移收集;`useTileBackground` 取值改 `getTile` |
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/vertex.ts` | `updateVertexArray` 取值改 `getTile` |
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/door.ts` | `openDoor` / `closeDoor` 取值改 `getTile` |
+| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/hero.ts` | `moveEntity` / `addFollower` / 跟随者循环三处取值改 `getTile`;D-28 的 `state.roleFace.getFaceOf` 与 `state` import 逐字保留 |
+| 生产源码(修改) | `packages-user/client-modules/src/render/elements/cache.ts` | legacy big-image 路径整体删除(字段 / 字面量 / 解构 / `enemys` 特判 / 大图分支 / 未使用声明) |
+| 摘要 | `.planning/phases/04-render-adaptation/04-07-SUMMARY.md` | 执行前核对结论(带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段(两个基线数字 + material 归属诊断逐行)、D-33..D-37 处置对照与逐处替换映射(含「`getIfBigImage ≡ getTile`」论据与 `cache.ts` 行为影响)、`textures` 承担 store 的用户裁定、门禁输出摘要、人工复核十条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
+
+
+Create `.planning/phases/04-render-adaptation/04-07-SUMMARY.md` when done
+
From f55a76297f0d4f929f8e0fab376e341d698a4631 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 11:22:51 +0800
Subject: [PATCH 066/173] fix(04): revise plans based on checker feedback
---
.../phases/04-render-adaptation/04-07-PLAN.md | 27 ++++++++++---------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-07-PLAN.md b/.planning/phases/04-render-adaptation/04-07-PLAN.md
index dddce7e..2ae6b6e 100644
--- a/.planning/phases/04-render-adaptation/04-07-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-07-PLAN.md
@@ -39,7 +39,7 @@ must_haves:
- "**D-37(E 类)落地**:`packages-user/client-base/package.json` 的 `dependencies` 补上 `\"@user/data-state\": \"workspace:*\"`(其源码 `material/types.ts:9` 与 `types.ts:3` 早已 import 该包);**不执行任何依赖安装、不修改 `pnpm-lock.yaml`**——Task 0 必须把「仅补声明 / 不同步 lockfile」这一处置原样告知用户并请其确认(若用户要求同步 lockfile,退出本计划、修订 `04-07-PLAN.md` 后重新汇报,不得自行改道)"
- "**D-33(范围硬边界)落地**:只修复 material 归属的诊断,**不追求解决全部类型错误**。执行前先记录当日 `pnpm exec vue-tsc --noEmit` 的基线(本次规划日实测:错误总行数 **199**、material 归属 **19**,与 `04-MATERIAL-INTERFACE-IMPACT.md` 记录一致);完工后要求 ① material 归属诊断为 **0**;② 错误总行数 **不超过基线总行数减 material 条数(≤ 180)**;③ 十个在范围文件内**不再有任何诊断**。`src/types/declaration/*`(E 类只报告)与 `packages/**` 一律不修改;其余约 180 条既有/无关诊断(加载系统重构 16 条 + 数据端测试与寻路 164 条)**一条都不追**"
- "**契约面与命名仅两处经批准的变更**:① `MaterialManager` / `AutotileProcessor` / `AssetBuilder` 三个**已导出类**的构造器各新增一个 `state: ICoreState` 形参(Task 0 单独列为待批准项);② `MaterialManager` 新增 `readonly textures: ITextureStore` 成员。`IMapHeroRenderer` / `IMapExtensionManager` / `IAutotileProcessor` / `IAssetBuilder` / `ITrackedAssetData` / `ITextureManager` 的**成员集合与既有 jsDoc 一字不改**(`textures` / `state` 本就是 `4e305e3` 已声明的接口成员,本次只是补实现);不新增 / 删除任何接口成员,不新增任何方法名"
- - "**既有注释零改写**:不修改任何既有 jsDoc / 注释文本(AGENTS.md);被 D-34 / D-36 删除的成员按其定义**连注释整体删除**(「删除 / 移动成员及其 jsDoc」属 AGENTS.md 预授权范围),不新增任何无价值注释;`renderer.ts` `getOffsetPool` 中随 big-image 循环一起删除的行内注释属同一次删除"
+ - "**既有注释零改写**:不修改任何既有 jsDoc / 注释文本(AGENTS.md);被 D-34 / D-36 删除的成员按其定义**连注释整体删除**(「删除 / 移动成员及其 jsDoc」属 AGENTS.md 预授权范围),不新增任何无价值注释;`renderer.ts` `getOffsetPool` 中随 big-image 循环一起删除的行内注释属同一次删除。**本条的失败检查(truth 第 7 条的判定条件)**:Task 3 人工复核第 ⑪ 条要求对 10 个在范围文件逐 hunk 执行 `git diff`(`git diff HEAD~1 -- <文件>` 或 `git show HEAD -- <文件>`)确认每一处既有 jsDoc / 注释文本要么**原样保留**、要么**随其所属成员被整体删除**;一旦发现任何既有注释文本被改写 / 重排 / 局部编辑(区别于整段随成员删除),即判本计划失败并把该 hunk 写入 SUMMARY"
- "**范围外零改动**:`action/move.ts` 与全部移动控制 API(D-19)、`packages/**`、`src/**`、所有数据端包(`data-common` / `data-base` / `data-system` / `data-state`)、贴图与不透明度来源(D-18)、跟随者族(D-22)、`core.*` / `client.*` legacy(D-12)、存量 `state` 单例使用(D-28)、`.planning/graphs/*`(E 类只报告,F-06 待用户裁决)一律不动;`hero.ts` 的 `state.roleFace.getFaceOf(...)` 三处与 `import { state } from '@user/data-state'` 逐字保留(D-28)"
- "**验证可运行且不臆造通过**:改动前先记录当日 `pnpm exec vue-tsc --noEmit` 的基线(总行数 + material 归属行数,逐行原样入 SUMMARY)与并发改动基线(porcelain + 内容 `sha256` 摘要);改动后要求 material 归属诊断为 0、十个在范围文件诊断为 0、总行数 ≤ 基线总行数减 material 条数;`pnpm exec eslint` 对全部被改 TS 文件 0 错误;静态门禁(`packages-user` 内 `bigImage` 记号归零、`textures` 声明就位、四个实现类 `state` 就位、7 处 `getTile(` 就位、`getOffsetPool` 已清理、`@user/data-state` 在 `client-base/package.json`)、CRLF 门禁、基线感知范围门禁与并发改动基线门禁全部转绿。渲染端**无测试设施**,本增量虽在运行时路径上(构造器注入)但**无可运行的渲染或 UAT 设施**,故实质验证为「scoped 类型 + 静态 + eslint / CRLF + 范围 / 并发基线」机器门禁加人工代码复核;**不得**以任何无法运行的门禁冒充通过"
- "用户并发的未提交改动(以 Task 1 起始 `git status --porcelain` 当日实测为准;规划日实测工作树内唯一未提交项为 ` M .planning/phases/04-render-adaptation/04-CONTEXT.md`)在 Task 1 起始记录基线(porcelain 条目 + 各文件内容 `sha256` 摘要),Task 3 复验逐条与摘要未变;**不回滚 / 不暂存 / 不提交 / 不修改**它们。本次基线**不沿用** 04-04 / 04-06 的旧列表,一律以 Task 1 当日实测为准"
@@ -131,6 +131,7 @@ Phase 4 第七个增量(D-32 **第二步**)—— **material 接口适应实
| `cache.ts` 11 处 | `RenderableDataBase.bigImage`、`AutotileRenderable.bigImage`、5 处字面量、`faceIds` / `bigImage` 解构 + `enemys` 特判、`if (bigImage) {...}` 分支 | **整体删除**(含随之未使用的 `enemys` 声明) | legacy 概念路径(走 `core.material.*`),big-image 概念删除(D-31)后旧兼容可痛丢弃 |
| `client-base/package.json` | 无 `@user/data-state` | `"@user/data-state": "workspace:*"` | 声明补全(源码已 import;不安装、不同步 lockfile) |
+- **替换计数的既有基线(必须知悉,避免门禁误判)**:`vertex.ts` 在本次 7 处替换面之外**已存在**一处 `getTile(` 调用(`packages-user/client-modules/src/render/map/vertex.ts:516`,`checkAutotileConnectionAround`),该行语义合法、**不属本次改动面、不得删除或改写**。故替换后 `vertex.ts` 的全文件 `getTile(` 计数为 **2**(既有 `:516` 一处 + 本次由 `:546` 的 `getIfBigImage(` 改写而来的一处);静态门禁据此**实测后**的期望值断言:door 2 / hero 3 / renderer 1 / vertex 2。
- **D-35 注入链(C 类)**:`ClientCore extends CoreState`(`client.ts:45`)→ `new MaterialManager(this)`(`:84`)→ `MaterialManager.constructor(readonly state: ICoreState)` → `new AssetBuilder(this, state)`(`manager.ts:86`)→ `AssetBuilder.constructor(readonly materials, readonly state)` → `new TrackedAssetData(materials, this, state)`(`builder.ts:28`)→ `TrackedAssetData.constructor(readonly materials, readonly builder, readonly state) { super(0); }`;另有 `new AutotileProcessor(this.materials, this)`(`client.ts:85`)与 `new AutotileProcessor(manager, manager.state)`(`renderer.ts:234`,从已注入的 `ITextureManager` 取 `state`,不取全局单例,D-23)。`MaterialManager` 同时补 `readonly textures: ITextureStore`(承担 store 由 Task 0 裁定,建议默认新建专用 `TextureStore`)。
- **范围硬边界(D-33)**:只修 material 归属诊断;约 180 条既有/无关诊断一条不追;`src/**`、`packages/**`、数据端只读;D-18 / D-19 / D-22 / D-23 / D-28 边界照旧。
@@ -173,7 +174,7 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。按「执行者实际触及量」估算(`estimate.tasks: 4` = Task 0 汇报关卡 + 三个工作任务):Task 0 只读汇报并等待用户确认(约 5k,不写文件);Task 1(tracer)起始复核 + 构造器注入链 + `textures` + `package.json` + scoped 类型门禁(约 26k,含 `manager.ts` 602 行与 `builder.ts` / `autotile.ts` 的定向读取);Task 2 `MaterialManager` big-image 删除 + 5 个消费者文件改写 + `getOffsetPool` 清理 + 门禁(约 26k);Task 3 `cache.ts` 删除 + 原子提交 + 全部门禁 + 人工复核 + SUMMARY(约 20k);固定开销约 5k;合计约 82k。
-**为何不拆分:** D-33 明令不追无关错误,A 类的删除与其消费者改写**不可分离编译**(删掉 `getIfBigImage` 后 7 处调用点立即报错),故本增量是一个不可再切的最小整体;本计划的四类改动落在同一条「material 接口适应」纵切面上,拆成多计划只会制造中间不可编译状态。按 context-budget 的 Phase Sizing 规则,本计划列为一个计划(3 个工作任务),估算超 smart-zone 预算属**建议性**提示,不阻塞。
+**为何不拆分:** D-33 明令不追无关错误,A 类的删除与其消费者改写**不可分离编译**(删掉 `getIfBigImage` 后 7 处调用点立即报错),故本增量是一个不可再切的最小整体;本计划的四类改动落在同一条「material 接口适应」纵切面上,拆成多计划只会制造中间不可编译状态。按 context-budget 的 Phase Sizing 规则,本计划列为一个计划(3 个工作任务);本计划估算为校准预算(100000)的约 **82%**(`over_budget: false`;`confidence: low`,项目无历史样本),属**建议性**提示,不阻塞。
@@ -372,7 +373,7 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
- packages-user/client-modules/src/render/map/extension/door.ts(整文件 73 行:`openDoor` `:30-43`、`closeDoor` `:45-56`)
- packages-user/client-modules/src/render/map/extension/hero.ts:225-251,350-390,415-438(`moveEntity` / `addFollower` / 跟随者循环三处取值点;`:238` / `:363` / `:423` 的 `state.roleFace.getFaceOf` 属 D-28 保留)
- packages-user/client-modules/src/render/map/renderer.ts:640-690(`getOffsetPool` 现行实现,`:651-657` 为待删的 big-image 循环)、`:1238-1278`(`useTileBackground`,`:1250` 为待改取值点)
- - packages-user/client-modules/src/render/map/vertex.ts:530-600(`updateVertexArray`,`:546` 为待改取值点;其后 `tile.cls` / `tile.texture.render()` / `tile.frames` / `tile.offset` 的用法必须保持不变)
+ - packages-user/client-modules/src/render/map/vertex.ts:530-600(`updateVertexArray`,`:546` 为待改取值点;其后 `tile.cls` / `tile.texture.render()` / `tile.frames` / `tile.offset` 的用法必须保持不变;另 `:516`(`checkAutotileConnectionAround`)为范围外既有 `getTile(` 调用,不得改动)
- .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md 的 `## A 已移除符号的剩余引用` 与 `## D big-image 残留(实现与消费者)`(本任务的两张事实表)
- packages-user/data-common/src/common/mover.ts(若需确认 `MovingBlock.setTexture` 的形参类型——通过 `render/map/moving.ts` 的 `IMovingBlock`)
@@ -392,7 +393,7 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
(2d) `render/map/extension/hero.ts:364`:`getIfBigImage(faced?.face ?? image)` → `getTile(faced?.face ?? image)`(`addFollower`)。
(2e) `render/map/extension/hero.ts:428-430`:`getIfBigImage(nextFace.identifier)` → `getTile(nextFace.identifier)`(跟随者循环)。
(2f) `render/map/renderer.ts:1250`:`const tex = this.manager.getIfBigImage(tile);` → `const tex = this.manager.getTile(tile);`(`useTileBackground`)。
- (2g) `render/map/vertex.ts:546`:`const tile = this.renderer.manager.getIfBigImage(num);` → `const tile = this.renderer.manager.getTile(num);`(`updateVertexArray`)。
+ (2g) `render/map/vertex.ts:546`:`const tile = this.renderer.manager.getIfBigImage(num);` → `const tile = this.renderer.manager.getTile(num);`(`updateVertexArray`)。**注意既有基线**:`vertex.ts:516`(`checkAutotileConnectionAround`)本就有一处范围外的 `getTile(` 调用,属既有代码——本任务不触碰该行;替换后 `vertex.ts` 的全文件 `getTile(` 计数为 2(既有 `:516` 一处 + 本次 `:546` 一处),门禁按此期望值断言。
**不得**改动这 7 处周围的分支结构、变量名、注释、`logger` 调用与后续用法(`status.useSpecifiedFrame` / `moving.useSpecifiedFrame` / `block.setTexture` / `tex.frames` / `tile.texture.render()` / `tile.offset` 等一律保持)。**不得**改动 `hero.ts:238` / `:363` / `:423` 的 `state.roleFace.getFaceOf(...)` 与 `import { state } from '@user/data-state'`(D-28)。
(3) **`renderer.ts` 的 `getOffsetPool` 清理**:删除 `:651-657` 的整段(`:651` 的行内注释「其他的都是 bigImage 了,直接遍历获取」+ `for (const identifier of this.manager.bigImageStore.keys()) { const data = this.manager.getBigImage(identifier); if (!data) continue; const offset = data.texture.width / data.frames; pool.add(offset); }`)。**保留** `const pool = new Set([32]);`、其后的「还有勇士图片」注释段与 `imageStore.values()` 循环、`pool.size > 64` 的 DEV 告警、`MAX_VERTEX_UNIFORM_VECTORS` 的错误分支与 `return [...pool];`。**不得**改方法名、签名与其它逻辑。
(4) **不做的事**:不碰 `cache.ts`(Task 3)、不碰 `material/types.ts` 与 `client-base/src/types.ts`、不碰 `action/move.ts`、不碰任何数据端文件与 `packages/**`、不改 `getOffsetPool` 之外的 renderer 逻辑、不新增注释、不新增 `as` 断言。
@@ -403,8 +404,8 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,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 MRE=/ITextureManager|IAutotileProcessor|IAssetBuilder|ITrackedAssetData|IBigImageReturn|getIfBigImage|getBigImage|isBigImage|setBigImage|getBigImageByAlias|bigImageStore|IMaterialManager|IMaterialGetter|IMaterialAliasGetter/;const isMat=l=>/client-base[\\/]src[\\/]material[\\/]/.test(l)||MRE.test(l);let t='';try{t=fs.readFileSync('.planning/phases/04-render-adaptation/04-07-SUMMARY.md','utf8')}catch(e){t=''}const bm=t.match(/([\s\S]*?)/);if(!bm){console.error('NO TYPE BASELINE');process.exit(1)}const dm=bm[1].match(/错误总行数:(\d+);material 归属行数:(\d+)/);if(!dm){console.error('NO BASELINE NUMBERS');process.exit(1)}const N=+dm[1],M=+dm[2];const mat=lines.filter(isMat);if(mat.length){console.error('MATERIAL DIAGNOSTICS REMAIN '+mat.length+'\n'+mat.join('\n'));process.exit(1)}const touched=['packages-user/client-base/src/material/manager.ts','packages-user/client-modules/src/render/map/renderer.ts','packages-user/client-modules/src/render/map/vertex.ts','packages-user/client-modules/src/render/map/extension/door.ts','packages-user/client-modules/src/render/map/extension/hero.ts'];const hit=lines.filter(l=>touched.some(p=>l.indexOf(p.replace(/\//g,'/'))>=0||l.indexOf(p)>=0));if(hit.length){console.error('DIAGNOSTICS IN TOUCHED FILES '+hit.length+'\n'+hit.join('\n'));process.exit(1)}if(lines.length>N-M){console.error('TOTAL INCREASED total='+lines.length+' allowed '+(N-M));process.exit(1)}console.log('OK A-class: material 0; touched 0; total '+lines.length+'<='+(N-M))"
non-zero exit, 或 stderr 含 "NO TYPE BASELINE" / "NO BASELINE NUMBERS"(SUMMARY 未按固定格式记录类型基线)/ "MATERIAL DIAGNOSTICS REMAIN"(material 归属诊断未清零)/ "DIAGNOSTICS IN TOUCHED FILES"(五个在范围文件仍有诊断,说明删除或改写引入了新问题)/ "TOTAL INCREASED"(错误总行数超过「基线总行数 - material 条数」)
- node -e "const fs=require('fs');const F={man:'packages-user/client-base/src/material/manager.ts',ren:'packages-user/client-modules/src/render/map/renderer.ts',ver:'packages-user/client-modules/src/render/map/vertex.ts',doo:'packages-user/client-modules/src/render/map/extension/door.ts',her:'packages-user/client-modules/src/render/map/extension/hero.ts'};const S={};for(const k of Object.keys(F))S[k]=fs.readFileSync(F[k],'utf8');const left=Object.keys(S).filter(k=>/bigimage/i.test(S[k]));if(left.length){console.error('BIGIMAGE TOKENS REMAIN IN '+left.join(','));process.exit(1)}const cnt=(s,t)=>(s.match(new RegExp('getTile\\(','g'))||[]).length;if(cnt(S.doo)!==2||cnt(S.her)!==3||cnt(S.ren)!==1||cnt(S.ver)!==1){console.error('getTile( COUNT WRONG door='+cnt(S.doo)+' hero='+cnt(S.her)+' renderer='+cnt(S.ren)+' vertex='+cnt(S.ver));process.exit(1)}if(!/const pool = new Set\(\[32\]\);/.test(S.ren)||!/imageStore\.values\(\)/.test(S.ren)||!/return \[\.\.\.pool\];/.test(S.ren)){console.error('getOffsetPool TAIL LOGIC LOST');process.exit(1)}const sd=S.man.indexOf('setDefaultFrame(');if(sd<0){console.error('setDefaultFrame MISSING');process.exit(1)}const sdBody=S.man.slice(sd,S.man.indexOf('}',sd)+1);if(!/defaultFrames\.set\(identifier, defaultFrame\)/.test(sdBody)){console.error('setDefaultFrame BODY WRONG');process.exit(1)}const h=S.her;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 D34 deletions + 7 getTile + getOffsetPool cleaned')"
- non-zero exit, 或 stderr 含 "BIGIMAGE TOKENS REMAIN IN"(五个在范围文件仍含 `bigimage` 记号)/ "getTile( COUNT WRONG"(7 处替换的分布不是 door 2 / hero 3 / renderer 1 / vertex 1)/ "getOffsetPool TAIL LOGIC LOST"(`getOffsetPool` 的其余偏移来源或返回被误删)/ "setDefaultFrame MISSING" / "setDefaultFrame BODY WRONG"(`setDefaultFrame` 被误删或其剩余本体不正确)/ "D28 STATE USAGE LOST"(存量 `state.roleFace` 用法被误删)
+ node -e "const fs=require('fs');const F={man:'packages-user/client-base/src/material/manager.ts',ren:'packages-user/client-modules/src/render/map/renderer.ts',ver:'packages-user/client-modules/src/render/map/vertex.ts',doo:'packages-user/client-modules/src/render/map/extension/door.ts',her:'packages-user/client-modules/src/render/map/extension/hero.ts'};const S={};for(const k of Object.keys(F))S[k]=fs.readFileSync(F[k],'utf8');const left=Object.keys(S).filter(k=>/bigimage/i.test(S[k]));if(left.length){console.error('BIGIMAGE TOKENS REMAIN IN '+left.join(','));process.exit(1)}const cnt=(s,t)=>(s.match(new RegExp('getTile\\(','g'))||[]).length;const gbLeft=Object.keys(S).filter(k=>/getIfBigImage\(/.test(S[k]));if(gbLeft.length){console.error('getIfBigImage( REMAINS IN '+gbLeft.join(','));process.exit(1)}const gt=(s)=>(s.match(/getTile\(/g)||[]).length;if(gt(S.doo)!==2||gt(S.her)!==3||gt(S.ren)!==1||gt(S.ver)!==2){console.error('getTile( COUNT WRONG door='+gt(S.doo)+' hero='+gt(S.her)+' renderer='+gt(S.ren)+' vertex='+gt(S.ver)+' (vertex 含范围外既有 vertex.ts:516)');process.exit(1)}if(!/const pool = new Set\(\[32\]\);/.test(S.ren)||!/imageStore\.values\(\)/.test(S.ren)||!/return \[\.\.\.pool\];/.test(S.ren)){console.error('getOffsetPool TAIL LOGIC LOST');process.exit(1)}const sd=S.man.indexOf('setDefaultFrame(');if(sd<0){console.error('setDefaultFrame MISSING');process.exit(1)}const sdBody=S.man.slice(sd,S.man.indexOf('}',sd)+1);if(!/defaultFrames\.set\(identifier, defaultFrame\)/.test(sdBody)){console.error('setDefaultFrame BODY WRONG');process.exit(1)}const h=S.her;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 D34 deletions + 7 getTile + getOffsetPool cleaned')"
+ non-zero exit, 或 stderr 含 "BIGIMAGE TOKENS REMAIN IN"(五个在范围文件仍含 `bigimage` 记号)/ "getIfBigImage( REMAINS IN"(五个在范围文件仍含未替换的 `getIfBigImage(` 调用)/ "getTile( COUNT WRONG"(替换后全文件 `getTile(` 计数不是 door 2 / hero 3 / renderer 1 / vertex 2——vertex 的 2 含范围外既有 `vertex.ts:516`)/ "getOffsetPool TAIL LOGIC LOST"(`getOffsetPool` 的其余偏移来源或返回被误删)/ "setDefaultFrame MISSING" / "setDefaultFrame BODY WRONG"(`setDefaultFrame` 被误删或其剩余本体不正确)/ "D28 STATE USAGE LOST"(存量 `state.roleFace` 用法被误删)
pnpm exec eslint packages-user/client-base/src/material/manager.ts packages-user/client-modules/src/render/map/renderer.ts packages-user/client-modules/src/render/map/vertex.ts packages-user/client-modules/src/render/map/extension/door.ts packages-user/client-modules/src/render/map/extension/hero.ts
non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用变量 / 未使用 import——务必确认删除 big-image 后无 import 变为未使用)
node -e "const fs=require('fs');const F=['packages-user/client-base/src/material/manager.ts','packages-user/client-modules/src/render/map/renderer.ts','packages-user/client-modules/src/render/map/vertex.ts','packages-user/client-modules/src/render/map/extension/door.ts','packages-user/client-modules/src/render/map/extension/hero.ts'];for(const f of F){const s=fs.readFileSync(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')"
@@ -415,7 +416,7 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
- `door.ts` / `hero.ts` 的 7 处取值全部改为 `getTile(...)`(door 2 / hero 3),`renderer.ts:1250` 与 `vertex.ts:546` 同样改为 `getTile(...)`;这些调用点周围的分支、变量与后续用法未变
- `renderer.ts` 的 `getOffsetPool` 已删除 big-image 循环,保留 `new Set([32])`、`imageStore.values()` 循环、DEV 告警与 `return [...pool]`
- `hero.ts` 的 `state.roleFace.getFaceOf` 三处与 `import { state } from '@user/data-state'` 逐字保留(D-28);`action/move.ts`、`cache.ts`、`material/types.ts`、`client-base/src/types.ts`、任何数据端文件均未被本任务触碰
- - 四条 `` 全部转绿:scoped 类型门禁(material 归属 0、五个在范围文件 0、总行数 ≤ 基线减 material 条数)、删除 / 替换门禁(`bigimage` 记号归零、`getTile(` 分布正确、`getOffsetPool` 尾部逻辑在、`setDefaultFrame` 正确、D-28 保留)、eslint 0 错误、CRLF
+ - 四条 `` 全部转绿:scoped 类型门禁(material 归属 0、五个在范围文件 0、总行数 ≤ 基线减 material 条数)、删除 / 替换门禁(`bigimage` 记号归零、`getIfBigImage(` 全部消失、`getTile(` 全文件计数为 door 2 / hero 3 / renderer 1 / vertex 2——vertex 的 2 含范围外既有 `vertex.ts:516`、`getOffsetPool` 尾部逻辑在、`setDefaultFrame` 正确、D-28 保留)、eslint 0 错误、CRLF
- 本任务**未**提交任何改动
`MaterialManager` 的 big-image 实现面全部删除、7 处消费者取值改为行为等价的 `getTile`、`getOffsetPool` 的 big-image 偏移收集删除,material 归属诊断清零且总行数降至 ≤ 基线减 19,D-28 保留项未动,四条机器门禁转绿
@@ -451,8 +452,8 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
(3) **范围门禁(基线感知,先跑,防止越界改动)**:把当前 `git status --porcelain` 的**生产路径**(`^packages(-user)?/`)与 SUMMARY 中 `CONCURRENT-BASELINE` 段记录的基线条目做差集;只有**基线之外**、且不属于上述 10 个在范围文件的生产改动才算越界。特别断言 `action/move.ts`(D-19)、`packages-user/client-base/src/load/**`、`client-base/src/material/types.ts`、`client-base/src/types.ts`、`data-common` / `data-base` / `data-system` / `data-state`、`packages/**`、`src/**` 均无基线之外的新改动。若出现基线之外的第九 / 第十一个生产文件改动:停止、汇报、修订计划(不得自行扩面)。
(4) **并发改动基线门禁**:用与 Task 1 步骤 (0c) 完全相同的固定命令重算(排除本计划在范围文件的四类前缀与 `.planning/phases/04-render-adaptation/`),断言 porcelain 条目集合与内容摘要 `sha256` 均与 SUMMARY 的 `CONCURRENT-BASELINE` 段记录一致。
(5) **复跑全部机器门禁**:scoped 类型门禁(material 归属 0、10 个在范围文件诊断 0、总行数 ≤ 基线总行数减 material 条数)、静态门禁 A(D-34 / D-35 / D-36 / D-37 全量)、静态门禁 B(D-28 保留 + `cache.ts` 删除面 + `getOffsetPool` 保留面)、`pnpm exec eslint` 对 9 个被改 TS 文件 0 错误、CRLF 门禁。
- (6) **人工复核(写进 SUMMARY 的「人工复核」段,逐条给结论)**:① D-34 的 7 处调用逐条对位(旧 `getIfBigImage` → 新 `getTile`)并复核「行为保持」论据(`bigImageData` 的唯一写入者 `setBigImage` 消费者调用点为 0,现场用 `git grep` 复验);② `getOffsetPool` 删除后池的来源只剩 `ImageStore` 与常量 32,说明该变化对 shader uniform 池的语义(不再有 big-image 偏移);③ `MaterialManager` 的五个 big-image 方法与三个字段确实全部删除、`setDefaultFrame` 本体与签名未变;④ D-35 的注入链逐环节对位(`client.ts:84` 的 `this` → `manager.ts` 构造器 → `manager.ts:86` → `builder.ts:28`;`client.ts:85`;`renderer.ts:234` 的 `manager.state`)并说明**未引入任何全局单例**(D-23);⑤ `textures` 的承担 store 与 Task 0 用户裁定一致(并在 SUMMARY 明写「本次范围内无写入者」的事实);⑥ D-36 的 `cache.ts` 11 处命中逐条对位(字段 / 字面量 / 解构 / 特判 / 分支 / 未使用声明),并记录其行为影响(大图怪物落回后续分支)与「未经运行验证」;⑦ D-37 的声明就位、`pnpm-lock.yaml` 未动、未执行安装;⑧ D-33 范围核对:`git diff --stat` 只含 10 个文件、其余约 180 条诊断未被触碰;⑨ D-28 保留:`hero.ts` 的 `state.roleFace.getFaceOf` 三处与 `state` import 未动;⑩ 运行时 UAT 不可得的理由已写明(无测试设施、不启动渲染)。
- (7) **SUMMARY 定稿**:写 `.planning/phases/04-render-adaptation/04-07-SUMMARY.md`,含:10 个文件的改动清单与职责;D-33..D-37 的逐条处置对照表(含逐处替换映射与「`getIfBigImage ≡ getTile`」论据、`textures` 承担 store 的用户裁定、`cache.ts` 的行为影响);执行前核对结论(带 `file:line`);`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`;`TYPE-BASELINE` 段(含两个基线数字与 material 归属诊断逐行);完工后的类型门禁实测值(material 归属 0、总行数实测值、10 个在范围文件 0);全部机器门禁的实际输出摘要;人工复核十条;未实现 / 显式延后项清单;REND-01 / REND-02 仍为 Pending 与运行时 UAT 不可得的明确声明。全文 CRLF。
+ (6) **人工复核(写进 SUMMARY 的「人工复核」段,逐条给结论)**:① D-34 的 7 处调用逐条对位(旧 `getIfBigImage` → 新 `getTile`)并复核「行为保持」论据(`bigImageData` 的唯一写入者 `setBigImage` 消费者调用点为 0,现场用 `git grep` 复验);② `getOffsetPool` 删除后池的来源只剩 `ImageStore` 与常量 32,说明该变化对 shader uniform 池的语义(不再有 big-image 偏移);③ `MaterialManager` 的五个 big-image 方法与三个字段确实全部删除、`setDefaultFrame` 本体与签名未变;④ D-35 的注入链逐环节对位(`client.ts:84` 的 `this` → `manager.ts` 构造器 → `manager.ts:86` → `builder.ts:28`;`client.ts:85`;`renderer.ts:234` 的 `manager.state`)并说明**未引入任何全局单例**(D-23);⑤ `textures` 的承担 store 与 Task 0 用户裁定一致(并在 SUMMARY 明写「本次范围内无写入者」的事实);⑥ D-36 的 `cache.ts` 11 处命中逐条对位(字段 / 字面量 / 解构 / 特判 / 分支 / 未使用声明),并记录其行为影响(大图怪物落回后续分支)与「未经运行验证」;⑦ D-37 的声明就位、`pnpm-lock.yaml` 未动、未执行安装;⑧ D-33 范围核对:`git diff --stat` 只含 10 个文件、其余约 180 条诊断未被触碰;⑨ D-28 保留:`hero.ts` 的 `state.roleFace.getFaceOf` 三处与 `state` import 未动;⑩ 运行时 UAT 不可得的理由已写明(无测试设施、不启动渲染);⑪ **既有注释零改写(AGENTS.md;truth 第 7 条的可失败检查)**:对 10 个在范围文件逐 hunk 复核 `git diff`(用 `git diff HEAD~1 -- <10 个文件>` 或 `git show HEAD -- <10 个文件>`,视执行环境是否强制每任务提交而定),确认每一处既有 jsDoc / 注释文本要么**原样保留**、要么**随其所属成员被整体删除**(「删除 / 移动成员及其 jsDoc」属 AGENTS.md 预授权范围);若发现任何既有注释文本被改写 / 重排 / 局部编辑(区别于整段随成员删除),即**判本计划失败**,并把该 hunk 与结论写入 SUMMARY 的人工复核段。
+ (7) **SUMMARY 定稿**:写 `.planning/phases/04-render-adaptation/04-07-SUMMARY.md`,含:10 个文件的改动清单与职责;D-33..D-37 的逐条处置对照表(含逐处替换映射与「`getIfBigImage ≡ getTile`」论据、`textures` 承担 store 的用户裁定、`cache.ts` 的行为影响);执行前核对结论(带 `file:line`);`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`;`TYPE-BASELINE` 段(含两个基线数字与 material 归属诊断逐行);完工后的类型门禁实测值(material 归属 0、总行数实测值、10 个在范围文件 0);全部机器门禁的实际输出摘要;人工复核十一条;未实现 / 显式延后项清单;REND-01 / REND-02 仍为 Pending 与运行时 UAT 不可得的明确声明。全文 CRLF。
(8) **提交 SUMMARY**:`docs(04-07): record execution state`,仅含 `04-07-SUMMARY.md`。
(9) **不做的事**:不接线任何被注释的接线点、不改 `action/move.ts`、不新增测试文件、不改任何数据端文件与 `packages/**`、不修任何无关诊断、不重建 `.planning/graphs/*`、不修改 `04-01..04-06` 的任何产物。
@@ -479,7 +480,7 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
- 10 个在范围文件已一次原子提交(`refactor(04-07)`),提交不含用户并发改动、不含 `.planning/**`
- 终验类型门禁通过:material 归属 0、9 个在范围 TS 文件诊断 0、错误总行数 ≤ 基线总行数减 material 条数(规划日 199 / 19 → 期望 ≤ 180)
- 静态门禁(D-34 / D-35 / D-36 / D-37 全量)、范围门禁(基线感知)、并发改动基线门禁、eslint 0 错误、CRLF 门禁全部转绿
- - `04-07-SUMMARY.md` 含执行前核对结论(带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段(含两个基线数字与 material 归属诊断逐行)、D-33..D-37 处置对照、`textures` 承担 store 的用户裁定、`cache.ts` 行为影响与「未经运行验证」、门禁输出摘要、人工复核十条、未实现 / 延后项清单、REND-01 / REND-02 仍 Pending 与运行时 UAT 不可得的明确说明;并以 `docs(04-07)` 提交(仅含该 SUMMARY)
+ - `04-07-SUMMARY.md` 含执行前核对结论(带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段(含两个基线数字与 material 归属诊断逐行)、D-33..D-37 处置对照、`textures` 承担 store 的用户裁定、`cache.ts` 行为影响与「未经运行验证」、门禁输出摘要、人工复核十一条、未实现 / 延后项清单、REND-01 / REND-02 仍 Pending 与运行时 UAT 不可得的明确说明;并以 `docs(04-07)` 提交(仅含该 SUMMARY)
`cache.ts` 的 legacy big-image 路径删净、10 个文件原子提交、material 归属诊断清零且总行数不增加、多类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(`textures` 写入侧、`.planning/graphs/*` 重建、其余渲染适配与 REND-02 双布局)
@@ -533,13 +534,13 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题(A / C / D / E 四类 + 199 / 19 总账)/ 大致解决方案与逐处替换映射表 / 成员命名清单 / 三个已导出类构造器签名变更 / `textures` 承担 store 的候选与建议默认 / `cache.ts` 删除范围与行为影响 / `pnpm-lock.yaml` 处置 / 范围边界 / 验证方式的诚实降级」后**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo` / `auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
1. 执行前核对与基线(Task 1 步骤 0):十项接口 / 构造面复核结论(带 `file:line`)、类型门禁基线(`TYPE-BASELINE` 段:错误总行数与 material 归属行数两个数字 + material 归属诊断逐行)、并发改动基线(`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`)写入 SUMMARY;任一与规划不一致即停止并修订计划;**不得沿用 04-04 / 04-06 SUMMARY 的旧并发列表**。
2. scoped 类型门禁(Task 1 / 2 / 3):`pnpm exec vue-tsc --noEmit` 过滤后要求——Task 1 完工时 C 类三形态诊断(`incorrectly implements interface` / `Property 'state' is missing` / `missing the following properties from type 'ITextureManager'`)为 0、material 归属数 == 基线减 9、且余下 material 归属全部命中 `bigimage`(大小写不敏感)、总行数 ≤ 基线减 9;Task 2 / Task 3 完工时 material 归属数为 0、在范围文件诊断为 0、总行数 ≤ 基线总行数减 material 条数(规划日 199 / 19 → 期望 ≤ 180)。整仓其余既有诊断不属本计划,不得以「整仓非 0 退出」判定失败,也不得漏判在范围文件的新诊断。
-3. 静态门禁(Task 1 / 2 / 3):① `MaterialManager` 含 `readonly textures: ITextureStore`;② 四个实现类各含 `readonly state: ICoreState`(或构造器形参形式);③ 五处构造点就位(`new MaterialManager(this)` / `new AutotileProcessor(this.materials, this)` / `new AutotileProcessor(manager, manager.state)` / `new AssetBuilder(this, state)` / `new TrackedAssetData(materials, this, state)`);④ 三个 client-base 文件的 `@user/data-state` import 只引入 `ICoreState`,无 `import type`、无 `state` 单例 import;⑤ `client-base/package.json` 的 `dependencies['@user/data-state'] === 'workspace:*'`;⑥ `git grep -i bigimage -- packages-user` 无输出;⑦ 7 处 `getTile(` 分布为 door 2 / hero 3 / renderer 1 / vertex 1;⑧ `getOffsetPool` 保留 `const pool = new Set([32]);`、`imageStore.values()` 循环与 `return [...pool];`;⑨ `setDefaultFrame` 本体只剩 `defaultFrames.set(...)`;⑩ `cache.ts` 保留 `interface RenderableDataBase` 且无 big-image 记号、`enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80` 声明已消失。
+3. 静态门禁(Task 1 / 2 / 3):① `MaterialManager` 含 `readonly textures: ITextureStore`;② 四个实现类各含 `readonly state: ICoreState`(或构造器形参形式);③ 五处构造点就位(`new MaterialManager(this)` / `new AutotileProcessor(this.materials, this)` / `new AutotileProcessor(manager, manager.state)` / `new AssetBuilder(this, state)` / `new TrackedAssetData(materials, this, state)`);④ 三个 client-base 文件的 `@user/data-state` import 只引入 `ICoreState`,无 `import type`、无 `state` 单例 import;⑤ `client-base/package.json` 的 `dependencies['@user/data-state'] === 'workspace:*'`;⑥ `git grep -i bigimage -- packages-user` 无输出;⑦ 7 处 `getIfBigImage(` 全部消失(五个在范围文件的 `getIfBigImage(` 计数为 0),且全文件 `getTile(` 计数为 door 2 / hero 3 / renderer 1 / vertex 2(vertex 的 2 = 范围外既有 `vertex.ts:516` 一处 + 本次由 `:546` 替换而来的一处;`vertex.ts:516` 不得改动);⑧ `getOffsetPool` 保留 `const pool = new Set([32]);`、`imageStore.values()` 循环与 `return [...pool];`;⑨ `setDefaultFrame` 本体只剩 `defaultFrames.set(...)`;⑩ `cache.ts` 保留 `interface RenderableDataBase` 且无 big-image 记号、`enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80` 声明已消失。
4. D-28 保留门禁(Task 2 / 3):`hero.ts` 的 `state.roleFace.getFaceOf` 计数 ≥ 3 且 `from '@user/data-state'` 仍在(这是**正向**门禁:D-28 要求保留存量用法)。
5. 范围门禁(Task 3,基线感知):将当前 `git status --porcelain` 的生产路径(`^packages(-user)?/`)与 `CONCURRENT-BASELINE` 段做差集;只有基线之外、且不属于 10 个在范围文件的生产改动才判越界;`action/move.ts`、`client-base/src/load/**`、`client-base/src/material/types.ts`、`client-base/src/types.ts`、数据端各包、`packages/**`、`src/**` 均无基线之外的新改动。
6. 并发改动基线门禁(Task 3):排除本计划在范围文件的四类前缀与 `.planning/phases/04-render-adaptation/` 后,porcelain 条目集合与内容 `sha256` 与 SUMMARY 的 `CONCURRENT-BASELINE` 段记录一致(用户改动未被回滚 / 暂存 / 提交 / 修改)。
7. ESLint / CRLF 门禁(Task 1 / 2 / 3):`pnpm exec eslint` 对全部被改 TS 文件(Task 1 五个、Task 2 五个、Task 3 九个)0 错误;9 个源文件无孤立 LF(dev.md「换行使用 CRLF 格式」)。SUMMARY 不施加该硬门禁(工具写出的 markdown),改由人工复核遵循仓库 markdown 惯例。
8. 提交门禁(Task 3):`refactor(04-07)` 仅含 10 个在范围文件、不含 `.planning/**` 与用户并发改动;`docs(04-07)` 仅含 SUMMARY;`04-01..04-06` 的任何产物未被改写 / 覆盖 / 改号。
-9. 人工复核(Task 3):十条逐条给结论(D-34 的 7 处对位与「行为保持」论据现场复验、`getOffsetPool` 池来源变化、`MaterialManager` 删除面完整性、D-35 注入链与「无全局单例」、`textures` 承担 store 与「本次无写入者」事实、D-36 的 `cache.ts` 11 处对位与行为影响、D-37 声明与 lockfile 未动、D-33 范围核对、D-28 保留、运行时 UAT 不可得理由)。
+9. 人工复核(Task 3):十一条逐条给结论(D-34 的 7 处对位与「行为保持」论据现场复验、`getOffsetPool` 池来源变化、`MaterialManager` 删除面完整性、D-35 注入链与「无全局单例」、`textures` 承担 store 与「本次无写入者」事实、D-36 的 `cache.ts` 11 处对位与行为影响、D-37 声明与 lockfile 未动、D-33 范围核对、D-28 保留、运行时 UAT 不可得理由、既有注释零改写(第 ⑪ 条:逐 hunk `git diff` 确认既有 jsDoc / 注释文本未被改写,truth 第 7 条的可失败检查))。
10. 运行时验证(诚实降级):渲染端无测试设施,本计划不启动渲染、不新增测试、不接线被注释的接线点,故**无运行时门禁可跑**;本计划的「运行结果」仅由上述编译 / 静态 / 格式 / 范围 / 并发基线机器门禁与人工复核承载。任何以「编译通过」代替运行时结论的表述都视为失败。
@@ -568,7 +569,7 @@ Output: 10 个文件的生产改动(一次原子提交)+ 会话内可复跑
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/door.ts` | `openDoor` / `closeDoor` 取值改 `getTile` |
| 生产源码(修改) | `packages-user/client-modules/src/render/map/extension/hero.ts` | `moveEntity` / `addFollower` / 跟随者循环三处取值改 `getTile`;D-28 的 `state.roleFace.getFaceOf` 与 `state` import 逐字保留 |
| 生产源码(修改) | `packages-user/client-modules/src/render/elements/cache.ts` | legacy big-image 路径整体删除(字段 / 字面量 / 解构 / `enemys` 特判 / 大图分支 / 未使用声明) |
-| 摘要 | `.planning/phases/04-render-adaptation/04-07-SUMMARY.md` | 执行前核对结论(带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段(两个基线数字 + material 归属诊断逐行)、D-33..D-37 处置对照与逐处替换映射(含「`getIfBigImage ≡ getTile`」论据与 `cache.ts` 行为影响)、`textures` 承担 store 的用户裁定、门禁输出摘要、人工复核十条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
+| 摘要 | `.planning/phases/04-render-adaptation/04-07-SUMMARY.md` | 执行前核对结论(带 `file:line`)、`CONCURRENT-BASELINE` 段 + `基线摘要 sha256`、`TYPE-BASELINE` 段(两个基线数字 + material 归属诊断逐行)、D-33..D-37 处置对照与逐处替换映射(含「`getIfBigImage ≡ getTile`」论据与 `cache.ts` 行为影响)、`textures` 承担 store 的用户裁定、门禁输出摘要、人工复核十一条、未实现项清单、REND-01 / REND-02 Pending 声明与运行时 UAT 不可得说明 |
Create `.planning/phases/04-render-adaptation/04-07-SUMMARY.md` when done
From 786c00c499e44cd2c2946c722ae204b843a07d27 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 11:31:00 +0800
Subject: [PATCH 067/173] docs(04): plan material interface adaptation (04-07)
---
.planning/STATE.md | 6 +++---
.planning/phases/04-render-adaptation/04-CONTEXT.md | 4 ++++
.planning/state.json | 2 +-
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/.planning/STATE.md b/.planning/STATE.md
index e919353..7c4e16d 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -5,14 +5,14 @@ current_phase: 04
current_phase_name: 渲染适配与双布局
status: executing
stopped_at: Completed 04-04-PLAN.md(勇士渲染修正:注入 IFaceManager、拆钩子类、补 AnimDir、换 @motajs/animate,三文件机器门禁全绿;REND-01/REND-02 保持 Pending)
-last_updated: "2026-09-22T02:28:19.679Z"
+last_updated: "2026-09-22T03:29:38.480Z"
last_activity: 2026-09-21
last_activity_desc: Phase 04 execution started
-state_head: 85f65b8f551e700992664b4f14700cd3cf0f4074
+state_head: f55a76297f0d4f929f8e0fab376e341d698a4631
progress:
total_phases: 7
completed_phases: 0
- total_plans: 77
+ total_plans: 78
completed_plans: 76
milestone_name: milestone
---
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
index e65d059..6c0e2a2 100644
--- a/.planning/phases/04-render-adaptation/04-CONTEXT.md
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -70,6 +70,10 @@
- **D-32:** 计划分**两步**:**第一步**收集影响范围(只读清点);**第二步**进行修改。第二步骤第一步结果与用户审阅后再规划。
- **边界确认(2026-09-21):** 范围限 `packages-user`;`packages` 不纳入(理应不受本改动影响)。**以 `packages-user/client-base/src/types.ts`(及 `material/types.ts`)为基准**。**若发现其余 big-image 残留,必须在清点文档中报告。** `src/` 或 `packages/` 内若命中,仅报告、不修改。第一步产出 `.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md`(只读,允许派只读子代理);第二步待用户审阅第一步结果后再规划。
- **D-33:** **范围外一律不改**;**不追求解决全部类型错误**(整仓 199 条中大量为既有/无关),**只修复与本次 material 接口适应相关的项**。第二步实施时严格按此约束。
+- **D-34:** (第二步·实施裁决 2026-09-21)清点文档 **A 类**(已移除的 big-image 符号引用)——**跟 big-image 有关的全部删除**;若有方法/函数依赖它,则做**合理修改或删除**。
+- **D-35:** (C 类)**补充实现**——`ITextureManager.textures` 与 `ICoreStateExtended`(`state: ICoreState`)在 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 上的实现;**`state` 统一通过构造器传入**。
+- **D-36:** (D 类)**全部删除**——`MaterialManager` 的 big-image 实现残留(`bigImageStore`/`bigImageData`/`bigImageId` 等)与 `render/elements/cache.ts` 的 legacy big-image 路径。
+- **D-37:** (E 类)**补充依赖声明**——为 `packages-user/client-base/package.json` 补上 `@user/data-state` 依赖(其源码已 import 该包)。
### the agent's Discretion
- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
diff --git a/.planning/state.json b/.planning/state.json
index f8f3805..824975a 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -44,5 +44,5 @@
"label": "Advance to the next step",
"reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-22T02:28:19.843Z"
+ "updated_at": "2026-09-22T03:29:38.601Z"
}
From 78873d0a27bf80c76a83ba1f3ac7d85a30083b5c Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 11:52:55 +0800
Subject: [PATCH 068/173] refactor(04-07): adapt material interfaces (drop
big-image, inject state)
- delete MaterialManager big-image implementation (IBigImageReturn import, bigImageStore/bigImageData/bigImageId fields, 5 methods, setDefaultFrame coupling)
- change 7 consumers getIfBigImage -> getTile (door x2, hero x3, renderer, vertex); drop getOffsetPool big-image offset loop
- delete cache.ts legacy big-image path (RenderableDataBase.bigImage, literals, destructuring, enemys special-case, big-image branch, unused enemys decl)
- inject state via constructor into MaterialManager/AutotileProcessor/AssetBuilder/TrackedAssetData (from ClientCore; renderer uses manager.state)
- add MaterialManager.textures (dedicated TextureStore)
- declare @user/data-state in client-base/package.json (lockfile not synced)
---
packages-user/client-base/package.json | 3 +-
.../client-base/src/material/autotile.ts | 6 +-
.../client-base/src/material/builder.ts | 11 +++-
.../client-base/src/material/manager.ts | 59 ++-----------------
packages-user/client-modules/src/client.ts | 4 +-
.../src/render/elements/cache.ts | 53 +----------------
.../src/render/map/extension/door.ts | 4 +-
.../src/render/map/extension/hero.ts | 8 +--
.../client-modules/src/render/map/renderer.ts | 11 +---
.../client-modules/src/render/map/vertex.ts | 2 +-
10 files changed, 31 insertions(+), 130 deletions(-)
diff --git a/packages-user/client-base/package.json b/packages-user/client-base/package.json
index c0e203d..78a0c8b 100644
--- a/packages-user/client-base/package.json
+++ b/packages-user/client-base/package.json
@@ -4,6 +4,7 @@
"@motajs/audio": "workspace:*",
"@motajs/render": "workspace:*",
"@motajs/client-base": "workspace:*",
- "@user/data-base": "workspace:*"
+ "@user/data-base": "workspace:*",
+ "@user/data-state": "workspace:*"
}
}
diff --git a/packages-user/client-base/src/material/autotile.ts b/packages-user/client-base/src/material/autotile.ts
index f4d0042..a0e9358 100644
--- a/packages-user/client-base/src/material/autotile.ts
+++ b/packages-user/client-base/src/material/autotile.ts
@@ -12,6 +12,7 @@ import {
IMaterialFramedData,
ITextureManager
} from './types';
+import { ICoreState } from '@user/data-state';
import { isNil } from 'lodash-es';
interface ConnectedAutotile {
@@ -45,7 +46,10 @@ export class AutotileProcessor implements IAutotileProcessor {
/** 自动元件父子关系映射,父元件 -> 子元件列表 */
readonly childMap: Map> = new Map();
- constructor(readonly manager: ITextureManager) {}
+ constructor(
+ readonly manager: ITextureManager,
+ readonly state: ICoreState
+ ) {}
private ensureChildSet(num: number) {
const set = this.childMap.get(num);
diff --git a/packages-user/client-base/src/material/builder.ts b/packages-user/client-base/src/material/builder.ts
index 3797688..41df693 100644
--- a/packages-user/client-base/src/material/builder.ts
+++ b/packages-user/client-base/src/material/builder.ts
@@ -7,6 +7,7 @@ import {
SizedCanvasImageSource
} from '@motajs/render';
import { IAssetBuilder, ITextureGetter, ITrackedAssetData } from './types';
+import { ICoreState } from '@user/data-state';
import { logger, PrivateListDirtyTracker } from '@motajs/common';
export class AssetBuilder implements IAssetBuilder {
@@ -24,8 +25,11 @@ export class AssetBuilder implements IAssetBuilder {
/** 贴图更新的 promise */
private pending: Promise = Promise.resolve();
- constructor(readonly materials: ITextureGetter) {
- this.trackedData = new TrackedAssetData(materials, this);
+ constructor(
+ readonly materials: ITextureGetter,
+ readonly state: ICoreState
+ ) {
+ this.trackedData = new TrackedAssetData(materials, this, state);
}
pipe(store: ITextureStore): void {
@@ -109,7 +113,8 @@ class TrackedAssetData
constructor(
readonly materials: ITextureGetter,
- readonly builder: AssetBuilder
+ readonly builder: AssetBuilder,
+ readonly state: ICoreState
) {
super(0);
}
diff --git a/packages-user/client-base/src/material/manager.ts b/packages-user/client-base/src/material/manager.ts
index 3062323..f49aaf4 100644
--- a/packages-user/client-base/src/material/manager.ts
+++ b/packages-user/client-base/src/material/manager.ts
@@ -17,11 +17,11 @@ import {
IIndexedIdentifier,
IMaterialAssetData,
BlockCls,
- IBigImageReturn,
IAssetBuilder,
IMaterialFramedData,
ITrackedAssetData
} from './types';
+import { ICoreState } from '@user/data-state';
import { logger } from '@motajs/common';
import { getClsByString, getTextureFrame } from './utils';
import { isNil } from 'lodash-es';
@@ -40,7 +40,7 @@ export class MaterialManager implements ITextureManager {
readonly tilesetStore: ITextureStore = new TextureStore();
readonly imageStore: ITextureStore = new TextureStore();
readonly assetStore: ITextureStore = new TextureStore();
- readonly bigImageStore: ITextureStore = new TextureStore();
+ readonly textures: ITextureStore = new TextureStore();
/** 自动元件图像源映射 */
readonly autotileSource: Map = new Map();
@@ -52,8 +52,6 @@ export class MaterialManager implements ITextureManager {
/** 带有脏标记追踪的图集对象 */
readonly trackedAsset: ITrackedAssetData;
- /** 大怪物数据 */
- readonly bigImageData: Map = new Map();
/** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
readonly tilesetOffsetMap: Map = new Map();
/** 图集打包器 */
@@ -73,8 +71,6 @@ export class MaterialManager implements ITextureManager {
/** 行切分器 */
readonly rowSplitter: TextureRowSplitter = new TextureRowSplitter();
- /** 大怪物贴图的标识符 */
- private bigImageId: number = 0;
/** 当前 tileset 索引 */
private nowTilesetIndex: number = -1;
/** 当前 tileset 偏移 */
@@ -82,8 +78,8 @@ export class MaterialManager implements ITextureManager {
/** 是否已经构建过素材 */
private built: boolean = false;
- constructor() {
- this.assetBuilder = new AssetBuilder(this);
+ constructor(readonly state: ICoreState) {
+ this.assetBuilder = new AssetBuilder(this, state);
this.assetBuilder.pipe(this.assetStore);
this.trackedAsset = this.assetBuilder.tracked();
}
@@ -230,10 +226,6 @@ export class MaterialManager implements ITextureManager {
setDefaultFrame(identifier: number, defaultFrame: number): void {
this.defaultFrames.set(identifier, defaultFrame);
- const bigImageData = this.bigImageData.get(identifier);
- if (bigImageData) {
- bigImageData.defaultFrame = defaultFrame;
- }
}
getDefaultFrame(identifier: number): number {
@@ -549,49 +541,6 @@ export class MaterialManager implements ITextureManager {
return this.numIdMap.get(identifier);
}
- setBigImage(
- identifier: number,
- image: ITexture,
- frames: number
- ): IBigImageReturn {
- const bigImageId = this.bigImageId++;
- this.bigImageStore.addTexture(bigImageId, image);
- const cls = this.clsMap.get(identifier) ?? BlockCls.Unknown;
- const store: IMaterialFramedData = {
- texture: image,
- cls,
- offset: image.width / 4,
- frames,
- defaultFrame: this.defaultFrames.get(identifier) ?? -1
- };
- this.bigImageData.set(identifier, store);
- const data: IBigImageReturn = {
- identifier: bigImageId,
- store: this.bigImageStore
- };
- return data;
- }
-
- isBigImage(identifier: number): boolean {
- return this.bigImageData.has(identifier);
- }
-
- getBigImage(identifier: number): Readonly | null {
- return this.bigImageData.get(identifier) ?? null;
- }
-
- getBigImageByAlias(alias: string): Readonly | null {
- const identifier = this.idNumMap.get(alias);
- if (isNil(identifier)) return null;
- return this.bigImageData.get(identifier) ?? null;
- }
-
- getIfBigImage(identifier: number): Readonly | null {
- const bigImage = this.bigImageData.get(identifier);
- if (bigImage) return bigImage;
- else return this.getTile(identifier);
- }
-
assetContainsTexture(texture: ITexture): boolean {
return this.trackedAsset.skipRef.has(texture.source);
}
diff --git a/packages-user/client-modules/src/client.ts b/packages-user/client-modules/src/client.ts
index ab97dc0..b70bdfe 100644
--- a/packages-user/client-modules/src/client.ts
+++ b/packages-user/client-modules/src/client.ts
@@ -81,8 +81,8 @@ export class ClientCore extends CoreState implements IClientCore {
//#region 素材系统
- this.materials = new MaterialManager();
- this.autotile = new AutotileProcessor(this.materials);
+ this.materials = new MaterialManager(this);
+ this.autotile = new AutotileProcessor(this.materials, this);
//#endregion
diff --git a/packages-user/client-modules/src/render/elements/cache.ts b/packages-user/client-modules/src/render/elements/cache.ts
index 8c57e2c..e81b3c8 100644
--- a/packages-user/client-modules/src/render/elements/cache.ts
+++ b/packages-user/client-modules/src/render/elements/cache.ts
@@ -32,8 +32,6 @@ interface RenderableDataBase {
frame: number;
/** 对应图块属性的动画帧数,-1表示没有设定,0表示第一帧 */
animate: number;
- /** 是否是大怪物 */
- bigImage: boolean;
render: [x: number, y: number, width: number, height: number][];
}
@@ -45,7 +43,6 @@ export interface RenderableData extends RenderableDataBase {
export interface AutotileRenderable extends RenderableDataBase {
image: Record;
autotile: true;
- bigImage: false;
}
class TextureCache {
@@ -124,7 +121,6 @@ class TextureCache {
num: number
): RenderableData | AutotileRenderable | null {
const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
- const enemys = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;
const icons = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1;
/** 特判空图块与空气墙 */
@@ -140,8 +136,7 @@ class TextureCache {
frame: 1,
render: [[x * 32, y * 32, 32, 32]],
animate: 0,
- autotile: false,
- bigImage: false
+ autotile: false
};
this.renderable.set(num, data);
return data;
@@ -150,48 +145,7 @@ class TextureCache {
const data = map[num as Exclude];
// 地狱般的分支if
if (data) {
- let { faceIds, bigImage } = data;
const { cls, id, animate } = data;
- if (cls === 'enemys' || cls === 'enemy48') {
- // 怪物需要特殊处理,因为它的大怪物信息不在 maps 里面
- ({ bigImage, faceIds } = enemys[id as EnemyIds]);
- }
- if (bigImage) {
- const image = core.material.images.images[bigImage];
- if (!image) {
- logger.warn(10, id);
- return null;
- }
- let line = 0;
- if (faceIds) {
- const arr = ['down', 'left', 'right', 'up'];
- for (let i = 0; i < arr.length; i++) {
- if (faceIds[arr[i] as Dir] === id) {
- line = i;
- break;
- }
- }
- }
- const totalLines = image.width / image.height >= 2 ? 1 : 4;
- const w = Math.round(image.width / 4);
- const h = Math.round(image.height / totalLines);
- const y = h * line;
- const data: RenderableData = {
- image,
- frame: 4,
- render: [
- [0, y, w, h],
- [w, y, w, h],
- [w * 2, y, w, h],
- [w * 3, y, w, h]
- ],
- animate: (animate ?? 0) - 1,
- autotile: false,
- bigImage: true
- };
- this.renderable.set(num, data);
- return data;
- }
// enemy48和npc48都应该视为大怪物
if (cls === 'enemy48' || cls === 'npc48') {
const img = core.material.images[cls];
@@ -211,8 +165,7 @@ class TextureCache {
[w * 3, y, w, h]
],
animate: (animate ?? 0) - 1,
- autotile: false,
- bigImage: true
+ autotile: false
};
this.renderable.set(num, data);
return data;
@@ -240,7 +193,6 @@ class TextureCache {
frame: auto.frame,
render,
autotile: true,
- bigImage: false,
animate: (animate ?? 0) - 1
};
this.renderable.set(num, data);
@@ -273,7 +225,6 @@ class TextureCache {
frame: frame,
render,
autotile: false,
- bigImage: false,
animate: (animate ?? 0) - 1
};
this.renderable.set(num, data);
diff --git a/packages-user/client-modules/src/render/map/extension/door.ts b/packages-user/client-modules/src/render/map/extension/door.ts
index 7420ddc..8b76892 100644
--- a/packages-user/client-modules/src/render/map/extension/door.ts
+++ b/packages-user/client-modules/src/render/map/extension/door.ts
@@ -33,7 +33,7 @@ export class MapDoorRenderer implements IMapDoorRenderer {
const array = this.layer.getMapRef().array;
const index = y * this.layer.width + x;
const num = array[index];
- const data = this.renderer.manager.getIfBigImage(num);
+ const data = this.renderer.manager.getTile(num);
if (!data) return;
const frames = data.frames;
for (let i = 0; i < frames; i++) {
@@ -43,7 +43,7 @@ export class MapDoorRenderer implements IMapDoorRenderer {
}
async closeDoor(num: number, x: number, y: number): Promise {
- const data = this.renderer.manager.getIfBigImage(num);
+ const data = this.renderer.manager.getTile(num);
if (!data) return;
const moving = this.renderer.addMovingBlock(this.layer, num, x, y);
const frames = data.frames;
diff --git a/packages-user/client-modules/src/render/map/extension/hero.ts b/packages-user/client-modules/src/render/map/extension/hero.ts
index cec2a6f..0cd69f5 100644
--- a/packages-user/client-modules/src/render/map/extension/hero.ts
+++ b/packages-user/client-modules/src/render/map/extension/hero.ts
@@ -236,7 +236,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
const tx = block.x + dx;
const ty = block.y + dy;
const nextTile = state.roleFace.getFaceOf(block.tile, direction);
- const nextTex = this.renderer.manager.getIfBigImage(
+ const nextTex = this.renderer.manager.getTile(
nextTile?.identifier ?? block.tile
);
entity.animateInterval = time;
@@ -361,7 +361,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
const nowFace =
degraded === FaceDirection.Unknown ? FaceDirection.Down : degraded;
const faced = state.roleFace.getFaceOf(image, nowFace);
- const tex = this.renderer.manager.getIfBigImage(faced?.face ?? image);
+ const tex = this.renderer.manager.getTile(faced?.face ?? image);
if (!tex) {
logger.warn(91, image.toString());
return;
@@ -425,9 +425,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
last.nextDirection
);
if (!nextFace) continue;
- const tile = this.renderer.manager.getIfBigImage(
- nextFace.identifier
- );
+ const tile = this.renderer.manager.getTile(nextFace.identifier);
if (!tile) continue;
moving.block.setTexture(tile);
moving.direction = last.nextDirection;
diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts
index 5850edf..4a879b6 100644
--- a/packages-user/client-modules/src/render/map/renderer.ts
+++ b/packages-user/client-modules/src/render/map/renderer.ts
@@ -231,7 +231,7 @@ export class MapRenderer
);
this.contextData = data;
this.vertex = new MapVertexGenerator(this, data);
- this.autotile = new AutotileProcessor(manager);
+ this.autotile = new AutotileProcessor(manager, manager.state);
this.tick = this.tick.bind(this);
this.viewport = new MapViewport(this);
this.tileAnimater = new TextureColumnAnimater();
@@ -648,13 +648,6 @@ export class MapRenderer
private getOffsetPool(): number[] {
const pool = new Set([32]);
- // 其他的都是 bigImage 了,直接遍历获取
- for (const identifier of this.manager.bigImageStore.keys()) {
- const data = this.manager.getBigImage(identifier);
- if (!data) continue;
- const offset = data.texture.width / data.frames;
- pool.add(offset);
- }
// 还有勇士图片
for (const tex of this.manager.imageStore.values()) {
if (!this.manager.assetContainsTexture(tex)) continue;
@@ -1247,7 +1240,7 @@ export class MapRenderer
tile: number
): Promise {
// 图块背景
- const tex = this.manager.getIfBigImage(tile);
+ const tex = this.manager.getTile(tile);
if (!tex) {
// 图块不存在
logger.error(35);
diff --git a/packages-user/client-modules/src/render/map/vertex.ts b/packages-user/client-modules/src/render/map/vertex.ts
index a8ca1af..85ff489 100644
--- a/packages-user/client-modules/src/render/map/vertex.ts
+++ b/packages-user/client-modules/src/render/map/vertex.ts
@@ -543,7 +543,7 @@ export class MapVertexGenerator
) {
// 此处仅更新当前图块,不更新周围一圈的自动元件
// 周围一圈的自动元件需要在更新某个图块或者某个区域时处理,不在这里处理
- const tile = this.renderer.manager.getIfBigImage(num);
+ const tile = this.renderer.manager.getTile(num);
if (!tile) {
// 不存在可渲染对象,认为是空图块
From 1bb42f66ca04d118a893c5b5222ea7927e145e04 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 12:12:16 +0800
Subject: [PATCH 069/173] docs(04-07): record execution state
---
.../04-render-adaptation/04-07-SUMMARY.md | 258 ++++++++++++++++++
1 file changed, 258 insertions(+)
create mode 100644 .planning/phases/04-render-adaptation/04-07-SUMMARY.md
diff --git a/.planning/phases/04-render-adaptation/04-07-SUMMARY.md b/.planning/phases/04-render-adaptation/04-07-SUMMARY.md
new file mode 100644
index 0000000..f6707c4
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-07-SUMMARY.md
@@ -0,0 +1,258 @@
+---
+phase: 04-render-adaptation
+plan: 7
+subsystem: render-material
+tags: [material, texture-manager, big-image-removal, constructor-injection, state, data-state]
+requires:
+ - phase: 04-render-adaptation
+ provides: 04-06 material 接口影响只读清点(04-MATERIAL-INTERFACE-IMPACT.md,A/C/D/E 四类待处置项)
+provides:
+ - MaterialManager.textures 补实现(专用 TextureStore)
+ - MaterialManager / AutotileProcessor / AssetBuilder / TrackedAssetData 的构造器注入 state
+ - big-image 实现与消费者全部删除,7 处取值改为 getTile
+ - cache.ts legacy big-image 路径整体删除
+ - client-base/package.json 补 @user/data-state 声明
+affects: [04-render-adaptation]
+actuals:
+ tokens: 4505
+ tasks: 3
+ commits: 2
+ plan_head_before: 786c00c
+tech-stack:
+ added: []
+ patterns: [构造器注入 state(D-35), big-image 概念删除(D-34/D-36)]
+key-files:
+ created: []
+ modified:
+ - packages-user/client-base/package.json
+ - packages-user/client-base/src/material/manager.ts
+ - packages-user/client-base/src/material/autotile.ts
+ - packages-user/client-base/src/material/builder.ts
+ - packages-user/client-modules/src/client.ts
+ - packages-user/client-modules/src/render/map/renderer.ts
+ - packages-user/client-modules/src/render/map/vertex.ts
+ - packages-user/client-modules/src/render/map/extension/door.ts
+ - packages-user/client-modules/src/render/map/extension/hero.ts
+ - packages-user/client-modules/src/render/elements/cache.ts
+key-decisions:
+ - "D-35: state 一律经构造器注入,不取全局单例(D-23)"
+ - "F-02 裁定:textures 由新建专用 TextureStore 承担(本次范围内无写入者)"
+ - "D-34: getIfBigImage ≡ getTile(bigImageData 无写入者),7 处调用行为保持改写"
+requirements-completed: []
+duration: ~35min
+completed: 2026-09-22
+status: complete
+---
+
+# Phase 4 Plan 7: material 接口适应实施 Summary
+
+**删除 material 的 big-image 全链路并把 state 经构造器注入四个实现类,`packages-user` 内 material 归属类型诊断从 19 条清零(总行数 199 → 180)。**
+
+## Performance
+
+- **Duration:** ~35min(单次顺序会话;refactor 提交 `78873d0` 于 2026-09-22 11:52:55 +0800)
+- **Started:** 2026-09-22
+- **Completed:** 2026-09-22
+- **Tasks:** 3(Task 0 为只读汇报关卡,不计入工作任务)
+- **Files modified:** 10
+
+## Accomplishments
+
+- D-34:`MaterialManager` 的 big-image 实现面(`IBigImageReturn` import、`bigImageStore` / `bigImageData` / `bigImageId` 三字段、`setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` 五方法、`setDefaultFrame` 的 big-image 耦合)全部删除;7 处消费者调用改为 `getTile(...)`;`getOffsetPool` 的 big-image 偏移收集循环删除。
+- D-35:`MaterialManager` 补 `readonly textures: ITextureStore`(专用 `TextureStore`)与构造器注入的 `state`;`AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 各补构造器注入的 `state`;`state` 一律构造器传入,未引入全局单例(D-23)。
+- D-36:`cache.ts` 的 legacy big-image 路径(字段、`AutotileRenderable` 的 `bigImage: false`、5 处对象字面量、`faceIds` / `bigImage` 解构与 `enemys` 特判、`if (bigImage)` 分支、随之未使用的 `enemys` 声明)全部删除。
+- D-37:`client-base/package.json` 补 `"@user/data-state": "workspace:*"`;未执行安装、未修改 `pnpm-lock.yaml`(pnpm 隐式同步的 lockfile 改动已还原)。
+- 门禁:material 归属诊断 19 → 0;总错误行数 199 → 180(= 基线减 material 条数);10 个在范围文件 0 诊断;`packages-user` 内 `bigimage` 记号归零;eslint 0 错误;CRLF 保持。
+
+## Task Commits
+
+1. **Task 1+2+3: material 接口适应实施** - `78873d0` (refactor) — 10 个文件一次原子提交
+2. **Task 3: 执行记录** - `docs(04-07): record execution state` (docs) — 仅含本 SUMMARY
+
+_说明:按 04-07-PLAN 要求,Task 1 / Task 2 为整体修正的中间态,不单独提交;Task 3 完成全部改动后一次原子提交。_
+
+## Files Created/Modified
+
+- `packages-user/client-base/package.json` - `dependencies` 新增 `@user/data-state: workspace:*`(D-37)
+- `packages-user/client-base/src/material/manager.ts` - 补 `textures` 与构造器注入 `state`;big-image 实现面全删
+- `packages-user/client-base/src/material/autotile.ts` - 构造器新增 `state`(保留 `manager` 形参)
+- `packages-user/client-base/src/material/builder.ts` - `AssetBuilder` / `TrackedAssetData` 构造器新增 `state`
+- `packages-user/client-modules/src/client.ts` - `new MaterialManager(this)`、`new AutotileProcessor(this.materials, this)`
+- `packages-user/client-modules/src/render/map/renderer.ts` - `new AutotileProcessor(manager, manager.state)`;`getOffsetPool` 清理;`useTileBackground` 取值改 `getTile`
+- `packages-user/client-modules/src/render/map/vertex.ts` - `updateVertexArray` 取值改 `getTile`
+- `packages-user/client-modules/src/render/map/extension/door.ts` - `openDoor` / `closeDoor` 取值改 `getTile`
+- `packages-user/client-modules/src/render/map/extension/hero.ts` - 三处取值改 `getTile`;D-28 保留项未动
+- `packages-user/client-modules/src/render/elements/cache.ts` - legacy big-image 路径整体删除
+
+## 执行前核对(Task 1 步骤 0a,带 file:line)
+
+- ① `MaterialManager` 仍 `implements ITextureManager`,字段仅 `tileStore` / `tilesetStore` / `imageStore` / `assetStore` / `bigImageStore`,无 `textures`、无 `state` —— 结论:一致(`manager.ts:38-43`,初始状态)
+- ② 四个实现类仍缺 `state` —— 结论:一致(`manager.ts:38`、`autotile.ts:42`、`builder.ts:12`、`builder.ts:99`)
+- ③ `ICoreStateExtended` 定义源仍为 `readonly state: ICoreState`,`ICoreState` 由 barrel 导出 —— 结论:一致(`data-state/src/types.ts:45-49`;`data-state/src/index.ts:11` `export * from './types';`)
+- ④ 四处 `extends ICoreStateExtended` 与 `readonly textures: ITextureStore` 仍在基准侧 —— 结论:一致(`material/types.ts:104`、`:260-263`、`:430`、`:462-463`)
+- ⑤ 构造 / 接线面仅五处 —— 结论:一致(`client.ts:84` / `client.ts:85` / `render/map/renderer.ts:234` / `manager.ts:86` / `builder.ts:28`;`git grep` 无第六处)
+- ⑥ `ClientCore extends CoreState implements IClientCore`、`CoreState implements ICoreState`、链 `IClientCore → IClientSystem → IClientBase → ICoreState` —— 结论:一致(`client.ts:45`、`core.ts:77`、`client-modules/types.ts:9`、`client-system/types.ts:3`、`client-base/types.ts:8`)
+- ⑦ `render/map/renderer.ts:217` 的 `manager` 类型仍为 `ITextureManager` —— 结论:一致
+- ⑧ `@user/data-state` 仍不在 `client-base/package.json`,而 `material/types.ts:9` 与 `client-base/src/types.ts:3` 已 import —— 结论:一致
+- ⑨ `ITextureGetter.getTile(identifier): Readonly | null` —— 结论:一致(`material/types.ts:201`)
+- ⑩ `setBigImage` / `isBigImage` / `getBigImageByAlias` 消费者调用点仍为 0 —— 结论:一致(`git grep` 仅命中 `manager.ts` 自身定义行)
+- ⑪ `cache.ts` 的 bigImage 命中与 `enemys` 声明仍在 —— 结论:一致(`cache.ts:36,48,144,153,157,159,160,190,215,243,276`、`:127`)
+- ⑫ `@motajs/render` barrel 仍导出 `TextureStore` —— 结论:一致(`packages/render/src/assets/index.ts` `export * from './store';`)
+
+十二项全部与规划基线一致,无需修订;其中第 ⑪ 项与 04-06 清点的 `cache.ts` 命中行号一致。
+
+## 类型门禁基线(Task 1 步骤 0b,当日实测)
+
+
+错误总行数:199;material 归属行数:19
+packages-user/client-base/src/material/autotile.ts(42,14): error TS2420: Class 'AutotileProcessor' incorrectly implements interface 'IAutotileProcessor'.
+packages-user/client-base/src/material/builder.ts(12,14): error TS2420: Class 'AssetBuilder' incorrectly implements interface 'IAssetBuilder'.
+packages-user/client-base/src/material/builder.ts(91,9): error TS2741: Property 'state' is missing in type 'TrackedAssetData' but required in type 'ITrackedAssetData'.
+packages-user/client-base/src/material/builder.ts(99,7): error TS2420: Class 'TrackedAssetData' incorrectly implements interface 'ITrackedAssetData'.
+packages-user/client-base/src/material/manager.ts(20,5): error TS2305: Module '"./types"' has no exported member 'IBigImageReturn'.
+packages-user/client-base/src/material/manager.ts(38,14): error TS2420: Class 'MaterialManager' incorrectly implements interface 'ITextureManager'.
+packages-user/client-base/src/material/manager.ts(86,9): error TS2741: Property 'state' is missing in type 'AssetBuilder' but required in type 'IAssetBuilder'.
+packages-user/client-modules/src/client.ts(84,9): error TS2739: Type 'MaterialManager' is missing the following properties from type 'ITextureManager': textures, state
+packages-user/client-modules/src/client.ts(85,9): error TS2741: Property 'state' is missing in type 'AutotileProcessor' but required in type 'IAutotileProcessor'.
+packages-user/client-modules/src/render/map/extension/door.ts(36,44): error TS2339: Property 'getIfBigImage' does not exist on type 'ITextureManager'.
+packages-user/client-modules/src/render/map/extension/door.ts(46,44): error TS2339: Property 'getIfBigImage' does not exist on type 'ITextureManager'.
+packages-user/client-modules/src/render/map/extension/hero.ts(239,47): error TS2339: Property 'getIfBigImage' does not exist on type 'ITextureManager'.
+packages-user/client-modules/src/render/map/extension/hero.ts(364,43): error TS2339: Property 'getIfBigImage' does not exist on type 'ITextureManager'.
+packages-user/client-modules/src/render/map/extension/hero.ts(428,48): error TS2339: Property 'getIfBigImage' does not exist on type 'ITextureManager'.
+packages-user/client-modules/src/render/map/renderer.ts(234,9): error TS2741: Property 'state' is missing in type 'AutotileProcessor' but required in type 'IAutotileProcessor'.
+packages-user/client-modules/src/render/map/renderer.ts(652,47): error TS2551: Property 'bigImageStore' does not exist on type 'ITextureManager'. Did you mean 'imageStore'?
+packages-user/client-modules/src/render/map/renderer.ts(653,39): error TS2551: Property 'getBigImage' does not exist on type 'ITextureManager'. Did you mean 'getImage'?
+packages-user/client-modules/src/render/map/renderer.ts(1250,34): error TS2339: Property 'getIfBigImage' does not exist on type 'ITextureManager'.
+packages-user/client-modules/src/render/map/vertex.ts(546,44): error TS2339: Property 'getIfBigImage' does not exist on type 'ITextureManager'.
+
+
+基线说明:`错误总行数 199`、`material 归属 19`(= `client-base` 7 + `client-modules` 12),与 `04-MATERIAL-INTERFACE-IMPACT.md` 的规划日实测完全一致,无差异需要说明。material 归属由固定谓词机器判定(路径含 `client-base/src/material/` 或诊断文本命中 14 个固定记号之一)。
+
+**完工后类型门禁实测**:material 归属 **0**;10 个在范围文件诊断 **0**;错误总行数 **180** = 基线 199 − material 19(≤ 允许上限 180)。
+
+## 并发改动基线(Task 1 步骤 0c,当日实测)
+
+
+
+基线摘要 sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+
+基线说明:当日 `git status --porcelain` **输出为空**(无用户并发未提交改动;规划日假设的 ` M .planning/phases/04-render-adaptation/04-CONTEXT.md` 当日已提交,故实测为空,与 04-04 / 04-06 的旧列表不同,本 run 一律以本次实测为准)。摘要 `sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` 为空集摘要。本计划未回滚 / 暂存 / 提交 / 修改任何条目;完工后同一命令复算,条目集合与摘要均未变(entries 0)。
+
+## D-33..D-37 处置对照
+
+| 裁定 | 类别 | 落地内容 | 结果 |
+|---|---|---|---|
+| D-34 | A 类(已移除符号引用) | `MaterialManager` big-image 实现面全删;7 处 `getIfBigImage` → `getTile`;`getOffsetPool` big-image 循环删除 | 完成 |
+| D-35 | C 类(新增约束未实现) | 四个实现类补构造器注入 `state`;`MaterialManager` 补 `textures`(专用 `TextureStore`) | 完成 |
+| D-36 | D 类(big-image 残留) | `MaterialManager` 残留(同 D-34)+ `cache.ts` legacy big-image 路径整体删除 | 完成 |
+| D-37 | E 类(依赖声明) | `client-base/package.json` 补 `@user/data-state: workspace:*`;不安装、不同步 lockfile | 完成 |
+| D-33 | 范围边界 | 只修 material 归属诊断;19 → 0,总行数 199 → 180(≤ 180);无关诊断一条未追 | 完成 |
+
+### 逐处替换映射(含行为保持论据)
+
+| 位置 | 旧 | 新 | 等价性 |
+|---|---|---|---|
+| `door.ts:36` | `this.renderer.manager.getIfBigImage(num)` | `this.renderer.manager.getTile(num)` | **行为保持** |
+| `door.ts:46` | 同上 | 同上 | 同上 |
+| `hero.ts:239` | `getIfBigImage(nextTile?.identifier ?? block.tile)` | `getTile(nextTile?.identifier ?? block.tile)` | 同上 |
+| `hero.ts:364` | `getIfBigImage(faced?.face ?? image)` | `getTile(faced?.face ?? image)` | 同上 |
+| `hero.ts:428` | `getIfBigImage(nextFace.identifier)` | `getTile(nextFace.identifier)` | 同上 |
+| `renderer.ts:1250` | `this.manager.getIfBigImage(tile)` | `this.manager.getTile(tile)` | 同上 |
+| `vertex.ts:546` | `this.renderer.manager.getIfBigImage(num)` | `this.renderer.manager.getTile(num)` | 同上 |
+| `renderer.ts:651-657` | `bigImageStore.keys()` + `getBigImage(identifier)` 偏移循环 | **整段删除**(含行内注释) | big-image 专属偏移,概念删除后不再存在 |
+| `manager.ts:20` | import `IBigImageReturn` | 从 import 列表删除 | 符号已不存在 |
+| `manager.ts` 三字段 | `bigImageStore` / `bigImageData` / `bigImageId` | 删除 | 非接口成员、无消费者 |
+| `manager.ts` `setDefaultFrame` | 对 `bigImageData` 的耦合 | 删除耦合,保留 `defaultFrames.set` | 接口方法本体保留(F-05) |
+| `manager.ts` 五方法 | `setBigImage` / `isBigImage` / `getBigImage` / `getBigImageByAlias` / `getIfBigImage` | 整体删除 | 非接口成员;消费者 0 或被 `getTile` 取代 |
+| `cache.ts` 11 处 | 字段 / 字面量 / 解构 / `enemys` 特判 / `if (bigImage)` 分支 | 整体删除(含 `enemys` 声明) | legacy 概念路径 |
+| `client-base/package.json` | 无 `@user/data-state` | `"@user/data-state": "workspace:*"` | 声明补全 |
+
+**`getIfBigImage ≡ getTile` 论据(现场复验)**:`getIfBigImage(x)` 的实现是 `bigImageData.get(x) ?? getTile(x)`(原 `manager.ts:589-593`),而 `bigImageData` 的唯一写入者 `setBigImage` 在全仓库**消费者调用点为 0**——完工后 `git grep -n -E "setBigImage|isBigImage|getBigImageByAlias" -- packages-user` **无输出**(退出码 1),故今日 `getIfBigImage ≡ getTile`,7 处改写行为保持。
+
+**`textures` 承担 store(F-02 裁定)**:按用户在 Task 0 的裁定,采用建议默认 (a) —— `readonly textures: ITextureStore = new TextureStore();`。**事实声明:本次改动范围内没有任何代码向它写入**(写入侧属新引擎后续步骤,loader 不在 D-33 范围内);它是接口要求的「统一纹理入口」占位实现,不是「已可用的统一纹理入口」。
+
+**`cache.ts` 删除的行为影响(D-36,未经运行验证)**:`if (bigImage)` 分支删除后,原本走该分支的 legacy 大图怪物会落到后续分支(`enemy48` / `npc48` 分支或普通图块分支)渲染。这与 D-31「big-image 概念删除、旧兼容可痛丢弃」一致,但确是一次**运行时可见**的变化;因无渲染设施,本计划**未在运行时验证它**。
+
+## 门禁输出摘要
+
+- Task 1 tracer 类型门禁:`OK tracer: C-class 0; material 10==M-9; total 190<=190`
+- Task 1 绑定门禁:`OK tracer bindings: textures + 4 state + 5 construction sites + D37 dependency`
+- Task 1 eslint(5 文件):0 错误
+- Task 1 CRLF(5 文件):`OK CRLF`
+- Task 2 scoped 类型门禁:`OK A-class: material 0; touched 0; total 180<=180`
+- Task 2 静态门禁:`OK D34 deletions + 7 getTile + getOffsetPool cleaned`
+- Task 2 eslint(5 文件):0 错误
+- Task 2 CRLF(5 文件):`OK CRLF`
+- Task 3 终验类型门禁:`OK final type gate: material 0; touched 0; total 180<=180`
+- Task 3 终验静态门禁(D34 / D35 / D36 / D37 全量):`OK final static gates D34 D35 D36 D37`
+- Task 3 范围门禁(基线感知):`OK scope (baseline-aware): out-of-scope extra 0`
+- Task 3 并发改动基线门禁:`OK user concurrent edits unchanged entries 0`
+- Task 3 eslint(9 文件):0 错误
+- Task 3 CRLF(9 源文件):`OK CRLF source files`
+- 提交后删除检查:`no deletions`
+- Task 3 SUMMARY 完整性门禁:见文末自检
+
+## 人工复核(Task 3 步骤 6,十一条)
+
+1. **D-34 的 7 处调用逐条对位**:door `:36` / `:46`、hero `:239` / `:364` / `:428`、renderer `:1250`、vertex `:546` 均由 `getIfBigImage` 改为 `getTile`;「行为保持」论据现场复验成立(`setBigImage` 消费者调用点 0,见上)。**结论:通过**。
+2. **`getOffsetPool` 删除后池来源**:现只剩常量 `32`(`const pool = new Set([32]);`)与 `imageStore.values()` 循环(经 `assetContainsTexture` 过滤),以及 DEV 告警与 `MAX_VERTEX_UNIFORM_VECTORS` 错误分支、`return [...pool]`;不再有 big-image 偏移。**结论:通过**。
+3. **`MaterialManager` 五个 big-image 方法与三个字段全部删除、`setDefaultFrame` 本体与签名未变**:`git grep -i bigimage -- packages-user` 无输出;`setDefaultFrame(identifier, defaultFrame): void` 本体仅剩 `this.defaultFrames.set(identifier, defaultFrame);`。**结论:通过**。
+4. **D-35 注入链逐环节对位**:`client.ts:84` 传 `this`(`ClientCore extends CoreState implements IClientCore`,`this` 即 `ICoreState`)→ `manager.ts` 构造器 `readonly state` → `manager.ts` `new AssetBuilder(this, state)` → `builder.ts` `new TrackedAssetData(materials, this, state)`;`client.ts:85` `new AutotileProcessor(this.materials, this)`;`renderer.ts:234` `new AutotileProcessor(manager, manager.state)`(取自注入的 `ITextureManager`,**未引入任何全局单例**,D-23)。**结论:通过**。
+5. **`textures` 承担 store 与 Task 0 裁定一致**:为新建专用 `TextureStore`(建议默认 (a));SUMMARY 已明写「本次范围内无写入者」的事实。**结论:通过**。
+6. **D-36 的 `cache.ts` 11 处命中逐条对位**:字段(`:36`)、`AutotileRenderable.bigImage`(`:48`)、对象字面量 `bigImage`(`:144` / `:215` / `:243` / `:276`)、解构(`:153`)、`enemys` 特判(`:155-158`)、`if (bigImage)` 分支(`:159-194`)、未使用 `enemys` 声明(`:127`)全部删除;行为影响(大图怪物落回后续分支)与「未经运行验证」已记录。**结论:通过**。
+7. **D-37 声明就位、`pnpm-lock.yaml` 未动、未执行安装**:`package.json.dependencies['@user/data-state'] === 'workspace:*'`;`pnpm exec` 隐式同步的 lockfile 改动已 `git checkout --` 还原,交付树内 lockfile 与 HEAD 一致。**结论:通过**(见 Deviations 第 1 条)。
+8. **D-33 范围核对**:`git show --stat HEAD` 恰含 10 个文件;无关诊断(加载系统 16 + 数据端测试 / 寻路 164)一条未动。**结论:通过**。
+9. **D-28 保留**:`hero.ts` 的 `state.roleFace.getFaceOf` 计数 = 3,`import { state } from '@user/data-state';` 未动,`hero.ts:423` 的 `getFaceOf` 调用逐字保留。**结论:通过**。
+10. **运行时 UAT 不可得的理由**:渲染端无测试设施(`client-modules` 无 `*.test.*`),本增量改动只在浏览器渲染流程可观察,本计划不启动渲染、不新增测试、不接线被注释的接线点。**结论:UAT 不可得,已如实声明,未伪造绿**。
+11. **既有注释零改写(AGENTS.md;truth 第 7 条的可失败检查)**:对 10 个在范围文件逐 hunk 复核 `git show HEAD -- <10 个文件>`,每一处既有 jsDoc / 注释文本要么**原样保留**、要么**随其所属成员 / 代码块被整体删除**:
+ - 整体删除的注释:`/** 大怪物数据 */`、`/** 大怪物贴图的标识符 */`、`/** 是否是大怪物 */`、`// 其他的都是 bigImage 了,直接遍历获取`、`// 怪物需要特殊处理,因为它的大怪物信息不在 maps 里面`(随其 `if` 块整体删除)——均属「删除成员 / 代码块连其注释整体删除」的预授权范围;
+ - 未改写的注释(逐字保留):`// 还有勇士图片`、`// 图块背景`、`// 地狱般的分支if`、`// 额外素材`、`/** 特判空图块与空气墙 */`、`// enemy48和npc48都应该视为大怪物`、`// 自动元件`、`vertex.ts` 的两行说明、`manager.ts` 各 store / 字段注释等。
+ - 未发现任何既有注释文本被改写 / 重排 / 局部编辑。**结论:通过**。
+
+## Deviations from Plan
+
+### Auto-fixed Issues
+
+**1. [Rule 3 - Blocking] 还原 pnpm 隐式同步的 `pnpm-lock.yaml`**
+- **Found during:** Task 3(提交前 `git status` 检查)
+- **Issue:** 修改 `client-base/package.json` 后,`pnpm exec eslint` / `pnpm exec vue-tsc` 触发 pnpm 的依赖校验,隐式向 `pnpm-lock.yaml` 的 `packages-user/client-base` importer 段写入了 `@user/data-state`(+3 行)。这违反 D-37「不修改 `pnpm-lock.yaml`」。
+- **Fix:** 在所有基于 pnpm 的门禁跑完后,`git checkout -- pnpm-lock.yaml` 还原该文件;提交仅含 10 个在范围文件。用户已知悉 lockfile 未同步(`pnpm install --frozen-lockfile` 在用户自行同步前会失败)。
+- **Files modified:** `pnpm-lock.yaml`(已还原,最终未变更)
+- **Verification:** 提交后 `git status --porcelain` 仅含未跟踪的 SUMMARY;并发改动基线门禁 `entries 0`。
+- **Committed in:** 未提交(还原后与 HEAD 一致)
+
+**2. [Plan-literal] `hero.ts:428` 的换行被 prettier 收为单行**
+- **Found during:** Task 2(eslint --fix)
+- **Issue:** 计划 Task 2 (2e) 要求「保持现有换行形态,仅换成员名」。原 `getIfBigImage(nextFace.identifier)` 因超 80 列被 prettier 折行;改为更短的 `getTile(...)` 后整行 ≤ 80 列,prettier 依规将其并为单行。
+- **Fix:** 依 dev.md「严格遵循 eslint 配置」与仓库 prettier 惯例,接受单行形态(此处为**代码**格式,不涉及任何注释文本)。
+- **Files modified:** `packages-user/client-modules/src/render/map/extension/hero.ts`
+- **Verification:** eslint 0 错误;语义(`moving.block.setTexture(tile)` 等后续用法)未变。
+- **Committed in:** `78873d0`
+
+**Deviation summary:** 2 项(1 项 Rule 3 阻塞还原、1 项 prettier 格式取舍);影响面均在计划允许的范围内,未见范围蔓延。
+
+## 未实现 / 显式延后项
+
+- `textures` 的**写入侧**(让 loader / `addGrid` / `addTileset` / `addImage` 等填充统一 store)—— 属新引擎后续步骤(本次范围内该 store 无写入者)。
+- `.planning/graphs/*` 重建(F-06)—— E 类只报告,待用户裁决。
+- 其余渲染适配与移动端 / 桌面端双布局(REND-01 剩余部分 / REND-02)—— 本 run 不含。
+- `core.*` legacy 适配(D-12,第五阶段);移动控制(D-19);跟随者位置 / 动画归属(D-22);贴图 / 不透明度来源(D-18);加载系统重构遗留 16 条诊断(D-33)。
+- 本次未新增任何测试(渲染端无测试设施)。
+
+## 需求状态
+
+REND-01 / REND-02 在本 run 后仍保持 **Pending**:本计划只落地「material 接口适应实施(第二步)」一个子切片,未实施其余渲染适配与移动端 / 桌面端双布局(见 04-07-PLAN 的两条 FLAGGED ASSUMPTION)。
+
+## 运行时 UAT 不可得声明
+
+渲染端**无测试设施**,本增量的改动(构造器注入、材质取值改写、`cache.ts` 大图分支删除)只在浏览器渲染流程里可观察。本计划**不启动渲染、不新增测试、不接线任何被注释的接线点**,故运行时渲染 UAT **不可得**;本计划的实质验证 = scoped 类型门禁 + 静态门禁 + eslint / CRLF + 基线感知范围 / 并发基线门禁 + 人工代码复核。**「编译通过」不等于「运行时可用」**,未以任何不可运行的门禁冒充通过。
+
+## Self-Check
+
+- 10 个在范围文件已提交于 `78873d0`;SUMMARY 单独提交。
+- material 归属诊断 0;在范围文件诊断 0;总行数 180 ≤ 180。
+- 用户并发改动未被触碰(基线空集,完工后仍为空)。
+
+---
+*Phase: 04-render-adaptation*
+*Completed: 2026-09-22*
From 3bcba48f48e3911e4fd5db57da097b14c3b0134b Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 12:39:32 +0800
Subject: [PATCH 070/173] docs(04-07): complete material interface adaptation
plan
---
.planning/ROADMAP.md | 8 ++++----
.planning/STATE.md | 23 +++++++++++++----------
.planning/state.json | 6 +++---
3 files changed, 20 insertions(+), 17 deletions(-)
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index cd7363b..f78ac2d 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,7 +181,7 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 7 plans — 04-01..04-06 已执行(04-06 = material 接口影响范围清点·只读);**04-07(material 接口适应实施·第二步)已规划待执行**;渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
+**Plans**: 7/7 plans executed — 04-01..04-06 已执行(04-06 = material 接口影响范围清点·只读);**04-07(material 接口适应实施·第二步)已规划待执行**;渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
**Plan register note (2026-09-21):** 第五个增量 `04-05`(收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性改经 `client.hero.attribute.getFinalAttribute(...)` 读取 + 逐图层钩子类 `RendererLayerHook`,2026-09-20 执行)此前未登记进本路线图,本次一并补登;第六个增量 `04-06` = material 接口适应(D-30/D-31/D-32)**第一步·只读影响清点**,产出 `04-MATERIAL-INTERFACE-IMPACT.md`,第二步(实施)待用户审阅后另行规划。
Plans:
@@ -204,11 +204,11 @@ Plans:
**Wave 5** *(blocked on Wave 4 completion)*
- [x] 04-05-PLAN.md — 收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性从失效的 `getHeroStatusOn` 改经 `client.hero.attribute.getFinalAttribute(...)` 读取(并移除 `main.tsx:28` 失效的 `@ts-expect-error`);新增模块内不导出类 `RendererLayerHook`(`Partial`)把逐图层 `onUpdateArea` / `onUpdateBlock` 转发到 `MapRenderer.updateLayerArea` / `updateLayerBlock`,并在 `setLayerState` / `updateLayerList` 两条路径注册与注销(M-06 使用 `client` 单例经用户裁定为 D-23 的显式例外)— **2026-09-20 已执行**(本次补登)
-- [ ] 04-06-PLAN.md — material 接口影响范围清点(**只读**,D-30/D-31/D-32 第一步):以 `4e305e3` 后的 `client-base/src/{types.ts,material/types.ts}` 为基准,清点 `packages-user` 内对已移除 7 符号(`IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`)、3 个改名接口、新增 `textures` 与 4 处 `ICoreStateExtended` 约束未实现、以及所有 big-image 残留(含 `material/manager.ts` 与 `render/elements/cache.ts`)的引用,按 A 已移除 / B 改名 / C 约束未实现 / D big-image 残留 / E 范围外命中(`src/`、`packages/` 仅报告)/ F 未确定 六类分组,每行精确到符号 + `file:line`;产出 `04-MATERIAL-INTERFACE-IMPACT.md`,生产代码零改动,第二步实施待用户审阅后另行规划
+- [x] 04-06-PLAN.md — material 接口影响范围清点(**只读**,D-30/D-31/D-32 第一步):以 `4e305e3` 后的 `client-base/src/{types.ts,material/types.ts}` 为基准,清点 `packages-user` 内对已移除 7 符号(`IBigImageReturn` / `isBigImage` / `getBigImage` / `getIfBigImage` / `getBigImageByAlias` / `setBigImage` / `bigImageStore`)、3 个改名接口、新增 `textures` 与 4 处 `ICoreStateExtended` 约束未实现、以及所有 big-image 残留(含 `material/manager.ts` 与 `render/elements/cache.ts`)的引用,按 A 已移除 / B 改名 / C 约束未实现 / D big-image 残留 / E 范围外命中(`src/`、`packages/` 仅报告)/ F 未确定 六类分组,每行精确到符号 + `file:line`;产出 `04-MATERIAL-INTERFACE-IMPACT.md`,生产代码零改动,第二步实施待用户审阅后另行规划
**Wave 6** *(blocked on Wave 5 completion)*
-- [ ] 04-07-PLAN.md — material 接口适应实施(**第二步·代码改动**,D-30/D-31/D-32 第二步 + D-33..D-37):按 04-06 清点的 A/C/D/E 四类落地——A 类(D-34)删除全部 big-image 引用并把 `getIfBigImage` 的 7 处消费者(`door.ts` 2 / `hero.ts` 3 / `renderer.ts` 1 / `vertex.ts` 1)改为行为等价的 `getTile`、删除 `renderer.ts` `getOffsetPool` 的 big-image 偏移收集;C 类(D-35)为 `MaterialManager` 补 `textures`、为 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 补构造器注入的 `state`(不取全局单例,D-23;`renderer.ts` 从 `manager.state` 取值);D 类(D-36)删除 `MaterialManager` 的 big-image 实现残留(`bigImageStore` / `bigImageData` / `bigImageId` / 五个方法 / `setDefaultFrame` 耦合)与 `render/elements/cache.ts` 的 legacy big-image 路径;E 类(D-37)为 `client-base/package.json` 补 `@user/data-state` 声明(不安装、不同步 lockfile)。10 个文件原子提交;门禁 = material 归属诊断归零 + 在范围文件零诊断 + 总行数不增加(规划日 199 / 19 → ≤ 180)+ `packages-user` 内 big-image 记号归零 + eslint / CRLF / 范围(基线感知)/ 并发基线;运行时 UAT 不可得(渲染端无测试设施)如实声明
+- [x] 04-07-PLAN.md — material 接口适应实施(**第二步·代码改动**,D-30/D-31/D-32 第二步 + D-33..D-37):按 04-06 清点的 A/C/D/E 四类落地——A 类(D-34)删除全部 big-image 引用并把 `getIfBigImage` 的 7 处消费者(`door.ts` 2 / `hero.ts` 3 / `renderer.ts` 1 / `vertex.ts` 1)改为行为等价的 `getTile`、删除 `renderer.ts` `getOffsetPool` 的 big-image 偏移收集;C 类(D-35)为 `MaterialManager` 补 `textures`、为 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 补构造器注入的 `state`(不取全局单例,D-23;`renderer.ts` 从 `manager.state` 取值);D 类(D-36)删除 `MaterialManager` 的 big-image 实现残留(`bigImageStore` / `bigImageData` / `bigImageId` / 五个方法 / `setDefaultFrame` 耦合)与 `render/elements/cache.ts` 的 legacy big-image 路径;E 类(D-37)为 `client-base/package.json` 补 `@user/data-state` 声明(不安装、不同步 lockfile)。10 个文件原子提交;门禁 = material 归属诊断归零 + 在范围文件零诊断 + 总行数不增加(规划日 199 / 19 → ≤ 180)+ `packages-user` 内 big-image 记号归零 + eslint / CRLF / 范围(基线感知)/ 并发基线;运行时 UAT 不可得(渲染端无测试设施)如实声明
**UI hint**: yes
@@ -403,7 +403,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. 事件系统 | 13/13 | In Progress| |
| 2. 寻路系统 | 5/5 | In Progress| |
| 3. 数据端完成 | 19/19 | Complete | 2026-09-12 |
-| 4. 渲染适配与双布局 | 6/6 | In Progress| |
+| 4. 渲染适配与双布局 | 7/7 | In Progress| |
| 5. Legacy 移植 | 0/TBD | Not started | - |
| 6. 单元测试 | 18/18 | In Progress| |
| 7. 数据端缺陷修复 | 15/16 | 暂缓 (Deferred) | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 7c4e16d..7e3fdd9 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -3,17 +3,17 @@ gsd_state_version: "1.0"
milestone: v1.0
current_phase: 04
current_phase_name: 渲染适配与双布局
-status: executing
-stopped_at: Completed 04-04-PLAN.md(勇士渲染修正:注入 IFaceManager、拆钩子类、补 AnimDir、换 @motajs/animate,三文件机器门禁全绿;REND-01/REND-02 保持 Pending)
-last_updated: "2026-09-22T03:29:38.480Z"
+status: verifying
+stopped_at: Completed 04-07-PLAN.md(material 接口适应实施:big-image 全删、7 处改 getTile、四实现类构造器注入 state、cache.ts legacy 路径删除、补 @user/data-state 声明;material 诊断 19→0、总行数 199→180;REND-01/REND-02 保持 Pending)
+last_updated: "2026-09-22T04:25:51.526Z"
last_activity: 2026-09-21
last_activity_desc: Phase 04 execution started
-state_head: f55a76297f0d4f929f8e0fab376e341d698a4631
+state_head: 1bb42f66ca04d118a893c5b5222ea7927e145e04
progress:
total_phases: 7
- completed_phases: 0
+ completed_phases: 1
total_plans: 78
- completed_plans: 76
+ completed_plans: 77
milestone_name: milestone
---
@@ -29,8 +29,8 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
-Plan: 6 of 6
-Status: Ready to execute
+Plan: 7 of 7
+Status: Phase complete — ready for verification
Last activity: 2026-09-21 — Completed quick task 260921-lwa: Upgrade toolchain to pnpm 12.5.1 and Node 24
Progress: [█████░░░░░] 50%
@@ -100,6 +100,7 @@ Progress: [█████░░░░░] 50%
| Phase 04 P2 | 23min | 4 tasks | 1 files |
| Phase 04 P3 | 40min | 4 tasks | 3 files |
| Phase 04 P4 | ~45min | 4 tasks | 3 files |
+| Phase 04 P7 | 35min | 3 tasks | 10 files |
## Accumulated Context
@@ -229,6 +230,8 @@ Recent decisions affecting current work:
- [Phase 04]: [Phase 04]: 04-04:MapHeroRenderer 注入 IFaceManager(faceManager 字段 + 派生 dir4),degrade/next 走 Dir4FaceHandler(FaceGroup.Dir4),movement 走 hero.mover.faceHandler(Dir8);IMapExtensionManager.addHero 新增 faceManager 形参并由 manager.ts 转发(D-24/D-29)
- [Phase 04]: [Phase 04]: 04-04:MapHeroHook 拆为 MapHeroLocationHook(onSetPos 无条件、只出现一次,承载 D-18/D-22 多余方法)与 MapHeroMoverHook(onMoveStart/onMoveEnd/onStepEnd/onSetFaceDir),各自注册各自 controller;onStepEnd 的 AnimDir 分支按 step.dir 设置动画方向(D-25/D-26)
- [Phase 04]: [Phase 04]: 04-04:hero.ts 弃用 mutate-animate 的 TimingFn<2>,改用 @motajs/animate 的 ExcitationCurve2D;D-28 存量 state.roleFace.getFaceOf 三处与 import 逐字保留;types.ts 单行 addHero 签名与 manager.ts 单行构造调用按仓库惯例加 // prettier-ignore 以同时满足计划的单行静态门禁与 prettier
+- [Phase 04]: [Phase 04]: 04-07:D-35 裁定 state 一律经构造器注入(ClientCore 传 this、renderer 从 manager.state 取),不取全局单例(D-23);textures 由新建专用 TextureStore 承担(本次范围内无写入者)
+- [Phase 04]: [Phase 04]: 04-07:D-34 的 getIfBigImage ≡ getTile(bigImageData 唯一写入者 setBigImage 消费者调用点 0),7 处消费者改写行为保持;getOffsetPool 的 big-image 偏移收集删除、其余来源保留
### Roadmap Evolution
@@ -267,6 +270,6 @@ None yet.
## Session Continuity
-Last session: 2026-09-19T08:59:26.713Z
-Stopped at: Completed 04-04-PLAN.md(勇士渲染修正:注入 IFaceManager、拆钩子类、补 AnimDir、换 @motajs/animate,三文件机器门禁全绿;REND-01/REND-02 保持 Pending)
+Last session: 2026-09-22T04:25:51.224Z
+Stopped at: Completed 04-07-PLAN.md(material 接口适应实施:big-image 全删、7 处改 getTile、四实现类构造器注入 state、cache.ts legacy 路径删除、补 @user/data-state 声明;material 诊断 19→0、总行数 199→180;REND-01/REND-02 保持 Pending)
Resume file: None
diff --git a/.planning/state.json b/.planning/state.json
index 824975a..04bbf9f 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -41,8 +41,8 @@
],
"next": {
"command": "/gsd:progress --next",
- "label": "Advance to the next step",
- "reason": "Phase 04 of 7 · executing"
+ "label": "Advance to the next step (verify)",
+ "reason": "Phase 04 of 7 · ready to verify"
},
- "updated_at": "2026-09-22T03:29:38.601Z"
+ "updated_at": "2026-09-22T04:17:12.336Z"
}
From d36ea69066a4ac5a44e8030a08f645bef745d3eb Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 21:11:21 +0800
Subject: [PATCH 071/173] =?UTF-8?q?refactor:=20=E8=B4=B4=E5=9B=BE=E5=AD=98?=
=?UTF-8?q?=E5=82=A8=E6=96=B9=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages-user/client-base/src/index.ts | 6 -
.../client-base/src/material/autotile.ts | 595 +++++++++---------
.../client-base/src/material/index.ts | 6 -
.../client-base/src/material/manager.ts | 350 ++++++-----
.../client-base/src/material/types.ts | 218 +++----
packages-user/client-base/src/types.ts | 4 +-
packages-user/client-modules/src/client.ts | 11 +-
packages-user/client-modules/src/core.ts | 7 +-
.../client-modules/src/render/map/renderer.ts | 2 +-
packages-user/client-modules/src/types.ts | 4 +
packages-user/entry-client/src/create.ts | 1 -
packages/common/src/logger.json | 8 +
12 files changed, 627 insertions(+), 585 deletions(-)
diff --git a/packages-user/client-base/src/index.ts b/packages-user/client-base/src/index.ts
index e72f62d..8be5ffa 100644
--- a/packages-user/client-base/src/index.ts
+++ b/packages-user/client-base/src/index.ts
@@ -1,9 +1,3 @@
-import { createMaterial } from './material';
-
-export function create() {
- createMaterial();
-}
-
export * from './load';
export * from './material';
export * from './save';
diff --git a/packages-user/client-base/src/material/autotile.ts b/packages-user/client-base/src/material/autotile.ts
index a0e9358..fb17d4b 100644
--- a/packages-user/client-base/src/material/autotile.ts
+++ b/packages-user/client-base/src/material/autotile.ts
@@ -6,65 +6,317 @@ import {
import {
AutotileConnection,
AutotileType,
- BlockCls,
IAutotileConnection,
IAutotileProcessor,
- IMaterialFramedData,
- ITextureManager
+ IMaterialFramedData
} from './types';
import { ICoreState } from '@user/data-state';
import { isNil } from 'lodash-es';
+import { logger } from '@motajs/common';
-interface ConnectedAutotile {
+// 3x4 自动元件索引图
+//
+// | <- 当上下左右都没有连接时,会使用左上角的内容,其实等同于使用 [12, 17, 42, 47]
+// |-------|-------|-------| <- 当上左、上右、下左、下右有连接时,会使用右上角的内容
+// | 00 01 | 02 03 | 04 05 |
+// | 06 07 | 08 09 | 10 11 |
+// |-------|-------|-------| <- 分割线,上面用于控制无连接(左)以及十字连接(右),中间用于判断父子关系(特殊连接)
+// | 12 13 | 14 15 | 16 17 |
+// | 18 19 | 20 21 | 22 23 |
+// |-------|-------|-------| <- 当仅下方有连接时,会用第二行的内容
+// | 24 25 | 26 27 | 28 29 |
+// | 30 31 | 32 33 | 34 35 |
+// |-------|-------|-------| <- 当上下都有连接时,会用第三行的内容
+// | 36 37 | 38 39 | 40 41 |
+// | 42 43 | 44 45 | 46 47 |
+// |-------|-------|-------| <- 当仅上方有连接时,会用第四行的内容
+// | | | |
+// | | | | <- 左右和上下的连接会相互干扰,具体干扰方式间右上角内容的描述
+// | | | <- 当仅左方有连接时,会使用第三列的内容
+// | | <- 当左右都有连接时,会使用第二列的内容
+// | <- 当仅右方有连接时,会用第一列的内容
+
+// 2x3 自动元件索引图
+//
+// |-------|-------|
+// | 00 01 | 02 03 |
+// | 04 05 | 06 07 |
+// |-------|-------| <- 分割线,上面用于控制无连接(左)以及十字连接(右),这种自动元件无法从图片获取父子关系
+// | 08 09 | 10 11 |
+// | 12 13 | 14 15 |
+// |-------|-------|
+// | 16 17 | 18 19 |
+// | 20 21 | 22 23 |
+// |-------|-------|
+// 此自动元件本质上是把 3x4 自动元件中间的 4x4 区域合并为了这里的 [13, 14, 17, 18]
+
+interface IConnectedAutotile {
+ /** 左上角 */
readonly lt: Readonly;
+ /** 右上角 */
readonly rt: Readonly;
+ /** 右下角 */
readonly rb: Readonly;
+ /** 左下角 */
readonly lb: Readonly;
}
-export interface IAutotileData {
- /** 图像源 */
- readonly source: SizedCanvasImageSource;
- /** 自动元件帧数 */
- readonly frames: number;
-}
-
-/** 3x4 自动元件的连接映射,元组表示将对应大小的自动元件按照格子 1/4 大小切分后对应的索引位置 */
-const connectionMap3x4 = new Map();
-/** 2x3 自动元件的连接映射,元组表示将对应大小的自动元件按照格子 1/4 大小切分后对应的索引位置 */
-const connectionMap2x3 = new Map();
-/** 3x4 自动元件各方向连接的矩形映射 */
-const rectMap3x4 = new Map();
-/** 2x3 自动元件各方向连接的矩形映射 */
-const rectMap2x3 = new Map();
-/** 不重复连接映射,用于平铺自动元件,一共 48 种 */
-const distinctConnectionMap = new Map();
-
export class AutotileProcessor implements IAutotileProcessor {
- /** 自动元件父子关系映射,子元件 -> 父元件 */
- readonly parentMap: Map = new Map();
- /** 自动元件父子关系映射,父元件 -> 子元件列表 */
- readonly childMap: Map> = new Map();
+ /** 自动元件特殊连接方式映射 */
+ private readonly spec: Map> = new Map();
- constructor(
- readonly manager: ITextureManager,
- readonly state: ICoreState
- ) {}
+ /** 3x4 自动元件的各方向连接索引 */
+ readonly conn3x4: Map = new Map();
+ /** 2x3 自动元件的各方向连接索引 */
+ readonly conn2x3: Map = new Map();
+ /** 不重复连接映射,用于平铺自动元件,一共 48 种 */
+ readonly distinct: Map = new Map();
- private ensureChildSet(num: number) {
- const set = this.childMap.get(num);
- if (set) return set;
- const ensure = new Set();
- this.childMap.set(num, ensure);
- return ensure;
+ constructor(readonly state: ICoreState) {
+ this.conn3x4 = this.mapAutotile(AutotileType.Big3x4);
+ this.conn2x3 = this.mapAutotile(AutotileType.Small2x3);
+ this.deduplicateConnection();
}
- setConnection(autotile: number, parent: number): void {
- this.parentMap.set(autotile, parent);
- const child = this.ensureChildSet(parent);
- child.add(autotile);
+ /**
+ * 映射自动元件连接
+ * @param type 自动元件类型
+ */
+ private mapAutotile(type: AutotileType) {
+ // 这些常量非常 magic,可以参考文件开头的索引注释来理解
+ const h = type === AutotileType.Big3x4 ? 2 : 1; // 横向偏移因子
+ const v = type === AutotileType.Big3x4 ? 12 : 4; // 纵向偏移因子
+ const luo = type === AutotileType.Big3x4 ? 12 : 8; // leftup origin
+ const ruo = type === AutotileType.Big3x4 ? 17 : 11; // rightup origin
+ const ldo = type === AutotileType.Big3x4 ? 42 : 20; // leftdown origin
+ const rdo = type === AutotileType.Big3x4 ? 47 : 23; // rightdown origin
+ const luc = type === AutotileType.Big3x4 ? 4 : 2; // leftup corner
+ const ruc = type === AutotileType.Big3x4 ? 5 : 3; // rightup corner
+ const rdc = type === AutotileType.Big3x4 ? 11 : 7; // rightdown corner
+ const ldc = type === AutotileType.Big3x4 ? 10 : 6; // leftdown corner
+
+ const result = new Map();
+
+ for (let i = 0; i <= 0b1111_1111; i++) {
+ // 自动元件由四个更小的矩形组合而成
+ // 初始状态下,四个矩形分别处在四个角的位置
+ // 而且对应角落的矩形只可能出现在每个大区块的对应角落
+
+ let lu = luo; // leftup
+ let ru = ruo; // rightup
+ let ld = ldo; // leftdown
+ let rd = rdo; // rightdown
+
+ // 先看四个方向,最后看斜角方向
+ if (i & 0b0000_0001) {
+ // 左侧有连接,左侧两个矩形向右偏移两个因子
+ lu += h * 2;
+ ld += h * 2;
+ // 如果右侧还有连接,那么右侧矩形和左侧矩形需要移动至中间
+ // 但是由于后面还处理了先右侧再左侧的情况,因此需要先向右偏移一个因子
+ // 结果就是先向右移动了一个因子,在后面又向左移动了两个因子,因此相当于向左移动了一个因子
+ if (i & 0b0001_0000) {
+ ru += h;
+ rd += h;
+ }
+ }
+ if (i & 0b0000_0100) {
+ // 下侧有连接,下侧两个矩形向上偏移两个因子
+ ld -= v * 2;
+ rd -= v * 2;
+ if (i & 0b0100_0000) {
+ lu -= v;
+ ru -= v;
+ }
+ }
+ if (i & 0b0001_0000) {
+ // 右侧有连接,右侧矩形向左移动两个因子
+ ru -= h * 2;
+ rd -= h * 2;
+ if (i & 0b0000_0001) {
+ lu -= h;
+ ld -= h;
+ }
+ }
+ if (i & 0b0100_0000) {
+ // 上侧有链接,上侧矩形向下移动两个因子
+ lu += v * 2;
+ ru += v * 2;
+ if (i & 0b0000_0100) {
+ ld += v;
+ rd += v;
+ }
+ }
+ // 斜角
+ // 如果左上仅与上和左连接
+ if ((i & 0b1100_0001) === 0b0100_0001) {
+ lu = luc;
+ }
+ // 如果右上仅与上和右连接
+ if ((i & 0b0111_0000) === 0b0101_0000) {
+ ru = ruc;
+ }
+ // 如果右下仅与右和下连接
+ if ((i & 0b0001_1100) === 0b0001_0100) {
+ rd = rdc;
+ }
+ // 如果左下仅与左和下连接
+ if ((i & 0b0000_0111) === 0b0000_0101) {
+ ld = ldc;
+ }
+ result.set(i, [lu, ru, rd, ld]);
+ }
+
+ return result;
}
+ /**
+ * 初始化自动元件连接配置
+ */
+ private deduplicateConnection() {
+ const usedRect: [number, number, number, number][] = [];
+ let flag = 0;
+ // 2x3 和 3x4 的自动元件连接方式一样,因此没必要映射两次
+ this.conn2x3.forEach((conn, num) => {
+ const index = usedRect.findIndex(
+ used =>
+ used[0] === conn[0] &&
+ used[1] === conn[1] &&
+ used[2] === conn[2] &&
+ used[3] === conn[3]
+ );
+ if (index === -1) {
+ this.distinct.set(num, flag);
+ usedRect.push(conn.slice() as [number, number, number, number]);
+ flag++;
+ } else {
+ this.distinct.set(num, index);
+ }
+ });
+ }
+
+ /**
+ * 获取自动元件指定连接方式在原贴图上的裁剪位置
+ * @param connection 连接方式,八位二进制数字
+ * @param type 自动元件类型
+ * @param cw 自动元件的 tile 宽度的一半
+ * @param ch 自动元件的 tile 高度的一半
+ */
+ private getSliceRect(
+ connection: number,
+ type: AutotileType,
+ hw: number,
+ hh: number
+ ): IConnectedAutotile | null {
+ const map = type === AutotileType.Big3x4 ? this.conn3x4 : this.conn2x3;
+ const data = map.get(connection);
+ if (!data) return null;
+ // 每行的切块数量,可以参考开头的注释理解其含义
+ const n = type === AutotileType.Big3x4 ? 6 : 4;
+ const [ltd, rtd, rbd, lbd] = data;
+ const ltx = (ltd % n) * hw;
+ const lty = Math.floor(ltd / n) * hh;
+ const rtx = (rtd % n) * hw;
+ const rty = Math.floor(rtd / n) * hh;
+ const rbx = (rbd % n) * hw;
+ const rby = Math.floor(rbd / n) * hh;
+ const lbx = (lbd % n) * hw;
+ const lby = Math.floor(lbd / n) * hh;
+ const rect: IConnectedAutotile = {
+ lt: { x: ltx, y: lty, w: hw, h: hh },
+ rt: { x: rtx, y: rty, w: hw, h: hh },
+ rb: { x: rbx, y: rby, w: hw, h: hh },
+ lb: { x: lbx, y: lby, w: hw, h: hh }
+ };
+ return rect;
+ }
+
+ /**
+ * 获取自动元件的单个图块尺寸
+ * @param frameWidth 自动元件每帧的宽度
+ * @param height 自动元件的高度
+ * @param type 自动元件的类型
+ */
+ private getAutotileCellSize(
+ frameWidth: number,
+ height: number,
+ type: AutotileType
+ ): [width: number, height: number] {
+ if (type === AutotileType.Big3x4) {
+ if (frameWidth % 3 !== 0 || height % 4 !== 0) {
+ logger.warn(190, frameWidth.toString(), height.toString());
+ return [0, 0];
+ }
+ return [frameWidth / 3, height / 4];
+ } else {
+ if (frameWidth % 2 !== 0 || height % 3 !== 0) {
+ logger.warn(190, frameWidth.toString(), height.toString());
+ return [0, 0];
+ }
+ return [frameWidth / 2, height / 3];
+ }
+ }
+
+ flatten(
+ source: SizedCanvasImageSource,
+ type: AutotileType,
+ frames: number
+ ): SizedCanvasImageSource | null {
+ if (source.width % frames !== 0) {
+ logger.warn(189, source.width.toString(), frames.toString());
+ return null;
+ }
+ const { width, height } = source;
+ // frame width
+ const fw = width / frames;
+ // cell width, cell height
+ const [cw, ch] = this.getAutotileCellSize(fw, height, type);
+ if (cw % 2 !== 0 || ch % 2 !== 0) {
+ logger.warn(190, fw.toString(), height.toString());
+ return null;
+ }
+ // 画到画布上
+ const canvas = document.createElement('canvas');
+ canvas.width = cw * frames;
+ canvas.height = ch * 48;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return null;
+ // half width, half height
+ const hw = cw / 2;
+ const hh = ch / 2;
+ // 遍历每个组合
+ this.distinct.forEach((index, conn) => {
+ const rect = this.getSliceRect(conn, type, hw, hh)!;
+ const { lt, rt, rb, lb } = rect;
+ const y = index * ch;
+ for (let i = 0; i < frames; i++) {
+ const x = i * cw;
+ const ox = i * fw;
+ // prettier-ignore
+ ctx.drawImage(source, lt.x + ox, lt.y, lt.w, lt.h, x, y, hw, hh);
+ // prettier-ignore
+ ctx.drawImage(source, rt.x + ox, rt.y, rt.w, rt.h, x + hw, y, hw, hh);
+ // prettier-ignore
+ ctx.drawImage(source, rb.x + ox, rb.y, rb.w, rb.h, x + hw, y + hh, hw, hh);
+ // prettier-ignore
+ ctx.drawImage(source, lb.x + ox, lb.y, lb.w, lb.h, x, y + hh, hw, hh);
+ }
+ });
+
+ return canvas;
+ }
+
+ setConnection(autotile: number, target: number): void {
+ const set = this.spec.getOrInsertComputed(autotile, () => new Set());
+ set.add(target);
+ }
+
+ /**
+ * 判断地图边缘连接点
+ * @param length 地图面积,也就是地图数组的总长度
+ * @param index 目标位置索引
+ * @param width 地图宽度
+ */
private connectEdge(length: number, index: number, width: number): number {
// 最高位表示左上,低位依次顺时针旋转
@@ -136,10 +388,12 @@ export class AutotileProcessor implements IAutotileProcessor {
center: 0
};
}
- let res: number = this.connectEdge(array.length, index, width);
- const childList = this.childMap.get(block);
+ let res = this.connectEdge(array.length, index, width);
+ const spec = this.spec.get(block);
// 最高位表示左上,低位依次顺时针旋转
+ // 在边缘时,在地图外的部分一定会连接上,所以哪怕索引可能导致串行,也对结果没有任何影响
+ // 例如在右边缘时,右侧一定连接,此时不论其与下一行的首个图块是否一个连接,都不会影响需要连接的结果
const a7 = array[index - width - 1] ?? 0;
const a6 = array[index - width] ?? 0;
const a5 = array[index - width + 1] ?? 0;
@@ -151,7 +405,7 @@ export class AutotileProcessor implements IAutotileProcessor {
// Benchmark https://www.measurethat.net/Benchmarks/Show/35271/0/convert-boolean-to-number
- if (!childList || childList.size === 0) {
+ if (!spec || spec.size === 0) {
// 不包含子元件,那么直接跟相同的连接
res |=
+(a0 === block) |
@@ -164,14 +418,14 @@ export class AutotileProcessor implements IAutotileProcessor {
(+(a7 === block) << 7);
} else {
res |=
- +childList.has(a0) |
- (+childList.has(a1) << 1) |
- (+childList.has(a2) << 2) |
- (+childList.has(a3) << 3) |
- (+childList.has(a4) << 4) |
- (+childList.has(a5) << 5) |
- (+childList.has(a6) << 6) |
- (+childList.has(a7) << 7);
+ +spec.has(a0) |
+ (+spec.has(a1) << 1) |
+ (+spec.has(a2) << 2) |
+ (+spec.has(a3) << 3) |
+ (+spec.has(a4) << 4) |
+ (+spec.has(a5) << 5) |
+ (+spec.has(a6) << 6) |
+ (+spec.has(a7) << 7);
}
return {
@@ -186,7 +440,7 @@ export class AutotileProcessor implements IAutotileProcessor {
target: number,
direction: AutotileConnection
): number {
- const childList = this.childMap.get(center);
+ const childList = this.spec.get(center);
if (!childList || !childList.has(target)) {
return connection & ~direction;
} else {
@@ -194,40 +448,13 @@ export class AutotileProcessor implements IAutotileProcessor {
}
}
- /**
- * 检查贴图是否是一个自动元件
- * @param tile 贴图数据
- */
- private checkAutotile(tile: IMaterialFramedData) {
- if (tile.cls !== BlockCls.Autotile) return false;
- const { texture, frames } = tile;
- if (texture.width !== 96 * frames) return false;
- if (texture.height === 128 || texture.height === 144) return true;
- else return false;
- }
-
- render(autotile: number, connection: number): ITextureRenderable | null {
- const tile = this.manager.getTile(autotile);
- if (!tile) return null;
- if (!this.checkAutotile(tile)) return null;
- return this.renderWithoutCheck(tile, connection);
- }
-
- renderWith(
- tile: IMaterialFramedData,
- connection: number
- ): ITextureRenderable | null {
- if (!this.checkAutotile(tile)) return null;
- return this.renderWithoutCheck(tile, connection);
- }
-
- renderWithoutCheck(
+ render(
tile: IMaterialFramedData,
connection: number
): ITextureRenderable | null {
const { texture } = tile;
const size = texture.height === 32 * 48 ? 32 : 48;
- const index = distinctConnectionMap.get(connection);
+ const index = this.distinct.get(connection);
if (isNil(index)) return null;
const { rect } = texture.render();
return {
@@ -237,22 +464,12 @@ export class AutotileProcessor implements IAutotileProcessor {
}
*renderAnimated(
- autotile: number,
- connection: number
- ): Generator {
- const tile = this.manager.getTile(autotile);
- if (!tile) return;
- yield* this.renderAnimatedWith(tile, connection);
- }
-
- *renderAnimatedWith(
tile: IMaterialFramedData,
connection: number
): Generator {
- if (!this.checkAutotile(tile)) return;
const { texture, frames } = tile;
const size = texture.height === 128 ? 32 : 48;
- const index = distinctConnectionMap.get(connection);
+ const index = this.distinct.get(connection);
if (isNil(index)) return;
for (let i = 0; i < frames; i++) {
yield {
@@ -261,198 +478,4 @@ export class AutotileProcessor implements IAutotileProcessor {
};
}
}
-
- /**
- * 将自动元件图片展平,平铺存储 48 种样式,此时可以只通过一次绘制来绘制出自动元件,不需要四次绘制
- * @param image 原始自动元件图片
- */
- static flatten(image: IAutotileData): SizedCanvasImageSource | null {
- const { source, frames } = image;
- if (source.width !== frames * 96) return null;
- if (source.height !== 128 && source.height !== 144) return null;
- const type =
- source.height === 128 ? AutotileType.Big3x4 : AutotileType.Small2x3;
- const size = type === AutotileType.Big3x4 ? 32 : 48;
- const width = frames * size;
- const height = 48 * size;
- // 画到画布上
- const canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- const ctx = canvas.getContext('2d');
- if (!ctx) return null;
- const half = size / 2;
- const map = type === AutotileType.Big3x4 ? rectMap3x4 : rectMap2x3;
- const used = new Set();
- // 遍历每个组合
- distinctConnectionMap.forEach((index, conn) => {
- if (used.has(conn)) return;
- used.add(conn);
- const { lt, rt, rb, lb } = map.get(conn)!;
- const y = index * size;
- for (let i = 0; i < frames; i++) {
- const x = i * size;
- // prettier-ignore
- ctx.drawImage(source, lt.x + i * 96, lt.y, lt.w, lt.h, x, y, half, half);
- // prettier-ignore
- ctx.drawImage(source, rt.x + i * 96, rt.y, rt.w, rt.h, x + half, y, half, half);
- // prettier-ignore
- ctx.drawImage(source, rb.x + i * 96, rb.y, rb.w, rb.h, x + half, y + half, half, half);
- // prettier-ignore
- ctx.drawImage(source, lb.x + i * 96, lb.y, lb.w, lb.h, x, y + half, half, half);
- }
- });
-
- return canvas;
- }
-}
-
-/**
- * 映射自动元件连接
- * @param target 输出映射对象
- * @param mode 自动元件类型,1 表示 3x4,2 表示 2x3
- */
-function mapAutotile(
- target: Map,
- mode: 1 | 2
-) {
- const h = mode === 1 ? 2 : 1; // 横向偏移因子
- const v = mode === 1 ? 12 : 4; // 纵向偏移因子
- const luo = mode === 1 ? 12 : 8; // leftup origin
- const ruo = mode === 1 ? 17 : 11; // rightup origin
- const ldo = mode === 1 ? 42 : 20; // leftdown origin
- const rdo = mode === 1 ? 47 : 23; // rightdown origin
- const luc = mode === 1 ? 4 : 2; // leftup corner
- const ruc = mode === 1 ? 5 : 3; // rightup corner
- const rdc = mode === 1 ? 11 : 7; // rightdown corner
- const ldc = mode === 1 ? 10 : 6; // leftdown corner
-
- for (let i = 0; i <= 0b1111_1111; i++) {
- // 自动元件由四个更小的矩形组合而成
- // 初始状态下,四个矩形分别处在四个角的位置
- // 而且对应角落的矩形只可能出现在每个大区块的对应角落
-
- let lu = luo; // leftup
- let ru = ruo; // rightup
- let ld = ldo; // leftdown
- let rd = rdo; // rightdown
-
- // 先看四个方向,最后看斜角方向
- if (i & 0b0000_0001) {
- // 左侧有连接,左侧两个矩形向右偏移两个因子
- lu += h * 2;
- ld += h * 2;
- // 如果右侧还有连接,那么右侧矩形和左侧矩形需要移动至中间
- // 但是由于后面还处理了先右侧再左侧的情况,因此需要先向右偏移一个因子
- // 结果就是先向右移动了一个因子,在后面又向左移动了两个因子,因此相当于向左移动了一个因子
- if (i & 0b0001_0000) {
- ru += h;
- rd += h;
- }
- }
- if (i & 0b0000_0100) {
- // 下侧有连接,下侧两个矩形向上偏移两个因子
- ld -= v * 2;
- rd -= v * 2;
- if (i & 0b0100_0000) {
- lu -= v;
- ru -= v;
- }
- }
- if (i & 0b0001_0000) {
- // 右侧有连接,右侧矩形向左移动两个因子
- ru -= h * 2;
- rd -= h * 2;
- if (i & 0b0000_0001) {
- lu -= h;
- ld -= h;
- }
- }
- if (i & 0b0100_0000) {
- // 上侧有链接,上侧矩形向下移动两个因子
- lu += v * 2;
- ru += v * 2;
- if (i & 0b0000_0100) {
- ld += v;
- rd += v;
- }
- }
- // 斜角
- // 如果左上仅与上和左连接
- if ((i & 0b1100_0001) === 0b0100_0001) {
- lu = luc;
- }
- // 如果右上仅与上和右连接
- if ((i & 0b0111_0000) === 0b0101_0000) {
- ru = ruc;
- }
- // 如果右下仅与右和下连接
- if ((i & 0b0001_1100) === 0b0001_0100) {
- rd = rdc;
- }
- // 如果左下仅与左和下连接
- if ((i & 0b0000_0111) === 0b0000_0101) {
- ld = ldc;
- }
- target.set(i, [lu, ru, rd, ld]);
- }
-}
-
-export function createAutotile() {
- mapAutotile(connectionMap3x4, 1);
- mapAutotile(connectionMap2x3, 2);
-
- connectionMap3x4.forEach((data, connection) => {
- const [ltd, rtd, rbd, lbd] = data;
- const ltx = (ltd % 6) * 16;
- const lty = Math.floor(ltd / 6) * 16;
- const rtx = (rtd % 6) * 16;
- const rty = Math.floor(rtd / 6) * 16;
- const rbx = (rbd % 6) * 16;
- const rby = Math.floor(rbd / 6) * 16;
- const lbx = (lbd % 6) * 16;
- const lby = Math.floor(lbd / 6) * 16;
- rectMap3x4.set(connection, {
- lt: { x: ltx, y: lty, w: 16, h: 16 },
- rt: { x: rtx, y: rty, w: 16, h: 16 },
- rb: { x: rbx, y: rby, w: 16, h: 16 },
- lb: { x: lbx, y: lby, w: 16, h: 16 }
- });
- });
- connectionMap2x3.forEach((data, connection) => {
- const [ltd, rtd, rbd, lbd] = data;
- const ltx = (ltd % 4) * 24;
- const lty = Math.floor(ltd / 4) * 24;
- const rtx = (rtd % 4) * 24;
- const rty = Math.floor(rtd / 4) * 24;
- const rbx = (rbd % 4) * 24;
- const rby = Math.floor(rbd / 4) * 24;
- const lbx = (lbd % 4) * 24;
- const lby = Math.floor(lbd / 4) * 24;
- rectMap2x3.set(connection, {
- lt: { x: ltx, y: lty, w: 24, h: 24 },
- rt: { x: rtx, y: rty, w: 24, h: 24 },
- rb: { x: rbx, y: rby, w: 24, h: 24 },
- lb: { x: lbx, y: lby, w: 24, h: 24 }
- });
- });
- const usedRect: [number, number, number, number][] = [];
- let flag = 0;
- // 2x3 和 3x4 的自动元件连接方式一样,因此没必要映射两次
- connectionMap2x3.forEach((conn, num) => {
- const index = usedRect.findIndex(
- used =>
- used[0] === conn[0] &&
- used[1] === conn[1] &&
- used[2] === conn[2] &&
- used[3] === conn[3]
- );
- if (index === -1) {
- distinctConnectionMap.set(num, flag);
- usedRect.push(conn.slice() as [number, number, number, number]);
- flag++;
- } else {
- distinctConnectionMap.set(num, index);
- }
- });
}
diff --git a/packages-user/client-base/src/material/index.ts b/packages-user/client-base/src/material/index.ts
index a5dd6a5..628014c 100644
--- a/packages-user/client-base/src/material/index.ts
+++ b/packages-user/client-base/src/material/index.ts
@@ -1,9 +1,3 @@
-import { createAutotile } from './autotile';
-
-export function createMaterial() {
- createAutotile();
-}
-
export * from './autotile';
export * from './builder';
export * from './manager';
diff --git a/packages-user/client-base/src/material/manager.ts b/packages-user/client-base/src/material/manager.ts
index f49aaf4..468c44b 100644
--- a/packages-user/client-base/src/material/manager.ts
+++ b/packages-user/client-base/src/material/manager.ts
@@ -2,7 +2,6 @@ import {
ITexture,
ITextureComposedData,
ITextureRenderable,
- ITextureSplitter,
ITextureStore,
SizedCanvasImageSource,
Texture,
@@ -11,36 +10,38 @@ import {
TextureStore
} from '@motajs/render';
import {
- IBlockIdentifier,
IMaterialData,
ITextureManager,
IIndexedIdentifier,
IMaterialAssetData,
- BlockCls,
IAssetBuilder,
IMaterialFramedData,
- ITrackedAssetData
+ ITrackedAssetData,
+ IAutotileProcessor,
+ AutotileType
} from './types';
import { ICoreState } from '@user/data-state';
import { logger } from '@motajs/common';
-import { getClsByString, getTextureFrame } from './utils';
import { isNil } from 'lodash-es';
import { AssetBuilder } from './builder';
import { AutotileProcessor } from './autotile';
+import { ITileStore, TileType } from '@user/data-common';
-interface TilesetCache {
+interface ITilesetCache {
/** 是否已经在贴图库中存在 */
readonly existed: boolean;
/** 贴图对象 */
readonly texture: ITexture;
}
-export class MaterialManager implements ITextureManager {
+export class TextureManager implements ITextureManager {
+ readonly autotile: IAutotileProcessor;
+
+ readonly textures: ITextureStore = new TextureStore();
readonly tileStore: ITextureStore = new TextureStore();
readonly tilesetStore: ITextureStore = new TextureStore();
readonly imageStore: ITextureStore = new TextureStore();
readonly assetStore: ITextureStore = new TextureStore();
- readonly textures: ITextureStore = new TextureStore();
/** 自动元件图像源映射 */
readonly autotileSource: Map = new Map();
@@ -52,19 +53,21 @@ export class MaterialManager implements ITextureManager {
/** 带有脏标记追踪的图集对象 */
readonly trackedAsset: ITrackedAssetData;
- /** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
+ /** tileset 中的偏移索引映射,可以处理超出 Tileset 图块数量单元的 Tileset */
readonly tilesetOffsetMap: Map = new Map();
/** 图集打包器 */
readonly assetBuilder: IAssetBuilder;
- /** 图块 id 到图块数字的映射 */
- readonly idNumMap: Map = new Map();
- /** 图块数字到图块 id 的映射 */
- readonly numIdMap: Map = new Map();
- /** 图块数字到图块类型的映射 */
- readonly clsMap: Map = new Map();
/** 图块的默认帧数 */
readonly defaultFrames: Map = new Map();
+ /** 每个图块的总帧数 */
+ readonly frames: Map = new Map();
+ /** 每个图块的帧偏移量 */
+ readonly tileOffsets: Map = new Map();
+ /** 每个 Tileset 的 tile 尺寸 */
+ readonly tilesetCells: Map = new Map();
+ /** 自动元件类型映射 */
+ readonly autotileType: Map = new Map();
/** 网格切分器 */
readonly gridSplitter: TextureGridSplitter = new TextureGridSplitter();
@@ -78,120 +81,151 @@ export class MaterialManager implements ITextureManager {
/** 是否已经构建过素材 */
private built: boolean = false;
- constructor(readonly state: ICoreState) {
+ constructor(
+ readonly state: ICoreState,
+ readonly tiles: ITileStore,
+ readonly tilesetReserve: number,
+ readonly tilesetUnit: number
+ ) {
+ this.autotile = new AutotileProcessor(state);
this.assetBuilder = new AssetBuilder(this, state);
this.assetBuilder.pipe(this.assetStore);
this.trackedAsset = this.assetBuilder.tracked();
}
/**
- * 添加由分割器和图块映射组成的图像源贴图
- * @param source 图像源
- * @param map 图块 id 与图块数字映射
- * @param store 要添加至的贴图存储对象
- * @param splitter 使用的分割器
- * @param splitterData 传递给分割器的数据
- * @param processTexture 对每个纹理进行处理
+ * 添加经由分割器分割的贴图素材
+ * @param splitted 分割器分割后的图像列表
+ * @param map 图块的图块数字映射数组
+ * @param frames 此素材中每一个贴图使用的帧率
+ * @param offsets 此素材中每一个贴图的帧偏移量
*/
- private addMappedSource(
- source: SizedCanvasImageSource,
- map: ArrayLike,
- store: ITextureStore,
- splitter: ITextureSplitter,
- splitterData: T,
- processTexture?: (tex: ITexture) => void
+ private addSplittedSource(
+ splitted: Iterable,
+ map: ArrayLike,
+ frames: number,
+ offsets: number
): Iterable {
- const tex = new Texture(source);
- const textures = [...splitter.split(tex, splitterData)];
- if (textures.length !== map.length) {
- logger.warn(75, textures.length.toString(), map.length.toString());
- }
- const res: IMaterialData[] = textures.map((v, i) => {
- if (!map[i]) {
+ return [...splitted].map((v, i) => {
+ if (isNil(map[i])) {
return {
- store,
+ store: this.tileStore,
texture: v,
identifier: -1,
alias: '@internal-unknown'
};
}
- const { id, num, cls } = map[i];
- store.addTexture(num, v);
- store.alias(num, id);
- this.clsMap.set(num, getClsByString(cls));
- processTexture?.(v);
+ const num = map[i];
+ const id = this.tiles.id(num);
+ if (isNil(id)) {
+ logger.warn(184, num.toString());
+ return {
+ store: this.tileStore,
+ texture: v,
+ identifier: -1,
+ alias: '@internal-unknown'
+ };
+ }
+ this.tileStore.addTexture(num, v);
+ this.tileStore.alias(num, id);
+ this.frames.set(num, frames);
+ this.tileOffsets.set(num, offsets);
const data: IMaterialData = {
- store,
+ store: this.tileStore,
texture: v,
identifier: num,
alias: id
};
return data;
});
- return res;
}
addGrid(
source: SizedCanvasImageSource,
- map: ArrayLike
+ map: ArrayLike,
+ width: number,
+ height: number
): Iterable {
- return this.addMappedSource(
- source,
- map,
- this.tileStore,
- this.gridSplitter,
- [32, 32]
- );
+ const tex = new Texture(source);
+ const textures = [...this.gridSplitter.split(tex, [width, height])];
+ if (textures.length !== map.length) {
+ logger.warn(75, textures.length.toString(), map.length.toString());
+ }
+ return this.addSplittedSource(textures, map, 1, width);
}
addRowAnimate(
source: SizedCanvasImageSource,
- map: ArrayLike,
+ map: ArrayLike,
+ width: number,
height: number
): Iterable {
- return this.addMappedSource(
- source,
- map,
- this.tileStore,
- this.rowSplitter,
- height
- );
+ if (source.width % width !== 0) {
+ logger.warn(185, source.width.toString(), width.toString());
+ return [];
+ }
+ const tex = new Texture(source);
+ const textures = [...this.rowSplitter.split(tex, height)];
+ if (textures.length !== map.length) {
+ logger.warn(75, textures.length.toString(), map.length.toString());
+ }
+ const frames = source.width / width;
+ return this.addSplittedSource(textures, map, frames, width);
}
addAutotile(
source: SizedCanvasImageSource,
- identifier: IBlockIdentifier
+ num: number,
+ type: AutotileType
): void {
- this.autotileSource.set(identifier.num, source);
- this.tileStore.alias(identifier.num, identifier.id);
- this.clsMap.set(identifier.num, BlockCls.Autotile);
+ const id = this.tiles.id(num);
+ if (isNil(id)) {
+ logger.warn(184, num.toString());
+ return;
+ }
+ this.autotileSource.set(num, source);
+ this.tileStore.alias(num, id);
+ this.autotileType.set(num, type);
}
addTileset(
source: SizedCanvasImageSource,
- identifier: IIndexedIdentifier
+ identifier: IIndexedIdentifier,
+ cellWidth: number,
+ cellHeight: number
): IMaterialData | null {
const tex = new Texture(source);
this.tilesetStore.addTexture(identifier.index, tex);
this.tilesetStore.alias(identifier.index, identifier.alias);
- const width = Math.floor(source.width / 32);
- const height = Math.floor(source.height / 32);
+ if (
+ source.width % cellWidth !== 0 ||
+ source.height % cellHeight !== 0
+ ) {
+ logger.warn(
+ 186,
+ source.width.toString(),
+ source.height.toString(),
+ cellWidth.toString(),
+ cellHeight.toString()
+ );
+ }
+ // 计算 tile 数量,溢出部分不算
+ const width = Math.floor(source.width / cellWidth);
+ const height = Math.floor(source.height / cellHeight);
const count = width * height;
- const offset = Math.ceil(count / 10000);
+ // 一个 tileset 可能不止 tilesetUnit 个图块,需要计算偏移
+ const offset = Math.ceil(count / this.tilesetUnit);
if (identifier.index === 0) {
this.tilesetOffsetMap.set(0, 0);
this.nowTilesetIndex = 0;
this.nowTilesetOffset = offset;
} else {
+ // 不允许不按顺序追加,因为这会导致图块数字难以维护
if (identifier.index - 1 !== this.nowTilesetIndex) {
logger.warn(78);
return null;
}
- // 一个 tileset 可能不止 10000 个图块,需要计算偏移
- const width = Math.floor(source.width / 32);
- const height = Math.floor(source.height / 32);
- const count = width * height;
- const offset = Math.ceil(count / 10000);
+ // 这个 tileset 所包含的所有单位,都应该映射至当前 tileset,所以循环设置这期间的所有映射
const end = this.nowTilesetOffset + offset;
for (let i = this.nowTilesetOffset; i < end; i++) {
this.tilesetOffsetMap.set(i, identifier.index);
@@ -205,6 +239,7 @@ export class MaterialManager implements ITextureManager {
identifier: identifier.index,
alias: identifier.alias
};
+ this.tilesetCells.set(identifier.index, [cellWidth, cellHeight]);
return data;
}
@@ -232,31 +267,43 @@ export class MaterialManager implements ITextureManager {
return this.defaultFrames.get(identifier) ?? -1;
}
- getTile(identifier: number): Readonly | null {
- if (identifier < 10000) {
- const cls = this.clsMap.get(identifier) ?? BlockCls.Unknown;
- if (
- cls === BlockCls.Autotile &&
- this.autotileSource.has(identifier)
- ) {
- this.cacheAutotile(identifier);
+ getFrameCount(num: number): number {
+ return this.frames.get(num) ?? 0;
+ }
+
+ getTile(num: number): Readonly | null {
+ if (num < this.tilesetReserve) {
+ const type = this.tiles.getType(num);
+ if (type === TileType.Autotile && this.autotileSource.has(num)) {
+ this.cacheAutotile(num);
}
- const texture = this.tileStore.getTexture(identifier);
+
+ const texture = this.tileStore.getTexture(num);
if (!texture) return null;
+
+ const frames = this.frames.get(num);
+ const offset = this.tileOffsets.get(num);
+
+ if (isNil(frames) || isNil(offset)) {
+ logger.warn(187);
+ return null;
+ }
+
return {
texture,
- cls,
- offset: 32,
- frames: getTextureFrame(cls, texture),
- defaultFrame: this.defaultFrames.get(identifier) ?? -1
+ tileType: type,
+ offset,
+ frames,
+ defaultFrame: this.defaultFrames.get(num) ?? -1
};
} else {
- const texture = this.cacheTileset(identifier);
+ const texture = this.cacheTileset(num);
if (!texture) return null;
return {
texture,
- cls: BlockCls.Tileset,
- offset: 32,
+ tileType: TileType.Tileset,
+ // Tileset 不需要偏移量
+ offset: 0,
frames: 1,
defaultFrame: -1
};
@@ -289,28 +336,34 @@ export class MaterialManager implements ITextureManager {
return this.imageStore.fromAlias(alias);
}
- private getTilesetOwnTexture(identifier: number): TilesetCache | null {
- const texture = this.tileStore.getTexture(identifier);
+ private getTilesetOwnTexture(num: number): ITilesetCache | null {
+ const texture = this.tileStore.getTexture(num);
if (texture) return { existed: true, texture };
// 如果 tileset 不存在,那么执行缓存操作
- const offset = Math.floor(identifier / 10000);
- const index = this.tilesetOffsetMap.get(offset - 1);
+ const adjustedNum = num - this.tilesetReserve;
+ const offset = Math.floor(adjustedNum / this.tilesetUnit);
+ const index = this.tilesetOffsetMap.get(offset);
if (isNil(index)) return null;
// 获取对应的 tileset 贴图
const tileset = this.tilesetStore.getTexture(index);
- if (!tileset) return null;
- // 计算图块位置
- const rest = identifier - offset * 10000;
+ const cell = this.tilesetCells.get(index);
+ if (!tileset || !cell) return null;
+ // 计算图块位置,首先计算没有偏移时图块的索引,即假如 Tileset 的左上角是 0,计算 num 的索引应该是什么
+ const unoffset = adjustedNum - offset * this.tilesetUnit;
const { width, height } = tileset;
- const tileWidth = Math.floor(width / 32);
- const tileHeight = Math.floor(height / 32);
+ const [cellWidth, cellHeight] = cell;
+ const tileWidth = Math.floor(width / cellWidth);
+ const tileHeight = Math.floor(height / cellHeight);
// 如果图块位置超出了贴图范围
- if (rest > tileWidth * tileHeight) return null;
+ if (unoffset > tileWidth * tileHeight) {
+ logger.warn(188, num.toString());
+ return null;
+ }
// 裁剪 tileset,生成贴图
- const x = rest % tileWidth;
- const y = Math.floor(rest / tileWidth);
+ const x = unoffset % tileWidth;
+ const y = Math.floor(unoffset / tileWidth);
const newTexture = new Texture(tileset.source);
- newTexture.clip(x * 32, y * 32, 32, 32);
+ newTexture.clip(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
return { existed: false, texture: newTexture };
}
@@ -356,8 +409,6 @@ export class MaterialManager implements ITextureManager {
if (existed) return texture;
// 缓存贴图
this.tileStore.addTexture(identifier, texture);
- this.idNumMap.set(`X${identifier}`, identifier);
- this.numIdMap.set(identifier, `X${identifier}`);
const data = this.assetBuilder.addTexture(texture);
texture.toAsset(data);
this.checkAssetDirty(data);
@@ -377,8 +428,6 @@ export class MaterialManager implements ITextureManager {
if (existed) return;
toAdd.push(texture);
this.tileStore.addTexture(v, texture);
- this.idNumMap.set(`X${v}`, v);
- this.numIdMap.set(v, `X${v}`);
});
const data = this.assetBuilder.addTextureList(toAdd);
@@ -390,38 +439,41 @@ export class MaterialManager implements ITextureManager {
/**
* 获取自动元件展开后的图片,如果图片不存在,或是已经展开并存储至了 `tileStore`,那么返回 `null`
- * @param identifier 自动元件标识符
+ * @param num 自动元件数字
*/
- private getFlattenedAutotile(
- identifier: number
- ): SizedCanvasImageSource | null {
- const cls = this.clsMap.get(identifier);
- if (cls !== BlockCls.Autotile) return null;
- if (this.tileStore.getTexture(identifier)) return null;
- const source = this.autotileSource.get(identifier);
+ private getFlattenedAutotile(num: number): SizedCanvasImageSource | null {
+ const type = this.tiles.getType(num);
+ if (type !== TileType.Autotile) return null;
+ if (this.tileStore.getTexture(num)) return null;
+ const source = this.autotileSource.get(num);
if (!source) return null;
- const frames = source.width === 96 ? 1 : 4;
- const flattened = AutotileProcessor.flatten({ source, frames });
+ const autotileType = this.autotileType.get(num);
+ const frames = this.frames.get(num);
+ if (isNil(autotileType) || isNil(frames)) {
+ logger.warn(191);
+ return null;
+ }
+ const flattened = this.autotile.flatten(source, autotileType, frames);
if (!flattened) return null;
return flattened;
}
- cacheAutotile(identifier: number): ITexture | null {
- const flattened = this.getFlattenedAutotile(identifier);
+ cacheAutotile(num: number): ITexture | null {
+ const existed = this.tileStore.getTexture(num);
+ if (existed) return existed;
+ const flattened = this.getFlattenedAutotile(num);
if (!flattened) return null;
const tex = new Texture(flattened);
- this.tileStore.addTexture(identifier, tex);
+ this.tileStore.addTexture(num, tex);
const data = this.assetBuilder.addTexture(tex);
tex.toAsset(data);
- this.autotileSource.delete(identifier);
+ this.autotileSource.delete(num);
this.checkAssetDirty(data);
return tex;
}
- cacheAutotileList(
- identifierList: Iterable
- ): Iterable {
- const arr = [...identifierList];
+ cacheAutotileList(list: Iterable): Iterable {
+ const arr = [...list];
const toAdd: ITexture[] = [];
arr.forEach(v => {
@@ -500,45 +552,31 @@ export class MaterialManager implements ITextureManager {
return this.assetDataStore.get(id) ?? null;
}
- private getTextureOf(identifier: number, cls: BlockCls): ITexture | null {
- if (cls === BlockCls.Unknown) return null;
- if (cls !== BlockCls.Tileset) {
- return this.tileStore.getTexture(identifier);
+ private getTextureOf(num: number): ITexture | null {
+ const existed = this.tileStore.getTexture(num);
+ if (existed) return existed;
+ if (num >= this.tilesetReserve) {
+ return this.cacheTileset(num);
+ } else {
+ const type = this.tiles.getType(num);
+ if (type === TileType.Autotile) {
+ return this.cacheAutotile(num);
+ } else {
+ return null;
+ }
}
- if (identifier < 10000) return null;
- return this.cacheTileset(identifier);
}
- getRenderable(identifier: number): ITextureRenderable | null {
- const cls = this.clsMap.get(identifier);
- if (isNil(cls)) return null;
- const texture = this.getTextureOf(identifier, cls);
+ getRenderable(num: number): ITextureRenderable | null {
+ const texture = this.getTextureOf(num);
if (!texture) return null;
return texture.render();
}
getRenderableByAlias(alias: string): ITextureRenderable | null {
- const identifier = this.idNumMap.get(alias);
- if (isNil(identifier)) return null;
- return this.getRenderable(identifier);
- }
-
- getBlockCls(identifier: number): BlockCls {
- return this.clsMap.get(identifier) ?? BlockCls.Unknown;
- }
-
- getBlockClsByAlias(alias: string): BlockCls {
- const id = this.idNumMap.get(alias);
- if (isNil(id)) return BlockCls.Unknown;
- return this.clsMap.get(id) ?? BlockCls.Unknown;
- }
-
- getIdentifierByAlias(alias: string): number | undefined {
- return this.idNumMap.get(alias);
- }
-
- getAliasByIdentifier(identifier: number): string | undefined {
- return this.numIdMap.get(identifier);
+ const num = this.tiles.num(alias);
+ if (isNil(num)) return null;
+ return this.getRenderable(num);
}
assetContainsTexture(texture: ITexture): boolean {
diff --git a/packages-user/client-base/src/material/types.ts b/packages-user/client-base/src/material/types.ts
index 7c8f0e1..0f47428 100644
--- a/packages-user/client-base/src/material/types.ts
+++ b/packages-user/client-base/src/material/types.ts
@@ -6,23 +6,13 @@ import {
ITextureStore,
SizedCanvasImageSource
} from '@motajs/render';
+import { ITileStore, TileType } from '@user/data-common';
import { ICoreStateExtended } from '@user/data-state';
-export const enum BlockCls {
- Unknown,
- Terrains,
- Animates,
- Enemys,
- Npcs,
- Items,
- Enemy48,
- Npc48,
- Tileset,
- Autotile
-}
-
export const enum AutotileType {
+ /** 2x3 大小的自动元件 */
Small2x3,
+ /** 3x4 大小的自动元件 */
Big3x4
}
@@ -85,8 +75,8 @@ export interface IAutotileConnection {
export interface IMaterialFramedData {
/** 贴图对象 */
texture: ITexture;
- /** 图块类型 */
- cls: BlockCls;
+ /** 贴图对应图块的图块类型 */
+ tileType: TileType;
/** 贴图总帧数 */
frames: number;
/** 每帧的横向偏移量 */
@@ -102,12 +92,21 @@ export interface IMaterialAsset
}
export interface IAutotileProcessor extends ICoreStateExtended {
- /** 该自动元件处理器使用的素材管理器 */
- readonly manager: ITextureManager;
+ /**
+ * 展平自动元件,纵向排列 48 种组合
+ * @param source 图像源
+ * @param type 自动元件的类型
+ * @param frames 自动元件的帧数
+ */
+ flatten(
+ source: SizedCanvasImageSource,
+ type: AutotileType,
+ frames: number
+ ): SizedCanvasImageSource | null;
/**
- * 设置一个自动元件的特殊连接方式,设置后当前自动元件将会单方面与目标元件连接,
- * 一个自动元件可以与多个自动元件有特殊连接
+ * 设置一个自动元件的特殊连接方式,设置后当前自动元件将会单方面与目标图块连接,
+ * 一个自动元件可以与多个图块有特殊连接
* @param autotile 自动元件
* @param target 当前自动元件将会连接至的自动元件
*/
@@ -142,52 +141,22 @@ export interface IAutotileProcessor extends ICoreStateExtended {
/**
* 根据图块数字,获取指定自动元件经过连接的可渲染对象
- * @param autotile 自动元件的图块数字
+ * @param tile 自动元件的图块信息
* @param connection 连接方式,上方连接是第一位,顺时针旋转位次依次升高
* @returns 连接方式的可渲染对象,可以通过偏移量依次获取其他帧
*/
- render(autotile: number, connection: number): ITextureRenderable | null;
-
- /**
- * 根据图块贴图对象,获取指定自动元件经过连接的可渲染对象
- * @param tile 自动元件的图块贴图数据
- * @param connection 连接方式,上方连接是第一位,顺时针旋转位次依次升高
- * @returns 连接方式的可渲染对象,可以通过偏移量依次获取其他帧
- */
- renderWith(
- tile: Readonly,
+ render(
+ tile: IMaterialFramedData,
connection: number
): ITextureRenderable | null;
/**
- * 根据图块贴图对象,获取指定自动元件经过连接的可渲染对象,但是会假设传入的图块就是自动元件,不做不必要的判断
- * @param tile 自动元件的图块贴图数据
- * @param connection 连接方式,上方连接是第一位,顺时针旋转位次依次升高
- * @returns 连接方式的可渲染对象,可以通过偏移量依次获取其他帧
- */
- renderWithoutCheck(
- tile: Readonly,
- connection: number
- ): ITextureRenderable | null;
-
- /**
- * 根据图块数字,获取指定自动元件经过链接的动态可渲染对象
- * @param autotile 自动元件的图块数字
+ * 根据图块贴图对象,获取指定自动元件经过链接的动态可渲染对象
+ * @param autotile 自动元件的图块信息
* @param connection 自动元件的连接方式
* @returns 生成器,每一个输出代表每一帧的渲染对象,不同自动元件的帧数可能不同
*/
renderAnimated(
- autotile: number,
- connection: number
- ): Generator;
-
- /**
- * 根据图块贴图对象,获取指定自动元件经过链接的动态可渲染对象
- * @param autotile 自动元件的图块数字
- * @param connection 自动元件的连接方式
- * @returns 生成器,每一个输出代表每一帧的渲染对象,不同自动元件的帧数可能不同
- */
- renderAnimatedWith(
tile: Readonly,
connection: number
): Generator;
@@ -196,33 +165,27 @@ export interface IAutotileProcessor extends ICoreStateExtended {
export interface ITextureGetter {
/**
* 根据图块数字获取图块,可以获取额外素材,会自动将未缓存的额外素材缓存
- * @param identifier 图块的图块数字
+ * @param num 图块的图块数字
*/
- getTile(identifier: number): Readonly | null;
-
- /**
- * 根据图块标识符获取图块类型
- * @param identifier 图块标识符,即图块数字
- */
- getBlockCls(identifier: number): BlockCls;
+ getTile(num: number): Readonly | null;
/**
* 根据标识符获取图集信息
- * @param identifier 图集的标识符
+ * @param num 图集的标识符
*/
- getAsset(identifier: number): ITextureComposedData | null;
+ getAsset(num: number): ITextureComposedData | null;
/**
* 根据额外素材索引获取额外素材
- * @param identifier 额外素材的索引
+ * @param num 额外素材的索引
*/
- getTileset(identifier: number): ITexture | null;
+ getTileset(num: number): ITexture | null;
/**
* 根据图片的索引获取图片
- * @param identifier 图片的索引
+ * @param num 图片的索引
*/
- getImage(identifier: number): ITexture | null;
+ getImage(num: number): ITexture | null;
}
export interface ITextureAliasGetter {
@@ -249,16 +212,15 @@ export interface ITextureAliasGetter {
* @param alias 图集的别名
*/
getAssetByAlias(alias: string): ITextureComposedData | null;
-
- /**
- * 根据图块别名获取图块类型
- * @param alias 图块别名,即图块的 id
- */
- getBlockClsByAlias(alias: string): BlockCls;
}
export interface ITextureManager
extends ITextureGetter, ITextureAliasGetter, ICoreStateExtended {
+ /** 使用的图块数据存储对象 */
+ readonly tiles: ITileStore;
+ /** 自动元件处理器 */
+ readonly autotile: IAutotileProcessor;
+
/** 通过加载获取的所有纹理贴图存储,包括图块、普通图片等 */
readonly textures: ITextureStore;
/** 贴图存储,把 terrains 等内容单独分开存储 */
@@ -275,51 +237,73 @@ export interface ITextureManager
/** 带有脏标记追踪的图集信息 */
readonly trackedAsset: ITrackedAssetData;
- /** 图块类型映射 */
- readonly clsMap: Map;
+ /** Tileset 额外素材预留多少数字,即从多少数字开始,素材变为额外素材 */
+ readonly tilesetReserve: number;
+ /**
+ * Tileset 额外素材的素材数量单位,例如 10000 就表示每个额外素材最少使用 10000 个数字位,
+ * 如果超出则变为 20000, 30000,以此类推。
+ */
+ readonly tilesetUnit: number;
/**
- * 添加网格类型的贴图,包括 terrains 和 items 类型
+ * 添加网格类型的贴图,此方式添加的贴图仅有一帧,不包含动画。
* @param source 图像源
- * @param map 贴图字符串 id 与图块数字映射,按照先从左到右,再从上到下的顺序映射
+ * @param width 每个贴图的像素宽度
+ * @param height 每个贴图的像素高度
+ * @param map 贴图的图块数字映射,按照先从左到右,再从上到下的顺序映射,如下图所示
+ *
+ * ```txt
+ * 0 1 2 ... n-1
+ * n n+1 n+2 ... 2n-1
+ * . ... ... ... ...
+ * ```
*/
addGrid(
source: SizedCanvasImageSource,
- map: ArrayLike
+ map: ArrayLike,
+ width: number,
+ height: number
): Iterable;
/**
- * 添加行动画的贴图,包括 animates enemys npcs enemy48 npc48 类型
+ * 添加行动画的贴图,要求每一行的帧数一致,如不一致请拆分成多次调用
* @param source 图像源
- * @param map 贴图字符串 id 与图块数字映射,按从上到下的顺序映射
- * @param frames 每一行的帧数
- * @param height 每一行的高度
+ * @param map 贴图的图块数字映射,按从上到下的顺序映射
+ * @param width 每一帧的像素宽度
+ * @param height 每一帧的像素高度,也就是每一行的高度
*/
addRowAnimate(
source: SizedCanvasImageSource,
- map: ArrayLike,
+ map: ArrayLike,
+ width: number,
height: number
): Iterable;
/**
* 添加自动元件
* @param source 图像源
- * @param identifier 自动元件的字符串 id 及图块数字
+ * @param identifier 自动元件的图块数字
+ * @param type 自动元件类型,是 3x4 还是 2x3
* @returns 由于自动元件是懒加载的,因此不会返回任何东西
*/
addAutotile(
source: SizedCanvasImageSource,
- identifier: IBlockIdentifier
+ identifier: number,
+ type: AutotileType
): void;
/**
* 添加一个 tileset 类型的素材
* @param source 图像源
- * @param alias tileset 的标识符,包含其在 tilesets 列表中的索引和图片名称
+ * @param identifier tileset 的标识符对象
+ * @param cellWidth Tileset 中每个 tile 的宽度
+ * @param cellHeight Tileset 中每个 tile 的高度
*/
addTileset(
source: SizedCanvasImageSource,
- identifier: IIndexedIdentifier
+ identifier: IIndexedIdentifier,
+ cellWidth: number,
+ cellHeight: number
): IMaterialData | null;
/**
@@ -334,44 +318,48 @@ export interface ITextureManager
/**
* 设置指定图块默认显示第几帧
- * @param identifier 图块标识符,即图块数字
+ * @param num 图块标识符,即图块数字
* @param defaultFrame 图块的默认帧数
*/
- setDefaultFrame(identifier: number, defaultFrame: number): void;
+ setDefaultFrame(num: number, defaultFrame: number): void;
/**
* 获取图块的默认帧数,-1 表示正常动画,非负整数表示默认使用指定帧数,除非单独指定
- * @param identifier 图块标识符,即图块数字
+ * @param num 图块标识符,即图块数字
*/
- getDefaultFrame(identifier: number): number;
+ getDefaultFrame(num: number): number;
/**
- * 缓存某个 tileset,当需要缓存多个时,请使用 {@link cacheTilesetList} 方法
- * @param identifier tileset 的标识符,即图块数字
+ * 获取指定图块的总帧数,若图块不存在,返回 0
+ * @param num 图块标识符,即图块数字
*/
- cacheTileset(identifier: number): ITexture | null;
+ getFrameCount(num: number): number;
/**
- * 缓存一系列 tileset
- * @param identifierList 标识符列表,即图块数字列表
+ * 缓存某个 tileset 上的图块,作用是生成贴图并打包入图集,
+ * 当需要缓存多个时,请使用 {@link cacheTilesetList} 方法
+ * @param num tileset 图块的标识符,即图块数字
*/
- cacheTilesetList(
- identifierList: Iterable
- ): Iterable;
+ cacheTileset(num: number): ITexture | null;
/**
- * 缓存某个自动元件,当需要缓存多个时,请使用 {@link cacheAutotileList} 方法
- * @param identifier 自动元件标识符,即图块数字
+ * 缓存一系列 tileset 上的图块
+ * @param list 标识符列表,即图块数字列表
*/
- cacheAutotile(identifier: number): ITexture | null;
+ cacheTilesetList(list: Iterable): Iterable;
+
+ /**
+ * 缓存某个自动元件,作用是生成展平贴图并打包入图集,展平贴图指将自动元件拆分成 48 种连接方式纵向堆叠组成的贴图。
+ * 当需要缓存多个时,请使用 {@link cacheAutotileList} 方法
+ * @param num 自动元件标识符,即图块数字
+ */
+ cacheAutotile(num: number): ITexture | null;
/**
* 缓存一系列自动元件
- * @param identifierList 自动元件标识符列表,即图块数字列表
+ * @param list 自动元件标识符列表,即图块数字列表
*/
- cacheAutotileList(
- identifierList: Iterable
- ): Iterable;
+ cacheAutotileList(list: Iterable): Iterable;
/**
* 把常用素材打包成为图集形式供后续使用
@@ -392,9 +380,9 @@ export interface ITextureManager
/**
* 根据图块标识符在图集中获取对应的可渲染对象
- * @param identifier 图块标识符,即图块数字
+ * @param num 图块标识符,即图块数字
*/
- getRenderable(identifier: number): ITextureRenderable | null;
+ getRenderable(num: number): ITextureRenderable | null;
/**
* 根据图块别名在图集中获取对应的可渲染对象
@@ -402,18 +390,6 @@ export interface ITextureManager
*/
getRenderableByAlias(alias: string): ITextureRenderable | null;
- /**
- * 根据图块别名获取图块标识符,即图块数字
- * @param alias 图块别名,即图块的 id
- */
- getIdentifierByAlias(alias: string): number | undefined;
-
- /**
- * 根据图块标识符获取图块别名,即图块的 id
- * @param identifier 图块标识符,即图块数字
- */
- getAliasByIdentifier(identifier: number): string | undefined;
-
/**
* 当前的所有图集中是否包含指定的贴图对象
* @param texture 贴图对象
diff --git a/packages-user/client-base/src/types.ts b/packages-user/client-base/src/types.ts
index 8119921..2ba5691 100644
--- a/packages-user/client-base/src/types.ts
+++ b/packages-user/client-base/src/types.ts
@@ -1,7 +1,7 @@
import { IBGMPlayer, IMotaAudioContext, ISoundPlayer } from '@motajs/audio';
import { ISaveSystem } from './save';
import { ICoreState } from '@user/data-state';
-import { IAutotileProcessor, ITextureManager } from './material';
+import { ITextureManager } from './material';
import { IRenderTreeRoot } from '@motajs/render';
import { IExcitation, IExcitationDivider } from '@motajs/animate';
@@ -16,8 +16,6 @@ export interface IClientBase extends ICoreState {
readonly bgmPlayer: IBGMPlayer;
/** 素材管理器 */
readonly materials: ITextureManager;
- /** 自动元件处理器 */
- readonly autotile: IAutotileProcessor;
/** 渲染画面的根元素 */
readonly renderer: IRenderTreeRoot;
/** 用于渲染系统的 Raf 激励源 */
diff --git a/packages-user/client-modules/src/client.ts b/packages-user/client-modules/src/client.ts
index b70bdfe..0e49898 100644
--- a/packages-user/client-modules/src/client.ts
+++ b/packages-user/client-modules/src/client.ts
@@ -12,7 +12,7 @@ import { IRenderTreeRoot, MotaRenderer } from '@motajs/render';
import {
ITextureManager,
IAutotileProcessor,
- MaterialManager,
+ TextureManager,
AutotileProcessor,
ISaveSystem,
SaveSystem
@@ -48,7 +48,6 @@ export class ClientCore extends CoreState implements IClientCore {
// Layer 5 渲染顶层
readonly materials: ITextureManager;
- readonly autotile: IAutotileProcessor;
readonly rafExcitation: IExcitation;
readonly excitationDivider: IExcitationDivider;
@@ -81,8 +80,12 @@ export class ClientCore extends CoreState implements IClientCore {
//#region 素材系统
- this.materials = new MaterialManager(this);
- this.autotile = new AutotileProcessor(this.materials, this);
+ this.materials = new TextureManager(
+ this,
+ this.tileStore,
+ config.tilesetReserve,
+ config.tilesetUnit
+ );
//#endregion
diff --git a/packages-user/client-modules/src/core.ts b/packages-user/client-modules/src/core.ts
index 8cc1770..067906a 100644
--- a/packages-user/client-modules/src/core.ts
+++ b/packages-user/client-modules/src/core.ts
@@ -3,4 +3,9 @@ import { ClientCore } from './client';
// TODO: 逐渐弱化 ClientCore 的单例概念,每个接口都通过参数传入 IClientCore 对象
/** 客户端实例 */
-export const client = new ClientCore();
+export const client = new ClientCore({
+ clientURL: 'placeholder',
+ // 这两个值不要随意调整,若需要调整,务必先在编辑器中调整“额外素材预留”与“额外素材单位”,否则必定会导致游戏出错
+ tilesetReserve: 100000,
+ tilesetUnit: 5000
+});
diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts
index 4a879b6..e3f6ae1 100644
--- a/packages-user/client-modules/src/render/map/renderer.ts
+++ b/packages-user/client-modules/src/render/map/renderer.ts
@@ -1261,7 +1261,7 @@ export class MapRenderer
} else {
// 多帧图块
if (tex.cls === BlockCls.Autotile) {
- const gen = this.autotile.renderAnimatedWith(tex, 0b1111_1111);
+ const gen = this.autotile.renderAnimated(tex, 0b1111_1111);
return this.useDynamicBackground(gl, data, [...gen]);
} else {
const gen = this.tileAnimater.once(tex.texture, tex.frames);
diff --git a/packages-user/client-modules/src/types.ts b/packages-user/client-modules/src/types.ts
index 1a910ac..9629211 100644
--- a/packages-user/client-modules/src/types.ts
+++ b/packages-user/client-modules/src/types.ts
@@ -4,6 +4,10 @@ import { IMapExtensionManager, IMapRenderer } from './render/map';
export interface IClientCoreConfig {
/** 渲染端数据配置文件路径,相对于 `src/content` */
readonly clientURL: string;
+ /** Tileset 预留数量 */
+ readonly tilesetReserve: number;
+ /** Tileset 单元数量 */
+ readonly tilesetUnit: number;
}
export interface IClientCore extends IClientSystem {
diff --git a/packages-user/entry-client/src/create.ts b/packages-user/entry-client/src/create.ts
index 10f9219..8156b04 100644
--- a/packages-user/entry-client/src/create.ts
+++ b/packages-user/entry-client/src/create.ts
@@ -33,7 +33,6 @@ export function create() {
}
async function createModule() {
- UserClientBase.create();
ClientModules.create();
LegacyUI.create();
diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json
index 716bc36..7c5d8a0 100644
--- a/packages/common/src/logger.json
+++ b/packages/common/src/logger.json
@@ -262,6 +262,14 @@
"181": "Number $1 has been registered to $2 store, old one will be overridden.",
"182": "Cannot bind '$1' instance to '$2' since they belong to different CoreState instance.",
"183": "An in-map location is expected to begin map graph building, but got outside.",
+ "184": "Cannot add tile texture $1 since no registered id for it in tile store.",
+ "185": "Cannot add row animated texture since it cannot be splitted averagely by specified width. Texture width: $1, split width: $2.",
+ "186": "Tileset has a size that cannot be splitted evenly by specified cell size, overflowed content will be ignored. Tileset size: [$1, $2], cell size: [$1, $2].",
+ "187": "Cannot get tile that indeed has a texture but no frame and offset infomation, which seems like a internal bug of mota-js engine, please report a bug.",
+ "188": "Tileset tile $1 exceeds tileset itself. You may check the num indeed corresponding to a tileset tile, or may a internal bug of mota-js engine.",
+ "189": "Cannot flatten autotile since it cannot be splitted evenly. Autotile width: $1, frames: $2.",
+ "190": "Cannot flatten autotile since its width or height cannot be divided evenly. Autotile frame size: [$1,$2].",
+ "191": "Cannot cache autotile since no its frame or type data stored.",
"1001": "Event(setBlock): Unknown tile '$1' when setting block."
}
}
From d25702ff56bc60a75393c5de46318bf762e18b18 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Tue, 22 Sep 2026 23:53:27 +0800
Subject: [PATCH 072/173] docs(04): plan TextureManager interface impact study
(04-08)
---
.planning/ROADMAP.md | 8 +-
.../phases/04-render-adaptation/04-08-PLAN.md | 480 ++++++++++++++++++
2 files changed, 486 insertions(+), 2 deletions(-)
create mode 100644 .planning/phases/04-render-adaptation/04-08-PLAN.md
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index f78ac2d..de580c9 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -181,8 +181,8 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
-**Plans**: 7/7 plans executed — 04-01..04-06 已执行(04-06 = material 接口影响范围清点·只读);**04-07(material 接口适应实施·第二步)已规划待执行**;渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
-**Plan register note (2026-09-21):** 第五个增量 `04-05`(收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性改经 `client.hero.attribute.getFinalAttribute(...)` 读取 + 逐图层钩子类 `RendererLayerHook`,2026-09-20 执行)此前未登记进本路线图,本次一并补登;第六个增量 `04-06` = material 接口适应(D-30/D-31/D-32)**第一步·只读影响清点**,产出 `04-MATERIAL-INTERFACE-IMPACT.md`,第二步(实施)待用户审阅后另行规划。
+**Plans**: 8/8 plans planned — 04-01..04-07 已执行(04-06 = material 接口影响范围清点·只读;04-07 = material 接口适应实施·第二步);**04-08(TextureManager 新接口影响范围清点·只读,D-38/D-39/D-40/D-41/D-42 第一步)已规划待执行**;渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
+**Plan register note (2026-09-21):** 第五个增量 `04-05`(收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性改经 `client.hero.attribute.getFinalAttribute(...)` 读取 + 逐图层钩子类 `RendererLayerHook`,2026-09-20 执行)此前未登记进本路线图,本次一并补登;第六个增量 `04-06` = material 接口适应(D-30/D-31/D-32)**第一步·只读影响清点**,产出 `04-MATERIAL-INTERFACE-IMPACT.md`,第二步(实施)待用户审阅后另行规划。第七个增量 `04-07` = material 接口适应实施(**第二步·代码改动**,D-33..D-37),2026-09-22 已执行(10 文件原子提交)。**第八个增量 `04-08` = TextureManager 新接口适应(D-38..D-42)第一步·只读影响清点**,产出 `04-TEXTURE-INTERFACE-IMPACT.md`;第二步(实施)待用户审阅后另行规划。
Plans:
**Wave 1**
@@ -210,6 +210,10 @@ Plans:
- [x] 04-07-PLAN.md — material 接口适应实施(**第二步·代码改动**,D-30/D-31/D-32 第二步 + D-33..D-37):按 04-06 清点的 A/C/D/E 四类落地——A 类(D-34)删除全部 big-image 引用并把 `getIfBigImage` 的 7 处消费者(`door.ts` 2 / `hero.ts` 3 / `renderer.ts` 1 / `vertex.ts` 1)改为行为等价的 `getTile`、删除 `renderer.ts` `getOffsetPool` 的 big-image 偏移收集;C 类(D-35)为 `MaterialManager` 补 `textures`、为 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` / `TrackedAssetData` 补构造器注入的 `state`(不取全局单例,D-23;`renderer.ts` 从 `manager.state` 取值);D 类(D-36)删除 `MaterialManager` 的 big-image 实现残留(`bigImageStore` / `bigImageData` / `bigImageId` / 五个方法 / `setDefaultFrame` 耦合)与 `render/elements/cache.ts` 的 legacy big-image 路径;E 类(D-37)为 `client-base/package.json` 补 `@user/data-state` 声明(不安装、不同步 lockfile)。10 个文件原子提交;门禁 = material 归属诊断归零 + 在范围文件零诊断 + 总行数不增加(规划日 199 / 19 → ≤ 180)+ `packages-user` 内 big-image 记号归零 + eslint / CRLF / 范围(基线感知)/ 并发基线;运行时 UAT 不可得(渲染端无测试设施)如实声明
+**Wave 7** *(blocked on Wave 6 completion)*
+
+- [ ] 04-08-PLAN.md — TextureManager 新接口影响范围清点(**只读**,D-38/D-39/D-40/D-41/D-42 第一步):以 `d36ea69`(`refactor: 贴图存储方式`)后的 `client-base/src/{types.ts,material/types.ts,material/manager.ts,material/autotile.ts,material/index.ts}` 为基准,清点 `packages-user` 消费者面对改名(`MaterialManager`→`TextureManager` 与构造器)、移除(`BlockCls` 枚举 / `IMaterialFramedData.cls`→`tileType` / `IAutotileProcessor` 三方法合一 / `renderAnimatedWith`→`renderAnimated` / `getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockCls(ByAlias)` / `IClientBase.autotile` / `create()` 与 `createMaterial()`)与新增收紧(`tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount` / 四个 `add*` 参数 / `flatten` / `AutotileType` / `IClientCoreConfig` 两个必填字段)的引用,按 A 符号改名与移除 / B 成员与签名变化 / C 地图渲染消费者定位索引 / D 被删且无替代(需用户反馈)/ E 加载相关与添加素材(仅报告,D-39/D-41)/ F 未确定 六类分组,每行精确到符号 + `file:line` 且 ≥2 锚点;产出 `04-TEXTURE-INTERFACE-IMPACT.md`,生产代码零改动(`material/` 按 D-42 只登记),第二步实施待用户审阅后另行规划
+
**UI hint**: yes
### Phase 5: Legacy 移植
diff --git a/.planning/phases/04-render-adaptation/04-08-PLAN.md b/.planning/phases/04-render-adaptation/04-08-PLAN.md
new file mode 100644
index 0000000..eb892bf
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-08-PLAN.md
@@ -0,0 +1,480 @@
+---
+phase: 04-render-adaptation
+plan: 8
+type: execute
+wave: 7
+depends_on:
+ - 04-01
+ - 04-02
+ - 04-03
+ - 04-04
+ - 04-05
+ - 04-06
+ - 04-07
+files_modified:
+ - .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
+autonomous: false
+requirements:
+ - REND-01
+ - REND-02
+estimate:
+ tokens: 82000
+ raw_tokens: 82000
+ tasks: 4
+ confidence: low
+must_haves:
+ truths:
+ - "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求)**:Task 0 必须先向用户汇报「要解决的问题(`d36ea69` 把贴图存储层由 `MaterialManager` 重写为 `TextureManager`,构造器签名、barrel 导出、`client.autotile`、`IAutotileProcessor` 全部改了,渲染端消费者未跟上)、大致解决方案(只读清点,唯一交付物 `04-TEXTURE-INTERFACE-IMPACT.md`,11 节结构 / A–F 六类分列 / 每行精确到符号 + `file:line`)、`d36ea69` 基准的三类差异(改名与移除 / 成员与签名 / 相邻接线)、范围与禁令(D-38..D-42)、规划日实测数值(总错误 202、texture 归属 23)、验证方式与只读证明」,然后必须**停下等待**用户明确回复「可以执行」四个字;收到该回复前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `mode: yolo`、`workflow.auto_advance`)如何,均不得自动放行。"
+ - "**只读硬约束(生产代码零改动)**:本 run 生产源码改动为 **0**——`git status --porcelain -- packages packages-user src` 在 Task 1 步骤 (0a) 起始逐行原样记录进 `## 方法`(规划日 2026-09-22 实测仅 ` M .planning/phases/04-render-adaptation/04-CONTEXT.md`,该路径不在集合内故集合为空),Task 1 / Task 3 门禁断言其后**逐条一致**(不新增、不消失、状态码不变);`pnpm exec vue-tsc --noEmit` 在 Task 1 步骤 (0b) 记录**错误总行数**与 **texture 归属错误行数**两个数字(规划日实测 **202 / 23**,见 assumption 的口径说明),Task 3 重跑并以**同一正则**统计,断言错误总行数等于 Task 1 记录值;`files_modified` 只含 `04-TEXTURE-INTERFACE-IMPACT.md` 一个交付物;本 PLAN 与 `04-08-SUMMARY.md` 属计划/执行簿记,不算生产改动。"
+ - "**D-42 硬边界:`packages-user/client-base/src/material/` 是用户已重构完毕的文件夹,AI 不得计划任何修改**:本计划的 A–F 记录若命中该文件夹内(规划日已知 `material/utils.ts:2` 的 `BlockCls` import 残留),**只能在 `## D 被删且无替代(需用户反馈)` 内作为「用户自有面」登记并向用户反馈**,**不得**为该文件夹内的任何文件提出修改方案、不得列入第二步的 AI 工作范围。"
+ - "**D-39 / D-41 硬边界:「加载相关」整体排除、只报告**——① **加载本身**(`packages-user/client-base/src/load/loader.ts`)与 ② **向 `TextureManager` 添加素材的内容**(`packages-user/client-modules/src/fallback/load.ts` 的 `addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` / `buildAssets` / `cacheAutotileList` / `cacheTilesetList` / `buildListToAsset` 调用,以及 `client.ts:93-96` 的 `fallbackLoad(this.materials)` 接线)必须**单独成 `## E 加载相关与添加素材(仅报告)` 一节**,**不得**提出修改方案、**不得**计入 AI 的第二步工作范围;用户将自行适配新加载系统。"
+ - "**A 类(符号改名与移除的引用点)必须给两侧 `file:line` 并覆盖 `d36ea69` 点名的每个符号**:`MaterialManager`→`TextureManager`(新名消费者 `client-modules/src/client.ts:15,83`,旧名引用须给机器证据)、`material/index.ts` 的 `createMaterial()` 与 `client-base/src/index.ts` 的 `create()` 被删除、`renderAnimatedWith`→`renderAnimated`(已适配点 `render/map/renderer.ts:1264`;旧名引用须给机器证据)、`IClientBase.autotile` 被删除(`client-base/src/types.ts`)、`IMaterialFramedData.cls: BlockCls`→`tileType: TileType`(消费者 `hero.ts:167`、`renderer.ts:1252,1263`、`vertex.ts:517,561,852`)、`BlockCls` 枚举本身被删除(消费者 import `hero.ts:15`、`renderer.ts:10`、`vertex.ts:27`)、`ITextureManager.getIdentifierByAlias` / `getAliasByIdentifier` 被删除、`ITextureGetter.getBlockCls` / `ITextureAliasGetter.getBlockClsByAlias` 被删除、`AutotileProcessor` 构造器改签名(消费者 `render/map/renderer.ts:234`)、新增 `AutotileType`。"
+ - "**B 类(成员与签名变化的调用点)必须逐条登记 `d36ea69` 收紧后的签名与新成员**:`ITextureManager` 新增 `readonly tiles: ITileStore` 与 `readonly autotile: IAutotileProcessor` 并保留 `readonly textures`、新增 `readonly tilesetReserve` / `readonly tilesetUnit`;新增 `getFrameCount(num)`;`addGrid(source, map: ArrayLike, width, height)`、`addRowAnimate(source, map: ArrayLike, width, height)`、`addAutotile(source, identifier: number, type: AutotileType)`、`addTileset(source, identifier, cellWidth, cellHeight)` 四者参数收紧;`IAutotileProcessor.flatten(source, type, frames)` 新增;`IClientCoreConfig` 新增两个必填字段(消费者 `client-modules/src/core.ts:6-11` 与 `client.ts:62,86-87`)。**每行须含基准侧声明 `file:line` + 命中侧调用 `file:line`(≥2 锚点)。**"
+ - "**C 类(地图渲染消费者定位索引)不新增独立事实,只把第二步将触及的定位点按文件汇总**:必须覆盖 `render/map/renderer.ts`、`render/map/vertex.ts`、`render/map/extension/hero.ts`、`render/map/moving.ts`、`render/map/types.ts`、`client-modules/src/client.ts`、`client-modules/src/types.ts`、`client-modules/src/core.ts`,每条给 `file:line`,并**交叉引用其 A / B / D 记录 ID**;C 行不得把加载相关(E)或 `material/` 内(D 的用户自有面)的定位点写成 AI 工作项。"
+ - "**D 类(被删且无替代·需用户反馈)非空且不得发明替代品**:至少覆盖 `IAutotileProcessor.renderWithoutCheck`(消费者 `render/map/renderer.ts:1253`、`render/map/vertex.ts:461,874`,规划日 **3 处**调用点,接口上**已无同名或等价成员**)、`BlockCls` 枚举整体删除(消费者 3 处 import + `material/utils.ts:2` 的 import;`utils.ts` 的 `getClsByString` / `getTextureFrame` 两个 `@deprecated` 函数随之失去类型)、`IAutotileProcessor.manager` 删除、`IAutotileProcessor.render(autotile: number, connection)` 与 `renderWith(tile, connection)` 删除并合并为 `render(tile, connection)`、`ITextureManager.getIdentifierByAlias` / `getAliasByIdentifier` 删除、`IClientBase.autotile` 删除、`createMaterial()` / `create()` 入口函数删除、`material/utils.ts` 的两处 `BlockCls` 依赖(用户自有面)。**每条必须明写「接口已删、AI 未发现替代、请用户反馈」;不得自拟替代、不得写成「应改为 X」。**"
+ - "**E 类(加载相关与添加素材·仅报告)与 in-scope 严格分开**:`client-base/src/load/loader.ts`(加载本身;其 6 条错误属加载系统重构提交既有、非 `d36ea69` 所致,须注明)与 `client-modules/src/fallback/load.ts`(向 `TextureManager` 添加素材;9 条 TS2554 参数个数错误在 `:76,:77,:80,:81,:82,:83,:84,:95,:105`)逐条登记事实;标题下明写「本节仅报告:D-39 / D-41 边界内的内容归用户自行适配,本阶段不修改、不提修改方案」;`packages/` 与 `src/` 另按 assumption 的对照扫描结果登记(规划日 `packages/` 对 texture 记号零命中;`src/types/declaration/*` 仅 legacy 声明)。"
+ - "**F 类(未能从阅读确定·未猜测)非空且只登记不判定**,至少覆盖:① `renderWithoutCheck` 的语义与性能意图能否由新 `render(tile, connection)` 等价承接;② `BlockCls` 删除后消费者应改用 `TileType`(`@user/data-common`)还是 `AutotileType`,以及「自动元件」在 `TileType` 中的取值;③ `hero.ts:167` 构造 `IMaterialFramedData` 时 `tileType` 应填何值(勇士贴图不是地图图块),以及该点是否落入 D-18 的 `HeroRendering` 暂不处理边界;④ `MapRenderer` 自建的 `new AutotileProcessor(manager, manager.state)`(`renderer.ts:234`)与新增的 `ITextureManager.autotile` 是否重复、第二步应复用还是保留自建;⑤ `ITextureManager.textures` / `tileStore` / `tiles`(`ITileStore`)三者的职责边界与地图背景取值应走哪一个;⑥ `textures` 的写入侧(04-07 已记录「本次范围内无写入者」)归属哪一步;⑦ `@user/client-base` 作为整体注册进 legacy 插件系统(`Mota.register('@user/client-base', UserClientBase)`)后,删除 `create()` / `createMaterial()` 是否破坏插件面;⑧ `fallback/load.ts` 的 `extractClsBlocks` 产出 `IBlockIdentifier[]` 而新 `addGrid` / `addRowAnimate` 收 `ArrayLike`,该转换是否属用户加载面(D-41)。**不得把本类任何一条写成错配、缺失或残留。**"
+ - "**第二步(修改工作)不被规划**:文档只清点、分类、定位、分组,**不含**修复方案、不含代码改动建议、不产出 `04-09-PLAN.md` 或任何新 `*-PLAN.md`;`## 处置` 明写「第一步只读清点完成;第二步(TextureManager 新接口适应实施)待用户审阅本文件后另行规划」(D-40)。"
+ - "**REND-01 / REND-02 在本 run 后仍保持 Pending**:本计划只清点 TextureManager 新接口对消费者的影响面,既未实施 texture 接口适应,也未实施移动端 / 桌面端双布局(见 assumptions 的两条 FLAGGED ASSUMPTION)。"
+ - "**验证可运行且不臆造通过**:本计划的「运行结果」= 只读门禁(porcelain 基线逐条一致 + 类型错误**总行数**与记录值一致)+ 静态内容门禁(11 节结构、A–F 六类行数下限与关键词、每行 ≥2 个 `file:line` 锚点、C 类交叉引用 ID、D 类「无替代」措辞计数、E 类只报告口径)+ 符号零命中复验(`MaterialManager` / `renderAnimatedWith` / `getBlockClsByAlias` / `getIdentifierByAlias` / `getAliasByIdentifier` 在 `packages-user` 源码面无引用)+ 既有计划保护门禁(`04-01..04-07-*` 的 porcelain 为空)+ 计划文件范围门禁(不得出现 `04-09+`)+ CRLF 门禁 + 人工代码复核;本步为只读清点,**无运行时行为可观察**,故不得以任何无法运行的门禁冒充通过。"
+ artifacts:
+ - path: .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
+ provides: "`d36ea69` TextureManager 基准在 `packages-user` 消费者面的只读影响台帐:元信息块 + 背景 / 方法 / 基准变更(d36ea69)/ 类型门禁实测基线 / A 符号改名与移除的引用点 / B 成员与签名变化的调用点 / C 地图渲染消费者定位索引 / D 被删且无替代(需用户反馈)/ E 加载相关与添加素材(仅报告)/ F 未能从阅读确定(未猜测)/ 处置;每行含符号名 + 命中处 `file:line` + 基准侧锚点 + 问题描述 + 影响 + 置信,供用户审阅后裁定修复范围并另行规划第二步"
+ key_links:
+ - "`packages-user/client-base/src/material/types.ts`(`d36ea69` 后的权威基准)↔ `packages-user/client-base/src/types.ts:18`(`ITextureManager` 是 `IClientBase.materials` 的唯一公共面):基准新增 `readonly tiles: ITileStore`(`:220`)/ `readonly autotile: IAutotileProcessor`(`:222`)/ `readonly tilesetReserve`(`:241`)/ `readonly tilesetUnit`(`:246`)/ `getFrameCount`(`:336`);`IMaterialFramedData.cls: BlockCls` 改为 `tileType: TileType`(`:79`);`BlockCls` 枚举已从该文件删除;`IAutotileProcessor.render(tile, connection)`(`:148-151`)取代原 `render`/`renderWith`/`renderWithoutCheck`,`renderAnimated`(`:159-162`)取代 `renderAnimatedWith`"
+ - "`packages-user/client-base/src/material/manager.ts:37`(`export class TextureManager implements ITextureManager`,构造器 `:84-89` 收 `(state, tiles, tilesetReserve, tilesetUnit)`)↔ `packages-user/client-modules/src/client.ts:83-88`(`new TextureManager(this, this.tileStore, config.tilesetReserve, config.tilesetUnit)`)↔ `packages-user/client-modules/src/core.ts:6-11`(`tilesetReserve: 100000` / `tilesetUnit: 5000`)↔ `packages-user/client-modules/src/types.ts:8-10`(`IClientCoreConfig` 新增两个必填字段)"
+ - "`packages-user/client-base/src/material/autotile.ts:64`(`export class AutotileProcessor implements IAutotileProcessor`;构造器 `:75` `constructor(readonly state: ICoreState)`,单参)↔ 消费者 `packages-user/client-modules/src/render/map/renderer.ts:234`(`new AutotileProcessor(manager, manager.state)`,实测 TS2554 期望 1 参得 2 参)↔ `packages-user/client-modules/src/client.ts:16`(`AutotileProcessor` 与 `:14` `IAutotileProcessor` 在 `client.autotile` 删除后成为未使用 import)"
+ - "`IMaterialFramedData.cls` → `tileType`(`material/types.ts:79`)↔ 消费者 `packages-user/client-modules/src/render/map/extension/hero.ts:167`(`cls: BlockCls.Unknown` 对象字面量,实测 TS2353)↔ `packages-user/client-modules/src/render/map/renderer.ts:1252,1263`(`tex.cls`,实测 TS2339)↔ `packages-user/client-modules/src/render/map/vertex.ts:517,561,852`(`tile.cls` / `cls` 解构,实测 TS2339)"
+ - "`BlockCls` 枚举删除(原 `packages-user/client-base/src/material/types.ts`)↔ 消费者 import `packages-user/client-modules/src/render/map/extension/hero.ts:15`、`packages-user/client-modules/src/render/map/renderer.ts:10`、`packages-user/client-modules/src/render/map/vertex.ts:27`(三处均实测 TS2305)↔ `packages-user/client-base/src/material/utils.ts:2`(用户自有面内的 `import { BlockCls } from './types'`,实测 TS2305)"
+ - "`IAutotileProcessor.renderWithoutCheck` 已删(`material/types.ts:148-151` 无该成员)↔ 3 处消费者调用 `packages-user/client-modules/src/render/map/renderer.ts:1253`、`packages-user/client-modules/src/render/map/vertex.ts:461`、`packages-user/client-modules/src/render/map/vertex.ts:874`(均实测 TS2339)—— 无替代,属 D 类「需用户反馈」"
+ - "`git status --porcelain -- packages packages-user src`(Task 1 步骤 (0a) 逐行记录)↔ Task 1 / Task 3 门禁的逐条一致断言 ↔ 只读(生产代码零改动)证明"
+ - "`pnpm exec vue-tsc --noEmit` 的错误**总行数**(Task 1 步骤 (0b) 记录、规划日实测 202)↔ Task 3 门禁的重跑一致断言 ↔ 只读证明;texture 归属子集(规划日 23 = `client-base` 1 + `client-modules` 22,其中加载相关 9 + `material/utils.ts` 1 属 E/D 报告面、地图渲染 13 属 C 面)为规划日人工归类、逐行列出,执行日按当日实测重新归类并由人工复核,**不作为机器门禁断言项**"
+ assumptions:
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付 TextureManager 新接口对消费者面的只读清点(D-38 / D-40 第一步)。用户须在推进第二步与其余渲染适配前补齐 REND-01 的边界判据。"
+ - "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局,也不发明其验收阈值。"
+ - "**规划日实测数值与口径须用户在审阅时确认:** 规划日 2026-09-22 在 HEAD `d36ea69` 实测 `pnpm exec vue-tsc --noEmit` 的错误总行数为 **202**;其中**与本次 `d36ea69` texture 接口改动直接相关**的为 **23** 条 —— `packages-user/client-base` **1** 条(`material/utils.ts(2,10)` TS2305,属用户自有面)+ `packages-user/client-modules` **22** 条(`fallback/load.ts` 9、`render/map/extension/hero.ts` 2、`render/map/renderer.ts` 5、`render/map/vertex.ts` 6)。**另有 15 条属加载系统重构(提交 `cc434a6`)的既有结果**(`client-base/src/load/loader.ts` 6 + `client-modules/src/render/ui/load.tsx` 9),**不计入** texture 归属;其余 **164** 条为数据端测试 / 寻路等既有错误。该「texture 归属 / 加载既有 / 其余既有」三分类是**规划日的人工归类**,须用户在审阅时确认;若用户判定某条应改类,执行者按裁定重新归类并同步数字与 `## 处置` 的口径说明,不得改动其它事实。"
+ - "**执行前必须复核(未确定项,不猜):** Task 1 步骤 (0a)/(0b) 必须在动手前打开当日 HEAD 的工作树逐条核对:① `material/types.ts` 的现行成员集合(`tiles` / `autotile` / `textures` / `tilesetReserve` / `tilesetUnit` / `getFrameCount` / `flatten` / `render` / `renderAnimated` / `tileType`)与 `BlockCls` 确不在其中;② `TextureManager` 构造器形参与 `AutotileProcessor` 构造器形参;③ `client-base/src/index.ts` 与 `material/index.ts` 确已无 `create()` / `createMaterial()`;④ A–C 各类命中的**当日行号**(`client.ts` / `core.ts` / `types.ts` / `renderer.ts` / `vertex.ts` / `hero.ts` / `moving.ts` / `map/types.ts`);⑤ `renderWithoutCheck` 的消费者调用点仍为 3 处且接口上确无替代;⑥ `fallback/load.ts` 与 `load/loader.ts` 的命中行号;⑦ `packages/` 与 `src/` 的对照扫描结论;⑧ `pnpm exec vue-tsc --noEmit` 的当日总数与 texture 子集。**任一与规划日数值不同即以当日实测为准**写入文档并在 SUMMARY 中说明差异,不得按本计划的规划日数值硬写(规划日 2026-09-22、HEAD = `d36ea69`)。"
+ - "**用户可能有执行日的并发未提交改动:** 一律以 Task 1 步骤 (0a) 当日实测为基线(规划日实测含 `.planning/phases/04-render-adaptation/04-CONTEXT.md` 的既有未提交改动,须逐字保留),**不得回滚 / 暂存 / 提交 / 修改**用户的任何未提交改动;若基线中出现 `packages` / `packages-user` / `src` 下的条目,属于本次清点范围内的既有改动,须一并记录并原样保留。"
+ - "**D-42 的用户自有面可能在清点过程中继续变化:** `packages-user/client-base/src/material/` 由用户持续重构(`d36ea69` 即为其一次提交),故该文件夹内的命中(规划日已知 `material/utils.ts:2`)以**当日实测**为准;本计划只登记、不修改、不规划。"
+ - "**无运行时验证可得(诚实降级):** 本步为只读清点,不产生任何运行时行为(不启动渲染、不改代码、不新增测试),因此承载体是「只读门禁 + 类型门禁一致性 + 静态内容门禁 + 符号零命中复验 + 人工代码复核」。**不得**以任何无法运行的门禁冒充通过。"
+ - "**`.planning/graphs/*` 是过期工装态:** 其中的接口标签早于 `d36ea69`(2026-09-22);该文件**不得**作为基准或命中依据,只作 E 类登记(是否重建由用户决定,见 F 类)。"
+ prohibitions:
+ - "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理(AGENTS.md 的第一个 block 点约束;不因触发方式或 `yolo` / `auto_advance` 模式而放宽)。"
+ - "不得修改任何生产源码:`packages/**`、`packages-user/**`、`src/**` 的生产文件一律零改动;不得新增 / 删除 / 改写任何 `.ts` / `.tsx` 文件;不得触碰用户 committed 的基准接口(`client-base/src/material/**` 与 `client-base/src/types.ts` 一字不动)。"
+ - "不得在 `packages-user/client-base/src/material/` 文件夹内规划任何修改(D-42:该文件夹已由用户重构完毕,AI 只修改消费者);该文件夹内命中一律只登记、并向用户反馈,不得自拟替代方案。"
+ - "不得为「加载相关」内容提出修改方案:`client-base/src/load/loader.ts` 与 `client-modules/src/fallback/load.ts` 的命中(含 `addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` 的参数个数错误)一律只报告(D-39 / D-41),用户将自行适配新加载系统。"
+ - "不得改写 / 覆盖 / 改号 `04-01`..`04-07` 的既有 `*-PLAN.md` 与 `*-SUMMARY.md`;除本 `04-08-PLAN.md`、`04-TEXTURE-INTERFACE-IMPACT.md` 与 `04-08-SUMMARY.md` 外不得写任何其它文件。"
+ - "不得规划或实施第二步:不得给出修复方案、不得给出代码改动建议、不得产出 `04-09+` 或任何新 `*-PLAN.md`;接口被删且无替代者只登记事实并请用户反馈。"
+ - "不得修改任何既有 jsDoc / 注释文本(AGENTS.md);不得新增公共 / 受保护 / 私有成员(本步零代码改动);不得新增或删除 `// @ts-expect-error` 指令。"
+ - "不得修改 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(当前工作树内有用户未提交改动,须逐字保留、不得回滚 / 暂存 / 提交)。"
+ - "不得把 `packages/` 与 `src/` 的命中写成待修改项、不得为其提出修改建议;该二者只登记事实与核对命令。"
+ - "不得把 A / B / C / D / E 混为一类:A 记符号级(改名 / 移除)引用点、B 记签名级(参数与类型变化)调用点、C 只做定位索引并交叉引用、D 记被删且无替代、E 记加载与添加素材;同一事实若跨类,拆条并互相引用,不得互相冒充。"
+ - "不得把「类型错误」当作「接口设计结论」:`tileType` 应填何值、`renderWithoutCheck` 的替代是什么、自建 `AutotileProcessor` 是否应复用 `ITextureManager.autotile`,一律进 F 类,不得自行裁定、不得写成缺失或残留。"
+ - "不得引入 `core.*` / `client.*` legacy 适配或清退建议(D-12 / D-23);不得为 `render/elements/cache.ts`、`render/elements/misc.ts`、`render/components/textbox.tsx` 的 legacy 路径提出清退计划(属 Phase 5 边界,本次只作对照登记)。"
+ - "不得新增依赖、不得修改 `package.json` / `pnpm-lock.yaml` / `tsconfig.json`;出现安装需求即暂停并告知用户。"
+ - "不得新增、删除或修改任何测试文件与既有测试。"
+ - "不得以「无运行时」为由弱化验证:每条 `` 必须有 ``;确实无法运行的门禁必须显式声明为不可得并降级为静态 + 人工,不得伪造绿。"
+---
+
+
+Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 / D-42 **第一步**)—— **TextureManager 新接口影响范围清点(只读)**。
+
+**要解决的问题:**
+
+1. **贴图存储层被用户重写,消费者未跟上(D-38)**:用户以提交 `d36ea69 refactor: 贴图存储方式` 重构了 texture 存储层——`MaterialManager` 改名并重写为 **`TextureManager`**(构造器改为 `(state, tiles, tilesetReserve, tilesetUnit)`,不再无参)、`client.autotile` / `IClientBase.autotile` 删除、`AutotileProcessor` 重写、`IAutotileProcessor` 的 `render` / `renderWith` / `renderWithoutCheck` 合并为单一 `render(tile, connection)`、`renderAnimatedWith` → `renderAnimated`、`BlockCls` 枚举整体删除且 `IMaterialFramedData.cls` → `tileType: TileType`、barrel 与入口调整(`material/index.ts` 的 `createMaterial()`、`client-base/src/index.ts` 的 `create()` 删除)、新增 logger codes。用户明示「影响面大、**主要在地图渲染部分**」。
+2. **影响面未知,不能直接动手(D-40)**:用户裁定本目标同样走**两步**——先收集(只读清点)再修复;第一步产出只读影响清单文档,第二步待用户审阅后再规划。没有第一手的范围台账就直接改,会按旧形状改错或漏改。
+3. **AI 只改消费者,`material/` 归用户(D-42)**:`packages-user/client-base/src/material/` 已由用户**重构完毕**;AI **只修改消费者**(如地图渲染)。**若某接口被删除且无替代,必须向用户反馈**,不得自行发明替代方案。
+4. **加载相关整体排除(D-39 / D-41)**:「加载本身」以及「向 `TextureManager` 中添加素材的内容」由用户自行适配新加载系统,本任务**不得修改**——须单列报告,让用户清楚哪些是他自己的面。
+5. **范围与边界(D-33 / D-32 沿用)**:范围限 `packages-user`;`packages/` 与 `src/` 命中**仅报告、不修改**;只修复与本次 texture 接口适应相关的项,不追求解决全部类型错误(整仓既有错误大量与本次无关)。
+
+**大致解决方案:** 只做**只读清点**,产出唯一交付物 `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`。文档 11 节:`## 背景` / `## 方法` / `## 基准变更(d36ea69)` / `## 类型门禁实测基线` / `## A 符号改名与移除的引用点` / `## B 成员与签名变化的调用点` / `## C 地图渲染消费者定位索引` / `## D 被删且无替代(需用户反馈)` / `## E 加载相关与添加素材(仅报告)` / `## F 未能从阅读确定(未猜测)` / `## 处置`。A–E 每节一张固定列序的表 `| ID | 符号/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信 |`,**每行至少两个 `file:line` 锚点**(命中处 + 基准侧),一条记录只归一类、跨类拆条并互相引用;F 类逐条登记静态阅读无法定论项(只登记不判定)。
+
+**分列规则(写死,防止类别互相冒充):**
+
+| 类别 | 含义 | 归属规则 |
+|---|---|---|
+| A | **符号级**:改名 / 移除的符号引用点 | `TextureManager`(新名消费者 `client.ts:15,83`)、`MaterialManager` / `renderAnimatedWith` / `getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockCls(ByAlias)` 的**旧名引用点**(规划日多为 0,须给机器证据)、`BlockCls` 消费者 import、`IMaterialFramedData.cls` 消费者读写点、`AutotileProcessor` 构造器调用点、`IClientBase.autotile` 与 `create()` / `createMaterial()` 的消费者 |
+| B | **签名级**:成员在但参数 / 类型变更 | `addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` 的调用点、`ITextureManager` 新增成员(`tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount`)与 `IClientCoreConfig` 新增必填字段的实现 / 传入点 |
+| C | **定位索引**(不新增独立事实) | 第二步将触及的 `file:line` 按文件汇总,逐条交叉引用其 A / B / D 记录 ID |
+| D | **被删且无替代**(需用户反馈) | `renderWithoutCheck`、`BlockCls` 及其 `@deprecated` 依赖(`material/utils.ts`,用户自有面)、`manager`、`renderWith`、`getIdentifierByAlias` / `getAliasByIdentifier`、`IClientBase.autotile`、`create()` / `createMaterial()` |
+| E | 加载相关与添加素材(仅报告) | `client-base/src/load/loader.ts`(加载本身)+ `client-modules/src/fallback/load.ts`(向 `TextureManager` 添加素材)+ `packages/` / `src/` 对照扫描 |
+| F | 未能从阅读确定 | 只登记,不得写成错配 / 缺失 / 残留 |
+
+**规划日实测(A–F 各类命中数为内容门禁下限;类型错误总行数为机器门禁断言值、texture 子集为规划日人工归类;执行日一律以当日实测为准):**
+
+| 类别 | 规划日实测结果(2026-09-22,HEAD `d36ea69`,工作树) |
+|---|---|
+| 基准变更 | 改名 1 类(`MaterialManager`→`TextureManager`);barrel 删除 2 个入口函数;`BlockCls` 枚举删除;`IMaterialFramedData.cls`→`tileType`;`renderAnimatedWith`→`renderAnimated`;`IAutotileProcessor` 三方法合为一;`IClientBase.autotile` 删除;新增 `tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount` / `flatten` / `AutotileType` |
+| A(符号级) | `BlockCls` 3 处 import + `cls` 6 处读写;`AutotileProcessor` 构造器 1 处(`renderer.ts:234`);`TextureManager` 新名 2 处;旧名 `MaterialManager` / `renderAnimatedWith` / `getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockClsByAlias` 源码面 **0** 引用 |
+| B(签名级) | `ITextureManager` 新增 5 个成员 + `IClientCoreConfig` 新增 2 个必填字段;`addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` 的调用点全部落在 `fallback/load.ts`(→ E 类) |
+| C(定位索引) | 8 个文件:`render/map/renderer.ts`、`render/map/vertex.ts`、`render/map/extension/hero.ts`、`render/map/moving.ts`、`render/map/types.ts`、`client-modules/src/client.ts`、`client-modules/src/types.ts`、`client-modules/src/core.ts` |
+| D(被删无替代) | ≥ 6 项:`renderWithoutCheck`(3 调用点)+ `BlockCls` + `manager` + `renderWith` + `getIdentifierByAlias` / `getAliasByIdentifier` + `IClientBase.autotile` + `material/utils.ts` 依赖(用户自有面) |
+| E(加载与添加素材) | `fallback/load.ts` **9** 条 TS2554(`:76,:77,:80,:81,:82,:83,:84,:95,:105`);`client-base/src/load/loader.ts` 加载本身(6 条错误属加载系统重构既有);`packages/` texture 记号 **0** 命中;`src/types/declaration/*` 仅 legacy 声明 |
+| F(未确定) | ≥ 8 项(见 must_haves) |
+| 类型门禁 | `pnpm exec vue-tsc --noEmit` 错误总行数 **202**(texture 归属 **23** = `client-base` 1 + `client-modules` 22;加载系统重构既有 **15**;其余既有 **164**) |
+
+**范围与硬边界:** 被查对象为 `packages-user` 全量(消费者面);基准为 `packages-user/client-base/src/material/types.ts`(+ `manager.ts` / `autotile.ts` / `index.ts`)与 `packages-user/client-base/src/types.ts`(`d36ea69` 后);`packages/` 与 `src/` **仅报告**(D-32 沿用);`material/` 文件夹**只登记、不修改**(D-42);「加载本身 + 向 `TextureManager` 添加素材」**只报告**(D-39 / D-41);接口被删且无替代者**必须请用户反馈**(D-42)。生产源码零改动;本步不规划第二步。
+
+**两步走(D-40):** 本 run = **第一步(只读清点)**;**第二步(TextureManager 新接口适应实施)不在本 run 规划**,待用户审阅本文件后另行规划。
+
+## Source Audit(多源覆盖审计)
+
+| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
+|--------|----|---------------------|------|--------|-------|
+| GOAL | — | Phase 4:「渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局」 | 04-08 | COVERED(增量·texture 只读清点子切片) | 本 run 只产出 TextureManager 新接口的消费者影响台账;其余渲染适配与双布局属后续增量(REND-02 见 assumptions) |
+| REQ | REND-01 | 渲染端适配新数据层接口 | 04-08 | COVERED(子切片·只读;需求保持 Pending) | texture/素材接口(`client-base`)是渲染端素材公共面;本 run 只清点其消费者影响面,不实施适配;验收边界未提供 → assumptions 的 FLAGGED ASSUMPTION |
+| REQ | REND-02 | 渲染端同时支持移动端与桌面端布局 | 04-08 | COVERED(不涉及) | 本 run 范围不含双布局;REND-02 验收边界未提供 → assumptions 的 FLAGGED ASSUMPTION |
+| RESEARCH | — | (本阶段无 `04-RESEARCH.md`) | — | N/A | Phase 4 未产出 RESEARCH.md;等价输入为 04-01 / 04-02 / 04-06 账本与 `d36ea69` 的接口改动 |
+| CONTEXT | D-38 | 用户已完成 TextureManager 相关重构(`d36ea69`),影响面大、主要在地图渲染部分;下一目标 = 适应新接口 | 04-08 | COVERED(第一步) | Task 1/2/3 清点消费者面并突出地图渲染(C 类);实施属第二步(未规划) |
+| CONTEXT | D-39 | 不得修改加载相关的内容(用户自行适配新加载系统) | 04-08 | COVERED(排除) | `load/loader.ts` 与 `fallback/load.ts` 归 E 类只报告;prohibitions 明列不得提出修改方案 |
+| CONTEXT | D-40 | 分两步:先收集(只读清点,产出只读影响清单文档)再修复;第二步待用户审阅后再规划 | 04-08 | COVERED | 只读、唯一交付物、`## 处置` 明写第二步未规划;Task 3 计划范围门禁禁 `04-09+` |
+| CONTEXT | D-41 | 「加载相关」的排除边界:加载本身 + 向 `TextureManager` 添加素材的内容(均由用户自行适配) | 04-08 | COVERED(排除) | E 类标题下明写该边界;`addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` 调用点全部归 E |
+| CONTEXT | D-42 | `client-base/src/material/` 已重构完毕;AI 只修改消费者;接口被删且无替代必须反馈、不得自拟替代 | 04-08 | COVERED | `material/utils.ts:2` 命中归 D 类「用户自有面」;D 类每条明写「无替代、请用户反馈」;prohibitions 禁在 `material/` 内规划修改 |
+| CONTEXT | D-33 | 范围外一律不改;不追求解决全部类型错误,只修与本次接口适应相关的项 | 04-08 | COVERED(边界) | 类型基线单列 texture 归属 23 与既有 179;第二步范围由用户据本文件裁定 |
+| CONTEXT | D-07 | 每条记录精确到接口名 + 所属文件 + 问题描述 + 影响 | 04-08 | COVERED(粒度) | 固定列序 + 每行 ≥2 个 `file:line` 锚点 + 门禁机器判定 |
+| CONTEXT | D-09 | 允许派只读子代理扫描(不得修改任何文件) | 04-08 | COVERED | Task 2 允许只读子代理回传事实表;子代理不得写文件;最终由主执行者门禁统一验证 |
+| CONTEXT | D-12 | `core.*` 属旧引擎适配,归第五阶段 | 04-08 | COVERED(边界) | `render/elements/cache.ts` / `misc.ts` / `textbox.tsx` 的 `core.material.*` 与本地 `autotile` 概念路径只作对照登记,不提出清退计划 |
+| CONTEXT | D-18 | `HeroRendering` 相关暂不处理 | 04-08 | COVERED(边界) | `render/map/extension/hero.ts` 仅作 material 引用点登记;`hero.ts:167` 是否落入 D-18 边界列入 F 类,不裁定 |
+| CONTEXT | D-19 | 移动控制由用户手动修改,AI 暂不处理 | 04-08 | COVERED(排除) | `action/move.ts` 与移动控制不在本计划的命中面枚举;`render/map/moving.ts` 仅因 `IMaterialFramedData` 类型变化被登记 |
+| CONTEXT | D-22 | 跟随者暂缓 | 04-08 | COVERED(排除) | 跟随者族不在本计划范围 |
+| CONTEXT | D-23 | 不使用 `client` / `state` 全局单例(仅约束新增/后续) | 04-08 | COVERED | 本计划零生产改动、不新增代码;`state` 的注入方式不属本步 |
+| CONTEXT | D-01..D-06, D-08, D-10, D-11, D-13..D-17, D-20, D-21, D-24..D-29, D-30..D-37 | 前序决策与已执行增量 | 04-01..04-07 | COVERED(前序计划) | 已由 04-01..04-07 落地;本计划是其后的新增量(D-30..D-37 的产物 `04-MATERIAL-INTERFACE-IMPACT.md` / `04-07-SUMMARY.md` 是本步的版式与基线输入) |
+| CONTEXT | Deferred Ideas | 阶段 4 后续步骤(适配实施、双布局);`packages` 全部、`entry-client`、`legacy-plugin-*`、`data-fallback` | — | DEFERRED | 未写入任何任务;`packages/` 仅作 E 类对照扫描 |
+
+## Estimate Basis(估算依据)
+
+`estimate-calibration` 返回 `factor: 1`、`applied: false`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens` 并如实标注 `confidence: low`。按「执行者实际触及量」估算(`estimate.tasks: 4` = Task 0 汇报关卡 + 三个工作任务):Task 0 只读汇报并等待用户确认(约 5k,不写文件);Task 1(tracer)起始基线(porcelain + `pnpm exec vue-tsc --noEmit` 全量,约 1–3 分钟;规划日实测 202 行)+ 交付文档骨架 + `## 基准变更(d36ea69)` + `## 类型门禁实测基线` + A 类首条端到端切片(约 32k);Task 2 横向补齐 A/B/C/D/E/F 六类(约 30k);Task 3 定稿 + 六道门禁 + 人工复核 + `04-08-SUMMARY.md`(约 12k);固定开销约 3k;合计约 82k。
+
+
+
+@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
+@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/REQUIREMENTS.md
+@.planning/phases/04-render-adaptation/04-CONTEXT.md
+@.planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md
+@.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md
+@.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
+@.planning/phases/04-render-adaptation/04-06-PLAN.md
+@.planning/phases/04-render-adaptation/04-07-PLAN.md
+@.planning/phases/04-render-adaptation/04-07-SUMMARY.md
+@.planning/phases/04-render-adaptation/04-PATTERNS.md
+@AGENTS.md
+@dev.md
+
+
+
+
+
+ Task 0: 预执行汇报关卡 —— 汇报 TextureManager 新接口影响清点(D-38/D-40 第一步)的范围、基准、A–F 六类分列规则与唯一交付物、D-39/D-41/D-42 三条硬边界与只读证明方式,等待用户回复「可以执行」
+ 用户对 TextureManager 新接口适应的裁定 D-38 / D-39 / D-40 / D-41 / D-42 已写入 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(以文件内实际存在 D-38..D-42 为准);`d36ea69 refactor: 贴图存储方式` 已在当日 HEAD(`git rev-parse HEAD` 以 `d36ea69` 开头)
+ (只读汇报,不修改任何文件)
+
+ - AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行;「如果发现任何问题,永远不要擅自将其视为问题并记录,必须经由我确认」;「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-38 / D-39 / D-40 / D-41 / D-42 裁定原文;D-33 范围、D-07 粒度、D-09 子代理、D-12 / D-18 / D-19 / D-22 / D-23 边界;Deferred Ideas)
+ - .planning/phases/04-render-adaptation/04-07-SUMMARY.md(前一个已执行增量:material 接口适应实施的落点与「第二步已完成、REND 仍 Pending」的声明;本步是其后的 texture 新接口增量)
+ - .planning/phases/04-render-adaptation/04-08-PLAN.md(本计划全文:objective 的三类差异与分列规则 / tasks / 禁令 / 门禁)
+ - packages-user/client-base/src/material/types.ts(基准:`:12-17` `AutotileType`、`:75-86` `IMaterialFramedData.tileType`、`:94-163` `IAutotileProcessor`、`:165-215` `ITextureGetter` / `ITextureAliasGetter`、`:217-404` `ITextureManager`(`:220` `tiles`、`:222` `autotile`、`:241` `tilesetReserve`、`:246` `tilesetUnit`、`:336` `getFrameCount`))
+ - packages-user/client-base/src/material/manager.ts:37-94(`export class TextureManager implements ITextureManager` 与构造器 `(state, tiles, tilesetReserve, tilesetUnit)`)
+ - packages-user/client-base/src/material/autotile.ts:64(`export class AutotileProcessor implements IAutotileProcessor`)
+ - packages-user/client-base/src/index.ts 与 packages-user/client-base/src/material/index.ts(barrel:`create()` / `createMaterial()` 已删;`export * from './utils'` 仍在)
+
+
+ **停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
+
+ ① **要解决的问题(D-38 / D-39 / D-40 / D-41 / D-42)**:用户以 `d36ea69 refactor: 贴图存储方式` 重构了贴图存储层——`MaterialManager` 改名并重写为 **`TextureManager`**(构造器从无参改为 `(state, tiles, tilesetReserve, tilesetUnit)`)、`client.autotile` 与 `IClientBase.autotile` 删除、`AutotileProcessor` 重写、`IAutotileProcessor` 的 `render` / `renderWith` / `renderWithoutCheck` 合并为单一 `render(tile, connection)`、`renderAnimatedWith` → `renderAnimated`、`BlockCls` 枚举整体删除且 `IMaterialFramedData.cls` → `tileType: TileType`、barrel 与入口调整(`createMaterial()` / `create()` 删除)、新增 logger codes。用户明示**影响面大、主要在地图渲染部分**,故裁定**分两步**:第一步只读清点影响范围(本 run),第二步再规划修改。用户并明确三条硬边界:**加载相关不得修改**(加载本身 + 向 `TextureManager` 添加素材,用户自行适配新加载系统);**`material/` 已重构完毕、AI 只改消费者**;**接口被删且无替代必须向用户反馈,不得自拟替代**。
+
+ ② **`d36ea69` 基准的三类差异(须逐条汇报)**:
+ - **改名 1 类**:`MaterialManager` → `TextureManager`(`material/manager.ts:37`),且构造器改为 `(state, tiles, tilesetReserve, tilesetUnit)`(`manager.ts:84-89`)。
+ - **移除**:`BlockCls` 枚举整体删除(消费者 `packages-user/client-modules/src/render/map/extension/hero.ts:15` / `render/map/renderer.ts:10` / `render/map/vertex.ts:27` 三处 import,规划日实测均报 TS2305)、`IMaterialFramedData.cls` 改为 `tileType`、`IAutotileProcessor.renderWithoutCheck` / `renderWith` / `render(autotile: number, connection)` 三者合并为 `render(tile, connection)`、`IAutotileProcessor.manager` 删除、`ITextureManager.getIdentifierByAlias` / `getAliasByIdentifier` 删除、`ITextureGetter.getBlockCls` / `ITextureAliasGetter.getBlockClsByAlias` 删除、`IClientBase.autotile` 删除、`material/index.ts` 的 `createMaterial()` 与 `client-base/src/index.ts` 的 `create()` 删除。
+ - **新增 / 收紧**:`ITextureManager` 新增 `readonly tiles: ITileStore`(`types.ts:220`)与 `readonly autotile: IAutotileProcessor`(`:222`)、`readonly tilesetReserve`(`:241`)/ `readonly tilesetUnit`(`:246`)/ `getFrameCount(num)`(`:336`);`addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` 四者参数收紧(分别新增 `width`+`height` / `width` / `type: AutotileType` / `cellWidth`+`cellHeight`);`IAutotileProcessor.flatten(source, type, frames)` 新增;`renderAnimatedWith` → `renderAnimated`;`IClientCoreConfig` 新增 `tilesetReserve` / `tilesetUnit` 两个必填字段。
+
+ ③ **规划日实测的影响面(A–F 各类命中数为内容门禁下限;类型错误总行数为机器门禁断言值、texture 子集为规划日人工归类)**:`pnpm exec vue-tsc --noEmit` 当前 **202** 条错误,其中 **23** 条由本次 texture 接口改动直接造成(`client-base` 1 条 = `material/utils.ts(2,10)`;`client-modules` 22 条 = `fallback/load.ts` 9 + `render/map/extension/hero.ts` 2 + `render/map/renderer.ts` 5 + `render/map/vertex.ts` 6);另有 **15** 条属加载系统重构(`cc434a6`)既有(`load/loader.ts` 6 + `render/ui/load.tsx` 9),**不算**本次改动;其余 **164** 条为数据端测试 / 寻路等既有错误(该划分口径须用户确认)。具体:A 类符号级——`BlockCls` 3 处 import + `cls` 6 处读写 + `AutotileProcessor` 构造器 1 处;旧名 `MaterialManager` / `renderAnimatedWith` / `getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockClsByAlias` 在源码面 **0** 引用(须给机器证据)。B 类签名级——`ITextureManager` 新增 5 个成员 + `IClientCoreConfig` 新增 2 个必填字段;四个 `add*` 方法的调用点全部落在 `fallback/load.ts`(归 E 类只报告)。C 类定位索引——8 个文件:`render/map/renderer.ts`、`render/map/vertex.ts`、`render/map/extension/hero.ts`、`render/map/moving.ts`、`render/map/types.ts`、`client-modules/src/client.ts`、`client-modules/src/types.ts`、`client-modules/src/core.ts`。D 类被删且无替代——`renderWithoutCheck`(`renderer.ts:1253`、`vertex.ts:461,874` 共 3 处调用点,接口上已无同名或等价成员)+ `BlockCls` + `manager` + `renderWith` + `getIdentifierByAlias` / `getAliasByIdentifier` + `IClientBase.autotile` + `material/utils.ts` 的两处 `BlockCls` 依赖(用户自有面)。
+
+ ④ **大致解决方案**:只读清点,唯一交付物 `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`;11 节结构(`背景` / `方法` / `基准变更(d36ea69)` / `类型门禁实测基线` / `A 符号改名与移除的引用点` / `B 成员与签名变化的调用点` / `C 地图渲染消费者定位索引` / `D 被删且无替代(需用户反馈)` / `E 加载相关与添加素材(仅报告)` / `F 未能从阅读确定(未猜测)` / `处置`);A–E 每节固定列序表(`ID | 符号/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信`),每行 ≥2 个 `file:line` 锚点;**分列规则**:A = 符号级、B = 签名级、C = 定位索引(交叉引用 A/B/D 的 ID)、D = 被删且无替代(每条明写「请用户反馈」)、E = 加载与添加素材(仅报告);F 类只登记不判定。重型整文件读取可按 **D-09** 派**只读**子代理(仅回传事实表,不得写文件)。
+
+ ⑤ **范围与硬边界**:被查 = `packages-user` 全量消费者面;基准 = `packages-user/client-base/src/material/{types,manager,autotile,index}.ts` + `client-base/src/types.ts`(`d36ea69` 后);`packages/` 与 `src/` 命中**仅报告、不修改**;**D-42**:`packages-user/client-base/src/material/` 只登记、不规划任何修改;**D-39 / D-41**:`client-base/src/load/loader.ts`(加载本身)与 `client-modules/src/fallback/load.ts`(向 `TextureManager` 添加素材)单列 E 节只报告。**生产源码零改动**;唯一写入为上述交付文档(外加本 PLAN 与 `04-08-SUMMARY.md` 两份簿记)。
+
+ ⑥ **计划号与 wave(须原样告知)**:本计划为 **04-08**(第七个增量),`wave: 7`,`depends_on` 列 `04-01`..`04-07`;phase 目录内既有 `04-01`..`04-07` 的 `*-PLAN.md` 与 `*-SUMMARY.md` **一律不得改写 / 覆盖 / 改号**。
+
+ ⑦ **验证方式与只读证明(须原样告知)**:本步为只读清点,**无运行时行为可观察**。证明方式 = ①**只读门禁**:`git status --porcelain -- packages packages-user src` 在 Task 1 起始逐行记录(规划日实测集合为空),Task 1 / Task 3 断言其后逐条一致;②**类型门禁一致性**:`pnpm exec vue-tsc --noEmit` 在 Task 1 起始记录**错误总行数**与 texture 子集两个数字(规划日 202 / 23),Task 3 重跑**只断言错误总行数**与记录值一致;texture / 加载既有 / 其余既有的划分属规划日人工归类(口径须用户确认),执行日按实测重新归类并逐行列出,由人工复核保证,**不作为机器门禁**;③**静态内容门禁**:11 节结构、A–F 行数下限与关键词、每行 ≥2 个 `file:line` 锚点、C 类交叉引用 ID 齐备、D 类「无替代·请用户反馈」措辞齐备、E 类只报告口径;④**符号零命中复验**:`MaterialManager` / `renderAnimatedWith` / `getBlockClsByAlias` / `getIdentifierByAlias` / `getAliasByIdentifier` 在 `packages-user` 源码面无引用;⑤**既有计划保护 + 计划范围门禁**:`04-01`..`04-07` 的 `*-PLAN.md` / `*-SUMMARY.md` porcelain 为空;phase 目录内 `-PLAN.md` 仅 `04-01`..`04-08`;⑥**CRLF 门禁**;⑦**人工代码复核**。**不冒充任何无法运行的门禁。**
+
+ ⑧ **第二步不被规划**:本 run **不含**修复方案、不含代码改动建议、不产出 `04-09+`;第二步(TextureManager 新接口适应实施)待用户审阅本清点文件后另行规划(D-40)。
+
+ ⑨ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `mode: yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
+
+ ⑩ **失败语义(等同硬门禁,非自动命令)**:用户未回复「可以执行」即继续,或回复的是其它任何内容(含修订意见、沉默、`yolo` / `auto_advance` 模式下的自动推进、或子代理/执行器的自行放行),一律视为**未通过本关卡**:必须立即停止执行,不派发任何执行子代理,保持只读、不写任何文件,直到用户在对话中明确回复「可以执行」四个字后方可继续。本条只以本 `` 与 `` 表达,不写成 XML 失败声明元素(人工关卡没有可运行的自动命令可与之绑定)。
+
+ ⑪ **若用户给出修订意见**(含不同意 A–F 分列规则 / 不同意 texture 与「加载既有 / 其余既有」的划分口径 / 不同意范围或基准 / 希望把加载面纳入本步):退出本计划、修订 `04-08-PLAN.md` 后重新汇报,不得自行改道执行。
+
+ 是否按本计划(TextureManager 新接口影响范围只读清点:唯一交付物 `04-TEXTURE-INTERFACE-IMPACT.md`,11 节 / A–F 六类,范围限 `packages-user` 消费者面,`packages/` 与 `src/` 仅报告,`material/` 与「加载相关」按 D-39 / D-41 / D-42 只报告,生产代码零改动,第二步不规划)开始执行
+
+ AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认;本计划虽是只读清点,仍不得例外。`.planning/config.json` 为 `mode: yolo`(execute-plan 默认自动推进),因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`,否则计划层面不提供任何 block 点。此外本次清点存在三条会改变范围归属的用户裁定(D-39 / D-41 加载排除、D-42 `material/` 归用户),必须在关卡中先行澄清,避免 AI 把用户自有的面当成自己的工作范围。
+
+
+
+
+
+
+ 回复「可以执行」四个字(或给出修订意见)
+
+ 用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无自动命令,失败语义写在 Task 0 的 action ⑩ 与 acceptance_criteria 中)
+
+
+ - 用户回复中明确包含「可以执行」四个字
+ - 汇报内容涵盖:要解决的问题(D-38 / D-40 第一步)、`d36ea69` 的三类差异(改名 1 类 / 移除:`BlockCls` / `cls`→`tileType` / 三方法合一 / 两个别名查找方法与 barrel 入口 / 新增:`tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount` / 四个 `add*` 参数收紧 / `IClientCoreConfig` 两个必填字段)、规划日实测的影响面(202 总错误与 23 条 texture 归属,及 A–F 六类具体命中)、大致解决方案(11 节结构 + 固定列序 + A/B/C/D/E 分列规则 + F 只登记不判定)、范围与硬边界(`packages-user` 消费者面、`packages/` 与 `src/` 仅报告、`material/` 与加载面只报告、生产代码零改动)、唯一交付物与两步走
+ - 汇报中已原样告知 **D-39 / D-41**(加载本身 + 向 `TextureManager` 添加素材 → E 类只报告、用户自理)与 **D-42**(`material/` 归用户、接口被删无替代必须反馈)
+ - 汇报中已原样告知**编号与 wave**(04-08 / wave 7 / `depends_on` 04-01..04-07)以及既有 `04-01`..`04-07` 产物不得改写 / 覆盖 / 改号
+ - 汇报中已原样告知**验证方式与只读证明**(只读门禁 / 类型门禁一致性 / 静态内容门禁 / 符号零命中复验 / 既有计划保护与计划范围门禁 / CRLF / 人工复核),并声明无运行时行为可观察
+ - 汇报中已原样告知**关卡性质**:执行者/子代理必须停下等待,不因触发方式或 `yolo` / `auto_advance` 模式自动放行
+
+ 用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
+
+
+
+ Task 1(tracer):交付文档骨架 + 一条端到端清点切片 —— 起始基线(porcelain + `pnpm exec vue-tsc --noEmit`)+ 11 节结构 + `## 基准变更(d36ea69)` 三类差异定稿 + `## 类型门禁实测基线` 实测数字 + A 类 `#04-08-A-01` 双锚点实测,结构 / CRLF / 只读门禁转绿
+ Task 0 已闭环:用户在对话中明确回复「可以执行」四个字(未收到则不得开始本任务)
+ .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
+
+ - .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md:1-50(前进量的元信息块、`## 背景`、`## 方法`、固定列序表与「只读起始基线」的既有写法,可直接对齐版式)
+ - .planning/phases/04-render-adaptation/04-06-PLAN.md(前进量的 objective / tasks / 门禁结构,可对齐版式与证据纪律)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-38 / D-39 / D-40 / D-41 / D-42 原文;D-33 范围;D-07 粒度;D-09 子代理;D-12 / D-18 / D-19 / D-22 / D-23 边界)
+ - .planning/phases/04-render-adaptation/04-08-PLAN.md(本计划全文:objective 的三类差异表与分列规则、tasks、禁令、门禁)
+ - packages-user/client-base/src/material/types.ts(整文件:`:12-17` `AutotileType`、`:75-86` `IMaterialFramedData`(`tileType: TileType`)、`:94-163` `IAutotileProcessor`(`:101-105` `flatten`、`:148-151` `render`、`:159-162` `renderAnimated`)、`:165-189` `ITextureGetter`、`:191-215` `ITextureAliasGetter`、`:217-404` `ITextureManager`;确认 `BlockCls` 不在其中)
+ - packages-user/client-base/src/material/manager.ts:37-94(`export class TextureManager implements ITextureManager`;构造器 `(readonly state, readonly tiles, readonly tilesetReserve, readonly tilesetUnit)`;`:40` `textures`)
+ - packages-user/client-base/src/material/autotile.ts:64(`export class AutotileProcessor implements IAutotileProcessor` 与构造器签名)
+ - packages-user/client-base/src/index.ts 与 packages-user/client-base/src/material/index.ts(barrel:`create()` / `createMaterial()` 确已删除;`export * from './utils'` 仍在,`utils.ts` 依赖 `BlockCls`)
+ - packages-user/client-base/src/types.ts:1-32(`IClientBase.materials` 是唯一公共面;`autotile` 已删)
+ - packages-user/client-modules/src/client.ts:12-19,45-95,125(`TextureManager` 构造点与 `AutotileProcessor` / `IAutotileProcessor` 未使用 import)
+ - packages-user/client-modules/src/types.ts:1-12 与 packages-user/client-modules/src/core.ts:1-11(`IClientCoreConfig` 新增必填字段与单例传入值)
+ - packages-user/client-modules/src/render/map/renderer.ts:8-15,217,234(A 类首条切片与构造点)
+ - dev.md(「项目结构」的单向依赖;「命名规则」;「其他要求」的 CRLF 要求)
+
+
+ (0a) **起始基线快照(只读前置,先做这一步)**:执行 `git status --porcelain -- packages packages-user src`,把输出**原样逐行**记为「只读起始基线」写入 `## 方法`:先写 bullet `- **只读起始基线(packages / packages-user / src)**:`,其后逐行抄录输出(每条一行,行首为两字符状态码;输出为空则写 `(无)`)。规划日 2026-09-22 实测该输出为**空**(工作树内唯一的未提交改动是 `.planning/phases/04-render-adaptation/04-CONTEXT.md`,不在该路径集合内)。**不得回滚、暂存、提交或修改**任何既有改动;若当日实测出现 `packages` / `packages-user` / `src` 下的条目,逐条记录并原样保留(属本次清点范围内的既有改动,需在 `## 方法` 注明)。同时记下 `git rev-parse HEAD` 的当日值写入元信息块(规划日为 `d36ea69`)。
+
+ (0b) **类型门禁实测基线(只读前置,与 (0a) 同批完成)**:执行 `pnpm exec vue-tsc --noEmit`,把完整输出的**错误总行数**与**texture 归属错误行数**两个数字写入 `## 类型门禁实测基线`:bullet 逐字写 `- **错误总行数**:` 与 `- **texture 归属错误行数**:`,其中 N = `node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出值(与 Task 3 门禁使用**同一正则**,保证可逐字比对)。规划日 2026-09-22 实测 **N = 202**、**M = 23**(texture 23 = `client-base` 1 条 `material/utils.ts(2,10)` TS2305 + `client-modules` 22 条:`fallback/load.ts` 9 条 TS2554(`:76,:77,:80,:81,:82,:83,:84,:95,:105`)、`render/map/extension/hero.ts` 2 条(`:15` TS2305、`:167` TS2353)、`render/map/renderer.ts` 5 条(`:10` TS2305、`:234` TS2554、`:1252` / `:1263` TS2339、`:1253` TS2339)、`render/map/vertex.ts` 6 条(`:27` TS2305、`:461` / `:874` TS2339、`:517` / `:561` / `:852` TS2339))。同时在该节**逐行列出全部 texture 归属错误**(`file:line` + `error TSxxxx` + 原文摘要),并把所述三分类**单列**:① texture 归属 **23**;② 加载系统重构(提交 `cc434a6`)既有 **15**(`client-base/src/load/loader.ts` 6 条 TS2305 + `client-modules/src/render/ui/load.tsx` 9 条 TS2339);③ 其余既有 **164**(`data-base` / `data-state` / `data-system` 的测试与寻路等)。**明写「texture / 加载既有 / 其余既有」的划分口径是规划日的人工归类、须用户确认**(见 assumptions)。**以当日实测为准**,与规划日数值不同即按实测写入并在 `## 处置` 说明差异,不得按规划日数值硬写。
+
+ (1) **新建交付文档** `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`,先写标题与 6 行元信息块:`**日期:**`(当日日期);`**范围:**` 被查对象 `packages-user` 全量消费者面;`packages/` 与 `src/` 命中仅报告、不修改(D-32 沿用);**`packages-user/client-base/src/material/` 按 D-42 只登记、不修改**;`**性质:**` 只读清点(D-38 / D-40 第一步),未修改任何生产代码;`**接口基准:**` `packages-user/client-base/src/material/{types,manager,autotile,index}.ts` 与 `packages-user/client-base/src/types.ts`(`d36ea69` 后);`**基准提交:**` `d36ea69 refactor: 贴图存储方式`(当日工作树 HEAD);`**下一步:**` 第二步(TextureManager 新接口适应实施)未规划,待用户审阅本文件后另行规划(D-40)。**全文使用 CRLF 换行**(dev.md「换行使用 CRLF 格式」);若写入工具产出 LF,用 `node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md';fs.writeFileSync(p,fs.readFileSync(p,'utf8').replace(/\r?\n/g,'\r\n'))"` 统一转换后落盘;Task 2 / Task 3 追加内容同样保持 CRLF,不得出现孤立 LF。
+
+ (2) 写 `## 背景`:D-38(用户已完成 TextureManager 重构 `d36ea69`,影响面大、主要在地图渲染部分;下一目标 = 适应新接口)、D-39(不得修改加载相关内容,用户自行适配新加载系统)、D-40(分两步,本步只读清点,产出只读影响清单;第二步待用户审阅后再规划)、D-41(「加载相关」排除边界 = 加载本身 + 向 `TextureManager` 添加素材的内容)、D-42(`material/` 已由用户重构完毕,AI 只修改消费者;接口被删且无替代必须向用户反馈、不得自拟替代)。并沿用 D-33 的范围约束(范围外一律不改、不追求解决全部类型错误、只清点与本次 texture 接口适应相关的项)。明写本步**只清点、不修改、不规划第二步**。
+
+ (3) 写 `## 方法`((0a) 的「只读起始基线」bullet 也在本节):枚举两侧面 ——(a)**基准面** = `packages-user/client-base/src/material/types.ts`(`ITextureManager` / `ITextureGetter` / `ITextureAliasGetter` / `IAutotileProcessor` / `IMaterialFramedData` / `AutotileType` 等现行成员)+ `manager.ts`(`TextureManager` 构造器)+ `autotile.ts`(`AutotileProcessor` 构造器)+ `index.ts` / `client-base/src/index.ts`(barrel)+ `client-base/src/types.ts`(`IClientBase.materials`)+ `client-modules/src/types.ts`(`IClientCoreConfig`);(b)**命中面** = `packages-user` 全量(`client-base` / `client-modules` / `client-system` / `entry-*` / `legacy-plugin-*` / `data-fallback`),另对 `packages/` 与 `src/` 做**对照扫描**(只登记、不修改)。判定口径固定为 **A 符号改名与移除的引用点 / B 成员与签名变化的调用点 / C 地图渲染消费者定位索引 / D 被删且无替代(需用户反馈)/ E 加载相关与添加素材(仅报告)/ F 未能从阅读确定** 六类,**一条记录只归一类**,跨类拆条并互相引用;**分列规则写死**:A 记「符号级(旧名 / 被删符号的消费者引用)」,B 记「签名级(成员在但参数 / 类型变化的消费者调用)」,C 记「第二步将触及的定位汇总(不新增独立事实,逐条交叉引用 A / B / D 的 ID)」,D 记「接口已删且 AI 未发现替代(每行明写需用户反馈)」,E 记「加载本身 + 向 `TextureManager` 添加素材(D-39 / D-41 归用户)」,F 记「静态阅读不能定论」。证据纪律:每行含 `符号/成员名 + 命中处 file:line + 基准侧锚点 file:line + 问题描述 + 影响 + 置信`,**每行至少两个锚点**;静态阅读不能定论者一律进 F 类,**不得写成错配 / 缺失 / 残留**。**D-42 纪律**:命中 `packages-user/client-base/src/material/` 内的记录一律归 D 类并标注「用户自有面」,不得写成 AI 工作项。允许按 **D-09** 派**只读**子代理做重型整文件扫描(子代理只回传事实表「符号 / 成员名 + 所属 `file:line` + 形态或签名」,**不得写文件、不得贴大段源码**),最终由主执行者按 `file:line` 切片复核。同时记录 (0b) 的类型门禁命令与两个数字的口径(正则 `error TS\d+` 的匹配数)。
+
+ (4) 补齐 11 个二级标题(`## 背景` / `## 方法` / `## 基准变更(d36ea69)` / `## 类型门禁实测基线` 已在前几步写入,此处补齐其余 7 个占位,其后 Task 2 在同一骨架上横向补齐,不再改结构):`## A 符号改名与移除的引用点` / `## B 成员与签名变化的调用点` / `## C 地图渲染消费者定位索引` / `## D 被删且无替代(需用户反馈)` / `## E 加载相关与添加素材(仅报告)` / `## F 未能从阅读确定(未猜测)` / `## 处置`。**每个类别标题下先写一行记录编号模板**(供 Task 2 横向补齐时对齐):A 类 `#04-08-A-NN`、B 类 `#04-08-B-NN`、C 类 `#04-08-C-NN`、D 类 `#04-08-D-NN`、E 类 `#04-08-E-NN`,行形如 `| #04-08-X-NN | 符号/成员名 | 命中处 file:line | 基准侧锚点 file:line | 问题描述 | 影响 | 置信 |`;F 类条目形如 `- **#04-08-F-NN**:现象 + 为什么读不出来 + 需要用户裁决的点`。模板行用 `NN` 占位、不含数字,故不计入任何行数门禁。
+
+ (5) **`## 基准变更(d36ea69)` 定稿(本任务的核心横向切片)**:以 `git show d36ea69 --stat` 与 `git show d36ea69 -- packages-user/client-base/src/material/types.ts packages-user/client-base/src/index.ts packages-user/client-base/src/material/index.ts packages-user/client-base/src/types.ts packages-user/client-modules/src/{client.ts,core.ts,types.ts}` 为依据,并与当日工作树**逐条比对**后写入三类差异:① **改名** —— `MaterialManager` → `TextureManager`(现 `material/manager.ts:37`),并记录构造器由无参改为 `(state, tiles, tilesetReserve, tilesetUnit)`(`manager.ts:84-89`)与 `AutotileProcessor` 构造器(`autotile.ts:64`);② **移除** —— `BlockCls` 枚举整体删除(附「现行 `material/types.ts` 成员集合不含」的锚点,如 `:75-86` 的 `IMaterialFramedData` 现为 `tileType: TileType`)、`IMaterialFramedData.cls` → `tileType`、`IAutotileProcessor` 的 `render(autotile: number, connection)` / `renderWith` / `renderWithoutCheck` 三者合并为 `render(tile: IMaterialFramedData, connection)`(`:148-151`)、`IAutotileProcessor.manager` 删除、`renderAnimatedWith` → `renderAnimated`(`:159-162`)、`ITextureManager.getIdentifierByAlias` / `getAliasByIdentifier` 删除、`ITextureGetter.getBlockCls` / `ITextureAliasGetter.getBlockClsByAlias` 删除、`IClientBase.autotile` 删除(`client-base/src/types.ts` 现仅 `:18` `materials`)、`material/index.ts` 的 `createMaterial()` 与 `client-base/src/index.ts` 的 `create()` 删除;③ **新增 / 收紧** —— `ITextureManager` 新增 `readonly tiles: ITileStore`(`:220`)/ `readonly autotile: IAutotileProcessor`(`:222`)/ `readonly tilesetReserve`(`:241`)/ `readonly tilesetUnit`(`:246`)/ `getFrameCount`(`:336`),`addGrid`(`:261-266`)/ `addRowAnimate`(`:275-280`)/ `addAutotile`(`:289-293`)/ `addTileset`(`:302-307`)四者参数收紧,`IAutotileProcessor.flatten`(`:101-105`)新增,`AutotileType`(`:12-17`)新增,`IClientCoreConfig` 新增 `tilesetReserve` / `tilesetUnit`(`client-modules/src/types.ts:8-10`)。另记录同提交内的**相邻变更**(登记,不作本步裁定):`client-modules/src/core.ts` 单例改传 config(`tilesetReserve: 100000` / `tilesetUnit: 5000`)、`entry-client/src/create.ts` 删除 `UserClientBase.create()` 调用、`packages/common/src/logger.json` 新增 8 行。**不得**修改任何被引用文件。
+
+ (6) **A 类首条切片(本任务唯一实测清点行)**:在 `## A 符号改名与移除的引用点` 下先写固定列序表头 `| ID | 符号/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信 |`,再写 `#04-08-A-01` 一行:符号 = `AutotileProcessor`(导出类,构造器改为单参 `constructor(readonly state: ICoreState)`;**请先打开 `autotile.ts:75` 的构造器逐字核对后在文档中写明形参**);命中处 = `packages-user/client-modules/src/render/map/renderer.ts:234`(`new AutotileProcessor(manager, manager.state)`)与 `packages-user/client-modules/src/client.ts:16`(`AutotileProcessor` 在 `client.autotile` 删除后成为未使用 import);基准侧锚点 = `packages-user/client-base/src/material/autotile.ts:75`(`constructor(readonly state: ICoreState)`)与 `packages-user/client-base/src/material/manager.ts:90`(`this.autotile = new AutotileProcessor(state)`);问题描述 = 消费者仍按旧形参个数构造(传 2 参),且 `client.ts` 的两处 import 已无使用点;影响 = 编译报 TS2554(`renderer.ts:234`,期望 1 参得 2 参)与潜在未使用 import 的 lint 诊断;置信 = 高;依据 = 上述锚点 + 实测错误行。同一行内**不得**出现修复建议或替代写法(替代是否成立属 F 类)。同时在 `## 类型门禁实测基线` 中确证该错误行已逐字列出。
+
+ (7) 写 `## 处置` 初稿:本步只读、未改任何生产代码、`files_modified` 仅本文件;相对 (0a) 记录的只读起始基线零变化;本步**不规划第二步**(D-40),第二步(TextureManager 新接口适应实施)待用户审阅本文件后另行规划;范围限 `packages-user` 消费者面、`packages/` 与 `src/` 仅报告(D-33 沿用);`material/` 与「加载相关」按 D-39 / D-41 / D-42 只报告并归用户;接口被删且无替代者一律请用户反馈(D-42);`core.*` legacy 适配(D-12)与移动控制(D-19)、跟随者(D-22)、`HeroRendering` 语义(D-18)不属本步;`REND-01` / `REND-02` 保持 Pending。
+
+ (8) **本任务只写该交付文档一个文件**;不得改动任何被查源码(含用户既有的未提交改动),不得派发会写文件的子代理,不得新增计划文件。
+
+ 交付文档是只读清点的记录,可随时修订或废弃;它不引入生产代码、公共接口或契约约束,因此这一步的任何取舍都不难回退。
+
+ node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u57fa\u51c6\u53d8\u66f4','## \u7c7b\u578b\u95e8\u7981\u5b9e\u6d4b\u57fa\u7ebf','## A \u7b26\u53f7\u6539\u540d','## B \u6210\u5458\u4e0e\u7b7e\u540d','## C \u5730\u56fe\u6e32\u67d3\u6d88\u8d39\u8005','## D \u88ab\u5220\u4e14\u65e0\u66ff\u4ee3','## E \u52a0\u8f7d\u76f8\u5173','## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length+' :: '+miss.join(' ; '));process.exit(1)}if(t.indexOf('\u53ea\u8bfb\u8d77\u59cb\u57fa\u7ebf')<0){console.error('MISSING READONLY BASELINE');process.exit(1)}if(!/d36ea69/.test(t)){console.error('MISSING BASELINE COMMIT');process.exit(1)}console.log('OK headings '+need.length)"
+ non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "MISSING READONLY BASELINE" / "MISSING BASELINE COMMIT"(11 个二级标题未齐备,或 `## 方法` 未记录只读起始基线,或未登记基准提交 `d36ea69`)
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const bs=seg('## \u57fa\u51c6\u53d8\u66f4');const need=['MaterialManager','TextureManager','BlockCls','tileType','renderAnimatedWith','renderAnimated','renderWithoutCheck','renderWith','getIdentifierByAlias','getAliasByIdentifier','getBlockCls','IClientBase','tiles','tilesetReserve','tilesetUnit','getFrameCount','addGrid','addRowAnimate','addAutotile','addTileset','AutotileType','createMaterial','IClientCoreConfig'];const miss=need.filter(k=>bs.indexOf(k)<0);if(miss.length>0){console.error('BASELINE SECTION MISSING '+miss.join('|'));process.exit(1)}if(!/[A-Za-z0-9_\-\/]+\.tsx?:\d+/.test(bs)){console.error('BASELINE SECTION WITHOUT ANCHOR');process.exit(1)}console.log('OK baseline tokens '+need.length)"
+ non-zero exit, 或 stderr 含 "BASELINE SECTION MISSING" / "BASELINE SECTION WITHOUT ANCHOR"(`## 基准变更(d36ea69)` 未覆盖改名 / 移除 / 新增收紧三类差异的关键符号,或该节无任何 `file:line` 锚点)
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const a=t.match(/\u9519\u8bef\u603b\u884c\u6570\uff1a\s*(\d+)/);const b=t.match(/texture \u5f52\u5c5e\u9519\u8bef\u884c\u6570\uff1a\s*(\d+)/);if(!a||!b){console.error('MISSING TSC BASELINE FIGURES');process.exit(1)}if(Number(b[1])<1||Number(b[1])>Number(a[1])){console.error('TSC BASELINE FIGURES INCONSISTENT '+a[1]+' '+b[1]);process.exit(1)}const n=(t.match(/error TS\d+/g)||[]).length;if(n<10){console.error('TOO FEW TEXTURE ERROR ANCHORS '+n);process.exit(1)}console.log('OK tsc total '+a[1]+' texture '+b[1]+' listed '+n)"
+ non-zero exit, 或 stderr 含 "MISSING TSC BASELINE FIGURES" / "TSC BASELINE FIGURES INCONSISTENT" / "TOO FEW TEXTURE ERROR ANCHORS"(未记录错误总行数与 texture 归属行数,或两数自相矛盾,或未逐行列出 texture 归属错误)
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-08-A-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO A ROWS');process.exit(1)}const bad=rows.filter(l=>((l.match(/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g)||[]).length<2));if(bad.length>0){console.error('A ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}console.log('OK A rows '+rows.length)"
+ non-zero exit, 或 stderr 含 "NO A ROWS" / "A ROW MISSING TWO-SIDED ANCHOR"(A 类未写端到端切片行,或该行缺命中处 / 基准侧锚点之一)
+ node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行,违反 dev.md「换行使用 CRLF 格式」)
+ node -e "const fs=require('fs'),{execSync}=require('child_process');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');if(ms.indexOf('\u53ea\u8bfb\u8d77\u59cb\u57fa\u7ebf')<0){console.error('READONLY BASELINE NOT RECORDED');process.exit(1)}const rec=(ms.match(/^[ MARCUD?!]{2} (?:packages|packages-user|src)[^\r\n]*/gm)||[]).map(s=>s.replace(/\s+$/,''));let raw='';try{raw=execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})}catch(e){console.error('GIT STATUS FAILED '+(e.message||''));process.exit(1)}const out=raw.replace(/\s+$/,'');const cur=out.length>0?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('READONLY BASELINE CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}console.log('OK read-only baseline verbatim entries '+cur.length)"
+ non-zero exit, 或 stderr 含 "READONLY BASELINE NOT RECORDED" / "READONLY BASELINE CHANGED"(`## 方法` 未把起始 `git status --porcelain -- packages packages-user src` 输出逐行原样记录,或记录内容与当前状态不一致 —— 违反只读约束)
+
+
+ - 交付文档存在于 `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`,11 个二级标题逐字存在(`## 背景` / `## 方法` / `## 基准变更(d36ea69)` / `## 类型门禁实测基线` / `## A 符号改名与移除的引用点` / `## B 成员与签名变化的调用点` / `## C 地图渲染消费者定位索引` / `## D 被删且无替代(需用户反馈)` / `## E 加载相关与添加素材(仅报告)` / `## F 未能从阅读确定(未猜测)` / `## 处置`)
+ - 元信息块 6 行齐备(日期 / 范围 / 性质 / 接口基准 / 基准提交 / 下一步),且「范围」明写限 `packages-user` 消费者面、`packages/` 与 `src/` 仅报告、`material/` 按 D-42 只登记
+ - `## 方法` 含「只读起始基线」条目,**逐行原样**记录起始 `git status --porcelain -- packages packages-user src` 的输出(规划日为空 → 写「(无)」);门禁断言记录条目与当前状态逐条一致
+ - `## 方法` 写死 A/B/C/D/E/F 六类判定口径与分列规则(A 符号级 / B 签名级 / C 定位索引并交叉引用 / D 被删无替代须反馈 / E 加载与添加素材只报告 / F 只登记不判定),并写明每行须含命中处与基准侧两个 `file:line` 锚点
+ - `## 基准变更(d36ea69)` 覆盖三类差异:改名(`MaterialManager`→`TextureManager` 与构造器)、移除(`BlockCls` / `cls`→`tileType` / 三方法合一 / `renderAnimatedWith` / 两个别名查找方法 / `getBlockCls(ByAlias)` / `IClientBase.autotile` / `create()` 与 `createMaterial()`)、新增收紧(`tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount` / 四个 `add*` 参数 / `flatten` / `AutotileType` / `IClientCoreConfig`),并登记同提交相邻变更(`core.ts` config、`entry-client/src/create.ts`、`logger.json`)
+ - `## 类型门禁实测基线` 逐字记录 `- **错误总行数**:` 与 `- **texture 归属错误行数**:`(规划日 202 / 23,执行日以实测为准),逐行列出全部 texture 归属错误,并把「加载系统重构既有 15」与「其余既有 164」**单列**,明写该划分口径「须用户确认」
+ - A 类含固定列序表头与至少 1 条 `#04-08-A-*` 行,该行含命中处与基准侧两个 `file:line` 锚点,且行内无修复建议
+ - 相对记录在 `## 方法` 的只读起始基线零变化(porcelain 条目逐条一致);未修改任何生产源码;未新增计划文件;全文 CRLF
+
+ 交付文档骨架(11 节)+ `## 基准变更(d36ea69)` 三类差异 + `## 类型门禁实测基线` 实测数字(202 / 23 及三分类单列)+ A 类 `#04-08-A-01` 端到端双锚点切片全部落位;结构 / 基准关键词 / 类型基线 / A 行双锚点 / CRLF / 只读基线六道门禁转绿;Task 2 只需在同一模板上横向补齐
+
+
+
+ Task 2:A–F 六类横向补齐 —— 符号级引用点与旧名零命中证据 / 签名级调用点 / 地图渲染定位索引(交叉引用)/ 被删且无替代逐条反馈 / 加载与添加素材仅报告 / 未确定项只登记
+ .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
+
+ - .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md(Task 1 的产出:在同一骨架上横向补齐,**不改结构、不改元信息块、不改已定稿的 `## 基准变更` 与 `## 类型门禁实测基线` 数字**)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-38 / D-39 / D-40 / D-41 / D-42 原文;D-33 范围;D-07 粒度;D-09 子代理;D-12 / D-18 / D-19 / D-22 / D-23 边界)
+ - packages-user/client-base/src/material/types.ts(基准侧锚点:`:75-86` / `:94-163` / `:165-215` / `:217-404`;`BlockCls` 确不在其中)
+ - packages-user/client-base/src/material/manager.ts:37-94(`TextureManager` 类与构造器;`:90` `new AutotileProcessor(state)`)
+ - packages-user/client-base/src/material/autotile.ts:64 与 packages-user/client-base/src/material/utils.ts:1-53(用户自有面:`AutotileProcessor` 类与 `utils.ts` 依赖 `BlockCls` 的两个 `@deprecated` 函数)
+ - packages-user/client-base/src/index.ts 与 packages-user/client-base/src/material/index.ts(barrel 现状)
+ - packages-user/client-base/src/types.ts:1-32(`IClientBase.materials`,`autotile` 已删)
+ - packages-user/client-modules/src/client.ts:1-136(`:14` / `:16` 未使用 import、`:50` `materials`、`:83-88` 构造点、`:93-96` `fallbackLoad` 接线、`:125` `new MapRenderer(this.materials)`)
+ - packages-user/client-modules/src/types.ts:1-20 与 packages-user/client-modules/src/core.ts:1-11(`IClientCoreConfig` 必填字段与单例值)
+ - packages-user/client-modules/src/render/map/renderer.ts:8-15,59-72,217,234,1243-1271,1638-1651(A/B/C 类:`BlockCls` import、`IAutotileProcessor` 属性、构造点、`useTileBackground` 的 `tex.cls` 与 `renderWithoutCheck` / `renderAnimated`、`addMovingBlock` 签名)
+ - packages-user/client-modules/src/render/map/vertex.ts:24-30,440-479,510-528,537-582,846-880(A/B/C 类:`BlockCls` import、`renderWithoutCheck` 两处、`tile.cls` 三处、`cls` 解构)
+ - packages-user/client-modules/src/render/map/extension/hero.ts:12-18,150-173,235-250,355-375,420-435(A/C 类:`BlockCls` import、`IMaterialFramedData` 构造、`getTile` 三处已适配点)
+ - packages-user/client-modules/src/render/map/moving.ts:1-110(`IMaterialFramedData` 消费者;`cls` 字段不再存在的影响面)
+ - packages-user/client-modules/src/render/map/types.ts:1-14,195-230,320-345,525-545(`IAutotileProcessor` / `ITextureManager` / `IMaterialFramedData` 接口面)
+ - packages-user/client-modules/src/fallback/load.ts:1-137(E 类:向 `TextureManager` 添加素材的 9 处参数个数错误)
+ - packages-user/client-base/src/load/loader.ts:1-30,53-60,123-160(E 类:加载本身;其 6 条错误属加载系统重构既有)
+ - packages-user/client-base/package.json(依赖声明面,仅登记现状)
+ - packages-user/entry-client/src/create.ts:1-42 与 packages-user/entry-data/src/mota.ts:1-40(`@user/client-base` 整体注册进 legacy 插件系统的面;`UserClientBase.create()` 调用已删)
+ - src/types/declaration/{core.d.ts,enemy.d.ts,map.d.ts,status.d.ts}(E 类:legacy 声明与 `core.getBlockCls`,仅报告)
+ - dev.md(「项目结构」单向依赖;「双端分离」)
+
+
+ (1) **A 类补齐(符号改名与移除的引用点)**:按固定列序表逐条写满 —— ① `TextureManager`(新名)的消费者:`client-modules/src/client.ts:15`(import)、`:83`(构造点);② `MaterialManager`(旧名)在 `packages-user` 源码面的**引用点 0 处**,并给出核对命令 `git grep -n -c MaterialManager -- packages-user` 的原样输出(无输出即 0);③ `renderAnimatedWith`(旧名)**引用点 0 处**(已适配点为 `render/map/renderer.ts:1264` 的 `this.autotile.renderAnimated(tex, 0b1111_1111)`,须写清「旧名 0、新名 1」);④ `IMaterialFramedData.cls` → `tileType` 的消费者读写点:`render/map/extension/hero.ts:167`(对象字面量 `cls: BlockCls.Unknown`)、`render/map/renderer.ts:1252` / `:1263`(`tex.cls`)、`render/map/vertex.ts:517` / `:561` / `:852`(`tile.cls` 与 `const { cls, ... } = block.texture`),基准侧锚点统一引用 `packages-user/client-base/src/material/types.ts:75-86`(现行成员为 `texture` / `tileType` / `frames` / `offset` / `defaultFrame`);⑤ `BlockCls` 枚举被删除的消费者 import:`render/map/extension/hero.ts:15`、`render/map/renderer.ts:10`、`render/map/vertex.ts:27`,基准侧锚点 = `material/types.ts`(该文件现行导出集合不含 `BlockCls`);⑥ `AutotileProcessor` 构造点:`render/map/renderer.ts:234` 与 `client.ts:16`(未使用 import),基准侧锚点 = `material/autotile.ts:64`;⑦ `IClientBase.autotile` 删除的消费者面:`packages-user/client-base/src/types.ts`(现行仅 `materials`,锚点 `:18`)与 `client-modules/src/client.ts:14` / `:16`(两处 import 失去使用点);⑧ `ITextureManager.getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockCls(ByAlias)` 的消费者**引用点 0 处**,各附 `git grep -n` 原样输出(无输出即 0);⑨ `material/index.ts` 的 `createMaterial()` 与 `client-base/src/index.ts` 的 `create()` 删除后的消费者:`entry-client/src/create.ts:26` 仍整体注册 `@user/client-base` 命名空间(该命名空间内 `create` 已不在),须写清「包内调用点 0(原调用点在 `entry-client/src/create.ts` 已随提交删除)」并附 `git grep -n -E "createMaterial|UserClientBase\.create" -- packages-user src` 的原样输出。**行内不得出现修复建议或替代写法**(替代是否成立属 F 类)。
+ (2) **B 类补齐(成员与签名变化的调用点)**:逐条写 —— ① `ITextureManager` 新增 `readonly tiles: ITileStore`(基准 `material/types.ts:220`)、`readonly autotile: IAutotileProcessor`(`:222`)、`readonly tilesetReserve`(`:241`)、`readonly tilesetUnit`(`:246`)、`getFrameCount`(`:336`)的实现 / 传入点:`material/manager.ts:37-44,84-89`(用户自有面,只登记)与 `client-modules/src/client.ts:83-88`;② `IClientCoreConfig` 新增 `tilesetReserve` / `tilesetUnit` 两个必填字段(基准 `client-modules/src/types.ts:8-10`),传入点 `client-modules/src/core.ts:6-11`(`100000` / `5000`)与消费点 `client-modules/src/client.ts:62,86-87`;③ `addGrid`(基准 `material/types.ts:261-266`,现收 `ArrayLike` + `width` + `height`)、`addRowAnimate`(`:275-280`,现收 `width`)、`addAutotile`(`:289-293`,现收 `identifier: number` + `type: AutotileType`)、`addTileset`(`:302-307`,现收 `cellWidth` + `cellHeight`)四个方法的调用点**全部落在** `client-modules/src/fallback/load.ts`(`:76,:77,:80,:81,:82,:83,:84,:95,:105`),须写明「按 D-39 / D-41 归 E 类只报告,不属第二步 AI 工作项」并交叉引用对应 E 行;④ `IAutotileProcessor.flatten`(基准 `:101-105`)消费者:`material/manager.ts:456`(用户自有面,只登记);`getFrameCount` 消费者 0 处(须给机器证据)。**每行须含基准侧声明 `file:line` + 命中侧调用 `file:line`。**
+ (3) **C 类补齐(地图渲染消费者定位索引)**:逐条登记第二步将触及的定位点,每条给 `file:line` 并**交叉引用其 A / B / D 记录 ID**(本类不新增独立事实)——`render/map/renderer.ts`(`:10`、`:234`、`:1252`、`:1253`、`:1263`)、`render/map/vertex.ts`(`:27`、`:461`、`:517`、`:561`、`:852`、`:874`)、`render/map/extension/hero.ts`(`:15`、`:167`)、`render/map/moving.ts`(`:3`、`:31`、`:75`、`:103`:`IMaterialFramedData` 消费者,`cls` 字段不再存在的影响面)、`render/map/types.ts`(`:8-12`、`:204`、`:217`、`:329`、`:334`、`:536`)、`client-modules/src/client.ts`(`:14`、`:16`、`:83-88`、`:125`)、`client-modules/src/types.ts`(`:8-10`)、`client-modules/src/core.ts`(`:6-11`)。**C 行不得把 E 类(加载相关)或 `material/` 内(D 类的用户自有面)的定位点写成 AI 工作项**;C 行须显式注明「第二步是否纳入由用户据本文件裁定」。
+ (4) **D 类补齐(被删且无替代·需用户反馈)**:逐条写「接口成员 / 命中处 / 基准侧锚点 / 现象 / 为什么认为无替代 / 请用户反馈的问题」——① `IAutotileProcessor.renderWithoutCheck`(原接口成员,现 `material/types.ts:148-151` 只有 `render(tile, connection)`):消费者 `render/map/renderer.ts:1253`、`render/map/vertex.ts:461`、`render/map/vertex.ts:874`(**3 处**;须写明 `vertex.ts:460` 附近既有注释表达了「使用不带检查的版本可以减少分支数量、提升性能」的意图,故不能仅凭签名判断 `render` 可等价承接);② `BlockCls` 枚举整体删除:消费者 3 处 import(A 类已登记)+ `material/utils.ts:2`(**用户自有面**,该文件 `getClsByString` / `getTextureFrame` 两个函数均带 `@deprecated` 标注并强依赖 `BlockCls`);③ `IAutotileProcessor.manager` 删除:基准 `material/types.ts:94-163` 无该成员,消费者引用点 0 处(须给机器证据);④ `IAutotileProcessor.render(autotile: number, connection)` 与 `renderWith(tile, connection)` 删除并合并入 `render(tile, connection)`:消费者引用点 0 处(须给机器证据);⑤ `ITextureManager.getIdentifierByAlias` / `getAliasByIdentifier` 删除:消费者引用点 0 处;⑥ `IClientBase.autotile` 删除:消费者 `client-modules/src/client.ts:14,16`(未使用 import)与 `MapRenderer` 自建 processor 的关系(交叉引用 F 类第 ④ 条);⑦ `createMaterial()` / `create()` 入口函数删除:包内调用点 0,但 `@user/client-base` 被整体注册进 legacy 插件系统(`entry-client/src/create.ts:26`),插件面是否仍调用须用户裁决(交叉引用 F 类第 ⑦ 条);⑧ `material/utils.ts` 的两处 `BlockCls` 依赖(用户自有面,与 ② 交叉引用)。**每条必须明写「接口已删、AI 未发现替代、请用户反馈」;不得自拟替代、不得写成「应改为 X」。**
+ (5) **E 类补齐(加载相关与添加素材·仅报告)**:标题下明写 `**本节仅报告:D-39 / D-41 边界内的内容归用户自行适配新加载系统,本阶段不修改、不提修改方案。**` 逐条登记 —— ① `client-modules/src/fallback/load.ts` 的 9 条 TS2554(`:76` / `:77` `addGrid` 期望 4 参得 2 参;`:80` / `:81` / `:82` / `:83` / `:84` `addRowAnimate` 期望 4 参得 3 参;`:95` `addAutotile` 期望 3 参得 2 参;`:105` `addTileset` 期望 4 参得 2 参),并登记其 `extractClsBlocks`(`:9-29`)产出 `IBlockIdentifier[]` 而新 `addGrid` / `addRowAnimate` 收 `ArrayLike` 的事实(是否属用户加载面进 F 类);② `packages-user/client-base/src/load/loader.ts` 的加载本身(`:25` / `:58` 使用 `ITextureManager`、`:77-78` `imageStore.addTexture` / `alias`、`:104` `addImage`、`:151` `core.material.images.autotile`、`:270-287` autotile 加载流程),并**明写其 6 条 TS2305(`:17`-`:21`、`:28`)属加载系统重构提交既有、与 `d36ea69` 无关**;③ 接线点 `client-modules/src/client.ts:93-96`(`loading.once('loaded', () => { fallbackLoad(this.materials); ... })`);④ `packages/` 的**对照扫描**结论与核对命令 `git grep -n -i -E "bigImage|BlockCls|IMaterialManager" -- packages` 的原样输出(规划日为空);⑤ `src/types/declaration/{core.d.ts:1286,enemy.d.ts:65,map.d.ts:354,map.d.ts:1006,map.d.ts:1443,status.d.ts:387-391,status.d.ts:602}` 的 legacy 声明(`bigImage` 字段与 `core.getBlockCls`,与渲染端 `ITextureManager` 无调用关系);⑥ `packages-user/client-base/package.json` 的依赖声明现状(只登记事实)。全节**不得**出现针对 `src/` 与 `packages/` 的修改性措辞。
+ (6) **F 类(未能从阅读确定·未猜测)**:每条以 `- **#04-08-F-NN**:` 开头,写「现象 + 为什么读不出来 + 需要用户裁决的点」,至少覆盖 must_haves 列出的 8 个方向:① `renderWithoutCheck` 的语义与性能意图能否由新 `render(tile, connection)` 等价承接;② `BlockCls` 删除后消费者应改用 `TileType`(`@user/data-common`)还是 `AutotileType`,以及「自动元件」在 `TileType` 中的取值;③ `hero.ts:167` 构造 `IMaterialFramedData` 时 `tileType` 应填何值(勇士贴图不是地图图块),以及该点是否落入 D-18 的 `HeroRendering` 暂不处理边界;④ `MapRenderer` 自建的 `new AutotileProcessor(manager, manager.state)`(`renderer.ts:234`)与新增的 `ITextureManager.autotile`(基准 `material/types.ts:222`)是否重复、第二步应复用还是保留自建(并交叉引用 D 类第 ⑥ 条);⑤ `ITextureManager.textures` / `tileStore` / `tiles`(`ITileStore`)三者的职责边界与地图背景取值应走哪一个;⑥ `textures` 的写入侧(04-07 已记录「本次范围内无写入者」)归属哪一步;⑦ 删除 `create()` / `createMaterial()` 是否破坏 legacy 插件面(`Mota.register('@user/client-base', UserClientBase)`);⑧ `fallback/load.ts` 的 `IBlockIdentifier[]` → `ArrayLike` 转换是否属用户加载面(D-41)。**不得**把本节任何一条写成错配、缺失或残留。
+ (7) **重型整文件读取可按 D-09 委派只读子代理**(`material/types.ts`、`material/manager.ts`、`material/autotile.ts`、`material/utils.ts`、`client.ts`、`renderer.ts`、`vertex.ts`、`hero.ts`、`fallback/load.ts`):子代理只回传事实表(每行:符号 / 成员名 + 所属 `file:line` + 形态或签名),**不得写文件、不得贴大段源码**;主执行者按 `file:line` 切片复核后落笔。
+ (8) **只修改该交付文档**;不得改动任何生产源码、不得新增计划文件、不得修改 Task 1 已定稿的数字与结构。
+
+
+
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const A=seg('## A \u7b26\u53f7\u6539\u540d'),B=seg('## B \u6210\u5458\u4e0e\u7b7e\u540d'),C=seg('## C \u5730\u56fe\u6e32\u67d3\u6d88\u8d39\u8005'),D=seg('## D \u88ab\u5220\u4e14\u65e0\u66ff\u4ee3'),E=seg('## E \u52a0\u8f7d\u76f8\u5173'),F=seg('## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a');const cnt=(s,re)=>(s.match(re)||[]).length;const rows=s=>cnt(s,/^\|\s*#04-08-[ABCDE]-\d+\s*\|/gm);const anch=l=>cnt(l,/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g);const p=[];if(!A||!B||!C||!D||!E||!F)p.push('MISSING SECTION');if(rows(A)<5)p.push('A ROWS '+rows(A));if(rows(B)<4)p.push('B ROWS '+rows(B));if(rows(C)<8)p.push('C ROWS '+rows(C));if(rows(D)<6)p.push('D ROWS '+rows(D));if(rows(E)<4)p.push('E ROWS '+rows(E));if(cnt(F,/^- \*\*#04-08-F-\d+/gm)<8)p.push('F ITEMS '+cnt(F,/^- \*\*#04-08-F-\d+/gm));for(const pr of [['A',A],['B',B],['C',C],['D',D],['E',E]]){const bad=pr[1].split(/\r?\n/).filter(l=>/^\|\s*#04-08-[ABCDE]-\d+\s*\|/.test(l)).filter(l=>anch(l)<2);if(bad.length>0)p.push(pr[0]+' ROWS WITHOUT TWO-SIDED ANCHOR '+bad.length)}const needA=['TextureManager','MaterialManager','renderAnimatedWith','BlockCls','tileType','AutotileProcessor','IClientBase','getIdentifierByAlias','getAliasByIdentifier','createMaterial'];const mA=needA.filter(k=>A.indexOf(k)<0);if(mA.length>0)p.push('A MISSING '+mA.join('|'));const needB=['tiles','autotile','tilesetReserve','tilesetUnit','getFrameCount','addGrid','addRowAnimate','addAutotile','addTileset','AutotileType','IClientCoreConfig'];const mB=needB.filter(k=>B.indexOf(k)<0);if(mB.length>0)p.push('B MISSING '+mB.join('|'));const needC=['renderer.ts','vertex.ts','hero.ts','moving.ts','client.ts','types.ts','core.ts','#04-08-'];const mC=needC.filter(k=>C.indexOf(k)<0);if(mC.length>0)p.push('C MISSING '+mC.join('|'));const needD=['renderWithoutCheck','BlockCls','manager','renderWith','getIdentifierByAlias','getAliasByIdentifier','utils.ts'];const mD=needD.filter(k=>D.indexOf(k)<0);if(mD.length>0)p.push('D MISSING '+mD.join('|'));if(cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g)<6)p.push('D FEEDBACK PHRASE '+cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g));const needE=['fallback/load.ts','loader.ts','packages','src/types/declaration'];const mE=needE.filter(k=>E.indexOf(k)<0);if(mE.length>0)p.push('E MISSING '+mE.join('|'));if(p.length>0){console.error('CATEGORY GATE FAILED :: '+p.join(' ;; '));process.exit(1)}console.log('OK categories A'+rows(A)+' B'+rows(B)+' C'+rows(C)+' D'+rows(D)+' E'+rows(E)+' F'+cnt(F,/^- \*\*#04-08-F-\d+/gm))"
+ non-zero exit, 或 stderr 含 "CATEGORY GATE FAILED"(A/B/C/D/E 任一类行数低于下限、任一行缺命中处或基准侧锚点、A 未覆盖符号级关键词、B 未覆盖新增成员与签名、C 未覆盖 8 个定位文件或未交叉引用 ID、D 未覆盖关键被删成员或缺「请用户反馈」措辞、E 未覆盖加载文件与对照扫描、F 少于 8 条)
+ node -e "const{execSync}=require('child_process');const names=['MaterialManager','renderAnimatedWith','getBlockClsByAlias','getIdentifierByAlias','getAliasByIdentifier'];const bad=[];for(const n of names){let o='',st=0;try{o=execSync('git grep -n -- '+n+' -- packages-user',{encoding:'utf8',stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'');st=e.status}const lines=o.replace(/\s+$/,'').length>0?o.replace(/\s+$/,'').split(/\r?\n/):[];if(lines.length>0){bad.push(n+' => '+lines.slice(0,3).join(' ; '))}else if(st!==0&&st!==1){bad.push(n+' grep status '+st)}}if(bad.length>0){console.error('STALE SYMBOL STILL REFERENCED: '+bad.join(' || '));process.exit(1)}console.log('OK zero stale symbol references ('+names.length+' names)')"
+ non-zero exit, 或 stderr 含 "STALE SYMBOL STILL REFERENCED"(`MaterialManager` / `renderAnimatedWith` / `getBlockClsByAlias` / `getIdentifierByAlias` / `getAliasByIdentifier` 在 `packages-user` 源码面仍存在引用,即 A 类的「0 引用」结论为臆造)
+ node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(追加内容未保持 CRLF)
+
+
+ - A 类 **≥ 5 行**,覆盖 `TextureManager`(新名)与 `MaterialManager` / `renderAnimatedWith` / `getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockClsByAlias` 的「0 引用」机器证据、`BlockCls` 3 处 import、`cls` 6 处读写、`AutotileProcessor` 构造点、`IClientBase.autotile` 与 `create()` / `createMaterial()` 的消费者面;行内无修复建议
+ - B 类 **≥ 4 行**,覆盖 `ITextureManager` 新增 `tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount`、`IClientCoreConfig` 新增两个必填字段、四个 `add*` 方法的调用点(并注明归 E 类只报告);每行含基准侧声明与命中侧调用两个锚点
+ - C 类 **≥ 8 行**,覆盖 `render/map/renderer.ts` / `render/map/vertex.ts` / `render/map/extension/hero.ts` / `render/map/moving.ts` / `render/map/types.ts` / `client-modules/src/client.ts` / `client-modules/src/types.ts` / `client-modules/src/core.ts`,每条带 `file:line` 并交叉引用其 A / B / D 记录 ID;未把 E 类或 `material/` 内命中写成 AI 工作项
+ - D 类 **≥ 6 行**,逐条覆盖 `renderWithoutCheck`(含 3 处调用点与性能意图说明)、`BlockCls`、`IAutotileProcessor.manager`、`renderWith`、`getIdentifierByAlias` / `getAliasByIdentifier`、`IClientBase.autotile`、`createMaterial()` / `create()`、`material/utils.ts` 的用户自有面;多条含「请用户反馈」措辞;**无任何自拟替代或「应改为 X」**
+ - E 类 **≥ 4 行**,覆盖 `fallback/load.ts`(9 条 TS2554 逐一列出)、`client-base/src/load/loader.ts`(并注明其 6 条错误属加载系统重构既有、与 `d36ea69` 无关)、`client.ts:93-96` 接线、`packages/` 零命中(附命令与结论)、`src/types/declaration/*` legacy 声明;节内明写「仅报告,不修改、不提建议」
+ - F 类 **≥ 8 条** `#04-08-F-*` 未确定项,覆盖 8 个既定方向,且均未写成错配 / 缺失 / 残留
+ - 所有 A–E 行至少含命中处与基准侧两个 `file:line` 锚点;相对记录在 `## 方法` 的只读起始基线零变化;未修改任何生产源码;全文 CRLF
+
+ A–F 六类横向补齐:符号级引用点与旧名零命中机器证据、签名级调用点、地图渲染定位索引(交叉引用)、被删且无替代逐条反馈、加载与添加素材仅报告、未确定项只登记;类别行数 / 双锚点 / 关键词 / 符号零命中 / CRLF 门禁转绿
+
+
+
+ Task 3:`## 处置` 定稿 + 只读/类型/类别/符号零命中/既有计划保护与计划范围/CRLF 六道门禁 + 人工复核 + `04-08-SUMMARY.md` 落盘与提交
+ .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md, .planning/phases/04-render-adaptation/04-08-SUMMARY.md
+
+ - .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md(Task 1/2 产出,做定稿而非重写)
+ - .planning/phases/04-render-adaptation/04-CONTEXT.md(D-38..D-42;Deferred Ideas)
+ - .planning/phases/04-render-adaptation/04-07-SUMMARY.md:1-50,130-200(既有 SUMMARY 的元信息块、「执行前核对」「门禁输出摘要」「人工复核」「运行时 UAT 不可得声明」的写法,可对齐版式)
+ - .planning/phases/04-render-adaptation/04-MATERIAL-INTERFACE-IMPACT.md:250-261(既有清点文档的 `## 处置` 写法)
+ - .planning/phases/04-render-adaptation/04-08-PLAN.md(本计划:门禁与禁令)
+ - dev.md(「其他要求」的 CRLF 要求;「双端分离」)
+
+
+ (1) **`## 处置` 定稿**:明写 —— 本文件是 TextureManager 新接口适应(D-38 / D-40)**第一步(只读清点)**的交付物;本步**未修改任何生产代码**、`files_modified` 仅本文件;相对 `## 方法` 记录的只读起始基线零变化;**第二步(TextureManager 新接口适应实施)未规划**,待用户审阅本文件后另行规划(D-40);范围限 `packages-user` 消费者面,`packages/` 与 `src/` 仅报告(D-33 沿用);A / B / C / D / E / F 六类中:**A / B 是事实层、C 是定位索引(第二步的候选工作集,最终由用户裁定)**,**D 类须用户反馈后才可能进入第二步**,**E 类归用户自行适配新加载系统(D-39 / D-41)**,**`material/` 内命中归用户(D-42)**,F 类须用户裁决;`tileType` 应填何值、`renderWithoutCheck` 的替代、自建 `AutotileProcessor` 是否复用一律由用户裁决(不得由 AI 裁定);`core.*` legacy 适配(D-12)与移动控制(D-19)、跟随者(D-22)、`HeroRendering` 语义(D-18)不属本步;`REND-01` / `REND-02` 保持 Pending;若当日实测数值与规划日(202 / 23)不同,在本节说明差异与原因。
+
+ (2) **运行六道门禁并按实测结果如实记录**:① 只读门禁(`git status --porcelain -- packages packages-user src` 与 `## 方法` 记录逐条一致);② 类型门禁一致性(重跑 `node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"`,输出必须等于文档记录的**错误总行数**);③ 类别门禁(A ≥ 5 / B ≥ 4 / C ≥ 8 / D ≥ 6 / E ≥ 4 / F ≥ 8、每行双锚点、A 的符号关键词、B 的新成员关键词、C 的 8 个定位文件与交叉引用 ID、D 的「请用户反馈」措辞、E 的加载文件与对照扫描);④ 符号零命中复验(`MaterialManager` / `renderAnimatedWith` / `getBlockClsByAlias` / `getIdentifierByAlias` / `getAliasByIdentifier` 在 `packages-user` 源码面仍为 0);⑤ 既有计划保护 + 计划范围门禁(`04-01`..`04-07` 的 `*-PLAN.md` / `*-SUMMARY.md` 的 porcelain 为空;phase 目录内 `-PLAN.md` 仅允许 `04-01`..`04-08`;`04-08-SUMMARY.md` 已落盘);⑥ CRLF 门禁。任一门禁失败即停下修正文档后重跑,**不得**改门禁口径或伪造通过。
+
+ (3) **人工代码复核(不可自动化,如实记录结论)**:抽查 A 类中 `renderer.ts:10` / `hero.ts:15` / `vertex.ts:27` 三处 `BlockCls` import、`hero.ts:167` 的 `IMaterialFramedData` 对象字面量、`vertex.ts:852` 的 `cls` 解构、D 类中 `renderWithoutCheck` 的三处调用(`renderer.ts:1253`、`vertex.ts:461`、`vertex.ts:874`)与 `material/utils.ts:2` 的用户自有面命中、E 类中 `fallback/load.ts` 的 9 处参数个数错误,逐条打开当日源码确认「命中处锚点指向的确实是该符号 / 成员的引用」「基准侧锚点指向的确实是该接口的现行声明行」,并把复核结论(通过 / 发现偏差及修正)写入 SUMMARY。发现偏差即回到文档修正。
+
+ (4) **写 `04-08-SUMMARY.md`**:按执行日实际结果写(元信息块:phase / plan / subsystem / tags / requires / provides / affects / tech-stack / key-files / key-decisions / actuals / metrics),并逐条记录:本 run 的实际产出(唯一交付文档 + 本 PLAN + 本 SUMMARY)、A–F 六类的实际行数与关键结论(旧名 0 引用、`BlockCls` 消费者 3 处 import、`renderWithoutCheck` 3 处调用点无替代、E 类 9 条参数个数错误归用户)、`git status --porcelain -- packages packages-user src` 与 `pnpm exec vue-tsc --noEmit` 的当日实测值(与规划日差异须说明)、六道门禁的实际结果、人工复核结论、与规划日数值的差异说明、以及 `REND-01` / `REND-02` 仍为 Pending 的声明与「第二步未规划」。**不得**把未运行的检查写成通过;**不得**把任何被删接口的替代方案写进 SUMMARY(替代由用户裁决)。
+
+ (5) **提交**:以 `docs(04-08): TextureManager 新接口影响范围清点(只读,D-38/D-40 第一步)` 提交**仅** `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`、`.planning/phases/04-render-adaptation/04-08-PLAN.md` 与 `.planning/phases/04-render-adaptation/04-08-SUMMARY.md` 三个文件;**不得**把任何生产源码改动或用户既有的未提交改动纳入本次提交(尤其不得提交 `.planning/phases/04-render-adaptation/04-CONTEXT.md` 的既有未提交改动),不得改写 / 覆盖 / 改号 `04-01`..`04-07` 的既有产物。
+
+
+ node -e "const fs=require('fs'),{execSync}=require('child_process');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');if(ms.indexOf('\u53ea\u8bfb\u8d77\u59cb\u57fa\u7ebf')<0){console.error('READONLY BASELINE NOT RECORDED');process.exit(1)}const rec=(ms.match(/^[ MARCUD?!]{2} (?:packages|packages-user|src)[^\r\n]*/gm)||[]).map(s=>s.replace(/\s+$/,''));let raw='';try{raw=execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})}catch(e){console.error('GIT STATUS FAILED '+(e.message||''));process.exit(1)}const out=raw.replace(/\s+$/,'');const cur=out.length>0?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('READONLY BASELINE CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}console.log('OK read-only unchanged entries '+cur.length)"
+ non-zero exit, 或 stderr 含 "READONLY BASELINE NOT RECORDED" / "READONLY BASELINE CHANGED"(`packages` / `packages-user` / `src` 下出现了新增 / 消失 / 状态码变化的 worktree 条目 —— 违反只读约束;用户既有改动不得回滚 / 暂存 / 提交)
+ node -e "const fs=require('fs'),{execSync}=require('child_process');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const a=t.match(/\u9519\u8bef\u603b\u884c\u6570\uff1a\s*(\d+)/);const b=t.match(/texture \u5f52\u5c5e\u9519\u8bef\u884c\u6570\uff1a\s*(\d+)/);if(!a||!b){console.error('MISSING TSC BASELINE FIGURES');process.exit(1)}let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}const n=(o.match(/error TS\d+/g)||[]).length;if(String(n)!==a[1]){console.error('TSC TOTAL CHANGED '+n+' vs '+a[1]);process.exit(1)}console.log('OK tsc total unchanged '+n+' texture '+b[1])"
+ non-zero exit, 或 stderr 含 "MISSING TSC BASELINE FIGURES" / "TSC TOTAL CHANGED"(类型错误总行数与文档记录不一致 —— 生产源码被改动或基线未如实记录;后者使只读证明失效)
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const A=seg('## A \u7b26\u53f7\u6539\u540d'),B=seg('## B \u6210\u5458\u4e0e\u7b7e\u540d'),C=seg('## C \u5730\u56fe\u6e32\u67d3\u6d88\u8d39\u8005'),D=seg('## D \u88ab\u5220\u4e14\u65e0\u66ff\u4ee3'),E=seg('## E \u52a0\u8f7d\u76f8\u5173'),F=seg('## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a'),Z=seg('## \u5904\u7f6e');const cnt=(s,re)=>(s.match(re)||[]).length;const rows=s=>cnt(s,/^\|\s*#04-08-[ABCDE]-\d+\s*\|/gm);const anch=l=>cnt(l,/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g);const p=[];if(!Z)p.push('MISSING DISPOSITION');if(rows(A)<5)p.push('A ROWS '+rows(A));if(rows(B)<4)p.push('B ROWS '+rows(B));if(rows(C)<8)p.push('C ROWS '+rows(C));if(rows(D)<6)p.push('D ROWS '+rows(D));if(rows(E)<4)p.push('E ROWS '+rows(E));if(cnt(F,/^- \*\*#04-08-F-\d+/gm)<8)p.push('F ITEMS '+cnt(F,/^- \*\*#04-08-F-\d+/gm));for(const pr of [['A',A],['B',B],['C',C],['D',D],['E',E]]){const bad=pr[1].split(/\r?\n/).filter(l=>/^\|\s*#04-08-[ABCDE]-\d+\s*\|/.test(l)).filter(l=>anch(l)<2);if(bad.length>0)p.push(pr[0]+' ROWS WITHOUT TWO-SIDED ANCHOR '+bad.length)}const needD=['renderWithoutCheck','BlockCls','utils.ts'];const mD=needD.filter(k=>D.indexOf(k)<0);if(mD.length>0)p.push('D MISSING '+mD.join('|'));if(cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g)<6)p.push('D FEEDBACK PHRASE');if(cnt(E,/\u4ec5\u62a5\u544a/g)<1)p.push('E REPORT-ONLY PHRASE');if(p.length>0){console.error('CATEGORY GATE FAILED :: '+p.join(' ;; '));process.exit(1)}console.log('OK final categories A'+rows(A)+' B'+rows(B)+' C'+rows(C)+' D'+rows(D)+' E'+rows(E)+' F'+cnt(F,/^- \*\*#04-08-F-\d+/gm))"
+ non-zero exit, 或 stderr 含 "CATEGORY GATE FAILED"(A/B/C/D/E 行数低于下限、任一行缺命中处或基准侧锚点、D 未覆盖关键被删成员或缺「请用户反馈」措辞、E 缺「仅报告」措辞、F 少于 8 条、缺 `## 处置`)
+ node -e "const{execSync}=require('child_process');const names=['MaterialManager','renderAnimatedWith','getBlockClsByAlias','getIdentifierByAlias','getAliasByIdentifier'];const bad=[];for(const n of names){let o='',st=0;try{o=execSync('git grep -n -- '+n+' -- packages-user',{encoding:'utf8',stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'');st=e.status}const lines=o.replace(/\s+$/,'').length>0?o.replace(/\s+$/,'').split(/\r?\n/):[];if(lines.length>0){bad.push(n+' => '+lines.slice(0,3).join(' ; '))}else if(st!==0&&st!==1){bad.push(n+' grep status '+st)}}if(bad.length>0){console.error('STALE SYMBOL STILL REFERENCED: '+bad.join(' || '));process.exit(1)}console.log('OK zero stale symbol references')"
+ non-zero exit, 或 stderr 含 "STALE SYMBOL STILL REFERENCED"(A 类的「0 引用」结论为臆造)
+ node -e "const fs=require('fs'),{execSync}=require('child_process');const dir='.planning/phases/04-render-adaptation';const prot=['04-01','04-02','04-03','04-04','04-05','04-06','04-07'].map(n=>dir+'/'+n+'-PLAN.md').concat(['04-01','04-02','04-03','04-04','04-05','04-06','04-07'].map(n=>dir+'/'+n+'-SUMMARY.md'));const args=prot.map(p=>'\"'+p+'\"').join(' ');const o=execSync('git status --porcelain -- '+args,{encoding:'utf8'}).replace(/\s+$/,'');if(o.length>0){console.error('EXISTING 04-01..04-07 ARTIFACTS MODIFIED: '+o);process.exit(1)}const plans=fs.readdirSync(dir).filter(f=>/-PLAN\.md$/.test(f)).sort();const extra=plans.filter(p=>!/^04-0[1-8]-PLAN\.md$/.test(p));if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.join(','));process.exit(1)}if(plans.length!==8){console.error('PLAN COUNT '+plans.length+' expected 8');process.exit(1)}if(!fs.readdirSync(dir).some(f=>f==='04-08-SUMMARY.md')){console.error('MISSING 04-08 SUMMARY');process.exit(1)}console.log('OK 04-01..04-07 untouched; plans '+plans.join(','))"
+ non-zero exit, 或 stderr 含 "EXISTING 04-01..04-07 ARTIFACTS MODIFIED" / "UNEXPECTED PLAN FILES" / "PLAN COUNT" / "MISSING 04-08 SUMMARY"(既有前序增量产物被改写,或出现了未授权的 `04-09+` 计划文件——即提前规划了第二步,或 `04-08-SUMMARY.md` 未落盘)
+ node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','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 '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)"
+ non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行)
+
+
+ - `## 处置` 定稿,明写:本步只读、生产代码零改动、唯一交付物、相对只读起始基线零变化、**第二步未规划**(D-40)、范围限 `packages-user` 消费者面(`packages/` 与 `src/` 仅报告)、**D 类须用户反馈后才可能进入第二步**、**E 类归用户自行适配新加载系统(D-39 / D-41)**、**`material/` 内命中归用户(D-42)**、`tileType` / `renderWithoutCheck` 替代 / 自建 processor 由用户裁决、D-12 / D-18 / D-19 / D-22 边界、`REND-01` / `REND-02` 保持 Pending,并(如与规划日不同)说明实测差异
+ - 六道门禁全部转绿:只读门禁(porcelain 逐条一致)、类型门禁一致性(错误总行数等于记录值)、类别门禁(A ≥ 5 / B ≥ 4 / C ≥ 8 / D ≥ 6 / E ≥ 4 / F ≥ 8 且每行双锚点与关键词齐备)、符号零命中复验、既有计划保护 + 计划范围门禁(`04-01..04-07-*` 未被改写、无 `04-09+`、`04-08-SUMMARY.md` 已落盘)、CRLF 门禁
+ - 人工代码复核结论已记录(抽查 A 类 3 处 `BlockCls` import 与 `hero.ts:167` / `vertex.ts:852`、D 类 3 处 `renderWithoutCheck` 与 `material/utils.ts:2`、E 类 `fallback/load.ts` 9 处),偏差均已修正
+ - `04-08-SUMMARY.md` 已产出,如实记录当日实测值(porcelain / 类型错误数)、A–F 实际行数与关键结论、六道门禁结果、人工复核结论、与规划日的差异说明、`REND-01` / `REND-02` 仍 Pending 与「第二步未规划」;**未写入任何被删接口的替代方案**
+ - 提交仅含 `04-TEXTURE-INTERFACE-IMPACT.md`、`04-08-PLAN.md`、`04-08-SUMMARY.md` 三个文件;未纳入任何生产源码改动,未提交 `04-CONTEXT.md` 的既有未提交改动,未改写 `04-01..04-07-*`
+
+ TextureManager 新接口影响台账定稿并交付:六道门禁全绿、既有前序产物零改写、`04-08-SUMMARY.md` 落盘并提交;用户可据此审阅 A / B / C 三类定位与事实、D 类待反馈项、E 类自有面,并裁定第二步的修复范围
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| 生产源码 → 清点文档 | 只读引用 `packages-user` / `packages` / `src` 的代码与行号;不得产生任何写回(`packages` / `packages-user` / `src` 的 porcelain 必须零变化) |
+| 类型检查结果 → 清点文档 | `pnpm exec vue-tsc --noEmit` 的输出被用作只读证据;记录值与重跑值必须一致,否则只读证明失效 |
+| 基准(`material/types.ts` 等)→ A–E 分类 | 基准决定「什么算改名 / 被删 / 签名收紧」;基准读错会整类错分 |
+| 用户自有面(`client-base/src/material/`)→ AI 工作范围(D-42) | 该文件夹已由用户重构完毕;若把其内部命中写成 AI 待改项,即越权改动用户代码 |
+| 「加载相关」(D-39 / D-41)→ AI 工作范围 | 加载本身与向 `TextureManager` 添加素材的内容归用户自行适配;若被写成 AI 待改项,即越权 |
+| 实现残留 / 类型错误 → 接口设计结论 | 把「编译器报错」当作「接口该怎么改」的结论会替用户设计公共接口(AGENTS.md 禁止) |
+| 执行者 / 子代理 → 执行前置关卡 | AGENTS.md 要求第一个 block 点先汇报并等待用户回复「可以执行」;`yolo` / `auto_advance` 或子代理派发都不是放行理由(Task 0 为 `blocking-human`、`autonomous: false`) |
+| 既有 04-01..04-07 产物 → 本次编号 | phase 目录内已有 7 个已执行增量的计划与 SUMMARY;改号/覆盖会破坏既有执行记录 |
+| 清点范围 → Phase 5 / 后续增量 | 把 `src/` / `packages/` 命中或 `core.*` legacy 清退写成本阶段待办会扩大范围(D-12 / D-33) |
+| 清点文档 → 第二步规划 | 第二步计划由用户审阅本文件后另行规划(D-40);文档若夹带修复方案即越权替用户设计 |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-04-08-01 | Tampering | 生产源码 `packages-user` / `packages` / `src` | high | mitigate | Task 1 步骤 (0a) 记录 `git status --porcelain -- packages packages-user src` 的逐行基线(规划日为集合空);Task 1 / Task 3 门禁断言其后逐条一致;Task 3 另以 `pnpm exec vue-tsc --noEmit` 错误总行数等于记录值作为第二重只读证明;`files_modified` 仅含交付文档;prohibitions 明列生产文件零改动、不得触碰用户 committed 的 `material/**` 与 `client-base/src/types.ts` |
+| T-04-08-02 | Elevation of Privilege | 执行前置关卡(AGENTS.md 要求的第一处 block 点) | high | mitigate | Task 0 为 `checkpoint:decision` + `gate="blocking-human"` 且本计划 `autonomous: false`;Task 0 的 action ⑨/⑩ 与 acceptance_criteria 明示未收到用户「可以执行」四个字即必须停止、不派发执行子代理;`yolo` / `workflow.auto_advance` 不得自动放行 |
+| T-04-08-03 | Tampering | 用户自有面 `packages-user/client-base/src/material/`(D-42) | high | mitigate | `## D` 类对 `material/utils.ts:2` 只登记并标注「用户自有面」;prohibitions 明列不得在 `material/` 内规划任何修改;Task 2 步骤 (1) 要求 `material/` 内命中一律归 D 类、不得写成 AI 工作项 |
+| T-04-08-04 | Tampering | 「加载相关」范围(D-39 / D-41) | high | mitigate | `## E` 节标题下明写「仅报告、不修改、不提修改方案」;B 类四个 `add*` 调用点交叉引用 E 行并注明归用户;Task 3 类别门禁要求 E 节含「仅报告」措辞;prohibitions 明列不得为加载面提出修改方案 |
+| T-04-08-05 | Spoofing | A / B / C / D / E 分类 | high | mitigate | 分列规则写死(A 符号级、B 签名级、C 定位索引并交叉引用、D 被删无替代须反馈、E 加载与添加素材、F 未确定);每行强制两侧 `file:line` 锚点;C 行必须携带交叉引用 ID;D 行必须含「请用户反馈」措辞;门禁机器判定行数下限、关键词、双锚点与措辞计数 |
+| T-04-08-06 | Spoofing | A 类「旧名 0 引用」结论 | high | mitigate | 不以文档自述为准:Task 2 / Task 3 门禁直接运行 `git grep -n -- -- packages-user`(5 个符号逐一),非零输出即失败;`.planning/graphs/*` 的过期标签单列为工装态并声明不计入源码命中 |
+| T-04-08-07 | Repudiation | A–E 各行条目不可追溯 | medium | mitigate | 固定列序(`ID | 符号/成员名 | 命中处 | 基准侧锚点 | 问题描述 | 影响 | 置信`)+ 每行 ≥2 个 `file:line` 锚点 + 行数下限 + 关键词齐备,由 Task 2 / Task 3 门禁机器判定 |
+| T-04-08-08 | Elevation of Privilege | 清点范围(`src/` / `packages/` / `core.*` legacy 清退 / 第二步实施) | medium | mitigate | D-12 / D-33 / D-40 为显式边界:E 类标题下明写「仅报告,不修改、不提建议」;prohibitions 禁止为 `src/` / `packages/` 与 `render/elements/cache.ts` 提修改建议;Task 3 门禁禁止出现 `04-09+` 计划文件与前序产物改写 |
+| T-04-08-09 | Denial of Service | 只读子代理越权写入(D-09) | low | mitigate | Task 2 步骤 (7) 明确子代理仅回传事实表、不得写文件、不得贴大段源码;最终由主执行者以只读门禁 + 类型门禁一致 + 类别门禁统一验证 |
+| T-04-08-10 | Information Disclosure | 清点文档引用的源码行与接口名 | low | accept | 记录只含符号名、文件行号、成员签名与编译错误摘要,不含凭据 / 密钥 / 个人数据 |
+| T-04-08-SC | Tampering | npm / pip / cargo 安装面 | high | mitigate | 本计划不新增依赖、不安装任何包(prohibitions);若出现安装需求即暂停并告知用户;门禁以 porcelain 基线覆盖 `package.json` / `pnpm-lock.yaml`(本计划零改动),无需包合法性核对 |
+
+
+
+- **只读门禁**:`node -e "const{execSync}=require('child_process');console.log(JSON.stringify(execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})))"` 的输出与交付文档 `## 方法` 的「只读起始基线」条目逐条一致(不新增、不消失、状态码不变)。
+- **类型门禁一致性**:`node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出等于文档记录的「错误总行数」(规划日 202);「texture 归属错误行数」(规划日 23 = `client-base` 1 + `client-modules` 22)为规划日人工归类、执行日按当日实测重新归类,只要求文档逐行列出(**非机器断言**,由人工复核保证)。
+- **类别门禁**:交付文档的 A ≥ 5 行 / B ≥ 4 行 / C ≥ 8 行 / D ≥ 6 行 / E ≥ 4 行 / F ≥ 8 条,A–E 每行 ≥2 个 `file:line` 锚点,A 覆盖符号级关键词,B 覆盖新增成员与签名关键词,C 覆盖 8 个定位文件并携带交叉引用 ID,D 覆盖关键被删成员与 `material/utils.ts` 且含「请用户反馈」措辞,E 包含加载文件与对照扫描结论并含「仅报告」措辞。
+- **符号零命中复验**:`git grep -n -- -- packages-user`(`MaterialManager` / `renderAnimatedWith` / `getBlockClsByAlias` / `getIdentifierByAlias` / `getAliasByIdentifier`)逐一无输出。
+- **既有计划保护 + 计划范围门禁**:`git status --porcelain -- <04-01..04-07 的 PLAN 与 SUMMARY>` 为空;phase 目录内 `-PLAN.md` 恰为 `04-01`..`04-08` 共 8 个;`04-08-SUMMARY.md` 已存在。
+- **CRLF 门禁**:交付文档的 `\n` 总数等于 `\r\n` 总数(无孤立 LF)。
+- **人工复核**:抽查 A 类 3 处 `BlockCls` import 与 `hero.ts:167` / `vertex.ts:852`、D 类 3 处 `renderWithoutCheck` 与 `material/utils.ts:2`、E 类 `fallback/load.ts` 的 9 处参数个数错误,结论与偏差修正记录在 `04-08-SUMMARY.md`。
+- **不可自动化项的诚实说明**:本步为只读清点,无运行时行为可观察;除上述命令外不存在可运行门禁,不得以任何无法运行的门禁冒充通过。
+
+
+
+Create `.planning/phases/04-render-adaptation/04-08-SUMMARY.md` when done
+
+
+## Artifacts this phase produces
+
+| Artifact | Kind | Purpose |
+|----------|------|---------|
+| `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md` | 交付文档(**唯一** `files_modified`) | `d36ea69` TextureManager 基准在 `packages-user` 消费者面的只读影响台账:元信息块 + 背景 / 方法 / 基准变更(d36ea69)/ 类型门禁实测基线 / A 符号改名与移除的引用点 / B 成员与签名变化的调用点 / C 地图渲染消费者定位索引 / D 被删且无替代(需用户反馈)/ E 加载相关与添加素材(仅报告)/ F 未能从阅读确定(未猜测)/ 处置 |
+| `.planning/phases/04-render-adaptation/04-08-PLAN.md` | 计划簿记 | 本计划(编号 04-08;`wave: 7`;`depends_on` 04-01..04-07) |
+| `.planning/phases/04-render-adaptation/04-08-SUMMARY.md` | 执行簿记 | 当日实测值(porcelain / `pnpm exec vue-tsc --noEmit` 错误数)、A–F 实际行数与关键结论、六道门禁结果、人工复核结论、与规划日的差异说明、`REND-01` / `REND-02` 仍 Pending 与「第二步未规划」 |
+| 门禁结果(会话内可复跑) | 验证证据 | 只读门禁(porcelain 逐条一致)、类型门禁一致性(错误总行数等于记录值)、类别门禁(A ≥ 5 / B ≥ 4 / C ≥ 8 / D ≥ 6 / E ≥ 4 / F ≥ 8 与双锚点 / 关键词 / 措辞)、符号零命中复验、既有计划保护 + 计划范围门禁(`04-01..04-07-*` 未改写、无 `04-09+`、SUMMARY 已落盘)、CRLF 门禁 |
+| 显式**不**产出 | 边界声明 | 任何生产代码改动、任何测试文件、任何依赖变更、任何修复方案或第二步实施计划(D-40:第二步待用户审阅本文件后另行规划)、任何针对 `material/` 与加载面的修改方案(D-39 / D-41 / D-42) |
+
From dd2dc52802bfe345c3e62749cf3d90c0c7937551 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Wed, 23 Sep 2026 10:53:11 +0800
Subject: [PATCH 073/173] docs: next goal
---
.planning/phases/04-render-adaptation/04-CONTEXT.md | 7 +++++++
pnpm-lock.yaml | 3 +++
2 files changed, 10 insertions(+)
diff --git a/.planning/phases/04-render-adaptation/04-CONTEXT.md b/.planning/phases/04-render-adaptation/04-CONTEXT.md
index 6c0e2a2..e2aa6aa 100644
--- a/.planning/phases/04-render-adaptation/04-CONTEXT.md
+++ b/.planning/phases/04-render-adaptation/04-CONTEXT.md
@@ -75,6 +75,13 @@
- **D-36:** (D 类)**全部删除**——`MaterialManager` 的 big-image 实现残留(`bigImageStore`/`bigImageData`/`bigImageId` 等)与 `render/elements/cache.ts` 的 legacy big-image 路径。
- **D-37:** (E 类)**补充依赖声明**——为 `packages-user/client-base/package.json` 补上 `@user/data-state` 依赖(其源码已 import 该包)。
+### 下一个目标:TextureManager 新接口适应(用户裁定,2026-09-22;待规划)
+- **D-38:** 用户已完成 **TextureManager 相关重构**(提交 `d36ea69「refactor: 贴图存储方式」`,本地已快进同步),影响面大、**主要在地图渲染部分**。下一目标 = **适应新接口**。
+- **D-39:** **不得修改加载相关的内容**(用户将自行适配新加载系统,属其处理范围)。
+- **D-40:** 本目标同样走**两步**:**先收集(只读清点)再修复**。第一步产出只读影响清单文档;第二步待用户审阅后再规划。
+- **D-41:** 「加载相关」的排除边界(用户界定):**加载本身**,以及**向 `TextureManager` 中添加素材的内容**(均由用户自行适配新加载系统)。
+- **D-42:** `packages-user/client-base/src/material/` 文件夹已由用户**重构完毕**;AI **只修改消费者**(如地图渲染)。**若某接口被删除且无替代,必须向用户反馈**,不得自行发明替代方案。
+
### the agent's Discretion
- D-07 的「影响」字段具体写法、「多余旧路径」是否需要进一步细分,交由 AI 在对账执行时按实际情况把握,但不得据此扩大范围。
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7cb9a6e..7411e7a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -398,6 +398,9 @@ importers:
'@user/data-base':
specifier: workspace:*
version: link:../data-base
+ '@user/data-state':
+ specifier: workspace:*
+ version: link:../data-state
packages-user/client-modules:
dependencies:
From b4c8482f874dd710f97a91f854283fe670734ecb Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Wed, 23 Sep 2026 11:12:03 +0800
Subject: [PATCH 074/173] fix(04): revise 04-08 plan based on checker feedback
---
.../phases/04-render-adaptation/04-08-PLAN.md | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/.planning/phases/04-render-adaptation/04-08-PLAN.md b/.planning/phases/04-render-adaptation/04-08-PLAN.md
index eb892bf..63d243d 100644
--- a/.planning/phases/04-render-adaptation/04-08-PLAN.md
+++ b/.planning/phases/04-render-adaptation/04-08-PLAN.md
@@ -172,7 +172,7 @@ Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 /
Task 0: 预执行汇报关卡 —— 汇报 TextureManager 新接口影响清点(D-38/D-40 第一步)的范围、基准、A–F 六类分列规则与唯一交付物、D-39/D-41/D-42 三条硬边界与只读证明方式,等待用户回复「可以执行」
- 用户对 TextureManager 新接口适应的裁定 D-38 / D-39 / D-40 / D-41 / D-42 已写入 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(以文件内实际存在 D-38..D-42 为准);`d36ea69 refactor: 贴图存储方式` 已在当日 HEAD(`git rev-parse HEAD` 以 `d36ea69` 开头)
+ 用户对 TextureManager 新接口适应的裁定 D-38 / D-39 / D-40 / D-41 / D-42 已写入 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(以文件内实际存在 D-38..D-42 为准);`d36ea69 refactor: 贴图存储方式` 是当日 HEAD 或其**祖先**(`git merge-base --is-ancestor d36ea69 HEAD` 退出码 0),且自 `d36ea69` 至当日 HEAD 对生产路径无提交差异(`git diff --stat d36ea69 HEAD -- packages packages-user src` 无输出)—— 即 `packages-user` / `packages` / `src` 的**生产代码基线 = `d36ea69`**(工作树内可能存在的并发未提交改动不属本前置条件,由 Task 1 步骤 (0a) 当日实测记录为只读起始基线)
(只读汇报,不修改任何文件)
- AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行;「如果发现任何问题,永远不要擅自将其视为问题并记录,必须经由我确认」;「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」)
@@ -233,7 +233,7 @@ Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 /
需用户先指定文件名;phase 目录内既有 `04-01`..`04-07` 产物不得改写 / 覆盖
- 回复「可以执行」四个字(或给出修订意见)
+ 回复「可以执行」四个字(修订意见请按 action ⑪ 处理)
用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无自动命令,失败语义写在 Task 0 的 action ⑩ 与 acceptance_criteria 中)
@@ -353,7 +353,7 @@ Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 /
- node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const A=seg('## A \u7b26\u53f7\u6539\u540d'),B=seg('## B \u6210\u5458\u4e0e\u7b7e\u540d'),C=seg('## C \u5730\u56fe\u6e32\u67d3\u6d88\u8d39\u8005'),D=seg('## D \u88ab\u5220\u4e14\u65e0\u66ff\u4ee3'),E=seg('## E \u52a0\u8f7d\u76f8\u5173'),F=seg('## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a');const cnt=(s,re)=>(s.match(re)||[]).length;const rows=s=>cnt(s,/^\|\s*#04-08-[ABCDE]-\d+\s*\|/gm);const anch=l=>cnt(l,/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g);const p=[];if(!A||!B||!C||!D||!E||!F)p.push('MISSING SECTION');if(rows(A)<5)p.push('A ROWS '+rows(A));if(rows(B)<4)p.push('B ROWS '+rows(B));if(rows(C)<8)p.push('C ROWS '+rows(C));if(rows(D)<6)p.push('D ROWS '+rows(D));if(rows(E)<4)p.push('E ROWS '+rows(E));if(cnt(F,/^- \*\*#04-08-F-\d+/gm)<8)p.push('F ITEMS '+cnt(F,/^- \*\*#04-08-F-\d+/gm));for(const pr of [['A',A],['B',B],['C',C],['D',D],['E',E]]){const bad=pr[1].split(/\r?\n/).filter(l=>/^\|\s*#04-08-[ABCDE]-\d+\s*\|/.test(l)).filter(l=>anch(l)<2);if(bad.length>0)p.push(pr[0]+' ROWS WITHOUT TWO-SIDED ANCHOR '+bad.length)}const needA=['TextureManager','MaterialManager','renderAnimatedWith','BlockCls','tileType','AutotileProcessor','IClientBase','getIdentifierByAlias','getAliasByIdentifier','createMaterial'];const mA=needA.filter(k=>A.indexOf(k)<0);if(mA.length>0)p.push('A MISSING '+mA.join('|'));const needB=['tiles','autotile','tilesetReserve','tilesetUnit','getFrameCount','addGrid','addRowAnimate','addAutotile','addTileset','AutotileType','IClientCoreConfig'];const mB=needB.filter(k=>B.indexOf(k)<0);if(mB.length>0)p.push('B MISSING '+mB.join('|'));const needC=['renderer.ts','vertex.ts','hero.ts','moving.ts','client.ts','types.ts','core.ts','#04-08-'];const mC=needC.filter(k=>C.indexOf(k)<0);if(mC.length>0)p.push('C MISSING '+mC.join('|'));const needD=['renderWithoutCheck','BlockCls','manager','renderWith','getIdentifierByAlias','getAliasByIdentifier','utils.ts'];const mD=needD.filter(k=>D.indexOf(k)<0);if(mD.length>0)p.push('D MISSING '+mD.join('|'));if(cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g)<6)p.push('D FEEDBACK PHRASE '+cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g));const needE=['fallback/load.ts','loader.ts','packages','src/types/declaration'];const mE=needE.filter(k=>E.indexOf(k)<0);if(mE.length>0)p.push('E MISSING '+mE.join('|'));if(p.length>0){console.error('CATEGORY GATE FAILED :: '+p.join(' ;; '));process.exit(1)}console.log('OK categories A'+rows(A)+' B'+rows(B)+' C'+rows(C)+' D'+rows(D)+' E'+rows(E)+' F'+cnt(F,/^- \*\*#04-08-F-\d+/gm))"
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const A=seg('## A \u7b26\u53f7\u6539\u540d'),B=seg('## B \u6210\u5458\u4e0e\u7b7e\u540d'),C=seg('## C \u5730\u56fe\u6e32\u67d3\u6d88\u8d39\u8005'),D=seg('## D \u88ab\u5220\u4e14\u65e0\u66ff\u4ee3'),E=seg('## E \u52a0\u8f7d\u76f8\u5173'),F=seg('## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a');const cnt=(s,re)=>(s.match(re)||[]).length;const rows=s=>cnt(s,/^\|\s*#04-08-[ABCDE]-\d+\s*\|/gm);const anch=l=>cnt(l,/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g);const p=[];if(!A||!B||!C||!D||!E||!F)p.push('MISSING SECTION');if(rows(A)<5)p.push('A ROWS '+rows(A));if(rows(B)<4)p.push('B ROWS '+rows(B));if(rows(C)<8)p.push('C ROWS '+rows(C));if(rows(D)<6)p.push('D ROWS '+rows(D));if(rows(E)<4)p.push('E ROWS '+rows(E));if(cnt(F,/^- \*\*#04-08-F-\d+/gm)<8)p.push('F ITEMS '+cnt(F,/^- \*\*#04-08-F-\d+/gm));for(const pr of [['A',A],['B',B],['C',C],['D',D],['E',E]]){const bad=pr[1].split(/\r?\n/).filter(l=>/^\|\s*#04-08-[ABCDE]-\d+\s*\|/.test(l)).filter(l=>anch(l)<2);if(bad.length>0)p.push(pr[0]+' ROWS WITHOUT TWO-SIDED ANCHOR '+bad.length)}const needA=['TextureManager','MaterialManager','renderAnimatedWith','BlockCls','tileType','AutotileProcessor','IClientBase','getIdentifierByAlias','getAliasByIdentifier','createMaterial'];const mA=needA.filter(k=>A.indexOf(k)<0);if(mA.length>0)p.push('A MISSING '+mA.join('|'));const needB=['tiles','autotile','tilesetReserve','tilesetUnit','getFrameCount','addGrid','addRowAnimate','addAutotile','addTileset','AutotileType','IClientCoreConfig'];const mB=needB.filter(k=>B.indexOf(k)<0);if(mB.length>0)p.push('B MISSING '+mB.join('|'));const needC=['renderer.ts','vertex.ts','hero.ts','moving.ts','client.ts','types.ts','core.ts'];const mC=needC.filter(k=>C.indexOf(k)<0);if(mC.length>0)p.push('C MISSING '+mC.join('|'));const cbad=C.split(/\r?\n/).filter(l=>/^\|\s*#04-08-C-\d+\s*\|/.test(l)).filter(l=>!/#04-08-[ABD]-\d+/.test(l));if(cbad.length>0)p.push('C ROW MISSING CROSS-REF '+cbad.length);const needD=['renderWithoutCheck','BlockCls','manager','renderWith','getIdentifierByAlias','getAliasByIdentifier','utils.ts'];const mD=needD.filter(k=>D.indexOf(k)<0);if(mD.length>0)p.push('D MISSING '+mD.join('|'));if(cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g)<6)p.push('D FEEDBACK PHRASE '+cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g));const needE=['fallback/load.ts','loader.ts','packages','src/types/declaration'];const mE=needE.filter(k=>E.indexOf(k)<0);if(mE.length>0)p.push('E MISSING '+mE.join('|'));if(p.length>0){console.error('CATEGORY GATE FAILED :: '+p.join(' ;; '));process.exit(1)}console.log('OK categories A'+rows(A)+' B'+rows(B)+' C'+rows(C)+' D'+rows(D)+' E'+rows(E)+' F'+cnt(F,/^- \*\*#04-08-F-\d+/gm))"
non-zero exit, 或 stderr 含 "CATEGORY GATE FAILED"(A/B/C/D/E 任一类行数低于下限、任一行缺命中处或基准侧锚点、A 未覆盖符号级关键词、B 未覆盖新增成员与签名、C 未覆盖 8 个定位文件或未交叉引用 ID、D 未覆盖关键被删成员或缺「请用户反馈」措辞、E 未覆盖加载文件与对照扫描、F 少于 8 条)
node -e "const{execSync}=require('child_process');const names=['MaterialManager','renderAnimatedWith','getBlockClsByAlias','getIdentifierByAlias','getAliasByIdentifier'];const bad=[];for(const n of names){let o='',st=0;try{o=execSync('git grep -n -- '+n+' -- packages-user',{encoding:'utf8',stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'');st=e.status}const lines=o.replace(/\s+$/,'').length>0?o.replace(/\s+$/,'').split(/\r?\n/):[];if(lines.length>0){bad.push(n+' => '+lines.slice(0,3).join(' ; '))}else if(st!==0&&st!==1){bad.push(n+' grep status '+st)}}if(bad.length>0){console.error('STALE SYMBOL STILL REFERENCED: '+bad.join(' || '));process.exit(1)}console.log('OK zero stale symbol references ('+names.length+' names)')"
non-zero exit, 或 stderr 含 "STALE SYMBOL STILL REFERENCED"(`MaterialManager` / `renderAnimatedWith` / `getBlockClsByAlias` / `getIdentifierByAlias` / `getAliasByIdentifier` 在 `packages-user` 源码面仍存在引用,即 A 类的「0 引用」结论为臆造)
@@ -395,12 +395,12 @@ Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 /
(5) **提交**:以 `docs(04-08): TextureManager 新接口影响范围清点(只读,D-38/D-40 第一步)` 提交**仅** `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`、`.planning/phases/04-render-adaptation/04-08-PLAN.md` 与 `.planning/phases/04-render-adaptation/04-08-SUMMARY.md` 三个文件;**不得**把任何生产源码改动或用户既有的未提交改动纳入本次提交(尤其不得提交 `.planning/phases/04-render-adaptation/04-CONTEXT.md` 的既有未提交改动),不得改写 / 覆盖 / 改号 `04-01`..`04-07` 的既有产物。
- node -e "const fs=require('fs'),{execSync}=require('child_process');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');if(ms.indexOf('\u53ea\u8bfb\u8d77\u59cb\u57fa\u7ebf')<0){console.error('READONLY BASELINE NOT RECORDED');process.exit(1)}const rec=(ms.match(/^[ MARCUD?!]{2} (?:packages|packages-user|src)[^\r\n]*/gm)||[]).map(s=>s.replace(/\s+$/,''));let raw='';try{raw=execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})}catch(e){console.error('GIT STATUS FAILED '+(e.message||''));process.exit(1)}const out=raw.replace(/\s+$/,'');const cur=out.length>0?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('READONLY BASELINE CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}console.log('OK read-only unchanged entries '+cur.length)"
- non-zero exit, 或 stderr 含 "READONLY BASELINE NOT RECORDED" / "READONLY BASELINE CHANGED"(`packages` / `packages-user` / `src` 下出现了新增 / 消失 / 状态码变化的 worktree 条目 —— 违反只读约束;用户既有改动不得回滚 / 暂存 / 提交)
+ node -e "const fs=require('fs'),{execSync}=require('child_process');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');if(ms.indexOf('\u53ea\u8bfb\u8d77\u59cb\u57fa\u7ebf')<0){console.error('READONLY BASELINE NOT RECORDED');process.exit(1)}const rec=(ms.match(/^[ MARCUD?!]{2} (?:packages|packages-user|src)[^\r\n]*/gm)||[]).map(s=>s.replace(/\s+$/,''));let raw='';try{raw=execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})}catch(e){console.error('GIT STATUS FAILED '+(e.message||''));process.exit(1)}const out=raw.replace(/\s+$/,'');const cur=out.length>0?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('READONLY BASELINE CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}const ro=execSync('git status --porcelain -- .planning/REQUIREMENTS.md',{encoding:'utf8'}).replace(/\s+$/,'');if(ro.length>0){console.error('READ-ONLY VIOLATION .planning/REQUIREMENTS.md: '+ro);process.exit(1)}const req=fs.readFileSync('.planning/REQUIREMENTS.md','utf8').split(/\r?\n/);const notPending=['REND-01','REND-02'].filter(id=>{const r=req.find(l=>l.indexOf('| '+id+' |')>=0);return !r||!/\|\s*Pending\s*\|\s*$/.test(r)});if(notPending.length>0){console.error('REND STATUS NOT PENDING: '+notPending.join(','));process.exit(1)}console.log('OK read-only unchanged entries '+cur.length+'; REND-01/REND-02 still Pending')"
+ non-zero exit, 或 stderr 含 "READONLY BASELINE NOT RECORDED" / "READONLY BASELINE CHANGED" / "READ-ONLY VIOLATION" / "REND STATUS NOT PENDING"(`packages` / `packages-user` / `src` 下出现了新增 / 消失 / 状态码变化的 worktree 条目,或 `.planning/REQUIREMENTS.md` 被改动,或该文件中 `REND-01` / `REND-02` 不再是 `Pending` —— 违反只读约束或「REND 保持 Pending」真值不成立;用户既有改动不得回滚 / 暂存 / 提交)
node -e "const fs=require('fs'),{execSync}=require('child_process');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const a=t.match(/\u9519\u8bef\u603b\u884c\u6570\uff1a\s*(\d+)/);const b=t.match(/texture \u5f52\u5c5e\u9519\u8bef\u884c\u6570\uff1a\s*(\d+)/);if(!a||!b){console.error('MISSING TSC BASELINE FIGURES');process.exit(1)}let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}const n=(o.match(/error TS\d+/g)||[]).length;if(String(n)!==a[1]){console.error('TSC TOTAL CHANGED '+n+' vs '+a[1]);process.exit(1)}console.log('OK tsc total unchanged '+n+' texture '+b[1])"
non-zero exit, 或 stderr 含 "MISSING TSC BASELINE FIGURES" / "TSC TOTAL CHANGED"(类型错误总行数与文档记录不一致 —— 生产源码被改动或基线未如实记录;后者使只读证明失效)
- node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const A=seg('## A \u7b26\u53f7\u6539\u540d'),B=seg('## B \u6210\u5458\u4e0e\u7b7e\u540d'),C=seg('## C \u5730\u56fe\u6e32\u67d3\u6d88\u8d39\u8005'),D=seg('## D \u88ab\u5220\u4e14\u65e0\u66ff\u4ee3'),E=seg('## E \u52a0\u8f7d\u76f8\u5173'),F=seg('## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a'),Z=seg('## \u5904\u7f6e');const cnt=(s,re)=>(s.match(re)||[]).length;const rows=s=>cnt(s,/^\|\s*#04-08-[ABCDE]-\d+\s*\|/gm);const anch=l=>cnt(l,/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g);const p=[];if(!Z)p.push('MISSING DISPOSITION');if(rows(A)<5)p.push('A ROWS '+rows(A));if(rows(B)<4)p.push('B ROWS '+rows(B));if(rows(C)<8)p.push('C ROWS '+rows(C));if(rows(D)<6)p.push('D ROWS '+rows(D));if(rows(E)<4)p.push('E ROWS '+rows(E));if(cnt(F,/^- \*\*#04-08-F-\d+/gm)<8)p.push('F ITEMS '+cnt(F,/^- \*\*#04-08-F-\d+/gm));for(const pr of [['A',A],['B',B],['C',C],['D',D],['E',E]]){const bad=pr[1].split(/\r?\n/).filter(l=>/^\|\s*#04-08-[ABCDE]-\d+\s*\|/.test(l)).filter(l=>anch(l)<2);if(bad.length>0)p.push(pr[0]+' ROWS WITHOUT TWO-SIDED ANCHOR '+bad.length)}const needD=['renderWithoutCheck','BlockCls','utils.ts'];const mD=needD.filter(k=>D.indexOf(k)<0);if(mD.length>0)p.push('D MISSING '+mD.join('|'));if(cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g)<6)p.push('D FEEDBACK PHRASE');if(cnt(E,/\u4ec5\u62a5\u544a/g)<1)p.push('E REPORT-ONLY PHRASE');if(p.length>0){console.error('CATEGORY GATE FAILED :: '+p.join(' ;; '));process.exit(1)}console.log('OK final categories A'+rows(A)+' B'+rows(B)+' C'+rows(C)+' D'+rows(D)+' E'+rows(E)+' F'+cnt(F,/^- \*\*#04-08-F-\d+/gm))"
- non-zero exit, 或 stderr 含 "CATEGORY GATE FAILED"(A/B/C/D/E 行数低于下限、任一行缺命中处或基准侧锚点、D 未覆盖关键被删成员或缺「请用户反馈」措辞、E 缺「仅报告」措辞、F 少于 8 条、缺 `## 处置`)
+ node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md','utf8');const seg=h=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const A=seg('## A \u7b26\u53f7\u6539\u540d'),B=seg('## B \u6210\u5458\u4e0e\u7b7e\u540d'),C=seg('## C \u5730\u56fe\u6e32\u67d3\u6d88\u8d39\u8005'),D=seg('## D \u88ab\u5220\u4e14\u65e0\u66ff\u4ee3'),E=seg('## E \u52a0\u8f7d\u76f8\u5173'),F=seg('## F \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a'),Z=seg('## \u5904\u7f6e');const cnt=(s,re)=>(s.match(re)||[]).length;const rows=s=>cnt(s,/^\|\s*#04-08-[ABCDE]-\d+\s*\|/gm);const anch=l=>cnt(l,/[A-Za-z0-9_\-.\/]+\.tsx?:\d+/g);const p=[];if(!Z)p.push('MISSING DISPOSITION');if(rows(A)<5)p.push('A ROWS '+rows(A));if(rows(B)<4)p.push('B ROWS '+rows(B));if(rows(C)<8)p.push('C ROWS '+rows(C));if(rows(D)<6)p.push('D ROWS '+rows(D));if(rows(E)<4)p.push('E ROWS '+rows(E));if(cnt(F,/^- \*\*#04-08-F-\d+/gm)<8)p.push('F ITEMS '+cnt(F,/^- \*\*#04-08-F-\d+/gm));for(const pr of [['A',A],['B',B],['C',C],['D',D],['E',E]]){const bad=pr[1].split(/\r?\n/).filter(l=>/^\|\s*#04-08-[ABCDE]-\d+\s*\|/.test(l)).filter(l=>anch(l)<2);if(bad.length>0)p.push(pr[0]+' ROWS WITHOUT TWO-SIDED ANCHOR '+bad.length)}const cbad=C.split(/\r?\n/).filter(l=>/^\|\s*#04-08-C-\d+\s*\|/.test(l)).filter(l=>!/#04-08-[ABD]-\d+/.test(l));if(cbad.length>0)p.push('C ROWS WITHOUT CROSS-REF '+cbad.length);const needD=['renderWithoutCheck','BlockCls','utils.ts'];const mD=needD.filter(k=>D.indexOf(k)<0);if(mD.length>0)p.push('D MISSING '+mD.join('|'));if(cnt(D,/\u8bf7\u7528\u6237\u53cd\u9988/g)<6)p.push('D FEEDBACK PHRASE');if(cnt(E,/\u4ec5\u62a5\u544a/g)<1)p.push('E REPORT-ONLY PHRASE');if(p.length>0){console.error('CATEGORY GATE FAILED :: '+p.join(' ;; '));process.exit(1)}console.log('OK final categories A'+rows(A)+' B'+rows(B)+' C'+rows(C)+' D'+rows(D)+' E'+rows(E)+' F'+cnt(F,/^- \*\*#04-08-F-\d+/gm))"
+ non-zero exit, 或 stderr 含 "CATEGORY GATE FAILED"(A/B/C/D/E 行数低于下限、任一行缺命中处或基准侧锚点、D 未覆盖关键被删成员或缺「请用户反馈」措辞、E 缺「仅报告」措辞、F 少于 8 条、C 行未交叉引用 A/B/D 记录 ID、缺 `## 处置`)
node -e "const{execSync}=require('child_process');const names=['MaterialManager','renderAnimatedWith','getBlockClsByAlias','getIdentifierByAlias','getAliasByIdentifier'];const bad=[];for(const n of names){let o='',st=0;try{o=execSync('git grep -n -- '+n+' -- packages-user',{encoding:'utf8',stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'');st=e.status}const lines=o.replace(/\s+$/,'').length>0?o.replace(/\s+$/,'').split(/\r?\n/):[];if(lines.length>0){bad.push(n+' => '+lines.slice(0,3).join(' ; '))}else if(st!==0&&st!==1){bad.push(n+' grep status '+st)}}if(bad.length>0){console.error('STALE SYMBOL STILL REFERENCED: '+bad.join(' || '));process.exit(1)}console.log('OK zero stale symbol references')"
non-zero exit, 或 stderr 含 "STALE SYMBOL STILL REFERENCED"(A 类的「0 引用」结论为臆造)
node -e "const fs=require('fs'),{execSync}=require('child_process');const dir='.planning/phases/04-render-adaptation';const prot=['04-01','04-02','04-03','04-04','04-05','04-06','04-07'].map(n=>dir+'/'+n+'-PLAN.md').concat(['04-01','04-02','04-03','04-04','04-05','04-06','04-07'].map(n=>dir+'/'+n+'-SUMMARY.md'));const args=prot.map(p=>'\"'+p+'\"').join(' ');const o=execSync('git status --porcelain -- '+args,{encoding:'utf8'}).replace(/\s+$/,'');if(o.length>0){console.error('EXISTING 04-01..04-07 ARTIFACTS MODIFIED: '+o);process.exit(1)}const plans=fs.readdirSync(dir).filter(f=>/-PLAN\.md$/.test(f)).sort();const extra=plans.filter(p=>!/^04-0[1-8]-PLAN\.md$/.test(p));if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.join(','));process.exit(1)}if(plans.length!==8){console.error('PLAN COUNT '+plans.length+' expected 8');process.exit(1)}if(!fs.readdirSync(dir).some(f=>f==='04-08-SUMMARY.md')){console.error('MISSING 04-08 SUMMARY');process.exit(1)}console.log('OK 04-01..04-07 untouched; plans '+plans.join(','))"
@@ -410,7 +410,7 @@ Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 /
- `## 处置` 定稿,明写:本步只读、生产代码零改动、唯一交付物、相对只读起始基线零变化、**第二步未规划**(D-40)、范围限 `packages-user` 消费者面(`packages/` 与 `src/` 仅报告)、**D 类须用户反馈后才可能进入第二步**、**E 类归用户自行适配新加载系统(D-39 / D-41)**、**`material/` 内命中归用户(D-42)**、`tileType` / `renderWithoutCheck` 替代 / 自建 processor 由用户裁决、D-12 / D-18 / D-19 / D-22 边界、`REND-01` / `REND-02` 保持 Pending,并(如与规划日不同)说明实测差异
- - 六道门禁全部转绿:只读门禁(porcelain 逐条一致)、类型门禁一致性(错误总行数等于记录值)、类别门禁(A ≥ 5 / B ≥ 4 / C ≥ 8 / D ≥ 6 / E ≥ 4 / F ≥ 8 且每行双锚点与关键词齐备)、符号零命中复验、既有计划保护 + 计划范围门禁(`04-01..04-07-*` 未被改写、无 `04-09+`、`04-08-SUMMARY.md` 已落盘)、CRLF 门禁
+ - 六道门禁全部转绿:只读门禁(porcelain 逐条一致;同时覆盖 `.planning/REQUIREMENTS.md` 并断言 `REND-01` / `REND-02` 行仍为 `Pending`)、类型门禁一致性(错误总行数等于记录值)、类别门禁(A ≥ 5 / B ≥ 4 / C ≥ 8 / D ≥ 6 / E ≥ 4 / F ≥ 8 且每行双锚点与关键词齐备;C 行均交叉引用 A / B / D 记录 ID)、符号零命中复验、既有计划保护 + 计划范围门禁(`04-01..04-07-*` 未被改写、无 `04-09+`、`04-08-SUMMARY.md` 已落盘)、CRLF 门禁
- 人工代码复核结论已记录(抽查 A 类 3 处 `BlockCls` import 与 `hero.ts:167` / `vertex.ts:852`、D 类 3 处 `renderWithoutCheck` 与 `material/utils.ts:2`、E 类 `fallback/load.ts` 9 处),偏差均已修正
- `04-08-SUMMARY.md` 已产出,如实记录当日实测值(porcelain / 类型错误数)、A–F 实际行数与关键结论、六道门禁结果、人工复核结论、与规划日的差异说明、`REND-01` / `REND-02` 仍 Pending 与「第二步未规划」;**未写入任何被删接口的替代方案**
- 提交仅含 `04-TEXTURE-INTERFACE-IMPACT.md`、`04-08-PLAN.md`、`04-08-SUMMARY.md` 三个文件;未纳入任何生产源码改动,未提交 `04-CONTEXT.md` 的既有未提交改动,未改写 `04-01..04-07-*`
@@ -454,7 +454,7 @@ Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 /
-- **只读门禁**:`node -e "const{execSync}=require('child_process');console.log(JSON.stringify(execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})))"` 的输出与交付文档 `## 方法` 的「只读起始基线」条目逐条一致(不新增、不消失、状态码不变)。
+- **只读门禁**:`node -e "const{execSync}=require('child_process');console.log(JSON.stringify(execSync('git status --porcelain -- packages packages-user src',{encoding:'utf8'})))"` 的输出与交付文档 `## 方法` 的「只读起始基线」条目逐条一致(不新增、不消失、状态码不变);并断言 `.planning/REQUIREMENTS.md` 的 `REND-01` / `REND-02` 行仍为 `Pending`(该文件亦纳入只读 porcelain 集合)。
- **类型门禁一致性**:`node -e "const{execSync}=require('child_process');let o='';try{o=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<28,stdio:['ignore','pipe','pipe']})}catch(e){o=(e.stdout||'')+(e.stderr||'')}console.log((o.match(/error TS\d+/g)||[]).length)"` 的输出等于文档记录的「错误总行数」(规划日 202);「texture 归属错误行数」(规划日 23 = `client-base` 1 + `client-modules` 22)为规划日人工归类、执行日按当日实测重新归类,只要求文档逐行列出(**非机器断言**,由人工复核保证)。
- **类别门禁**:交付文档的 A ≥ 5 行 / B ≥ 4 行 / C ≥ 8 行 / D ≥ 6 行 / E ≥ 4 行 / F ≥ 8 条,A–E 每行 ≥2 个 `file:line` 锚点,A 覆盖符号级关键词,B 覆盖新增成员与签名关键词,C 覆盖 8 个定位文件并携带交叉引用 ID,D 覆盖关键被删成员与 `material/utils.ts` 且含「请用户反馈」措辞,E 包含加载文件与对照扫描结论并含「仅报告」措辞。
- **符号零命中复验**:`git grep -n -- -- packages-user`(`MaterialManager` / `renderAnimatedWith` / `getBlockClsByAlias` / `getIdentifierByAlias` / `getAliasByIdentifier`)逐一无输出。
@@ -475,6 +475,6 @@ Create `.planning/phases/04-render-adaptation/04-08-SUMMARY.md` when done
| `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md` | 交付文档(**唯一** `files_modified`) | `d36ea69` TextureManager 基准在 `packages-user` 消费者面的只读影响台账:元信息块 + 背景 / 方法 / 基准变更(d36ea69)/ 类型门禁实测基线 / A 符号改名与移除的引用点 / B 成员与签名变化的调用点 / C 地图渲染消费者定位索引 / D 被删且无替代(需用户反馈)/ E 加载相关与添加素材(仅报告)/ F 未能从阅读确定(未猜测)/ 处置 |
| `.planning/phases/04-render-adaptation/04-08-PLAN.md` | 计划簿记 | 本计划(编号 04-08;`wave: 7`;`depends_on` 04-01..04-07) |
| `.planning/phases/04-render-adaptation/04-08-SUMMARY.md` | 执行簿记 | 当日实测值(porcelain / `pnpm exec vue-tsc --noEmit` 错误数)、A–F 实际行数与关键结论、六道门禁结果、人工复核结论、与规划日的差异说明、`REND-01` / `REND-02` 仍 Pending 与「第二步未规划」 |
-| 门禁结果(会话内可复跑) | 验证证据 | 只读门禁(porcelain 逐条一致)、类型门禁一致性(错误总行数等于记录值)、类别门禁(A ≥ 5 / B ≥ 4 / C ≥ 8 / D ≥ 6 / E ≥ 4 / F ≥ 8 与双锚点 / 关键词 / 措辞)、符号零命中复验、既有计划保护 + 计划范围门禁(`04-01..04-07-*` 未改写、无 `04-09+`、SUMMARY 已落盘)、CRLF 门禁 |
+| 门禁结果(会话内可复跑) | 验证证据 | 只读门禁(porcelain 逐条一致;覆盖 `.planning/REQUIREMENTS.md` 并断言 `REND-01` / `REND-02` 仍为 `Pending`)、类型门禁一致性(错误总行数等于记录值)、类别门禁(A ≥ 5 / B ≥ 4 / C ≥ 8 / D ≥ 6 / E ≥ 4 / F ≥ 8 与双锚点 / 关键词 / 措辞)、符号零命中复验、既有计划保护 + 计划范围门禁(`04-01..04-07-*` 未改写、无 `04-09+`、SUMMARY 已落盘)、CRLF 门禁 |
| 显式**不**产出 | 边界声明 | 任何生产代码改动、任何测试文件、任何依赖变更、任何修复方案或第二步实施计划(D-40:第二步待用户审阅本文件后另行规划)、任何针对 `material/` 与加载面的修改方案(D-39 / D-41 / D-42) |
From e78309b3266a33915e7a36799ba0df9c74f5d3a2 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Wed, 23 Sep 2026 11:18:07 +0800
Subject: [PATCH 075/173] docs(04): plan TextureManager interface impact study
(04-08)
---
.planning/STATE.md | 10 +++++-----
.planning/state.json | 6 +++---
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 7e3fdd9..e091a49 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -3,16 +3,16 @@ gsd_state_version: "1.0"
milestone: v1.0
current_phase: 04
current_phase_name: 渲染适配与双布局
-status: verifying
+status: executing
stopped_at: Completed 04-07-PLAN.md(material 接口适应实施:big-image 全删、7 处改 getTile、四实现类构造器注入 state、cache.ts legacy 路径删除、补 @user/data-state 声明;material 诊断 19→0、总行数 199→180;REND-01/REND-02 保持 Pending)
-last_updated: "2026-09-22T04:25:51.526Z"
+last_updated: "2026-09-23T03:17:38.728Z"
last_activity: 2026-09-21
last_activity_desc: Phase 04 execution started
-state_head: 1bb42f66ca04d118a893c5b5222ea7927e145e04
+state_head: b4c8482f874dd710f97a91f854283fe670734ecb
progress:
total_phases: 7
completed_phases: 1
- total_plans: 78
+ total_plans: 79
completed_plans: 77
milestone_name: milestone
---
@@ -30,7 +30,7 @@ See: .planning/PROJECT.md (updated 2026-09-17)
Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
Plan: 7 of 7
-Status: Phase complete — ready for verification
+Status: Ready to execute
Last activity: 2026-09-21 — Completed quick task 260921-lwa: Upgrade toolchain to pnpm 12.5.1 and Node 24
Progress: [█████░░░░░] 50%
diff --git a/.planning/state.json b/.planning/state.json
index 04bbf9f..dff5ede 100644
--- a/.planning/state.json
+++ b/.planning/state.json
@@ -41,8 +41,8 @@
],
"next": {
"command": "/gsd:progress --next",
- "label": "Advance to the next step (verify)",
- "reason": "Phase 04 of 7 · ready to verify"
+ "label": "Advance to the next step",
+ "reason": "Phase 04 of 7 · executing"
},
- "updated_at": "2026-09-22T04:17:12.336Z"
+ "updated_at": "2026-09-23T03:17:38.824Z"
}
From 7743795948b0c0649981aca32496ccfb37eb8341 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Wed, 23 Sep 2026 13:09:19 +0800
Subject: [PATCH 076/173] refactor: Hero equipment system
---
packages-user/data-base/src/hero/attribute.ts | 48 +----
.../data-base/src/hero/equipStore.ts | 197 ++++++++++++------
packages-user/data-base/src/hero/equipment.ts | 4 +-
packages-user/data-base/src/hero/modifier.ts | 42 +++-
packages-user/data-base/src/hero/types.ts | 52 ++++-
packages-user/data-common/src/replay/func.ts | 2 +-
6 files changed, 225 insertions(+), 120 deletions(-)
diff --git a/packages-user/data-base/src/hero/attribute.ts b/packages-user/data-base/src/hero/attribute.ts
index 4e9a12d..01b1d0d 100644
--- a/packages-user/data-base/src/hero/attribute.ts
+++ b/packages-user/data-base/src/hero/attribute.ts
@@ -1,53 +1,14 @@
import { logger } from '@motajs/common';
-import { SaveCompression } from '@user/data-common';
+import { SaveCompression, shouldReplay } from '@user/data-common';
import {
IHeroAttribute,
IHeroAttributeCloneOption,
IHeroAttributeSave,
IHeroModifier,
- IHeroModifierOwner,
IModifierStateSave
} from './types';
import { isNil } from 'lodash-es';
-export abstract class BaseHeroModifier implements IHeroModifier {
- abstract readonly type: string;
- abstract readonly priority: number;
-
- owner: IHeroModifierOwner | null = null;
-
- constructor(private currentValue: V) {}
-
- get value(): V {
- return this.currentValue;
- }
-
- setValue(value: V): void {
- this.currentValue = value;
- this.owner?.markModifierDirty(this);
- }
-
- getValue(): V {
- return this.currentValue;
- }
-
- bindAttribute(attribute: IHeroModifierOwner | null): void {
- this.owner = attribute;
- }
-
- saveState(_compression: SaveCompression): V {
- return this.currentValue;
- }
-
- loadState(state: V, _compression: SaveCompression): void {
- this.setValue(state);
- }
-
- abstract modify(value: T, baseValue: T, name: string): T;
-
- abstract clone(): IHeroModifier;
-}
-
export class HeroAttribute implements IHeroAttribute {
/** 当前勇士属性修饰器 */
private readonly modifier: Map = new Map();
@@ -142,21 +103,25 @@ export class HeroAttribute implements IHeroAttribute {
//#region 属性操作
+ @shouldReplay('Hero attribute change should be replayed.')
set(name: K, value: THero[K]): void {
this.attribute[name] = value;
this.markDirty(name);
}
+ @shouldReplay('Hero attribute change should be replayed.')
add(name: SelectKey, value: number): void {
(this.attribute[name] as number) += value;
this.markDirty(name);
}
+ @shouldReplay('Hero attribute change should be replayed.')
mul(name: SelectKey, value: number): void {
(this.attribute[name] as number) *= value;
this.markDirty(name);
}
+ @shouldReplay('Hero attribute change should be replayed.')
div(name: SelectKey, value: number): void {
(this.attribute[name] as number) /= value;
this.markDirty(name);
@@ -187,6 +152,7 @@ export class HeroAttribute implements IHeroAttribute {
return arr.indexOf(modifier);
}
+ @shouldReplay('Changing hero attrubute modifier should be replayed.')
addModifier(
name: K,
modifier: IHeroModifier,
@@ -210,6 +176,7 @@ export class HeroAttribute implements IHeroAttribute {
this.markDirty(name);
}
+ @shouldReplay('Changing hero attrubute modifier should be replayed.')
deleteModifier(
name: K,
modifier: IHeroModifier
@@ -227,6 +194,7 @@ export class HeroAttribute implements IHeroAttribute {
this.markDirty(name);
}
+ @shouldReplay('Changing hero attrubute modifier should be replayed.')
deleteModifierByIndex(
name: K,
index: number
diff --git a/packages-user/data-base/src/hero/equipStore.ts b/packages-user/data-base/src/hero/equipStore.ts
index e9253f9..6fbe81c 100644
--- a/packages-user/data-base/src/hero/equipStore.ts
+++ b/packages-user/data-base/src/hero/equipStore.ts
@@ -1,4 +1,9 @@
-import { IItemRawData, SaveCompression, IDataCommon } from '@user/data-common';
+import {
+ IItemRawData,
+ SaveCompression,
+ IDataCommon,
+ shouldReplay
+} from '@user/data-common';
import { isNil, maxBy } from 'lodash-es';
import { ValueModifier, PercentageModifier } from './modifier';
import {
@@ -8,63 +13,131 @@ import {
IHeroEquipsStore,
IEquipmentSorter,
IEquipmentSortHandler,
- IHeroEquipsStoreSave
+ IHeroEquipsStoreSave,
+ IEquipmentStateHooks
} from './types';
-import { logger } from '@motajs/common';
+import {
+ Hookable,
+ HookController,
+ IHookController,
+ logger
+} from '@motajs/common';
-export class EquipmentState implements IEquipmentState {
+type Modifiers = Map, IHeroModifier>;
+
+export class EquipmentState
+ extends Hookable>
+ implements IEquipmentState
+{
readonly item: IItemRawData;
- /** 装备产生的所有修饰器,每个元素为 [属性名, 修饰器] */
- readonly modifiers: [SelectKey, IHeroModifier][];
+ /** 数值修饰器列表 */
+ readonly valueModifiers: Modifiers = new Map();
+ /** 百分比修饰器列表 */
+ readonly perModifiers: Modifiers = new Map();
/** 装备的数值属性 */
- private readonly value: Map, number>;
+ private readonly value: Map, number> = new Map();
/** 装备的百分比属性 */
- private readonly percentage: Map, number>;
+ private readonly per: Map, number> = new Map();
+
+ /** 是否已经构建过修饰器 */
+ private built: boolean = false;
constructor(
readonly uid: number,
- item: IItemRawData,
- buildModifier: boolean = true
+ item: IItemRawData
) {
+ super();
this.item = item;
- this.modifiers = [];
const equip = item.equip;
- this.value = new Map(
- Object.entries(equip.value) as [SelectKey, number][]
- );
- this.percentage = new Map(
- Object.entries(equip.percentage) as [
- SelectKey,
- number
- ][]
- );
-
- if (buildModifier) {
- this.rebuildModifiers();
+ for (const [key, value] of Object.entries(equip.value)) {
+ this.value.set(key as SelectKey, value as number);
}
+ for (const [key, value] of Object.entries(equip.percentage)) {
+ this.per.set(key as SelectKey, value as number);
+ }
+ }
+
+ protected createController(
+ hook: Partial>
+ ): IHookController> {
+ return new HookController(this, hook);
}
/**
- * 从装备属性重新生成装备修饰器
+ * 触发修饰器变动钩子
*/
- private rebuildModifiers() {
- this.modifiers.length = 0;
-
- for (const [name, baseVal] of this.value) {
- this.modifiers.push([name, new ValueModifier(baseVal)]);
- }
- for (const [name, basePct] of this.percentage) {
- this.modifiers.push([name, new PercentageModifier(basePct)]);
- }
+ private notifyModifierChange() {
+ this.forEachHook(hook => hook.onChangeModifier?.(this.getModifiers()));
}
- getModifiers(): Iterable<
- [keyof SelectType, IHeroModifier]
+ @shouldReplay('Setting equipment value should be replayed.')
+ setValue(name: keyof SelectType, value: number): void {
+ if (value === 0) {
+ this.value.delete(name);
+ this.valueModifiers.delete(name);
+ } else {
+ this.value.set(name, value);
+ const modifier = this.valueModifiers.getOrInsertComputed(
+ name,
+ () => new ValueModifier(value, 0)
+ );
+ modifier.setValue(value);
+ }
+ this.notifyModifierChange();
+ }
+
+ @shouldReplay('Setting equipment percentage value should be replayed.')
+ setPercentage(name: keyof SelectType, value: number): void {
+ if (value === 0) {
+ this.per.delete(name);
+ this.perModifiers.delete(name);
+ } else {
+ this.per.set(name, value);
+ const modifier = this.perModifiers.getOrInsertComputed(
+ name,
+ () => new PercentageModifier(value, 10)
+ );
+ modifier.setValue(value);
+ }
+ this.notifyModifierChange();
+ }
+
+ getValue(name: keyof SelectType): number {
+ return this.value.get(name) ?? 0;
+ }
+
+ getPercentage(name: keyof SelectType): number {
+ return this.per.get(name) ?? 0;
+ }
+
+ buildModifiers() {
+ if (this.built) return;
+ this.built = true;
+
+ this.valueModifiers.clear();
+ this.perModifiers.clear();
+
+ for (const [name, baseVal] of this.value) {
+ this.valueModifiers.set(name, new ValueModifier(baseVal, 0));
+ }
+ for (const [name, basePct] of this.per) {
+ this.perModifiers.set(name, new PercentageModifier(basePct, 10));
+ }
+
+ this.notifyModifierChange();
+ }
+
+ *getModifiers(): Iterable<
+ [SelectKey, IHeroModifier]
> {
- return this.modifiers;
+ if (!this.built) {
+ this.buildModifiers();
+ }
+ yield* this.valueModifiers;
+ yield* this.perModifiers;
}
/**
@@ -76,7 +149,7 @@ export class EquipmentState implements IEquipmentState {
uid: this.uid,
num: this.item.num,
value: new Map(this.value),
- percentage: new Map(this.percentage)
+ percentage: new Map(this.per)
};
}
@@ -94,7 +167,7 @@ export class EquipmentState implements IEquipmentState {
}
}
const perDiff = new Map, number>();
- for (const [name, equipPer] of this.percentage) {
+ for (const [name, equipPer] of this.per) {
const base = percentage[name];
if (base !== equipPer) {
perDiff.set(name, equipPer);
@@ -122,14 +195,14 @@ export class EquipmentState implements IEquipmentState {
*/
private loadNoCompression(state: IEquipmentStateSave): void {
this.value.clear();
- this.percentage.clear();
+ this.per.clear();
for (const [name, value] of state.value) {
this.value.set(name, value);
}
for (const [name, value] of state.percentage) {
- this.percentage.set(name, value);
+ this.per.set(name, value);
}
- this.rebuildModifiers();
+ this.buildModifiers();
}
/**
@@ -138,7 +211,7 @@ export class EquipmentState implements IEquipmentState {
*/
private loadDiff(state: IEquipmentStateSave): void {
this.value.clear();
- this.percentage.clear();
+ this.per.clear();
// 基准为装备原始定义,再叠加存档中的差异条目
for (const [name, value] of Object.entries(
@@ -149,7 +222,7 @@ export class EquipmentState implements IEquipmentState {
for (const [name, value] of Object.entries(
this.item.equip.percentage
)) {
- this.percentage.set(name as SelectKey, value);
+ this.per.set(name as SelectKey, value);
}
// 差异内容
@@ -157,10 +230,10 @@ export class EquipmentState implements IEquipmentState {
this.value.set(name, value);
}
for (const [name, value] of state.percentage) {
- this.percentage.set(name, value);
+ this.per.set(name, value);
}
- this.rebuildModifiers();
+ this.buildModifiers();
}
loadState(
@@ -192,6 +265,7 @@ export class HeroEquipsStore implements IHeroEquipsStore {
this.state = state;
}
+ @shouldReplay('Adding equipment to equip store should be replayed.')
add(item: number | string): number {
const num = this.state.tileStore.num(item);
if (isNil(num)) return -1;
@@ -202,9 +276,11 @@ export class HeroEquipsStore implements IHeroEquipsStore {
const uid = this.nextUid++;
const state = new EquipmentState(uid, raw);
this.instanceMap.set(uid, state);
+ state.buildModifiers();
return uid;
}
+ @shouldReplay('Deleting equipment from equip store should be replayed.')
delete(uid: number): void {
this.instanceMap.delete(uid);
}
@@ -279,34 +355,16 @@ export class HeroEquipsStore implements IHeroEquipsStore {
state: IHeroEquipsStoreSave,
compression: SaveCompression
): void {
- // 按 uid 复用现有实例原地读档,使外部持有的引用跨读档仍然有效;
- // 仅当 uid 相同但装备图块数字不同时才替换实例
- const savedUids = new Set();
+ this.instanceMap.clear();
for (const save of state.equipments) {
const raw = this.state.itemStore.getData(save.num);
if (!raw) {
logger.error(59, save.num.toString());
continue;
}
- savedUids.add(save.uid);
- const existing = this.instanceMap.get(save.uid);
- if (existing && existing.item.num === save.num) {
- existing.loadState(save, compression);
- } else {
- const instance = new EquipmentState(
- save.uid,
- raw,
- false
- );
- instance.loadState(save, compression);
- this.instanceMap.set(save.uid, instance);
- }
- }
- // 以存档为准:存档中不存在的装备实例一律删除
- for (const uid of this.instanceMap.keys()) {
- if (!savedUids.has(uid)) {
- this.instanceMap.delete(uid);
- }
+ const ins = new EquipmentState(save.uid, raw);
+ ins.loadState(save, compression);
+ this.instanceMap.set(save.uid, ins);
}
if (state.equipments.length === 0) {
this.nextUid = 0;
@@ -314,9 +372,10 @@ export class HeroEquipsStore implements IHeroEquipsStore {
const maxUid = maxBy(state.equipments, 'uid');
if (!maxUid) {
logger.error(58);
- return;
+ this.nextUid = 0;
+ } else {
+ this.nextUid = maxUid.uid + 1;
}
- this.nextUid = maxUid.uid + 1;
}
}
}
diff --git a/packages-user/data-base/src/hero/equipment.ts b/packages-user/data-base/src/hero/equipment.ts
index 6b16472..42e7386 100644
--- a/packages-user/data-base/src/hero/equipment.ts
+++ b/packages-user/data-base/src/hero/equipment.ts
@@ -1,5 +1,5 @@
import { isNil } from 'lodash-es';
-import { IDataCommon, ReplayCode } from '@user/data-common';
+import { IDataCommon, ReplayCode, shouldReplay } from '@user/data-common';
import {
EquipStatus,
IEquipmentState,
@@ -150,6 +150,7 @@ export class HeroEquipment implements IHeroEquipment {
}
}
+ @shouldReplay('Equiping equipment should be replayed.')
equip(
uid: number,
slot: number | string,
@@ -197,6 +198,7 @@ export class HeroEquipment implements IHeroEquipment {
return curr;
}
+ @shouldReplay('Unequiping equipment should be replayed.')
unequip(slot: number): number | undefined {
const uid = this.equips.get(slot);
if (isNil(uid)) return void 0;
diff --git a/packages-user/data-base/src/hero/modifier.ts b/packages-user/data-base/src/hero/modifier.ts
index f8eba54..0e99e34 100644
--- a/packages-user/data-base/src/hero/modifier.ts
+++ b/packages-user/data-base/src/hero/modifier.ts
@@ -1,5 +1,43 @@
-import { BaseHeroModifier } from './attribute';
-import { IHeroModifier } from './types';
+import { SaveCompression } from '@user/data-common';
+import { IHeroModifier, IHeroModifierOwner } from './types';
+
+export abstract class BaseHeroModifier implements IHeroModifier {
+ abstract readonly type: string;
+ abstract readonly priority: number;
+
+ owner: IHeroModifierOwner | null = null;
+
+ constructor(private currentValue: V) {}
+
+ get value(): V {
+ return this.currentValue;
+ }
+
+ setValue(value: V): void {
+ this.currentValue = value;
+ this.owner?.markModifierDirty(this);
+ }
+
+ getValue(): V {
+ return this.currentValue;
+ }
+
+ bindAttribute(attribute: IHeroModifierOwner | null): void {
+ this.owner = attribute;
+ }
+
+ saveState(_compression: SaveCompression): V {
+ return this.currentValue;
+ }
+
+ loadState(state: V, _compression: SaveCompression): void {
+ this.setValue(state);
+ }
+
+ abstract modify(value: T, baseValue: T, name: string): T;
+
+ abstract clone(): IHeroModifier;
+}
export class ValueModifier extends BaseHeroModifier {
readonly type = '@system/value';
diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts
index 54abe15..036c620 100644
--- a/packages-user/data-base/src/hero/types.ts
+++ b/packages-user/data-base/src/hero/types.ts
@@ -662,18 +662,56 @@ export interface IHeroEquipsStoreSave {
readonly equipments: readonly IEquipmentStateSave[];
}
-/**
- * 装备实例。其修饰器由装备自身加成重建而来,是派生视图:
- * 跨读档不保留旧的修饰器对象引用属预期行为,读档后它们会被重新创建
- */
-export interface IEquipmentState extends ISaveableContent<
- IEquipmentStateSave
-> {
+export interface IEquipmentStateHooks extends IHookBase {
+ /**
+ * 当装备的修饰器发生变化时执行,当发生修饰器增加、减少、数值修改时都会触发
+ * @param modifiers 当前装备所拥有的所有修饰器
+ */
+ onChangeModifier?(
+ modifiers: Iterable<[SelectKey, IHeroModifier]>
+ ): void;
+}
+
+export interface IEquipmentState
+ extends
+ ISaveableContent>,
+ IHookable> {
/** 装备实例 uid */
readonly uid: number;
/** 装备的定义数据引用 */
readonly item: IItemRawData;
+ /**
+ * 设置此装备对指定勇士属性值的增加量
+ * @param name 勇士属性名
+ * @param value 要将装备的此属性增量设置为的值
+ */
+ setValue(name: SelectKey, value: number): void;
+
+ /**
+ * 设置此装备对指定勇士属性值的百分比增加量
+ * @param name 勇士属性名
+ * @param value 要将装备的此属性百分比增量设置为的值
+ */
+ setPercentage(name: SelectKey, value: number): void;
+
+ /**
+ * 获取装备对指定勇士属性值的增加量,若装备不增加对应属性,则返回 `0`
+ * @param name 勇士属性名
+ */
+ getValue(name: SelectKey): number;
+
+ /**
+ * 获取装备对指定勇士属性值的百分比增加量,若装备不增加对应属性,则返回 `0`
+ * @param name 勇士属性名
+ */
+ getPercentage(name: SelectKey): number;
+
+ /**
+ * 构建所有的修饰器,每个装备实例只需要进行一次构建
+ */
+ buildModifiers(): void;
+
/**
* 获取装备产生的所有修饰器,每个元素为 [属性名, 修饰器]
*/
diff --git a/packages-user/data-common/src/replay/func.ts b/packages-user/data-common/src/replay/func.ts
index 4c1c623..3dd1428 100644
--- a/packages-user/data-common/src/replay/func.ts
+++ b/packages-user/data-common/src/replay/func.ts
@@ -18,7 +18,7 @@ interface IReplaySafetyDetailQueue {
type ReplayMethod = (
this: This,
...args: Args
-) => Return;
+) => Return | any;
type ReplayDecorator = (
method: ReplayMethod,
From d74cc35c8e3e974852a2e81a289d2859cde4dcf5 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Wed, 23 Sep 2026 13:09:43 +0800
Subject: [PATCH 077/173] feat: Adding should replay flag
---
packages-user/data-base/src/flag/field.ts | 3 +
packages-user/data-base/src/flag/system.ts | 26 +--
packages-user/data-base/src/hero/items.ts | 5 +-
packages-user/data-base/src/hero/location.ts | 5 +-
packages-user/data-base/src/hero/mover.ts | 6 +-
.../data-base/src/map/dynamicTile.ts | 7 +-
packages-user/data-base/src/map/eventView.ts | 3 +
packages-user/data-base/src/map/gameMap.ts | 7 +-
packages-user/data-base/src/map/mapLayer.ts | 20 ++-
packages-user/data-base/src/map/mapState.ts | 166 ++----------------
packages-user/data-base/src/map/mover.ts | 4 +-
packages-user/data-base/src/map/staticTile.ts | 4 +-
packages-user/data-base/src/map/tile.ts | 4 +-
.../data-system/src/combat/combat.ts | 3 +
.../data-system/src/event/executor.ts | 5 +-
packages-user/data-system/src/path/system.ts | 4 +-
16 files changed, 86 insertions(+), 186 deletions(-)
diff --git a/packages-user/data-base/src/flag/field.ts b/packages-user/data-base/src/flag/field.ts
index b21e4aa..b6ecf5a 100644
--- a/packages-user/data-base/src/flag/field.ts
+++ b/packages-user/data-base/src/flag/field.ts
@@ -1,3 +1,4 @@
+import { shouldReplay } from '@user/data-common';
import { IFlagCommonField, IFlagSystem } from './types';
import { logger } from '@motajs/common';
@@ -16,10 +17,12 @@ export class FlagCommonField implements IFlagCommonField {
this.value = value;
}
+ @shouldReplay('Flag field value set should be replayed.')
set(value: T): void {
this.value = value;
}
+ @shouldReplay('Flag field value set should be replayed.')
add(value: number): void {
if (typeof this.value !== 'number') {
logger.warn(111, String(this.key));
diff --git a/packages-user/data-base/src/flag/system.ts b/packages-user/data-base/src/flag/system.ts
index d7d6dd8..b288d7e 100644
--- a/packages-user/data-base/src/flag/system.ts
+++ b/packages-user/data-base/src/flag/system.ts
@@ -1,5 +1,6 @@
import { IFlagCommonField, IFlagSystem, IFlagSystemSave } from './types';
import { FlagCommonField } from './field';
+import { shouldReplay } from '@user/data-common';
export class FlagSystem implements IFlagSystem {
private readonly fieldMap: Map> =
@@ -9,6 +10,7 @@ export class FlagSystem implements IFlagSystem {
return this.fieldMap.has(field);
}
+ @shouldReplay('Flag field set should be replayed.')
setField(field: PropertyKey, value: T): IFlagCommonField {
return this.getOrInsert(field, value);
}
@@ -34,6 +36,7 @@ export class FlagSystem implements IFlagSystem {
);
}
+ @shouldReplay('Flag field deleting should be replayed.')
deleteField(field: PropertyKey): void {
this.fieldMap.delete(field);
}
@@ -63,26 +66,11 @@ export class FlagSystem implements IFlagSystem {
}
loadState(state: IFlagSystemSave): void {
- // 按 key 复用现有字段原地写值,使外部持有的引用跨读档仍然有效
+ this.fieldMap.clear();
for (const [key, data] of state.fields) {
- const existing = this.fieldMap.get(key);
- if (existing) {
- existing.fromStructured(data);
- } else {
- const field = new FlagCommonField(
- this,
- key,
- undefined
- );
- field.fromStructured(data);
- this.fieldMap.set(key, field);
- }
- }
- // 以存档为准:存档中不存在的字段一律删除
- for (const key of this.fieldMap.keys()) {
- if (!state.fields.has(key)) {
- this.fieldMap.delete(key);
- }
+ const field = new FlagCommonField(this, key, void 0);
+ field.fromStructured(data);
+ this.fieldMap.set(key, field);
}
}
}
diff --git a/packages-user/data-base/src/hero/items.ts b/packages-user/data-base/src/hero/items.ts
index d3145be..5143dc7 100644
--- a/packages-user/data-base/src/hero/items.ts
+++ b/packages-user/data-base/src/hero/items.ts
@@ -3,7 +3,8 @@ import {
IDataCommon,
ItemCategory,
ReplayCode,
- SaveCompression
+ SaveCompression,
+ shouldReplay
} from '@user/data-common';
import { HeroEquipsStore } from './equipStore';
import {
@@ -75,6 +76,7 @@ export class HeroItems implements IHeroItems {
return this.getItemState(item)?.count ?? 0;
}
+ @shouldReplay('Adding item to hero should be replayed.')
addItem(item: number | string, count: number = 1): void {
const num = this.resolveNum(item);
if (isNil(num)) return;
@@ -113,6 +115,7 @@ export class HeroItems implements IHeroItems {
this.addItem(item, 1);
}
+ @shouldReplay('Using item should be replayed.')
useItem(item: number | string): boolean {
const state = this.internalGetItemState(item);
if (!state) return false;
diff --git a/packages-user/data-base/src/hero/location.ts b/packages-user/data-base/src/hero/location.ts
index caca751..1af9ec0 100644
--- a/packages-user/data-base/src/hero/location.ts
+++ b/packages-user/data-base/src/hero/location.ts
@@ -7,7 +7,8 @@ import {
import {
FaceDirection,
IDataCommon,
- IFacedTileLocator
+ IFacedTileLocator,
+ shouldReplay
} from '@user/data-common';
import { HeroMover } from './mover';
import {
@@ -50,12 +51,14 @@ export class HeroLocation
return new HookController(this, hook);
}
+ @shouldReplay('Setting hero floor should be replayed.')
setFloor(map: IGameMap): void {
this.floorId = map.floorId;
this.map = map;
this.forEachHook(hook => hook.onSetFloor?.(map));
}
+ @shouldReplay('Setting hero position should be replayed.')
setPos(x: number, y: number): void {
this.x = x;
this.y = y;
diff --git a/packages-user/data-base/src/hero/mover.ts b/packages-user/data-base/src/hero/mover.ts
index 8e3f10e..7e1c913 100644
--- a/packages-user/data-base/src/hero/mover.ts
+++ b/packages-user/data-base/src/hero/mover.ts
@@ -6,7 +6,8 @@ import {
ObjectMover,
ObjectMoveType,
IDataCommon,
- ReplayCode
+ ReplayCode,
+ shouldReplay
} from '@user/data-common';
import {
HeroMoveCode,
@@ -152,6 +153,7 @@ export class HeroMover
protected async onMoveEnd(): Promise {}
+ @shouldReplay('Hero moving step should be replayed.')
protected async onStepStart(
step: Readonly,
tile: IHeroLocation
@@ -238,6 +240,7 @@ export class HeroMover
return HeroMoveCode.Step;
}
+ @shouldReplay('Hero moving step should be replayed.')
protected async onStepEnd(
code: number,
step: Readonly,
@@ -279,6 +282,7 @@ export class HeroMover
return { x: tile.x, y: tile.y };
}
+ @shouldReplay('Hero moving step should be replayed.')
protected async onStepSettled(
before: ITileLocator,
curr: ITileLocator,
diff --git a/packages-user/data-base/src/map/dynamicTile.ts b/packages-user/data-base/src/map/dynamicTile.ts
index f15941f..625b4df 100644
--- a/packages-user/data-base/src/map/dynamicTile.ts
+++ b/packages-user/data-base/src/map/dynamicTile.ts
@@ -3,7 +3,8 @@ import {
FaceDirection,
IMoverController,
IObjectMover,
- ITileRawData
+ ITileRawData,
+ shouldReplay
} from '@user/data-common';
import {
IDynamicBlockSave,
@@ -53,6 +54,7 @@ export class DynamicTile
return this.tileRaw;
}
+ @shouldReplay('Setting dynamic block num should be replayed.')
set(num: number): void {
this.tileNum = num;
const data = this.state.tileStore.getData(num);
@@ -65,6 +67,7 @@ export class DynamicTile
this.restoreDefaultEvents();
}
+ @shouldReplay('Setting dynamic block position should be replayed.')
setPos(x: number, y: number): void {
this.x = x;
this.y = y;
@@ -81,10 +84,12 @@ export class DynamicTile
}
}
+ @shouldReplay('Transfering dynamic tile to static should be replayed.')
toStatic(): IStaticTile | null {
return this.layer.transferToStatic(this);
}
+ @shouldReplay('Transfering dynamic tile to static should be replayed.')
toStaticIfSafe(): IStaticTile | null {
return this.layer.transferToStaticIfSafe(this);
}
diff --git a/packages-user/data-base/src/map/eventView.ts b/packages-user/data-base/src/map/eventView.ts
index 40af7d1..cd6fbc0 100644
--- a/packages-user/data-base/src/map/eventView.ts
+++ b/packages-user/data-base/src/map/eventView.ts
@@ -1,5 +1,6 @@
import { logger } from '@motajs/common';
import { ILayerEventView } from './types';
+import { shouldReplay } from '@user/data-common';
export class LayerEventView implements ILayerEventView {
/** 当前绑定的事件 */
@@ -43,6 +44,7 @@ export class LayerEventView implements ILayerEventView {
}
}
+ @shouldReplay('Setting layer event view should be replayed.')
set(priority: number, event: string): void {
if (this.store.has(priority)) {
logger.warn(136, priority.toString());
@@ -52,6 +54,7 @@ export class LayerEventView implements ILayerEventView {
this.updateDirtyEntry(priority, before);
}
+ @shouldReplay('Deleting layer event view should be replayed.')
delete(priority: number): void {
const before = this.isEntryDirty(priority);
this.store.delete(priority);
diff --git a/packages-user/data-base/src/map/gameMap.ts b/packages-user/data-base/src/map/gameMap.ts
index 024ad75..6b85008 100644
--- a/packages-user/data-base/src/map/gameMap.ts
+++ b/packages-user/data-base/src/map/gameMap.ts
@@ -19,7 +19,8 @@ import {
ILocationIndexer,
ITileStore,
MapLocIndexer,
- SaveCompression
+ SaveCompression,
+ shouldReplay
} from '@user/data-common';
import { MapLayer } from './mapLayer';
@@ -56,6 +57,7 @@ export class GameMap extends Hookable implements IGameMap {
this.indexer.setWidth(width);
}
+ @shouldReplay('Adding game map layer should be replayed.')
addLayer(): IMapLayer {
const array = new Uint32Array(this.width * this.height);
const layer = new MapLayer(array, this.width, this.height, this);
@@ -69,6 +71,7 @@ export class GameMap extends Hookable implements IGameMap {
return layer;
}
+ @shouldReplay('Removing game map layer should be replayed.')
removeLayer(layer: IMapLayer): void {
this.layerList.delete(layer as IResizableMapLayer);
const alias = this.layerAliasMap.get(layer);
@@ -109,6 +112,7 @@ export class GameMap extends Hookable implements IGameMap {
return this.layerAliasMap.get(layer);
}
+ @shouldReplay('Resizing game map layer should be replayed.')
resizeLayer(
width: number,
height: number,
@@ -141,6 +145,7 @@ export class GameMap extends Hookable implements IGameMap {
this.active = active;
}
+ @shouldReplay("Setting game map's event layer should be replayed.")
setEventLayer(layer: IMapLayer | null): void {
if (!layer) {
this.eventLayer = null;
diff --git a/packages-user/data-base/src/map/mapLayer.ts b/packages-user/data-base/src/map/mapLayer.ts
index 292cbbd..f32a33a 100644
--- a/packages-user/data-base/src/map/mapLayer.ts
+++ b/packages-user/data-base/src/map/mapLayer.ts
@@ -19,7 +19,8 @@ import {
FaceDirection,
IDataCommon,
IRoleFaceBinder,
- SaveCompression
+ SaveCompression,
+ shouldReplay
} from '@user/data-common';
import { DynamicTile } from './dynamicTile';
import { LayerEventView } from './eventView';
@@ -233,6 +234,7 @@ export class MapLayer
return x >= 0 && y >= 0 && x < this.width && y < this.height;
}
+ @shouldReplay('Setting map layer block should be replayed.')
setBlock(block: number, x: number, y: number): void {
if (!this.inMap(x, y)) return;
const index = y * this.width + x;
@@ -254,6 +256,7 @@ export class MapLayer
return this.mapArray[y * this.width + x];
}
+ @shouldReplay('Removing map layer block should be replayed.')
removeBlock(x: number, y: number): number {
if (!this.inMap(x, y)) {
return -1;
@@ -287,6 +290,7 @@ export class MapLayer
};
}
+ @shouldReplay('Setting map layer data should be replayed.')
putMapData(array: Uint32Array, x: number, y: number, width: number): void {
if (array.length % width !== 0) {
logger.warn(8);
@@ -394,6 +398,7 @@ export class MapLayer
return this.mapData;
}
+ @shouldReplay('Setting map layer block direction should be replayed.')
setStaticDirection(x: number, y: number, direction: FaceDirection): number {
const tile = this.getTile(x, y);
if (!tile) return -1;
@@ -415,6 +420,7 @@ export class MapLayer
//#region 动态图层操作
+ @shouldReplay('Creating dynamic tile should be replayed.')
createDynamic(num: number, x: number, y: number): IDynamicTile {
const tile = new DynamicTile(num, x, y, this);
const location = this.getLocationData(x, y);
@@ -432,6 +438,7 @@ export class MapLayer
return tile;
}
+ @shouldReplay('Transfering static tile to dynamic should be replayed.')
transferToDynamic(
x: number,
y: number,
@@ -457,6 +464,7 @@ export class MapLayer
return tile;
}
+ @shouldReplay('Transfering dynamic tile to static should be replayed.')
transferToStatic(
tile: IDynamicTile,
keepEvent: boolean = true
@@ -477,6 +485,7 @@ export class MapLayer
return this.getTile(x, y);
}
+ @shouldReplay('Transfering dynamic tile to static should be replayed.')
transferToStaticIfSafe(
tile: IDynamicTile,
keepEvent: boolean = true
@@ -495,6 +504,7 @@ export class MapLayer
return this.getTile(x, y);
}
+ @shouldReplay('Deleting dynamic tile should be replayed.')
async deleteDynamic(tile: IDynamicTile): Promise {
if (!this.posTileMap.has(tile)) {
logger.warn(130);
@@ -513,6 +523,7 @@ export class MapLayer
return this.posTileMap.keys();
}
+ @shouldReplay('Setting map layer block direction should be replayed.')
setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): number {
const numBefore = tile.num();
tile.setFaceDirection(direction);
@@ -524,6 +535,7 @@ export class MapLayer
return tile.num();
}
+ @shouldReplay('Updating dynamic tile position should be replayed.')
updateDynamicTile(tile: IDynamicTile): void {
const oldPos = this.posTileMap.get(tile);
if (oldPos) {
@@ -542,6 +554,7 @@ export class MapLayer
//#region 开关门
+ @shouldReplay('Opening door should be replayed.')
async openDoor(x: number, y: number): Promise {
const index = y * this.width + x;
const num = this.mapArray[index];
@@ -554,6 +567,7 @@ export class MapLayer
this.setBlock(0, x, y);
}
+ @shouldReplay('Closing door should be replayed.')
async closeDoor(num: number, x: number, y: number): Promise {
const index = y * this.width + x;
const nowNum = this.mapArray[index];
@@ -573,10 +587,12 @@ export class MapLayer
//#region 图层操作
+ @shouldReplay('Setting z-index of map layer should be replayed.')
setZIndex(zIndex: number): void {
this.zIndex = zIndex;
}
+ @shouldReplay('Setting role face binder of map layer should be replayed.')
setFaceBinder(binder: IRoleFaceBinder | null): void {
if (!binder) return;
this.faceBinder = binder;
@@ -616,6 +632,7 @@ export class MapLayer
return new MapLayerHookController(this, hook);
}
+ @shouldReplay('Resizing map layer should be replayed.')
resize(width: number, height: number): void {
if (this.width === width && this.height === height) {
return;
@@ -656,6 +673,7 @@ export class MapLayer
});
}
+ @shouldReplay('Resizing map layer should be replayed.')
resize2(width: number, height: number): void {
this.layerDirty = true;
this.pointEvents.clear();
diff --git a/packages-user/data-base/src/map/mapState.ts b/packages-user/data-base/src/map/mapState.ts
index d1ccf20..fe78b00 100644
--- a/packages-user/data-base/src/map/mapState.ts
+++ b/packages-user/data-base/src/map/mapState.ts
@@ -1,5 +1,9 @@
-import { uniq } from 'lodash-es';
-import { IDataCommon, IMapRawData, SaveCompression } from '@user/data-common';
+import {
+ IDataCommon,
+ IMapRawData,
+ SaveCompression,
+ shouldReplay
+} from '@user/data-common';
import { ITileStore } from '@user/data-common';
import {
IGameMap,
@@ -35,160 +39,9 @@ export class MapState implements IMapState {
public readonly state: IDataCommon
) {}
- /**
- * 判断原始数据中的值是否为可枚举的对象容器
- * @param value 待判断的运行时值
- */
- private isRecord(value: unknown): value is Record {
- return !!value && typeof value === 'object' && !Array.isArray(value);
- }
-
- /**
- * 判断字符串键是否能无歧义地转换为有限数字
- * @param key 原始对象键
- */
- private isNumericKey(key: string): boolean {
- return key.trim() !== '' && Number.isFinite(Number(key));
- }
-
- /**
- * 在创建地图前验证外部原始地图及事件结构
- * @param raw 待验证的楼层原始数据
- */
- private validateRaw(raw: IMapRawData): boolean {
- const rawMap: unknown = raw.map;
- const rawEvents: unknown = raw.events;
- const rawAliases: unknown = raw.layerAlias;
- if (!this.isRecord(rawMap)) {
- logger.error(63, 'map', raw.floorId);
- return false;
- }
- if (!this.isRecord(rawEvents)) {
- logger.error(63, 'events', raw.floorId);
- return false;
- }
- if (!this.isRecord(rawAliases)) {
- logger.error(63, 'layerAlias', raw.floorId);
- return false;
- }
- if (!Number.isInteger(raw.width) || raw.width <= 0) {
- logger.error(64, 'width', raw.floorId);
- return false;
- }
-
- let length = 0;
- for (const [zIndex, map] of Object.entries(rawMap)) {
- if (!this.isNumericKey(zIndex)) {
- logger.error(
- 62,
- 'layer',
- raw.floorId,
- 'IMapRawData.map',
- zIndex
- );
- return false;
- }
- if (!Array.isArray(map)) {
- logger.error(64, 'map layer', raw.floorId);
- return false;
- }
- if (
- !map.every(
- value =>
- typeof value === 'number' &&
- Number.isFinite(value) &&
- Number.isInteger(value) &&
- value >= 0
- )
- ) {
- logger.error(64, 'map value', raw.floorId);
- return false;
- }
- if (length > 0 && map.length !== length) {
- logger.error(
- 60,
- map.length.toString(),
- length.toString(),
- raw.floorId
- );
- return false;
- }
- length = map.length;
-
- const alias = rawAliases[zIndex];
- if (typeof alias !== 'string') {
- logger.error(64, 'layer alias', raw.floorId);
- return false;
- }
- const events = rawEvents[zIndex];
- if (!this.isRecord(events)) {
- logger.error(63, `events layer ${zIndex}`, raw.floorId);
- return false;
- }
- for (const [index, tileEvents] of Object.entries(events)) {
- if (!this.isNumericKey(index)) {
- logger.error(
- 62,
- 'event',
- raw.floorId,
- 'IMapRawData.events',
- index
- );
- return false;
- }
- const indexNum = Number(index);
- if (
- !Number.isInteger(indexNum) ||
- indexNum < 0 ||
- indexNum >= length
- ) {
- logger.error(64, 'event position', raw.floorId);
- return false;
- }
- if (!this.isRecord(tileEvents)) {
- logger.error(63, `events position ${index}`, raw.floorId);
- return false;
- }
- for (const [priority, id] of Object.entries(tileEvents)) {
- if (!this.isNumericKey(priority)) {
- logger.error(
- 62,
- 'event',
- raw.floorId,
- 'IMapRawData.events',
- priority
- );
- return false;
- }
- if (typeof id !== 'string') {
- logger.error(64, 'event id', raw.floorId);
- return false;
- }
- }
- }
- }
- if (length % raw.width !== 0) {
- logger.error(
- 61,
- length.toString(),
- raw.width.toString(),
- raw.floorId
- );
- return false;
- }
- for (const zIndex of Object.keys(rawEvents)) {
- if (!this.isNumericKey(zIndex) || !Object.hasOwn(rawMap, zIndex)) {
- logger.error(64, 'event layer', raw.floorId);
- return false;
- }
- }
- return true;
- }
-
//#region 楼层管理
fromRaw(raw: IMapRawData): IGameMap | null {
- if (!this.validateRaw(raw)) return null;
let length = 0;
const entries = Object.entries(raw.map);
for (const [_, map] of Object.entries(raw.map)) {
@@ -246,6 +99,7 @@ export class MapState implements IMapState {
return state;
}
+ @shouldReplay('Creating map should be replayed.')
createMap(id: string, width: number, height: number): IGameMap {
if (this.mapData.has(id)) {
logger.warn(121, id);
@@ -262,9 +116,10 @@ export class MapState implements IMapState {
return state;
}
+ @shouldReplay('Setting map list should be replayed.')
setMapList(maps: string[]): void {
this.maps.length = 0;
- this.maps.push(...uniq(maps));
+ this.maps.push(...maps);
}
getMap(id: string): IGameMap | null {
@@ -285,6 +140,7 @@ export class MapState implements IMapState {
this.areaList = areas;
}
+ @shouldReplay('Activating area should be replayed.')
activeArea(id: string): void {
const idx = this.maps.indexOf(id);
if (idx === -1) return;
@@ -293,6 +149,7 @@ export class MapState implements IMapState {
this.setAreaActive(area, true);
}
+ @shouldReplay('Deactivating area should be replayed.')
deactiveArea(id: string): void {
const idx = this.maps.indexOf(id);
if (idx === -1) return;
@@ -357,6 +214,7 @@ export class MapState implements IMapState {
return this.mapData.get(id)?.active ?? false;
}
+ @shouldReplay('Setting map active status should be replayed.')
setMapActiveStatus(id: string, active: boolean): void {
this.mapData.get(id)?.setActiveStatus(active);
}
diff --git a/packages-user/data-base/src/map/mover.ts b/packages-user/data-base/src/map/mover.ts
index aaf9891..bb3e49b 100644
--- a/packages-user/data-base/src/map/mover.ts
+++ b/packages-user/data-base/src/map/mover.ts
@@ -3,7 +3,8 @@ import {
getFaceMovement,
ObjectMover,
ObjectMoveStep,
- ObjectMoveType
+ ObjectMoveType,
+ shouldReplay
} from '@user/data-common';
import { IDynamicTile } from './types';
import { DYNAMIC_MOVER_FACE } from '../shared';
@@ -31,6 +32,7 @@ export class DynamicTileMover extends ObjectMover {
return Promise.resolve(DynamicMoveCode.Success);
}
+ @shouldReplay('Dynamic tile moving step should be replayed.')
protected onStepEnd(
code: number,
step: ObjectMoveStep,
diff --git a/packages-user/data-base/src/map/staticTile.ts b/packages-user/data-base/src/map/staticTile.ts
index 50b6960..27a6305 100644
--- a/packages-user/data-base/src/map/staticTile.ts
+++ b/packages-user/data-base/src/map/staticTile.ts
@@ -1,4 +1,4 @@
-import { ITileRawData } from '@user/data-common';
+import { ITileRawData, shouldReplay } from '@user/data-common';
import {
IDynamicTile,
IMapLayer,
@@ -24,11 +24,13 @@ export class StaticTile
return this.state.tileStore.getData(this.num());
}
+ @shouldReplay('Setting static tile num should be replayed.')
set(num: number): void {
this.layer.setBlock(num, this.locator.x, this.locator.y);
this.restoreDefaultEvents();
}
+ @shouldReplay('Transfering static tile to dynamic should be replayed.')
toDynamic(): IDynamicTile {
return this.layer.transferToDynamic(this.locator.x, this.locator.y)!;
}
diff --git a/packages-user/data-base/src/map/tile.ts b/packages-user/data-base/src/map/tile.ts
index b900dca..f882c76 100644
--- a/packages-user/data-base/src/map/tile.ts
+++ b/packages-user/data-base/src/map/tile.ts
@@ -4,7 +4,8 @@ import {
IDataCommon,
IDataCommonExtended,
ISaveableContent,
- ITileRawData
+ ITileRawData,
+ shouldReplay
} from '@user/data-common';
import { LayerEventView } from './eventView';
import {
@@ -52,6 +53,7 @@ export abstract class MapTileBase
eventView.markPure();
}
+ @shouldReplay('Setting map tile face direction should be replayed.')
setFaceDirection(direction: FaceDirection): number {
const cur = this.num();
const next = this.layer.faceBinder.getFaceOf(cur, direction);
diff --git a/packages-user/data-system/src/combat/combat.ts b/packages-user/data-system/src/combat/combat.ts
index 364947a..2316532 100644
--- a/packages-user/data-system/src/combat/combat.ts
+++ b/packages-user/data-system/src/combat/combat.ts
@@ -23,6 +23,7 @@ import {
IReadonlyEnemy,
IStateBase
} from '@user/data-base';
+import { shouldReplay } from '@user/data-common';
export class CombatFlow
extends Hookable>
@@ -193,6 +194,7 @@ export class CombatFlow
return damage;
}
+ @shouldReplay('Battle with enemy should be replayed.')
battle(
enemy: IEnemyView
): Promise | null> {
@@ -214,6 +216,7 @@ export class CombatFlow
return this.combatFlow(handler, eHandler);
}
+ @shouldReplay('Battle with enemy should be replayed.')
battleComputed(
enemy: IReadonlyEnemy
): Promise | null> {
diff --git a/packages-user/data-system/src/event/executor.ts b/packages-user/data-system/src/event/executor.ts
index fd1d565..906c777 100644
--- a/packages-user/data-system/src/event/executor.ts
+++ b/packages-user/data-system/src/event/executor.ts
@@ -1,5 +1,5 @@
import { logger } from '@motajs/common';
-import { IGameEventStore, ignoreReplay } from '@user/data-common';
+import { IGameEventStore, shouldReplay } from '@user/data-common';
import { AnonTokyoInterpreter } from '@motajs/anon-tokyo';
import {
EventExecuteMode,
@@ -52,8 +52,7 @@ export class EventExecutor implements IGameEventExecutor {
}
}
- // @ts-expect-error 泛型无法推导
- @ignoreReplay('事件一般由其他动作被动触发,不应计入录像')
+ @shouldReplay('Executing event should be replayed.')
async execute(
events: IGameEventInvocation[],
param: IBlockEventParam
diff --git a/packages-user/data-system/src/path/system.ts b/packages-user/data-system/src/path/system.ts
index 4e0f286..ad02076 100644
--- a/packages-user/data-system/src/path/system.ts
+++ b/packages-user/data-system/src/path/system.ts
@@ -1,5 +1,5 @@
import { ITileLocator, logger } from '@motajs/common';
-import { IObjectMovable, IObjectMover } from '@user/data-common';
+import { IObjectMovable, IObjectMover, shouldReplay } from '@user/data-common';
import { IStateBase } from '@user/data-base';
import { PathFinder } from './finder';
import {
@@ -79,12 +79,14 @@ export class PathfindingSystem implements IPathfindingSystem {
return result;
}
+ @shouldReplay('Moving hero by path finding system should be replayed.')
moveTo(target: ITileLocator): IPathfindingController | null {
const path = this.getPath(target);
if (path.status !== PathfindingStatus.Success) return null;
return this.startMove(path, false);
}
+ @shouldReplay('Teleporting hero by path finding system should be replayed.')
teleportTo(target: ITileLocator): IPathfindingController | null {
const path = this.getPath(target);
if (path.status !== PathfindingStatus.Success) return null;
From 04a43cbfaa368ae5616e28d5d59eaa99a91193cd Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Wed, 23 Sep 2026 13:13:48 +0800
Subject: [PATCH 078/173] =?UTF-8?q?docs(04-08):=20TextureManager=20?=
=?UTF-8?q?=E6=96=B0=E6=8E=A5=E5=8F=A3=E5=BD=B1=E5=93=8D=E8=8C=83=E5=9B=B4?=
=?UTF-8?q?=E6=B8=85=E7=82=B9=EF=BC=88=E5=8F=AA=E8=AF=BB=EF=BC=8CD-38/D-40?=
=?UTF-8?q?=20=E7=AC=AC=E4=B8=80=E6=AD=A5=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../04-render-adaptation/04-08-SUMMARY.md | 178 ++++++++++++
.../04-TEXTURE-INTERFACE-IMPACT.md | 268 ++++++++++++++++++
2 files changed, 446 insertions(+)
create mode 100644 .planning/phases/04-render-adaptation/04-08-SUMMARY.md
create mode 100644 .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
diff --git a/.planning/phases/04-render-adaptation/04-08-SUMMARY.md b/.planning/phases/04-render-adaptation/04-08-SUMMARY.md
new file mode 100644
index 0000000..029b68e
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-08-SUMMARY.md
@@ -0,0 +1,178 @@
+---
+phase: 04-render-adaptation
+plan: 8
+subsystem: render-texture
+tags: [texture-manager, autotile-processor, block-cls-removal, read-only-impact, d38-d42]
+requires:
+ - phase: 04-render-adaptation
+ provides: 04-01..04-07 前序增量(04-MATERIAL-INTERFACE-IMPACT.md / 04-07-SUMMARY.md 的版式、证据纪律与基线输入)
+provides:
+ - 04-TEXTURE-INTERFACE-IMPACT.md(`d36ea69` TextureManager 基准在 `packages-user` 消费者面的只读影响台账)
+ - A/B/C/D/E/F 六类分列(A13 B8 C8 D9 E7 F8),每行含命中处 + 基准侧双 `file:line` 锚点
+ - 旧名零命中机器证据(MaterialManager / renderAnimatedWith / getBlockClsByAlias / getIdentifierByAlias / getAliasByIdentifier)
+ - 只读起始基线(porcelain 点对点快照)+ 类型门禁一致性(错误总行数)
+affects: [04-render-adaptation]
+actuals:
+ tokens: 12513
+ tasks: 3
+ commits: 1
+ plan_head_before: d74cc35
+tech-stack:
+ added: []
+ patterns: [只读影响清点(D-40 第一步), A–F 六类固定列序 + 每行双 file:line 锚点, 符号零命中以 git grep 为机器证据]
+key-files:
+ created:
+ - .planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
+ modified: []
+key-decisions:
+ - "只读清点唯一交付物为 04-TEXTURE-INTERFACE-IMPACT.md;A–F 六类分列,A/B 事实层、C 定位索引、D 待用户反馈、E 归用户加载面、F 待用户裁决"
+ - "旧名 MaterialManager / renderAnimatedWith / getIdentifierByAlias / getAliasByIdentifier / getBlockClsByAlias 在 packages-user 源码面引用为 0(git grep 机器证据)"
+ - "material/utils.ts 的 BlockCls 依赖归 D 类「用户自有面」(D-42),只登记不规划修改"
+ - "texture 归属错误行数 23 与规划日一致;总错误行数由规划日 202 变为当日实测 263(差异源于用户并发数据端改动)"
+ - "第二步(TextureManager 新接口适应实施)未规划;未提供任何被删接口的替代方案"
+requirements-completed: []
+duration: ~45min
+completed: 2026-09-23
+status: complete
+---
+
+# Phase 4 Plan 8: TextureManager 新接口影响范围清点 Summary
+
+**以只读清点交付 `04-TEXTURE-INTERFACE-IMPACT.md`:把 `d36ea69` TextureManager 重构对 `packages-user` 消费者面的影响落成 A13/B8/C8/D9/E7/F8 六类台账(每行含命中处 + 基准侧双 `file:line` 锚点),旧名符号 `git grep` 零命中,六道门禁全绿;零生产代码改动。**
+
+## Performance
+
+- **Duration:** ~45min(含读取上下文;执行计时约 8min)
+- **Started:** 2026-09-23
+- **Completed:** 2026-09-23
+- **Tasks:** 3(Task 0 为只读汇报关卡,用户已回复「可以执行」,不计入工作任务)
+- **Files modified:** 1(唯一交付文档 `04-TEXTURE-INTERFACE-IMPACT.md`)
+
+## Accomplishments
+
+- 交付 `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`:元信息块 + 11 节(背景 / 方法 / 基准变更(d36ea69)/ 类型门禁实测基线 / A / B / C / D / E / F / 处置),全文 CRLF。
+- `## 基准变更(d36ea69)` 定稿三类差异:改名 1 类(`MaterialManager` → `TextureManager`,构造器收紧为 4 参);移除(`BlockCls` 枚举、`cls`→`tileType`、`IAutotileProcessor` 三方法合一、`renderAnimatedWith`→`renderAnimated`、`getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockCls(ByAlias)` / `clsMap`、`IClientBase.autotile`、`create()` / `createMaterial()` / `createAutotile`);新增 / 收紧(`tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount`、四个 `add*` 参数、`flatten`、`AutotileType`、`IClientCoreConfig` 两必填字段)。同提交相邻变更(`core.ts` config / `entry-client` / `logger.json`)一并登记。
+- A–F 六类横向补齐:A13(符号级;旧名 5 个源码面 0 引用 + `BlockCls` 3 import + `cls` 7 处读写 + `AutotileProcessor` 构造 1 处);B8(签名级,四个 `add*` 调用点全部归 E);C8(8 个定位文件,逐条交叉引用 A/B/D ID);D9(被删无替代,每条明写「请用户反馈」);E7(加载面仅报告 + `packages/` 零命中 + `src/` legacy 声明);F8(未确定项,只登记不判定)。
+- 类型门禁实测:**texture 归属 23 条**(与规划日一致);总错误行数 **263**(规划日 202;差异 +61 源于用户并发数据端改动)。`## 类型门禁实测基线` 逐行列出全部 23 条 texture 归属错误,并把「加载既有 15」「其余既有 225」单列,明写划分口径须用户确认。
+- 六道门禁全绿:只读基线 / 类型门禁一致性 / 类别门禁(A13 B8 C8 D9 E7 F8 + 双锚点 + 关键词 + 交叉引用 + 措辞)/ 符号零命中 / 既有计划保护 + 计划范围(`04-01..04-07-*` 未改写、无 `04-09+`、SUMMARY 落盘)/ CRLF。
+- 生产代码零改动:`packages/**` / `packages-user/**` / `src/**` 的 porcelain 相对只读起始基线**零变化**(本 run 未 touch 任何生产文件)。
+
+## Task Commits
+
+1. **Task 1+2+3: TextureManager 新接口影响范围清点(只读)** - `docs(04-08): TextureManager 新接口影响范围清点(只读,D-38/D-40 第一步)` (docs) — 仅含 `04-TEXTURE-INTERFACE-IMPACT.md`、`04-08-PLAN.md`、`04-08-SUMMARY.md` 三个文件
+
+_说明:按 04-08-PLAN 要求,Task 1(tracer)/ Task 2 为同一交付文档骨架与横向补齐的中间态,不单独提交;Task 3 定稿并完成全部门禁后一次原子提交三个规划文件。_
+
+## Files Created/Modified
+
+- `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md` - 唯一交付文档(11 节 / A13 B8 C8 D9 E7 F8 / 每行双锚点 / CRLF)
+
+## A–F 六类实际行数
+
+| 类别 | 实际行数 | 门禁下限 | 关键结论 |
+|---|---|---|---|
+| A 符号改名与移除的引用点 | 13 | ≥ 5 | `TextureManager` 新名消费者 2 处;5 个旧名源码面 0 引用(`git grep` 证据);`BlockCls` 3 处 import + `cls` 7 处读写;`AutotileProcessor` 构造 1 处 |
+| B 成员与签名变化的调用点 | 8 | ≥ 4 | `ITextureManager` 新增 5 成员 + `IClientCoreConfig` 2 必填字段;四个 `add*` 调用点全在 `fallback/load.ts`(归 E) |
+| C 地图渲染消费者定位索引 | 8 | ≥ 8 | 8 个定位文件,逐条交叉引用 A/B/D ID;未把 E 类或 `material/` 内命中写成 AI 工作项 |
+| D 被删且无替代(需用户反馈) | 9 | ≥ 6 | `renderWithoutCheck`(3 调用点)+ `BlockCls` + `manager` + `renderWith`/旧 `render` + `getIdentifierByAlias`/`getAliasByIdentifier` + `getBlockCls(ByAlias)` + `IClientBase.autotile` + `create()`/`createMaterial()` + `material/utils.ts`(用户自有面);每条含「请用户反馈」 |
+| E 加载相关与添加素材(仅报告) | 7 | ≥ 4 | `fallback/load.ts` 9 条 TS2554 逐一列出;`load/loader.ts` 6 条属加载系统重构既有(与 `d36ea69` 无关);`client.ts:93-96` 接线;`packages/` 零命中;`src/` legacy 声明 |
+| F 未能从阅读确定(未猜测) | 8 | ≥ 8 | 8 条未确定项(`renderWithoutCheck` 替代 / `BlockCls` 改用什么 / `hero.ts:167` 的 `tileType` / 自建 vs `ITextureManager.autotile` / `textures`·`tileStore`·`tiles` 边界 / `textures` 写入侧 / 插件面 / `IBlockIdentifier`→`ArrayLike`),只登记不判定 |
+
+## 类型门禁实测基线(当日实测,HEAD `d74cc35`)
+
+- 命令:`pnpm exec vue-tsc --noEmit`;口径:正则 `error TS\d+` 的匹配数
+- **错误总行数:263**
+- **texture 归属错误行数:23**(= `client-base` 1 条 `material/utils.ts:2` TS2305 + `client-modules` 22 条:`fallback/load.ts` 9 + `hero.ts` 2 + `renderer.ts` 5 + `vertex.ts` 6)
+- **与规划日(2026-09-22 / HEAD `d36ea69` / 干净工作树)差异**:规划日总行数 **202** → 当日 **263**,差 **+61**;**texture 归属仍为 23(无变化)**。差异来源 = 用户并发进行中的数据端未提交改动(`packages-user/data-base` / `data-common` / `data-system`)。
+- 三分类单列:texture 归属 **23**;加载系统重构既有 **15**(`load/loader.ts` 6 + `render/ui/load.tsx` 9);其余既有 **225**。该划分为人工归类,**须用户确认**,**不由机器门禁断言**。
+
+## 六道门禁结果(Task 3 终跑)
+
+| 门禁 | 结果 |
+|---|---|
+| ① 只读门禁(porcelain 逐条一致 + REQUIREMENTS 未改 + REND 仍 Pending) | PASS(entries 0;REND-01/REND-02 still Pending) |
+| ② 类型门禁一致性(重跑错误总行数 == 记录值) | PASS(tsc total unchanged 263) |
+| ③ 类别门禁(A≥5/B≥4/C≥8/D≥6/E≥4/F≥8 + 双锚点 + 关键词 + 交叉引用 + 措辞) | PASS(A13 B8 C8 D9 E7 F8) |
+| ④ 符号零命中复验(5 个旧名) | PASS(OK zero stale symbol references) |
+| ⑤ 既有计划保护 + 计划范围(`04-01..04-07-*` 未改、无 `04-09+`、SUMMARY 落盘) | PASS(04-01..04-07 untouched; plans 04-01..04-08) |
+| ⑥ CRLF | PASS(OK CRLF lines) |
+| (Task 1 附)结构 / 基准关键词 / 类型基线 / A 行双锚点 / CRLF / 只读基线 | 六道全 PASS |
+
+## 人工代码复核(Task 3 步骤 3,逐条打开当日源码)
+
+- A 类 `BlockCls` 三处 import:`renderer.ts:10` = ` BlockCls,`;`hero.ts:15` = `import { BlockCls, IMaterialFramedData } from '@user/client-base';`;`vertex.ts:27` 同形 —— **命中处锚点指向确为该符号的引用**,基准侧 `material/types.ts` 现行导出不含 `BlockCls` —— 通过。
+- `hero.ts:167` = ` cls: BlockCls.Unknown,`(`IMaterialFramedData` 对象字面量)—— 通过。
+- `vertex.ts:852` = ` const { cls, frames, offset, texture } = block.texture;`(自 `IMaterialFramedData` 解构 `cls`)—— 通过。
+- D 类 `renderWithoutCheck` 三处调用:`renderer.ts:1253` = `const renderable = this.autotile.renderWithoutCheck(`;`vertex.ts:461` = `const renderable = autotile.renderWithoutCheck(tile, connection);`;`vertex.ts:874` = `const renderable = this.renderer.autotile.renderWithoutCheck(` —— 均指向被删成员,接口上确无替代 —— 通过。
+- `material/utils.ts:2` = `import { BlockCls } from './types';`(用户自有面命中,只登记)—— 通过。
+- E 类 `fallback/load.ts` 9 处参数个数错误:`:76`/`:77` `addGrid` 期望 4 得 2;`:80`/`:81`/`:82`/`:83`/`:84` `addRowAnimate` 期望 4 得 3;`:95` `addAutotile` 期望 3 得 2;`:105` `addTileset` 期望 4 得 2 —— 通过。
+- 基准侧锚点核对:`manager.ts:37` `TextureManager implements ITextureManager`、`manager.ts:84-89` 构造器 `(state, tiles, tilesetReserve, tilesetUnit)`、`manager.ts:90` `new AutotileProcessor(state)`;`autotile.ts:64` 类 / `autotile.ts:75` 单参构造器 —— 与文档一致 —— 通过。
+- **结论:全部通过,未发现偏差,无修正项。**
+
+## 只读基线与并发改动说明(重要)
+
+- 规划日(2026-09-22)`git status --porcelain -- packages packages-user src` 为**空**;当日 0a 起始实测**非空(15 条)**,用户在本 run 期间持续编辑数据端,条目数一度增至 **22** 条,随后用户提交 `7743795 refactor: Hero equipment system` 与 `d74cc35 feat: Adding should replay flag`(均为数据端)后**归零**;门禁终跑时基线为「(无)」,entries **0**。
+- 本 run 一律以「门禁当次实测」为只读起始基线(点对点快照),**不回滚 / 不暂存 / 不提交 / 不修改**用户的任何未提交改动;Task 1 / Task 3 门禁断言快照与当次实测**逐条一致**(终跑 entries 0)。
+- 用户改动全部位于 `packages-user/data-base` / `data-common` / `data-system`,属数据端进行中的工作,**不在本计划改动范围内**;其提交未触及 texture 基准与本次清点的消费者文件(`git diff --stat e78309b HEAD -- packages-user/client-base/src/material packages-user/client-base/src/types.ts packages-user/client-modules/src/render/map packages-user/client-modules/src/fallback` 无输出),故文档行号在当日实测下有效。
+- HEAD 由 0a 的 `e78309b` 前移至 `d74cc35`(仅用户数据端提交),texture 接口基准仍为 `d36ea69`。
+
+## Deviations from Plan
+
+### Auto-fixed / 环境适配 Issues
+
+**1. [Rule 3 - Blocking] 只读基线随用户并发编辑持续增长,改为「点对点快照 + 当次实测」**
+- **Found during:** Task 1 门禁(gate6 只读门禁)
+- **Issue:** 0a 起始实测 15 条未提交改动;本 run 期间用户持续编辑数据端,实测条目增至 21 / 22 条,随后用户提交 `7743795` / `d74cc35` 后归零,使「porcelain 与起始记录逐条一致」无法稳定成立。这属**外部并发**(用户自己的数据端重构与提交),非本 run 造成。
+- **Fix:** 依 plan assumptions「执行日以 Task 1 步骤 (0a) 当日实测为基线」,把只读基线更新为门禁当次的点对点快照(并同步更新文中条数),逐条原样记录;门禁随即转绿。**未改写任何门禁口径、未伪造通过**。
+- **Files modified:** `04-TEXTURE-INTERFACE-IMPACT.md`(基线块与条数)
+- **Verification:** gate6 / gate398 均 PASS(终跑 entries 0)。
+- **Committed in:** 本次 docs 提交
+
+**2. [Rule 3 - Blocking] 类型错误总行数随用户并发编辑变化,同步记录值**
+- **Found during:** Task 3 类型门禁
+- **Issue:** 记录值 264(Task 1 起始实测)在本 run 期间因用户数据端编辑变为 263;门禁断言「重跑 == 记录值」。
+- **Fix:** 重跑 `pnpm exec vue-tsc --noEmit`,以当次实测 **263** 同步文档记录值(texture 归属保持 23 不变),并同步「差 +61 / 其余既有 225」。**texture 归属 23 与规划日一致**,只读证明不受影响。
+- **Files modified:** `04-TEXTURE-INTERFACE-IMPACT.md`(类型门禁实测基线数字)
+- **Verification:** gate400 PASS(tsc total unchanged 263)。
+- **Committed in:** 本次 docs 提交
+
+**3. [Plan-literal] 交叉引用改为完整 ID 形,且在正文中避免以 `` `## 方法` `` / `` `## 处置` `` 形式提前引用标题**
+- **Found during:** Task 2 / Task 3 类别门禁
+- **Issue:** (a) C 行原用短形 `→ A-06`,而门禁要求完整 `#04-08-A-06`;(b) 元信息块与类型基线正文曾以 `` `## 方法` `` / `` `## 处置` `` 字面引用标题,导致门禁的 `indexOf` 首次命中落在正文而非标题。
+- **Fix:** C 行交叉引用统一改为完整 `#04-08-X-NN`;正文引用改为「方法」一节 / 「处置」一节。**不改变任何事实、结构或语义**。
+- **Files modified:** `04-TEXTURE-INTERFACE-IMPACT.md`
+- **Verification:** gate356 / gate402 PASS(A13 B8 C8 D9 E7 F8)。
+- **Committed in:** 本次 docs 提交
+
+**Deviation summary:** 3 项(2 项外部并发导致的基线同步、1 项格式 / 引用的 plan-literal 适配);均未越出 plan 允许的范围(零生产改动、零方案、零 `04-09+`)。
+
+## Issues Encountered
+
+- 用户**并发手工重构数据端**(`packages-user/data-base` / `data-common` / `data-system`),工作树在清点期间持续变化。本计划已按 D-42 与 assumptions 的并发改动条款处置(只读、点对点快照、原样保留),未把用户的进行中工作当作缺陷登记,也未修复其引入的类型错误。
+
+## 未实现 / 显式延后项
+
+- **第二步(TextureManager 新接口适应实施)未规划**,待用户审阅本文件后另行规划(D-40)。本文件不含修复方案、不含代码改动建议、未产出任何 `04-09+` 或新 `*-PLAN.md`。
+- D 类 9 项的替代方案、F 类 8 条的裁决点(`renderWithoutCheck` 替代 / `tileType` 取值 / 自建 processor 是否复用等)一律待用户裁决,**未由 AI 裁定**。
+- `packages/` 与 `src/` 命中仅报告(D-33);`material/` 与「加载相关」按 D-39 / D-41 / D-42 归用户。
+- `core.*` legacy 适配(D-12,第五阶段);移动控制(D-19);跟随者(D-22);`HeroRendering` 语义(D-18)。
+- 本 run 未新增任何测试、未新增依赖、未改任何 `package.json` / `tsconfig.json`。
+
+## 需求状态
+
+`REND-01` / `REND-02` 在本 run 后仍保持 **Pending**:本计划只交付 TextureManager 新接口影响的**只读清点(D-40 第一步)**,既未实施 texture 接口适应,也未实施移动端 / 桌面端双布局(见 04-08-PLAN 的两条 FLAGGED ASSUMPTION)。`.planning/REQUIREMENTS.md` 未被改动,两需求行仍为 `Pending`。
+
+## 运行时 UAT 不可得声明
+
+本步为**只读清点**,不产生任何运行时行为(不启动渲染、不改代码、不新增测试)。承载体 = 只读门禁(porcelain 逐条一致)+ 类型门禁一致性(错误总行数等于记录值)+ 静态内容门禁(11 节结构 / A–F 行数下限与关键词 / 每行双锚点 / C 交叉引用 / D「请用户反馈」措辞 / E「仅报告」口径)+ 符号零命中复验(`git grep`)+ 既有计划保护与计划范围门禁 + CRLF + 人工代码复核。**除上述命令外不存在可运行门禁,未以任何无法运行的门禁冒充通过。**
+
+## Self-Check
+
+- 交付文档已落盘 `.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md`;11 节齐备;全文 CRLF。
+- 六道门禁(Task 1 六道 / Task 3 六道)全部转绿;标志性输出:`OK headings 11`、`OK baseline tokens 23`、`OK tsc total 263 texture 23 listed 25`、`OK A rows 13`、`OK zero stale symbol references (5 names)`、`OK categories A13 B8 C8 D9 E7 F8`、`OK read-only unchanged entries 0; REND-01/REND-02 still Pending`、`OK 04-01..04-07 untouched; plans 04-01,...`、`OK CRLF`。
+- 生产源码零改动(porcelain 逐条一致,条目全部为用户并发改动)。
+- 提交仅含 `04-TEXTURE-INTERFACE-IMPACT.md` / `04-08-PLAN.md` / `04-08-SUMMARY.md` 三个文件。
+
+---
+*Phase: 04-render-adaptation*
+*Completed: 2026-09-23*
diff --git a/.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md b/.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
new file mode 100644
index 0000000..6a4a485
--- /dev/null
+++ b/.planning/phases/04-render-adaptation/04-TEXTURE-INTERFACE-IMPACT.md
@@ -0,0 +1,268 @@
+# TextureManager 新接口影响范围清点(只读)
+
+**日期:** 2026-09-23
+**范围:** 被查对象 `packages-user` 全量消费者面;`packages/` 与 `src/` 命中仅报告、不修改(D-32 沿用);`packages-user/client-base/src/material/` 按 D-42 只登记、不修改
+**性质:** 只读清点(D-38 / D-40 第一步),未修改任何生产代码
+**接口基准:** `packages-user/client-base/src/material/{types,manager,autotile,index}.ts` 与 `packages-user/client-base/src/types.ts`(`d36ea69` 后)
+**基准提交:** `d36ea69 refactor: 贴图存储方式`(当日工作树 HEAD `d74cc35`;`d36ea69`..`d74cc35` 的用户提交仅触及数据端 `data-base` / `data-common` / `data-system`,未触及 texture 基准与本次清点消费者文件,见「方法」一节)
+**下一步:** 第二步(TextureManager 新接口适应实施)未规划,待用户审阅本文件后另行规划(D-40)
+
+## 背景
+
+本文件是 Phase 4 第七个增量(用户裁定 2026-09-22 · D-38 / D-39 / D-40 / D-41 / D-42 **第一步**)的交付物:对 `d36ea69` TextureManager 接口基准在 `packages-user` 消费者面的**只读影响清点**。
+
+- **D-38**:用户已完成 **TextureManager 相关重构**(提交 `d36ea69「refactor: 贴图存储方式」`),`MaterialManager` 改名并重写为 `TextureManager`(构造器不再无参)、`client.autotile` / `IClientBase.autotile` 删除、`AutotileProcessor` 重写、`IAutotileProcessor` 的 `render` / `renderWith` / `renderWithoutCheck` 合并为单一 `render(tile, connection)`、`renderAnimatedWith` → `renderAnimated`、`BlockCls` 枚举整体删除且 `IMaterialFramedData.cls` → `tileType: TileType`、barrel 与入口调整(`createMaterial()` / `create()` 删除)。**影响面大、主要在地图渲染部分**。下一目标 = **适应新接口**。
+- **D-39**:**不得修改加载相关的内容**(用户将自行适配新加载系统,属其处理范围)。
+- **D-40**:本目标同样走**两步**:**先收集(只读清点)再修复**。第一步产出只读影响清单文档(即本文件);第二步待用户审阅后再规划。
+- **D-41**:「加载相关」的排除边界(用户界定):**加载本身**,以及**向 `TextureManager` 中添加素材的内容**(均由用户自行适配新加载系统)。
+- **D-42**:`packages-user/client-base/src/material/` 文件夹已由用户**重构完毕**;AI **只修改消费者**(如地图渲染)。**若某接口被删除且无替代,必须向用户反馈**,不得自行发明替代方案。
+- **D-33(沿用范围约束)**:范围外一律不改;**不追求解决全部类型错误**(整仓既有错误大量与本次无关),只清点与本次 texture 接口适应相关的项。
+
+本步**只清点、不修改、不规划第二步**。本文件不含修复方案、不含代码改动建议、不产出任何 `04-09-PLAN.md` 或新 `*-PLAN.md`(D-40)。
+
+## 方法
+
+**枚举面(两侧):**
+
+- **(a)基准面**:`packages-user/client-base/src/material/types.ts`(`ITextureManager` / `ITextureGetter` / `ITextureAliasGetter` / `IAutotileProcessor` / `IMaterialFramedData` / `AutotileType` / `IAssetBuilder` / `ITrackedAssetData` 等现行成员)+ `packages-user/client-base/src/material/manager.ts`(`TextureManager` 类与构造器)+ `packages-user/client-base/src/material/autotile.ts`(`AutotileProcessor` 类与构造器)+ `packages-user/client-base/src/material/index.ts` 与 `packages-user/client-base/src/index.ts`(barrel)+ `packages-user/client-base/src/types.ts`(`IClientBase.materials`)+ `packages-user/client-modules/src/types.ts`(`IClientCoreConfig`)。
+- **(b)命中面**:`packages-user` 全量(`client-base` / `client-modules` / `client-system` / `entry-*` / `legacy-plugin-*` / `data-fallback`),另对 `packages/` 与 `src/` 做**对照扫描**(只登记、不修改)。
+
+**判定口径(六类固定,一条记录只归一类,跨类拆条并互相引用):**
+
+| 类别 | 含义 | 归属规则 |
+|---|---|---|
+| A | **符号级**:改名 / 移除的符号引用点 | `TextureManager`(新名消费者)与 `MaterialManager` / `renderAnimatedWith` / `getIdentifierByAlias` / `getAliasByIdentifier` / `getBlockCls(ByAlias)` 的**旧名引用点**(本次实测均为 0,须给机器证据)、`BlockCls` 消费者 import、`IMaterialFramedData.cls` 消费者读写点、`AutotileProcessor` 构造器调用点、`IClientBase.autotile` 与 `create()` / `createMaterial()` 的消费者 |
+| B | **签名级**:成员在但参数 / 类型变化的调用点 | `addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` 的调用点、`ITextureManager` 新增成员(`tiles` / `autotile` / `tilesetReserve` / `tilesetUnit` / `getFrameCount`)与 `IClientCoreConfig` 新增必填字段的实现 / 传入点、`IAutotileProcessor.flatten` 新增 |
+| C | **定位索引**(不新增独立事实) | 第二步将触及的 `file:line` 按文件汇总,逐条**交叉引用其 A / B / D 记录 ID** |
+| D | **被删且无替代**(需用户反馈) | `renderWithoutCheck`、`BlockCls` 及其 `@deprecated` 依赖(`material/utils.ts`,用户自有面)、`manager`、`renderWith`、`getIdentifierByAlias` / `getAliasByIdentifier`、`getBlockCls(ByAlias)`、`IClientBase.autotile`、`create()` / `createMaterial()` |
+| E | 加载相关与添加素材(仅报告) | `client-base/src/load/loader.ts`(加载本身)+ `client-modules/src/fallback/load.ts`(向 `TextureManager` 添加素材)+ `packages/` / `src/` 对照扫描 |
+| F | 未能从阅读确定 | 只登记,不得写成错配 / 缺失 / 残留 |
+
+**分列规则(写死,防止类别互相冒充):** A 记「符号级(旧名 / 被删符号的消费者引用)」;B 记「签名级(成员在但参数 / 类型变化的消费者调用)」;C 记「第二步将触及的定位汇总(不新增独立事实,逐条交叉引用 A / B / D 的 ID)」;D 记「接口已删且 AI 未发现替代(每行明写需用户反馈)」;E 记「加载本身 + 向 `TextureManager` 添加素材(D-39 / D-41 归用户)」;F 记「静态阅读不能定论」。**一条记录只归一类,跨类拆条并互相引用。**
+
+**证据纪律:** 每行含 `符号/成员名 + 命中处 file:line + 基准侧锚点 file:line + 问题描述 + 影响 + 置信`,**每行至少两个 `file:line` 锚点**;静态阅读不能定论者一律进 F 类,**不得写成错配 / 缺失 / 残留**。
+
+**D-42 纪律:** 命中 `packages-user/client-base/src/material/` 内的记录一律归 D 类并标注「用户自有面」,不得写成 AI 工作项。
+
+**只读起始基线(packages / packages-user / src)**:Task 1 起始执行 `git status --porcelain -- packages packages-user src`,当日实测输出(原样逐行记录,为空):
+
+```
+(无)
+```
+
+**基线说明(重要):** 规划日(2026-09-22)该输出为**空**;当日 0a 起始实测**非空(15 条)**,用户在本 run 期间持续编辑数据端(条目数一度增至 22),随后用户提交 `7743795 refactor: Hero equipment system` 与 `d74cc35 feat: Adding should replay flag`(均为数据端)后归零,故门禁终跑时本块为「(无)」。本 run 一律**不回滚 / 不暂存 / 不提交 / 不修改**用户的任何改动,并以其为只读起始基线(点对点快照);Task 1 / Task 3 门禁断言其后**逐条一致**(不新增、不消失、状态码不变)。用户改动全部位于 `packages-user/data-base` / `data-common` / `data-system`,不在本计划改动范围内;其提交未触及 texture 基准与本次消费者文件。`packages` / `client-base` / `client-modules` / `src` 下无条目。
+
+**基准提交核对:** 当日 0a 起始 HEAD = `e78309b`;用户在本 run 期间提交 `7743795 refactor: Hero equipment system` 与 `d74cc35 feat: Adding should replay flag`(均为数据端),HEAD 现为 `d74cc35`;`git merge-base --is-ancestor d36ea69 HEAD` 退出码 0。用户新提交仅触及 `packages-user/data-base` / `data-common` / `data-system`;`git diff --stat e78309b HEAD -- packages-user/client-base/src/material packages-user/client-base/src/types.ts packages-user/client-modules/src/render/map packages-user/client-modules/src/fallback packages-user/entry-client/src/create.ts` **无输出**,即 texture 基准(`material/` 与 `client-base/src/types.ts`)与本次清点的消费者文件未被触及,故 texture 接口基准 = `d36ea69`,文档行号在当日实测下有效。
+
+**类型门禁命令与口径:** `pnpm exec vue-tsc --noEmit`;「错误总行数」= 输出中正则 `error TS\d+` 的匹配数(与 Task 3 门禁同一正则,保证可逐字比对);「texture 归属错误行数」为**规划日 / 执行日人工归类**,执行日按当日实测重新归类并逐行列出,**不由任何门禁断言**、由人工复核保证。
+
+**只读子代理:** 重型整文件读取可按 D-09 委派**只读**子代理(子代理只回传事实表「符号 / 成员名 + 所属 `file:line` + 形态或签名」,**不得写文件、不得贴大段源码**),最终由主执行者按 `file:line` 切片复核。
+
+## 基准变更(d36ea69)
+
+依据:`git show d36ea69 --stat`(共触及 12 个文件:`client-base/src/{index.ts,types.ts,material/{types,manager,autotile,index}.ts}`、`client-modules/src/{client.ts,core.ts,types.ts,render/map/renderer.ts}`、`entry-client/src/create.ts`、`packages/common/src/logger.json`)与 `git show d36ea69 -- <基准文件>`,并与当日工作树逐条比对一致。以下三类差异中,每条附**现行基准侧锚点**(`file:line`)。
+
+**① 改名 1 类:**
+
+| 旧名 | 新名 | 现行基准侧锚点 | 说明 |
+|---|---|---|---|
+| `MaterialManager` | `TextureManager` | `packages-user/client-base/src/material/manager.ts:37` | `export class TextureManager implements ITextureManager`;构造器由无参改为 `constructor(readonly state: ICoreState, readonly tiles: ITileStore, readonly tilesetReserve: number, readonly tilesetUnit: number)`(`packages-user/client-base/src/material/manager.ts:84-89`);消费者 `packages-user/client-modules/src/client.ts:15`(import)与 `packages-user/client-modules/src/client.ts:83`(构造点) |
+
+另记 `AutotileProcessor` 构造器一并收紧为单参 `constructor(readonly state: ICoreState)`(`packages-user/client-base/src/material/autotile.ts:64` 类声明、`packages-user/client-base/src/material/autotile.ts:75` 构造器);`TextureManager` 构造内 `this.autotile = new AutotileProcessor(state);`(`packages-user/client-base/src/material/manager.ts:90`)。
+
+**② 移除(现行基准成员集合已不含):**
+
+- **`BlockCls` 枚举整体删除**:现行 `packages-user/client-base/src/material/types.ts` 的成员集合不含 `BlockCls`(对照:`IMaterialFramedData` 现为 `tileType: TileType`,锚点 `packages-user/client-base/src/material/types.ts:75-86`)。消费者 import 见 A 类 `#04-08-A-06`,用户自有面命中见 D 类 `#04-08-D-03`。
+- **`IMaterialFramedData.cls: BlockCls` → `tileType: TileType`**:现行锚点 `packages-user/client-base/src/material/types.ts:79`;消费者读写点见 A 类 `#04-08-A-03`..`#04-08-A-05`。
+- **`IAutotileProcessor` 的 `render(autotile: number, connection)` / `renderWith(tile, connection)` / `renderWithoutCheck(tile, connection)` 三者合并为 `render(tile: IMaterialFramedData, connection: number)`**:现行锚点 `packages-user/client-base/src/material/types.ts:148-151`(`render(tile: IMaterialFramedData, connection: number): ITextureRenderable | null`);`renderWithoutCheck` 消费者 3 处见 D 类 `#04-08-D-01`,`renderWith` 消费者 0 处见 D 类 `#04-08-D-05`。
+- **`IAutotileProcessor.manager` 删除**:现行 `IAutotileProcessor`(`packages-user/client-base/src/material/types.ts:94-163`)无 `manager` 成员;消费者 0 处见 D 类 `#04-08-D-04`。
+- **`renderAnimatedWith` → `renderAnimated`**:现行锚点 `packages-user/client-base/src/material/types.ts:159-162`;已适配点为 `packages-user/client-modules/src/render/map/renderer.ts:1264`(`this.autotile.renderAnimated(tex, 0b1111_1111)`);旧名 `renderAnimatedWith` 引用 0 处,见 A 类 `#04-08-A-12`。
+- **`ITextureManager.getIdentifierByAlias` / `getAliasByIdentifier` 删除**:现行 `ITextureManager`(`packages-user/client-base/src/material/types.ts:217-404`)无此二者;消费者 0 处见 A 类 `#04-08-A-09` / D 类 `#04-08-D-06`。
+- **`ITextureManager.clsMap` 删除**:现行 `ITextureManager`(`packages-user/client-base/src/material/types.ts:217-404`)无 `clsMap`。
+- **`ITextureGetter.getBlockCls` 删除 / `ITextureAliasGetter.getBlockClsByAlias` 删除**:现行 `ITextureGetter`(`packages-user/client-base/src/material/types.ts:165-189`)与 `ITextureAliasGetter`(`packages-user/client-base/src/material/types.ts:191-215`)均无 `getBlockCls` / `getBlockClsByAlias`;消费者 0 处见 A 类 `#04-08-A-10` / D 类 `#04-08-D-09`。
+- **`IClientBase.autotile` 删除**:现行 `IClientBase`(`packages-user/client-base/src/types.ts:8-25`)仅保留 `materials: ITextureManager`(`packages-user/client-base/src/types.ts:18`),无 `autotile` 成员;消费者面见 A 类 `#04-08-A-08` / D 类 `#04-08-D-07`。
+- **barrel 入口函数删除**:`packages-user/client-base/src/material/index.ts` 的 `createMaterial()`(连同 `createAutotile` import)已删除(现行文件仅剩 5 行 `export * from ...`,锚点 `packages-user/client-base/src/material/index.ts:1-5`);`packages-user/client-base/src/index.ts` 的 `create()`(连同 `createMaterial` import)已删除(现行文件锚点 `packages-user/client-base/src/index.ts:1-5`)。消费者面见 A 类 `#04-08-A-11` / D 类 `#04-08-D-08`;`git grep -n -E "export function create\b|createMaterial|createAutotile" -- packages-user/client-base` **无输出**(退出码 1)。
+
+**③ 新增 / 收紧:**
+
+- `ITextureManager` 新增 `readonly tiles: ITileStore`(`packages-user/client-base/src/material/types.ts:220`)与 `readonly autotile: IAutotileProcessor`(`packages-user/client-base/src/material/types.ts:222`),并保留 `readonly textures`(`packages-user/client-base/src/material/types.ts:225`);新增 `readonly tilesetReserve`(`packages-user/client-base/src/material/types.ts:241`)与 `readonly tilesetUnit`(`packages-user/client-base/src/material/types.ts:246`);新增 `getFrameCount(num: number): number`(`packages-user/client-base/src/material/types.ts:336`)。
+- 四个 `add*` 方法参数收紧:`addGrid(source, map: ArrayLike, width, height)`(`packages-user/client-base/src/material/types.ts:261-266`)、`addRowAnimate(source, map: ArrayLike, width, height)`(`packages-user/client-base/src/material/types.ts:275-280`)、`addAutotile(source, identifier: number, type: AutotileType)`(`packages-user/client-base/src/material/types.ts:289-293`)、`addTileset(source, identifier, cellWidth, cellHeight)`(`packages-user/client-base/src/material/types.ts:302-307`)。
+- `IAutotileProcessor.flatten(source: SizedCanvasImageSource, type: AutotileType, frames: number)` 新增(`packages-user/client-base/src/material/types.ts:101-105`)。
+- `AutotileType` 新增(`packages-user/client-base/src/material/types.ts:12-17`,`Small2x3` / `Big3x4`)。
+- `IClientCoreConfig` 新增 `tilesetReserve` / `tilesetUnit` 两个必填字段(`packages-user/client-modules/src/types.ts:8-10`)。
+
+**同提交内的相邻变更(登记,不作本步裁定):**
+
+- `packages-user/client-modules/src/core.ts:6-11`:单例构造由 `new ClientCore()` 改为传入 config(`tilesetReserve: 100000` / `tilesetUnit: 5000`)。
+- `packages-user/entry-client/src/create.ts:26`:`Mota.register('@user/client-base', UserClientBase)` 仍整体注册该命名空间;同提交删除了 `createModule` 内的 `UserClientBase.create();` 调用(现行 `packages-user/entry-client/src/create.ts:36` 仅剩 `ClientModules.create();`)。
+- `packages/common/src/logger.json`:新增 8 行(logger codes)。
+
+**不得**修改任何被引用文件。
+
+## 类型门禁实测基线
+
+命令:`pnpm exec vue-tsc --noEmit`(整仓非 0 退出属预期)。统计口径:正则 `error TS\d+` 的匹配数。
+
+- **错误总行数**:263
+- **texture 归属错误行数**:23
+
+机器比对形式(与本节门禁的同一正则同形,便于逐字比对):错误总行数:263;texture 归属错误行数:23。
+
+**与规划日(2026-09-22,HEAD `d36ea69`,工作树干净)的差异:** 规划日实测总行数为 **202**,当日实测为 **263**,差 **+61**。原因:用户并发进行中的数据端重构(`packages-user/data-base` / `data-common` / `data-system`;本 run 期间先为未提交改动,后经 `7743795` / `d74cc35` 提交)为数据层类型错误的新增来源(`data-base` 116 + `data-state` 57 + `data-system` 53),与 `d36ea69` texture 接口改动无关。**texture 归属错误行数仍为 23(与规划日一致)**,说明 texture 接口改动对消费者面的影响未因工作树改动而扩大。以上以当日实测为准(见 assumptions)。
+
+**texture 归属错误逐行列出(23 条 = `client-base` 1 条 + `client-modules` 22 条):**
+
+`packages-user/client-base`(1 条):
+
+1. `packages-user/client-base/src/material/utils.ts:2` `error TS2305`:`Module '"./types"' has no exported member 'BlockCls'.`(用户自有面,见 D 类 `#04-08-D-03`)
+
+`packages-user/client-modules`(22 条):
+
+2. `packages-user/client-modules/src/fallback/load.ts:76` `error TS2554`:`Expected 4 arguments, but got 2.`(`addGrid`,加载面,见 E 类 `#04-08-E-01`)
+3. `packages-user/client-modules/src/fallback/load.ts:77` `error TS2554`:`Expected 4 arguments, but got 2.`(`addGrid`)
+4. `packages-user/client-modules/src/fallback/load.ts:80` `error TS2554`:`Expected 4 arguments, but got 3.`(`addRowAnimate`)
+5. `packages-user/client-modules/src/fallback/load.ts:81` `error TS2554`:`Expected 4 arguments, but got 3.`(`addRowAnimate`)
+6. `packages-user/client-modules/src/fallback/load.ts:82` `error TS2554`:`Expected 4 arguments, but got 3.`(`addRowAnimate`)
+7. `packages-user/client-modules/src/fallback/load.ts:83` `error TS2554`:`Expected 4 arguments, but got 3.`(`addRowAnimate`)
+8. `packages-user/client-modules/src/fallback/load.ts:84` `error TS2554`:`Expected 4 arguments, but got 3.`(`addRowAnimate`)
+9. `packages-user/client-modules/src/fallback/load.ts:95` `error TS2554`:`Expected 3 arguments, but got 2.`(`addAutotile`)
+10. `packages-user/client-modules/src/fallback/load.ts:105` `error TS2554`:`Expected 4 arguments, but got 2.`(`addTileset`)
+11. `packages-user/client-modules/src/render/map/extension/hero.ts:15` `error TS2305`:`Module '"@user/client-base"' has no exported member 'BlockCls'.`
+12. `packages-user/client-modules/src/render/map/extension/hero.ts:167` `error TS2353`:`Object literal may only specify known properties, and 'cls' does not exist in type 'IMaterialFramedData'.`
+13. `packages-user/client-modules/src/render/map/renderer.ts:10` `error TS2305`:`Module '"@user/client-base"' has no exported member 'BlockCls'.`
+14. `packages-user/client-modules/src/render/map/renderer.ts:234` `error TS2554`:`Expected 1 arguments, but got 2.`(`AutotileProcessor`)
+15. `packages-user/client-modules/src/render/map/renderer.ts:1252` `error TS2339`:`Property 'cls' does not exist on type 'Readonly