docs(04-07): create material interface adaptation plan (D-33..D-37)

This commit is contained in:
unanmed 2026-09-22 11:07:18 +08:00
parent ac69d31cbb
commit bb1364b495
2 changed files with 580 additions and 1 deletions

View File

@ -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<IMapLayerHooks>`)把逐图层 `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 移植

View File

@ -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<IMaterialFramedData> | 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` 之外的源码;不得回滚 / 暂存 / 提交用户的并发未提交改动。"
- "不得新增、删除或修改任何测试文件(渲染端无测试设施,本计划不建测试)。"
- "不得以「无测试设施」为由弱化验证:每条 `<automated>` 必须有 `<fails_when>`;确实无法运行的门禁必须显式声明为不可得并降级为静态 + 人工,不得伪造绿。"
---
<objective>
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 预算属**建议性**提示,不阻塞。
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/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
</context>
<tasks>
<task type="checkpoint:decision" gate="blocking-human">
<name>Task 0: 预执行汇报关卡 —— 汇报 material 接口适应实施(D-34..D-37)的逐处替换映射表、成员命名清单、三个已导出类构造器签名变更、`textures` 承担 store 的未决项与建议默认、`cache.ts` 删除范围与行为影响,等待用户回复「可以执行」</name>
<precondition>用户对 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 六类清点结果存在)</precondition>
<files>(只读汇报,不修改任何文件)</files>
<read_first>
- 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` 定义源)
</read_first>
<action>
**停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
① **要解决的问题(四类,来自 `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` 后重新汇报,不得自行改道执行。
</action>
<decision>是否按本计划(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` 处置</decision>
<context>
AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认,且任何新增 / 改写的成员命名必须先经用户同意。本计划改的是渲染端系统层的实现与消费者,涉及三个已导出类的公共构造器签名与一个新成员(`textures`)的语义,属「接口 / 依赖事实归用户」的边界;`.planning/config.json` 为 `mode: yolo`,因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`。`textures` 的承担 store 若不先裁决,执行者会在「新建空 store」与「复用 `tileStore`」之间自行猜测;`pnpm-lock.yaml` 是否同步若不先澄清,执行者可能擅自扩大改动面。
</context>
<options>
<option id="execute">
<name>可以执行</name>
<pros>按本计划 10 个文件落地:big-image 全部删除、7 处消费者改 `getTile`、四个实现类补 `state`(构造器注入)、`MaterialManager` 补 `textures`(按 ③ 的建议默认 (a))、`cache.ts` legacy 路径删除、`package.json` 补声明;命名与签名变更均按 ③ 汇报内容执行</pros>
<cons>`pnpm-lock.yaml` 不同步,`pnpm install --frozen-lockfile` 在用户同步前会失败;`textures` 按默认 (a) 落地时暂无写入者</cons>
</option>
<option id="execute-textures-b">
<name>可以执行,但 `textures` 指向 `tileStore`</name>
<pros>零新增状态,接口立刻有意义的「已加载图块纹理」入口</pros>
<cons>语义与接口 jsDoc(「包括图块、普通图片等」)不完全吻合,未来改动需再调整</cons>
</option>
<option id="revise">
<name>修订方案 / 命名 / 删除范围后再执行</name>
<pros>避免带着错误的承担 store、错误的改写或过大的删除面落地(本计划动的是系统层实现面,返工成本高)</pros>
<cons>退出本计划并修订 PLAN.md 后重新汇报,增加一轮往返</cons>
</option>
</options>
<resume-signal>回复「可以执行」四个字(如需指定 `textures` 承担者 / 改名 / 改删除范围 / 要求同步 lockfile,请在回复中一并给出),或给出修订意见</resume-signal>
<verify>
<human-check>用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无可运行的 `<automated>` 命令,故不携带 `<fails_when>`;失败语义完整写在 Task 0 的 action ⑧ 与 acceptance_criteria 中)</human-check>
</verify>
<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 指定)
</acceptance_criteria>
<done>用户明确回复「可以执行」(如需指定 `textures` 承担者 / 改名 / 改删除范围 / 同步 lockfile,已在回复中给出);在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理</done>
</task>
<task type="tracer">
<name>Task 1(tracer):D-35 / D-37 端到端最小切片 —— `ClientCore` 经构造器把 `state` 注入 `MaterialManager` → `AssetBuilder` → `TrackedAssetData`,并注入 `AutotileProcessor`;`MaterialManager` 补 `textures`;`client-base/package.json` 补 `@user/data-state` 声明</name>
<files>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</files>
<read_first>
- 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<Set<number>>, 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 新增与收紧约束的未实现` 与 `## 构造 / 接线面登记`(本任务的两张事实表)
</read_first>
<action>
(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<IMaterialFramedData> | 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,格式固定为:
```
<!-- TYPE-BASELINE:START -->
错误总行数:<N>;material 归属行数:<M>
<material 归属的诊断行,逐行原样(含 file(line,col) 与 error TSxxxx)>
<!-- TYPE-BASELINE:END -->
```
**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,格式固定为:
```
<!-- CONCURRENT-BASELINE:START -->
<porcelain 条目逐行>
<!-- CONCURRENT-BASELINE:END -->
基线摘要 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 错误。
</action>
<reversibility rating="costly">本任务把 `state` 的注入方式钉进 `MaterialManager` / `AutotileProcessor` / `AssetBuilder` 三个**已导出类**的公共构造器签名,并决定 `MaterialManager.textures` 的承担 store——这是公共契约面与依赖声明面的选择(Task 0 已获批)。回退需同时还原三个构造器、五处构造点与 `package.json`,且 `textures` 的语义选择被后续消费者依赖后不易更改,故记为 `costly`(Task 0 已在 `blocking-human` 关卡取得用户批准)。</reversibility>
<verify>
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<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(/<!-- TYPE-BASELINE:START -->([\s\S]*?)<!-- TYPE-BASELINE:END -->/);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))"</automated>
<fails_when>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 条,说明改动引入了新诊断)</fails_when>
<automated>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')"</automated>
<fails_when>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:*`)</fails_when>
<automated>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</automated>
<fails_when>non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用变量 / 未使用 import / 行宽超限)</fails_when>
<automated>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')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」)</fails_when>
</verify>
<acceptance_criteria>
- `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)
- 五条 `<automated>` 全部转绿(tracer 类型门禁:C 类归零 / material 归属 = 基线减 9 / 总行数 ≤ 基线减 9;绑定门禁;eslint;CRLF)
- 本任务**未**触碰 `getOffsetPool`、`cache.ts`、`hero.ts` / `door.ts` / `vertex.ts`、`material/types.ts`、`client-base/src/types.ts`;未提交任何改动
</acceptance_criteria>
<done>`state` 经构造器从 `ClientCore` 注入 `MaterialManager` → `AssetBuilder` → `TrackedAssetData` 并注入 `AutotileProcessor`(渲染端侧从 `manager.state` 取值),`MaterialManager.textures` 就位,`client-base/package.json` 补上 `@user/data-state` 声明,C 类 9 条诊断消除且总行数下降 9 条,五条机器门禁转绿</done>
</task>
<task type="auto">
<name>Task 2:D-34(A 类)—— 删除 `MaterialManager` 的 big-image 实现面,把 7 处消费者调用改为 `getTile`,删除 `getOffsetPool` 的 big-image 偏移收集</name>
<files>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</files>
<read_first>
- 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`)
</read_first>
<action>
(1) **`manager.ts` 的 big-image 删除(逐处,按删除前行号)**:
(1a) `:20` 从 `./types` 的 import 列表中删除 `IBigImageReturn,` 一行(其余导入名全部保留)。
(1b) `:43` 删除 `readonly bigImageStore: ITextureStore = new TextureStore();`(连其前后空行按上下文处理,保持文件中 store 字段块整齐)。
(1c) `:55-56` 删除 `/** 大怪物数据 */` 注释与其下 `readonly bigImageData: Map<number, IMaterialFramedData> = 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 错误。
</action>
<reversibility rating="reversible">本任务删除的是无写入者 / 无消费者的 dead big-image 实现面,并把 7 处调用换成与今日行为等价的 `getTile`(`getIfBigImage ≡ getTile`,因 `bigImageData` 的唯一写入者 `setBigImage` 消费者调用点为 0);`getOffsetPool` 只是不再收集已不存在的 big-image 偏移。回退只需还原 5 个文件的这一次删除,故为 `reversible`。</reversibility>
<verify>
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<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(/<!-- TYPE-BASELINE:START -->([\s\S]*?)<!-- TYPE-BASELINE:END -->/);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))"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NO TYPE BASELINE" / "NO BASELINE NUMBERS"(SUMMARY 未按固定格式记录类型基线)/ "MATERIAL DIAGNOSTICS REMAIN"(material 归属诊断未清零)/ "DIAGNOSTICS IN TOUCHED FILES"(五个在范围文件仍有诊断,说明删除或改写引入了新问题)/ "TOTAL INCREASED"(错误总行数超过「基线总行数 - material 条数」)</fails_when>
<automated>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')"</automated>
<fails_when>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` 用法被误删)</fails_when>
<automated>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</automated>
<fails_when>non-zero exit 或输出含 "error"(ESLint / Prettier 违规,含未使用变量 / 未使用 import——务必确认删除 big-image 后无 import 变为未使用)</fails_when>
<automated>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')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(出现孤立 LF)</fails_when>
</verify>
<acceptance_criteria>
- `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`、任何数据端文件均未被本任务触碰
- 四条 `<automated>` 全部转绿:scoped 类型门禁(material 归属 0、五个在范围文件 0、总行数 ≤ 基线减 material 条数)、删除 / 替换门禁(`bigimage` 记号归零、`getTile(` 分布正确、`getOffsetPool` 尾部逻辑在、`setDefaultFrame` 正确、D-28 保留)、eslint 0 错误、CRLF
- 本任务**未**提交任何改动
</acceptance_criteria>
<done>`MaterialManager` 的 big-image 实现面全部删除、7 处消费者取值改为行为等价的 `getTile`、`getOffsetPool` 的 big-image 偏移收集删除,material 归属诊断清零且总行数降至 ≤ 基线减 19,D-28 保留项未动,四条机器门禁转绿</done>
</task>
<task type="auto">
<name>Task 3:D-36 —— 删除 `cache.ts` 的 legacy big-image 路径;原子提交全部 10 个文件;终验门禁(类型 / 静态 / 范围 / 并发基线 / eslint / CRLF)+ 人工复核 + `04-07-SUMMARY.md` 定稿</name>
<files>packages-user/client-modules/src/render/elements/cache.ts, .planning/phases/04-render-adaptation/04-07-SUMMARY.md</files>
<read_first>
- 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 的决策段)
</read_first>
<action>
(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` 的任何产物。
</action>
<reversibility rating="costly">本任务删除 `cache.ts` 的 legacy big-image 渲染路径——这是一处**运行时可见**的行为变化(原本走该分支的 legacy 大图怪物会落回 `enemy48` / `npc48` 或普通图块分支渲染)。删除依据是用户 D-36 的明确裁定与 D-31「旧兼容可痛丢弃」,但该语义变化一旦被后续消费者依赖便不易收回,且本计划无运行时验证设施,故记为 `costly`。</reversibility>
<verify>
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out='';try{out=execSync('pnpm exec vue-tsc --noEmit',{encoding:'utf8',maxBuffer:1<<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(/<!-- TYPE-BASELINE:START -->([\s\S]*?)<!-- TYPE-BASELINE:END -->/);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+')')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NO TYPE BASELINE" / "NO BASELINE NUMBERS" / "MATERIAL DIAGNOSTICS REMAIN" / "DIAGNOSTICS IN TOUCHED FILES" / "TOTAL INCREASED"(终验类型门禁未过)</fails_when>
<automated>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')"</automated>
<fails_when>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"</fails_when>
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process');let out;try{out=execSync('git status --porcelain',{encoding:'utf8'})}catch(e){console.error('GIT STATUS FAILED '+e.message);process.exit(1)}const allow=['packages-user/client-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(/<!-- CONCURRENT-BASELINE:START -->([\s\S]*?)<!-- CONCURRENT-BASELINE:END -->/);if(!bm){console.error('NO CONCURRENT BASELINE');process.exit(1)}const base=bm[1].split(/\r?\n/).map(s=>s.replace(/\s+$/,'')).filter(Boolean).map(s=>s.slice(3).replace(/^\"|\"$/g,''));const lines=out.split(/\r?\n/).filter(l=>l.trim().length>0);const prod=lines.map(l=>l.slice(3).replace(/^\"|\"$/g,'')).filter(p=>/^packages(-user)?\//.test(p));const extra=prod.filter(p=>allow.indexOf(p)<0&&base.indexOf(p)<0);if(extra.length){console.error('OUT-OF-SCOPE FILE MODIFIED '+extra.join(' ; '));process.exit(1)}console.log('OK scope (baseline-aware): out-of-scope extra 0')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "OUT-OF-SCOPE FILE MODIFIED"(出现 10 个在范围文件之外、且不在并发基线内的生产文件改动,含 `action/move.ts`、`client-base/src/load/**`、`material/types.ts`、`client-base/src/types.ts`、任何数据端包或 `packages/**` 的新改动;基线内的用户并发改动不触发)/ "GIT STATUS FAILED" / "NO CONCURRENT BASELINE"</fails_when>
<automated>node -e "const fs=require('fs'),c=require('crypto'),{execSync}=require('child_process');const keep=l=>!/packages-user\/client-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(/<!-- CONCURRENT-BASELINE:START -->([\s\S]*?)<!-- CONCURRENT-BASELINE:END -->/);if(!bm){console.error('NO CONCURRENT BASELINE');process.exit(1)}const rec=bm[1].split(/\r?\n/).map(s=>s.replace(/\s+$/,'')).filter(Boolean).filter(keep);const dm=t.match(/基线摘要[^\r\n]*?sha256=([0-9a-f]{64})/);if(!dm){console.error('NO BASELINE DIGEST');process.exit(1)}const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length||gone.length){console.error('BASELINE ENTRY SET CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}if(dm[1]!==digest){console.error('BASELINE DIGEST CHANGED '+dm[1]+' vs '+digest);process.exit(1)}console.log('OK user concurrent edits unchanged entries '+cur.length)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NO BASELINE DIGEST" / "BASELINE ENTRY SET CHANGED" / "BASELINE DIGEST CHANGED"(用户并发的未提交改动被本计划回滚 / 暂存 / 提交 / 修改)</fails_when>
<automated>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</automated>
<fails_when>non-zero exit 或输出含 "error"(ESLint / Prettier 违规;`cache.ts` 删除后若有 import / 局部变量变为未使用即为此类失败)</fails_when>
<automated>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')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(源文件出现孤立 LF,违反 dev.md「换行使用 CRLF 格式」;SUMMARY 不在本机器门禁范围——工具写出的 markdown 不施加此硬门禁,改由人工复核遵循仓库 markdown 惯例)</fails_when>
<automated>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(','))"</automated>
<fails_when>non-zero exit, 或 stderr 含 "SUMMARY MISSING SECTIONS"(SUMMARY 缺少执行前核对 / 基线段 / 人工复核 / 需求 Pending 声明 / D-33..D-37 关键项 / 运行时不可得说明)/ "UNEXPECTED PLAN FILES"(phase 目录内出现了预期之外的 `-PLAN.md`)</fails_when>
</verify>
<acceptance_criteria>
- `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)
</acceptance_criteria>
<done>`cache.ts` 的 legacy big-image 路径删净、10 个文件原子提交、material 归属诊断清零且总行数不增加、多类机器门禁全绿、用户并发改动零变化、范围无越界、SUMMARY 定稿并提交;用户可据此继续裁决后续渲染适配增量(`textures` 写入侧、`.planning/graphs/*` 重建、其余渲染适配与 REND-02 双布局)</done>
</task>
</tasks>
<threat_model>
## 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 已可解析);若执行中出现安装需求,立即停止并汇报(不擅自安装、不评估包合法性) |
</threat_model>
<verification>
<!-- plan-criteria-allow: R4 - 本计划判据区内的 `|` 全部是 node -e 脚本里 JS 源码的转义 / 正则交替 / 对象分隔,不是 shell 管道;脚本内的 git / tsc / eslint 调用由 execSync 的 try-catch 捕获或直接断言退出码,不会把失败读成通过。注释中出现的 big-image 相关记号与旧接口名是 D-31 / D-34 / D-36 的裁定对象,已由下方 allow 标记豁免。 -->
<!-- planner-discipline-allow: bigImage -->
<!-- planner-discipline-allow: bigImageStore -->
<!-- planner-discipline-allow: bigImageData -->
<!-- planner-discipline-allow: bigImageId -->
<!-- planner-discipline-allow: getIfBigImage -->
<!-- planner-discipline-allow: getBigImage -->
<!-- planner-discipline-allow: getBigImageByAlias -->
<!-- planner-discipline-allow: isBigImage -->
<!-- planner-discipline-allow: setBigImage -->
<!-- planner-discipline-allow: IBigImageReturn -->
<!-- planner-discipline-allow: IMaterialManager -->
<!-- planner-discipline-allow: IMaterialGetter -->
<!-- planner-discipline-allow: IMaterialAliasGetter -->
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. 运行时验证(诚实降级):渲染端无测试设施,本计划不启动渲染、不新增测试、不接线被注释的接线点,故**无运行时门禁可跑**;本计划的「运行结果」仅由上述编译 / 静态 / 格式 / 范围 / 并发基线机器门禁与人工复核承载。任何以「编译通过」代替运行时结论的表述都视为失败。
</verification>
<success_criteria>
- **执行前置关卡(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 不可得说明 |
<output>
Create `.planning/phases/04-render-adaptation/04-07-SUMMARY.md` when done
</output>