diff --git a/.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md b/.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md new file mode 100644 index 0000000..5fdcef8 --- /dev/null +++ b/.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md @@ -0,0 +1,479 @@ +--- +phase: 09-minimum-browser-runnable +plan: 52 +type: execute +# 编号说明:09-01..09-51 的计划均已占用,本计划使用下一个空闲编号 09-52。 +# depends_on 为空且 wave 为 1:只新建 packages/render-anim 下的文件,与任何既有计划都没有文件重叠或依赖。 +wave: 1 +depends_on: [] +files_modified: + - packages/render-anim/package.json + - packages/render-anim/src/types.ts + - packages/render-anim/src/index.ts + - .planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md +autonomous: false +requirements: [] +estimate: + # 未校准估算(confidence: low),SUMMARY 须声明。 + # 成本主要在读取既有类型 / 工具链约定、写 3 个小文件、以及 prettier / eslint / 结构门禁的往返。 + tokens: 30000 + raw_tokens: 30000 + tasks: 5 + confidence: low +must_haves: + truths: + - "**第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(D-03 / AGENTS.md)**:汇报本次要新建的包、三个文件的精确形态、类型骨架逐成员清单、jsDoc 方案、dev.md 合规点、OI-1 已按用户裁定 (B) 落为 `export const enum AnimationBlendMode` 的记录、影响范围、验证形态、编号说明与不提交声明;然后停下等待用户恰好回复「可以执行」。" + - "**只交付类型骨架**:`packages/render-anim` 下只新增 `package.json`、`src/types.ts`、`src/index.ts`;不存在 `RenderAnimater` / `parseRenderAnimation` 的任何实现,`types.ts` 内没有 `class` / `function` / 箭头函数 / `let` / `export default`(无实现、无默认导出);`const` / `enum` 仅出现在 `export const enum AnimationBlendMode` 这一处声明(枚举成员为隐式数值,不写显式初始化)。" + - "**类型骨架与用户给定内容逐字一致**:`types.ts` 导出 7 个类型——1 个 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`,隐式数值 0 / 1)与 6 个接口 `IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame`、`IAnimationAnimaterData`、`IAnimationData`;成员名、成员顺序、类型、`readonly`、可选性与骨架完全相同;`ITexture` / `ITextureAnimater` 由 `@motajs/render` 普通引入(不使用 `import type`)。" + - "**`index.ts` 与 `package.json` 保持最小**:`index.ts` 只有一行 `export * from './types';`,不转发 `@motajs/render` 的符号;`package.json` 只有 `name` 与 `dependencies['@motajs/render'] = 'workspace:*'` 两个顶层键,没有 `version` / `private` / `type` / `main` / `module` / `types` / `exports` / `scripts`。" + - "**dev.md 合规**:三个文件均为 UTF-8 无 BOM + CRLF + 4 空格缩进、无制表符;`types.ts` 第一行是 `import`;每个公开成员(含枚举成员 `Normal` / `Add`)在源头(`types.ts`)有单行中文 jsDoc,接口与枚举本身不写 jsDoc;无 `any` / `as` / `?.` / `!` / `import type` / 多行 `/* */` 注释 / 复述代码的注释。" + - "**验证只读静态**:prettier `--check`、eslint(两个 ts 文件)、node 结构 / 内容门禁全部通过;可选类型门禁只判 `packages/render-anim` 相关诊断(全仓既有类型错误不视为本次失败);不运行测试、构建、dev server 或浏览器。" + - "**范围受控**:除 `files_modified` 的 4 个路径外零改动;09-50 / 09-51 的计划与摘要哈希逐字不变;不运行 `pnpm install`;不执行任何 git 写命令,0 次提交。" + artifacts: + - path: packages/render-anim/package.json + provides: "`name: @motajs/render-anim` 与 `dependencies: { @motajs/render: workspace:* }` 两个键,4 空格缩进 + CRLF" + - path: packages/render-anim/src/types.ts + provides: "7 个导出类型(1 个 `export const enum AnimationBlendMode` + 6 个接口)的完整类型骨架,成员级中文 jsDoc,`ITexture` / `ITextureAnimater` 从 `@motajs/render` 普通引入" + - path: packages/render-anim/src/index.ts + provides: "唯一的 `export * from './types';`" + - path: .planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md + provides: "逐文件最终形态、Task 1 记录的类型 / 命名方案、各门禁真实结果、跳过项声明、未校准估算、0 次提交、编号说明与待确认项" + key_links: + - "`ITexture` / `ITextureAnimater` ← `@motajs/render` 根导出 ← `packages/render/src/index.ts` → `assets/index.ts` → `assets/types.ts`;解析路径只来自根 `tsconfig.json` 的 `@motajs/*` → `./packages/*/src`,不得为让导入生效新增包级 `tsconfig.json`" + - "`IAnimationData.animater` 的泛型实参 `IAnimationAnimaterData` ↔ 后续 `RenderAnimater implements ITextureAnimater`(本计划不实现,只固定泛型形状)" + - "`types.ts` 的 `readonly` 数组 ↔ 后续解析器对外返回的只读视图;实现阶段不得把 `objects` / `bitmaps` / `frames` / `sound` 改成可变数组" + - "`index.ts` 的单一 `export *` ↔ `@motajs/render` / `@motajs/animate` 的包导出习惯;星号导出不得跨包转发 `@motajs/render` 的类型" + - "`package.json` 的 `workspace:*` ↔ `pnpm-workspace.yaml` 的 `packages/*`;本计划不安装依赖,锁文件变更由用户后续决定" + assumptions: + - "**工作树基线(规划日实测)**:`git status --porcelain -uall -- packages/render-anim` 输出为空,`packages/render-anim` 目录不存在;09-50 / 09-51 的计划与摘要已提交(HEAD 为 `42a3d41`)。" + - "**解析与配置基线(规划日实测)**:根 `tsconfig.json` 的 `include` 已覆盖 `packages/**/*.ts`,`paths` 已把 `@motajs/*` 映射到 `./packages/*/src`;因此新包无需新增或修改任何 tsconfig / eslint / prettier 配置。" + - "**包清单范式(规划日实测)**:`packages/render/package.json` 只有 `name`;`packages/audio/package.json` 是 `name` + `dependencies`(`workspace:*`)的既有范式;新包按后者书写。" + - "**工具链(规划日实测)**:`node` v24.21.0;`node_modules/prettier/bin/prettier.cjs`、`node_modules/eslint/bin/eslint.js`、`node_modules/vue-tsc/bin/vue-tsc.js`、`node_modules/typescript/bin/tsc` 均存在;仓库根 `.prettierrc` 为 `printWidth: 80` / `tabWidth: 4` / `singleQuote: true` / `arrowParens: avoid` / `trailingComma: none` / `endOfLine: crlf`;`core.autocrlf=true`。" + - "**本 run 无 RESEARCH / 无 VALIDATION**:本阶段无 requirement ID(`requirements: []`),Nyquist 不适用;spec-less probe fallback skipped: phase has no requirement IDs to probe (visible skip)。" + prohibitions: + - "不得在用户恰好回复「可以执行」之前执行任何任务或派发执行子代理(D-03 / AGENTS.md)。" + - "不得实现 `RenderAnimater` / `parseRenderAnimation`,不得写任何 class、function、测试、示例、demo 或入口逻辑。" + - "不得新增 `packages/render-anim/tsconfig.json`、构建脚本、`exports` / `main` / `module` / `types` / `version` / `private` / `sideEffects` 等字段;用户只要求 name + workspace 依赖。" + - "不得修改 09-50 / 09-51 的计划与摘要、其它包、根配置(`tsconfig.json` / `eslint.config.js` / `.prettierrc` / `pnpm-workspace.yaml`)、锁文件、`STATE.md`、`ROADMAP.md`、`09-CONTEXT.md`。" + - "不得运行 `pnpm install` / 构建 / 测试 / dev server / 浏览器;不得执行任何 git 写命令(`add` / `commit` / `stash` / `checkout` / `restore` / `reset`),本计划全程 0 次提交。" + - "不得改写用户给定的类型名、成员名、成员顺序、成员类型、`readonly` 与可选性;不得新增任何导出符号或成员;不得把 `AnimationBlendMode` 改回字符串字面量联合,不得给枚举成员显式初始化、改成员名或调整 `Normal` → `Add` 顺序。" + - "不得出现 `import type`、`any`、`as`、非 jsDoc 的多行注释、解释下一行代码的注释,或改写既有注释(本计划不触碰任何既有文件)。" + open_items: + - "**OI-1(已裁定:用户选择 B):** `AnimationBlendMode` 由字符串字面量联合 `'normal' | 'add'` 改为 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`,隐式数值 0 / 1,成员 jsDoc 分别为「普通混合」「加算混合」),符合 dev.md「不使用字符串作为键或特殊标识符(如枚举值、事件名、状态名等),应使用枚举代替」。`IAnimationData.blend` 类型不变,仍为 `AnimationBlendMode`。Task 0 只记录该裁定,不再询问 (A)/(B)。" + - "**OI-2(只记录):** `IAnimationSound.name` 为 `string`,属 dev.md 允许的「字符串类型的 id 别名 / 文件路径」一类,保留。" + - "**OI-3(只记录):** 用户将自行审阅 / 修改类型骨架后再进入实现;本计划不实现、不测试、不宣称任何运行时行为。`RenderAnimater` 与 `parseRenderAnimation(buffer: ArrayBuffer): Promise` 另立后续计划。" + - "**OI-4(只记录):** 成员(含枚举成员 `Normal` / `Add`)jsDoc 使用单行中文风格(与 `packages/render/src/assets/types.ts` 一致);接口与枚举本身按 dev.md 不写 jsDoc。如需英文注释或接口级说明,请指示。" + - "**OI-5(只记录):** `estimate` 未校准(`confidence: low`);本计划未运行任何构建或测试,因此不做运行时可用性主张。" +--- + + +Phase 9 · 第 52 个计划(新增的独立增量)—— **为即将新增的包 `@motajs/render-anim` 建立类型声明骨架**,让用户先审阅 / 修改公开类型形状,再进入实现阶段。 + +**要解决的问题:** 动画渲染需要一个新的包承载动画数据与动画器,但公开类型的字段、只读性、可选性与泛型形状需要先由用户确认。若直接写实现,后续调整类型会连带返工实现与调用方。因此本计划只落类型骨架,不写任何实现。 + +**做法(锁定设计,仅类型):** +- `packages/render-anim/package.json`:只声明 `name` 与 `dependencies['@motajs/render'] = 'workspace:*'`。 +- `packages/render-anim/src/types.ts`:只放用户给定的 7 个导出类型——1 个 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`,隐式数值,成员 jsDoc「普通混合」「加算混合」)与 6 个接口(`IAnimationBitmap` / `IAnimationSound` / `IAnimationObject` / `IAnimationFrame` / `IAnimationAnimaterData` / `IAnimationData`),成员级中文 jsDoc,`ITexture` / `ITextureAnimater` 从 `@motajs/render` 普通引入。 +- `packages/render-anim/src/index.ts`:只 `export * from './types';`。 +- 已确认的设计语义只记录、不实现:浏览器专用;`RenderAnimater` 将实现 `ITextureAnimater`(`once` / `cycled` 返回 `Generator`,帧时序 / 循环由外部驱动);逐帧惰性合成,产出的可渲染对象在下一次 `.next()` 前有效;使用 `MotaOffscreenCanvas2D` 并调用 `setHD(false)` / `setAntiAliasing(false)`,不直接调浏览器 canvas API;空帧产出透明小图;帧尺寸可不同,每帧自带 `width` / `height` / `originX` / `originY`;`blend` 以 `AnimationBlendMode` 存于文件并由解析数据暴露,动画器不决定混合;`sound` 由宿主播放;解析函数为 `parseRenderAnimation(buffer: ArrayBuffer): Promise`。 + +**约束:** 只写 `files_modified` 的 4 个路径;不新增依赖安装、不改任何既有文件与配置;不提交代码;不做浏览器 / 测试验证,只做只读静态门禁。(D-01 增量派发 / D-02 计划累积追加 / D-04 命名先反馈 / D-05 默认不逐阶段验证 / D-06 范围由用户界定。) + +**修改方式声明(AGENTS.md):** 本计划的全部文件写入(新建 3 个包文件、写 1 个 SUMMARY)由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 外无任何写入。 + +**可见跳过:** spec-less probe fallback skipped: phase has no requirement IDs to probe (visible skip)。本阶段无 requirement ID,不创建 RESEARCH.md / VALIDATION.md,Nyquist 验证不适用。 + + +## 本计划产出物(Artifacts this plan produces) + +| 类型 | 名称 / 路径 | 说明 | +|------|-------------|------| +| 新建(包清单) | `packages/render-anim/package.json` | `name` + `@motajs/render` 的 `workspace:*` 依赖,仅两个键 | +| 新建(类型) | `packages/render-anim/src/types.ts` | 1 个 `export const enum` + 6 个接口 + 成员级 jsDoc,无任何实现 | +| 新建(导出) | `packages/render-anim/src/index.ts` | 仅 `export * from './types';` | +| 交付 | `.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md` | 执行摘要、门禁结果、跳过项、未校准估算、0 次提交 | + +## 类型骨架逐成员对照(Task 2 的唯一依据) + +| 导出类型 | 成员(顺序固定) | 成员 jsDoc 语义(中文单行) | +|----------|------------------|------------------------------| +| `AnimationBlendMode`(`export const enum`) | `Normal` | 普通混合(隐式数值 0) | +| | `Add` | 加算混合(隐式数值 1) | +| `IAnimationBitmap` | `readonly x: number` | 位图在纹理中的横坐标 | +| | `readonly y: number` | 位图在纹理中的纵坐标 | +| | `readonly width: number` | 位图宽度 | +| | `readonly height: number` | 位图高度 | +| `IAnimationSound` | `readonly name: string` | 音效名称 | +| | `readonly volume?: number` | 播放音量 | +| | `readonly pitch?: number` | 播放音高 | +| `IAnimationObject` | `readonly index: number` | 使用的位图索引 | +| | `readonly x: number` | 相对帧原点的横坐标 | +| | `readonly y: number` | 相对帧原点的纵坐标 | +| | `readonly scale: number` | 缩放比例 | +| | `readonly opacity: number` | 不透明度 | +| | `readonly mirror: number` | 镜像方向 | +| | `readonly rotate: number` | 旋转角度 | +| `IAnimationFrame` | `readonly width: number` | 帧宽度 | +| | `readonly height: number` | 帧高度 | +| | `readonly originX: number` | 帧原点的横坐标 | +| | `readonly originY: number` | 帧原点的纵坐标 | +| | `readonly objects: readonly IAnimationObject[]` | 帧内的绘制对象 | +| | `readonly sound?: readonly IAnimationSound[]` | 帧内播放的音效 | +| `IAnimationAnimaterData` | `readonly bitmaps: readonly IAnimationBitmap[]` | 位图列表 | +| | `readonly frames: readonly IAnimationFrame[]` | 帧列表 | +| `IAnimationData` | `readonly texture: ITexture` | 合成帧使用的纹理 | +| | `readonly animater: ITextureAnimater` | 动画器 | +| | `readonly blend: AnimationBlendMode` | 混合模式 | + +**`AnimationBlendMode` 声明形态(OI-1 B):** `export const enum AnimationBlendMode {`,随后 4 空格缩进的 `/** 普通混合 */` 与 `Normal,`,再随后 4 空格缩进的 `/** 加算混合 */` 与 `Add`(末成员不带尾逗号),最后顶格 `}`。成员为隐式数值(`Normal` = 0、`Add` = 1),不写显式初始化;声明本身不写 jsDoc;成员顺序固定为 `Normal` → `Add`。`IAnimationData.blend` 仍为 `AnimationBlendMode`。 + +## 覆盖审计(Multi-Source Coverage Audit) + +| 源 | 条目 | 覆盖位置 | +|----|------|----------| +| GOAL(ROADMAP §Phase 9) | 「可在浏览器中运行并验证的最小版本」伞目标 | 本增量只建立类型契约,不新增可运行能力;SUMMARY 如实记录 | +| REQ | 本阶段无 requirement ID(`requirements: []`) | Nyquist 不适用 | +| RESEARCH | 本 run 无 RESEARCH.md | 不适用 | +| CONTEXT D-01 | 增量派发,一个任务一个计划 | 本计划即该派发的唯一计划,不拆分 | +| CONTEXT D-02 | 计划可累积追加 | plan 52,排在 09-01..09-51 之后(编号说明见 frontmatter) | +| CONTEXT D-03 | 执行前必须先汇报并等待「可以执行」 | Task 0(`blocking-human`),`autonomous: false` | +| CONTEXT D-04 | 新命名必须先反馈获批 | 类型 / 成员名全部由用户提供,Task 0 声明不新增任何未获批名称;OI-1 已按用户裁定 (B) 定为 `export const enum` | +| CONTEXT D-05 | 默认不逐阶段验证 | 本计划仍做只读静态门禁,但明确不做运行时验证 | +| CONTEXT D-06 | 范围由用户界定,不擅自扩大 | `files_modified` 4 项 + Task 4 范围 / 哈希闸门 | +| 用户设计 | 新包 `@motajs/render-anim`,依赖 `@motajs/render`(`workspace:*`) | Task 2(`package.json`) | +| 用户设计 | 只建 `package.json` / `src/types.ts` / `src/index.ts`,只含类型 | Task 2 + Task 3(无实现门禁) | +| 用户设计 | 1 个枚举 + 6 个接口与逐成员形状(名称、类型、`readonly`、可选性) | 「类型骨架逐成员对照」+ Task 2 + Task 3 内容门禁 | +| 用户设计 | `ITexture` / `ITextureAnimater` 来自 `@motajs/render` | Task 2(普通引入)+ Task 3 | +| 用户设计 | 已确认的运行时设计只记录、不实现 | objective + OI-3 + SUMMARY | +| 用户设计 | 验证只读静态,不跑游戏 / 浏览器 / 测试 | Task 3 / Task 4 + `` | +| 用户设计 | `autonomous: false`,Task 0 汇报并等待「可以执行」 | Task 0 | +| 用户裁定 OI-1 | `AnimationBlendMode` 用 `export const enum`(成员 `Normal` / `Add`) | 「类型骨架逐成员对照」+ Task 0 ⑤ 记录 + Task 2 / Task 3 门禁 | +| 用户设计 | 不提交、不越界、不改 09-50 / 09-51 | prohibitions + Task 4 范围 / 哈希闸门 | + + +@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md +@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md +@AGENTS.md +@dev.md +@tsconfig.json +@.prettierrc +@eslint.config.js +@pnpm-workspace.yaml +@packages/render/package.json +@packages/render/src/index.ts +@packages/render/src/assets/types.ts +@packages/render/src/assets/texture.ts +@packages/render/src/core/canvas2d.ts +@packages/audio/package.json +@packages/animate/src/index.ts + + + + + + Task 0: 预执行汇报关卡 —— 汇报新包、三个文件形态、类型骨架(含 OI-1 B 的 `export const enum AnimationBlendMode`)、dev.md 合规、影响范围、验证形态,等待「可以执行」 + `packages/render-anim` 目录尚不存在;根 `tsconfig.json` 的 `include` 覆盖 `packages/**/*.ts` 且 `paths` 已映射 `@motajs/*`;`node_modules` 内 prettier / eslint / vue-tsc / typescript 均存在;`node` v24.21.0。 + 无(本任务只读,不写任何文件;汇报后停下等待「可以执行」) + + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-01 增量派发、D-03 汇报关卡、D-04 命名批准、D-06 范围界定) + - AGENTS.md(执行前须获批、命名先反馈、既有注释不得修改、最小修改也走 GSD、不派发额外子代理) + - dev.md(CRLF、4 空格、首行 import、禁止 `import type`、无 `any`、成员 jsDoc 在源头、字符串标识符规则、文件命名) + - packages/render/src/assets/types.ts(`ITexture` / `ITextureAnimater` / `ITextureRenderable` 的既有形状与 jsDoc 风格) + - packages/render/package.json 与 packages/audio/package.json(包清单范式) + - 根 tsconfig.json(`@motajs/*` 路径映射)与 .prettierrc / eslint.config.js + + + - 已向用户逐条汇报 ①–⑫,包含三个文件的逐字形态、1 个 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`)+ 6 个接口的逐成员名称 / 类型 / `readonly` / 可选性,以及 `IAnimationData extends IAnimationAnimaterData`。 + - 已把 `readonly` 修饰、成员类型、可选性与 `extends` 关系列为显式批准项,未以「名称级骨架一致」代替成员级确认。 + - 已向用户报告 OI-1 的最终裁定:用户选择 (B),`AnimationBlendMode` 使用 `export const enum`(成员 `Normal` / `Add`,隐式数值,成员 jsDoc 为「普通混合」/「加算混合」),`IAnimationData.blend` 仍为 `AnimationBlendMode`;本轮不再询问 (A)/(B)。 + - 用户针对本修订版明确回复「可以执行」后才进入 Task 1–4;OI-1 的 (B) 裁定已在规划期由用户消息「B,可以执行」记录,本关卡只做汇报与执行批准。 + + + 一份待批准的执行计划(`09-52-PLAN.md`):新建 `@motajs/render-anim` 包的**类型声明骨架**(3 个文件),不写任何实现;用户审阅 / 修改类型后再进入实现阶段。 + + + **停下并向用户汇报,等待其恰好回复「可以执行」后才继续;本任务不写任何文件、不建目录、不派发执行子代理。** + + ① **要做什么 / 为什么:** 为即将新增的包 `@motajs/render-anim` 建立公开类型契约,先让用户确认字段、只读性、可选性与泛型形状,避免后续实现返工。本计划**只写类型**,不实现 `RenderAnimater`、不实现 `parseRenderAnimation`。 + + ② **精确文件形态(3 个新文件,逐字):** + - `packages/render-anim/package.json`:只有两个顶层键,4 空格缩进 + CRLF + 末尾换行——`name` 为 `@motajs/render-anim`;`dependencies` 为 `{ "@motajs/render": "workspace:*" }`。不写 `version` / `private` / `type` / `main` / `module` / `types` / `exports` / `scripts`。 + - `packages/render-anim/src/types.ts`:第一行是 `import { ITexture, ITextureAnimater } from '@motajs/render';`(普通引入,随后一个空行);之后按固定顺序导出 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`,隐式数值,末成员不带尾逗号)、`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame`、`IAnimationAnimaterData`、`IAnimationData`;成员名 / 顺序 / 类型 / `readonly` / 可选性与用户骨架完全相同(对照表见计划正文「类型骨架逐成员对照」);**汇报时必须附完整逐成员清单(所属类型 / 成员名 / 类型 / `readonly` / 可选性 / `extends`),不得只报 7 个类型名**;枚举成员与接口成员上方各一行中文 jsDoc(`普通混合` / `加算混合`),枚举与接口本身不写 jsDoc;文件内无 `class` / `function` / 箭头函数 / `let` / `export default` / 其它运行时语句,`const` 仅出现在 `export const enum AnimationBlendMode` 声明处。 + - `packages/render-anim/src/index.ts`:只有一行 `export * from './types';`(CRLF 结尾),不转发 `@motajs/render` 的符号。 + + ③ **已确认但本计划不实现的设计(只记录):** 浏览器专用;`RenderAnimater` 将实现 `ITextureAnimater`(`once` / `cycled` 返回 `Generator`,帧时序与循环由外部驱动);逐帧惰性合成,产出的可渲染对象在下一次 `.next()` 前有效,下一次恢复或 `return` 时清理上一帧;使用 `MotaOffscreenCanvas2D` 并调用 `setHD(false)` / `setAntiAliasing(false)`,不直接调浏览器 canvas API;空帧产出透明小图;帧尺寸可不同,每帧自带 `width` / `height` / `originX` / `originY`;`blend` 以 `AnimationBlendMode` 存于文件并由解析数据暴露,动画器不决定混合;`sound` 由宿主播放;解析函数为 `parseRenderAnimation(buffer: ArrayBuffer): Promise`。 + + ④ **dev.md 合规点(逐条):** UTF-8 无 BOM + CRLF + 4 空格、无制表符;`types.ts` 第一行必须是 `import`,import 之间不夹注释与空行;不使用 `import type`;不使用 `any` / `as` / `?.` / `!`;所有成员显式类型并加 `readonly`;枚举成员与接口成员 jsDoc 在源头(`types.ts`)且为单行中文;接口与枚举本身不写 jsDoc(dev.md 不建议);不使用非 jsDoc 的多行注释;不写复述代码的注释;文件名为小驼峰;`AnimationBlendMode` 使用 `export const enum` 且成员为隐式数值,符合 dev.md「不使用字符串作为键或特殊标识符(如枚举值、事件名、状态名等),应使用枚举代替」;公开类型集中在 `types.ts`;`index.ts` 只做同目录星号导出,不跨包转发。 + + ⑤ **OI-1 已裁定为 (B)(记录,不再询问):** `AnimationBlendMode` 使用 `export const enum`,成员为 `Normal`(jsDoc「普通混合」)与 `Add`(jsDoc「加算混合」),4 空格缩进、隐式数值、末成员不带尾逗号,声明本身不写 jsDoc;`IAnimationData.blend` 仍为 `AnimationBlendMode`。这正是 dev.md「不使用字符串作为键或特殊标识符(如枚举值、事件名、状态名等),应使用枚举代替」的要求,原有字符串字面量联合的例外不再成立。另 `IAnimationSound.name: string` 属允许的字符串 id 一类(OI-2),保留。 + + ⑥ **命名与成员级内容:** 类型名与成员名全部由用户提供,本计划不新增任何用户未提供的名称;jsDoc 不引入新标识符。若需改名,请在批准前说明。以下成员级内容同样须逐项批准,不得仅以「7 个名称一致」视为通过:`readonly` 修饰、成员类型(`number` / `string` / `readonly` 数组 / `ITexture` / `ITextureAnimater` / `AnimationBlendMode`)、可选性(`volume?` / `pitch?` / `sound?`)、`IAnimationData extends IAnimationAnimaterData` 与 `AnimationBlendMode` 的 `export const enum` 形态(成员序列 `Normal` → `Add`),一律以计划正文「类型骨架逐成员对照」为准。 + + ⑦ **影响范围:** 只新增 `packages/render-anim/package.json`、`packages/render-anim/src/types.ts`、`packages/render-anim/src/index.ts`,以及执行后的 `09-52-SUMMARY.md`;不改任何既有文件 / 配置 / 其它包 / 09-50 与 09-51 的计划与摘要;不运行 `pnpm install`,锁文件不变;不新增包级 tsconfig(根配置已覆盖并已映射 `@motajs/*`)。 + + ⑧ **验证形态(只读静态):** prettier `--check`(3 个文件)、eslint(2 个 ts 文件)、node 结构 / 内容门禁(CRLF、无制表符、1 个 `export const enum`(成员 `Normal` / `Add`)+ 6 个接口、逐成员片段、逐接口成员归属、无 `import type` / `any` / `class` / `function` / `let` / 箭头函数 / `export default`,且 `const` / `enum` 仅出现在 `AnimationBlendMode` 声明处、`index.ts` 唯一内容、`package.json` 仅两个键);可选类型门禁只过滤 `packages/render-anim` 的诊断,全仓既有类型错误与 `vue-tsc` 非 0 退出码属既有基线、不视为本次失败。任何 prettier / eslint 失败都由 Task 2 修正,Task 3 只上报并重跑,不改源码。**不运行测试、构建、dev server 或浏览器;不做运行时主张。** + + ⑨ **不提交:** 全程 0 次提交,禁止任何 git 写命令;改动留在工作树供审阅。 + + ⑩ **编号说明(须报备):** 派发编号为 09-52 / 波次 1;`09-01`..`09-51` 的计划均已占用,本计划不删除、不覆盖任何既有记录。 + + ⑪ **待确认项:** OI-1 **已裁定为 (B)**(`export const enum AnimationBlendMode`),本轮不再询问;OI-4(jsDoc 语言与接口级注释)、OI-5(估算未校准);OI-3(实现阶段另立计划)。 + + ⑫ **已知限制:** 本计划不验证类型能否通过全仓类型检查(仓库存在大量既有 / 预期类型错误);不验证任何运行时行为;SUMMARY 将如实声明未运行项。 + + 用户针对本修订版恰好回复「可以执行」后才进入 Task 1–4;OI-1 已按用户此前的「B,可以执行」裁定为 (B),本任务只汇报 `export const enum AnimationBlendMode` 形态并请求执行批准,不再询问 (A)/(B)。在此之前不执行 Task 1 / Task 2 / Task 3 / Task 4 的任何一步,也不派发执行子代理。 + 已完整汇报 ①–⑫(含三个文件逐字形态、1 个 `export const enum` + 6 个接口的逐成员类型 / `readonly` / 可选性、已确认但暂不实现的设计、dev.md 合规点、OI-1 B 的裁定记录、命名、影响范围、验证形态、不提交、编号、待确认项、已知限制),并收到用户针对本修订版的「可以执行」回复。 + + 逐条核对 ①–⑫ 是否与你的意图一致,特别是:② 的 `package.json` 仅两个键与 `index.ts` 单行导出;⑤ 的 OI-1 已裁定为 (B) 的 `export const enum` 形态(成员 `Normal` / `Add`);⑥ 的成员级 `readonly`、成员类型、可选性与 `extends` 逐项确认;④ 的 jsDoc 方案;⑦ 的范围;⑧ 的「只读静态、可选类型门禁」口径;⑩ 的编号说明。 + + OI-1 已按你的「B,可以执行」裁定为 (B)(`export const enum AnimationBlendMode`),无需再次选择;请在确认本修订版(尤其是 ② 的枚举形态、⑤ 的 (B) 记录、④ 的 jsDoc、⑧ 的验证口径)后,回复恰好四个字「可以执行」。任何修改意见请直接说明。 + + + + Task 1: 读取既有类型与工具链约定,记录精确的类型 / 命名方案与执行基线(只读) + 无(本任务只读;不写仓库内任何文件) + + - AGENTS.md(执行前获批、命名先反馈、既有注释不得修改、最小修改也走 GSD、GSD 流程豁免范围) + - dev.md(CRLF、4 空格、首行 import、禁止 `import type`、无 `any`、成员 jsDoc 在源头、字符串标识符规则) + - packages/render/src/assets/types.ts(`ITexture` / `ITextureAnimater` / `ITextureRenderable` 的既有形状与 jsDoc 风格) + - packages/render/src/assets/texture.ts(确认 `ITexture` 是接口而非类) + - packages/render/src/core/canvas2d.ts(`MotaOffscreenCanvas2D.setHD` / `setAntiAliasing` 签名) + - packages/render/package.json 与 packages/audio/package.json(包清单范式) + - tsconfig.json、.prettierrc、eslint.config.js(`@motajs/*` 路径映射与格式化 / lint 配置) + - 09-50-PLAN.md、09-50-SUMMARY.md、09-51-PLAN.md、09-51-SUMMARY.md、09-52-PLAN.md(哈希基线对象) + + + 仅在收到用户恰好回复「可以执行」之后执行。本任务不写仓库内文件,只读取并记录,供 Task 2 落码、Task 4 写入 SUMMARY。 + + 1. 读取并记录以下事实(逐条写进执行笔记,Task 4 汇总进 SUMMARY): + - `AGENTS.md`:执行前须获批、命名先反馈、不得修改既有注释、最小修改也走 GSD、GSD 流程豁免范围。 + - `dev.md`:CRLF、4 空格、模块无副作用、不转发导出、无类型导入、import 必须在首行且中间不夹注释 / 空行、命名规则、注释规范(成员 jsDoc 在源头、接口 / 枚举本身不写、单行注释 `// ` 后一个空格、不得复述代码)、类型规范(无必要 `any`、显式成员类型、合理 `readonly`、对象类型单独 `interface`、公开接口放 `types.ts`)、字符串标识符规则。 + - `packages/render/src/assets/types.ts`:`ITexture`、`ITextureAnimater`(`once` / `cycled` 返回 `Generator`)、`ITextureRenderable` 的既有形状与成员 jsDoc 风格(单行、中文、无句号)。 + - `packages/render/src/assets/texture.ts`:`export class Texture implements ITexture` 的存在,确认 `ITexture` 是接口而非类。 + - `packages/render/src/core/canvas2d.ts`:确认 `MotaOffscreenCanvas2D` 的 `setHD(hd)` / `setAntiAliasing(anti)` 方法签名(仅供 SUMMARY 记录后续实现约定,本计划不调用)。 + - `packages/render/package.json`(只有 `name`)与 `packages/audio/package.json`(`name` + `dependencies` 的 `workspace:*` 范式)。 + - 根 `tsconfig.json`:`include` 覆盖 `packages/**/*.ts`,`paths['@motajs/*'] = ['./packages/*/src']`,`strict: true`,`isolatedModules: true`,`noEmit: true`。 + - `.prettierrc`(`printWidth: 80` / `tabWidth: 4` / `singleQuote: true` / `arrowParens: avoid` / `trailingComma: none` / `endOfLine: crlf`)与 `eslint.config.js`(typescript-eslint + prettier 插件)。 + + 2. 记录**精确类型 / 命名方案**:三个文件的逐行结构、`types.ts` 的 1 个 `export const enum AnimationBlendMode` + 6 个接口与逐成员名称 / 类型 / `readonly` / 可选性(以计划正文「类型骨架逐成员对照」为准)、每个成员的单行中文 jsDoc 文本(含枚举成员「普通混合」/「加算混合」)、`package.json` 的两个键与缩进、`index.ts` 的唯一一行。方案中不得出现用户未提供的公开名称。 + + 3. 采集**范围基线**:`%TEMP%` **不是文件系统路径**,基线文件的实际位置由 Node 的 `process.env.TEMP` 解析;后续所有门禁都写作 `(process.env.TEMP||'.')`,Windows 下 `TEMP` 始终存在,两处解析到同一目录。用下面这条 `node` 命令把当前 `git status --porcelain -uall` 的完整输出(先统一为 LF)写入 `gsd-09-52-status.txt`(UTF-8 无 BOM),并回显绝对路径:`node -e "const{execSync}=require('child_process'),fs=require('fs');const out=execSync('git status --porcelain -uall',{encoding:'utf8'}).replace(/\r\n/g,'\n');const p=(process.env.TEMP||'.')+'/gsd-09-52-status.txt';fs.writeFileSync(p,out,{encoding:'utf8'});console.log(p);"`。该基线用于 Task 4 做集合差,因为工作树可能已存在 09-51 的未提交改动。写入后必须立刻断言:基线文件内容与当前 `git status --porcelain -uall` 完全一致(见 `` 第 3 条),否则重新采集;不得用 `.trim()` 之外的方式放宽比较。 + + 4. 采集**哈希基线**:对 `.planning/phases/09-minimum-browser-runnable/09-50-PLAN.md`、`09-50-SUMMARY.md`、`09-51-PLAN.md`、`09-51-SUMMARY.md`、`09-52-PLAN.md` 逐个执行 `git hash-object`,把 `路径 哈希` 写入同一 `process.env.TEMP` 目录下的 `gsd-09-52-hashes.txt`(UTF-8 无 BOM),命令:`node -e "const{execSync}=require('child_process'),fs=require('fs');const files=['.planning/phases/09-minimum-browser-runnable/09-50-PLAN.md','.planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md','.planning/phases/09-minimum-browser-runnable/09-51-PLAN.md','.planning/phases/09-minimum-browser-runnable/09-51-SUMMARY.md','.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md'];const lines=files.map(f=>f+' '+execSync('git hash-object '+f,{encoding:'utf8'}).trim());const p=(process.env.TEMP||'.')+'/gsd-09-52-hashes.txt';fs.writeFileSync(p,lines.join('\n')+'\n',{encoding:'utf8'});console.log(p);console.log(lines.join('\n'));"`(每行恰为「无空格路径 + 空格 + 40 位小写十六进制」)。把 `09-52-PLAN.md` 纳入基线,是为了证明执行期间计划文件本身未被改写(它不在 `files_modified` 内)。 + + 5. 确认 `packages/render-anim` 目录当前不存在、`git status --porcelain -uall -- packages/render-anim` 输出为空。任一命令非 0 退出或输出与预期不符,必须停下上报,不得继续 Task 2。 + + + node -e "const{execSync}=require('child_process');const out=execSync('git status --porcelain -uall -- packages/render-anim',{encoding:'utf8'});if(out.trim()){console.error(out);process.exit(1);}console.log('OK: render-anim scope empty');" + `git status --porcelain -uall -- packages/render-anim` 输出非空(打印该输出,脚本退出码 1)。 + node -e "const fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-52-hashes.txt';const s=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n').trim().split('\n').filter(Boolean);const ok=s.length===5&&s.every(l=>{const p=l.split(' ');return p.length===2&&p[1].length===40&&!/[^0-9a-f]/.test(p[1]);});if(!ok){console.error(s.join('\n'));process.exit(1);}console.log(s.join('\n'));" + 哈希基线不是 5 行,或任一行不是「无空格路径 + 40 位小写十六进制」,脚本打印内容并以 1 退出。 + node -e "const{execSync}=require('child_process'),fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-52-status.txt';const base=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n').trim();const now=execSync('git status --porcelain -uall',{encoding:'utf8'}).replace(/\r\n/g,'\n').trim();if(base!==now){console.error('baseline drift');process.exit(1);}console.log('OK: status baseline matches current tree');" + 基线文件与当前 `git status --porcelain -uall` 不一致(打印 `baseline drift`,退出码 1)。 + + + - 已读取并在执行笔记中记录 `read_first` 列出的全部文件事实(规则、既有类型、包清单范式、根配置、格式化 / lint 配置)。 + - `process.env.TEMP` 目录下的 `gsd-09-52-status.txt`(与门禁 `(process.env.TEMP||'.')` 解析一致,不是 `%TEMP%` 字面量)已生成,且 Task 1 结束时与当前 `git status --porcelain -uall` 完全一致。 + - `process.env.TEMP` 目录下的 `gsd-09-52-hashes.txt`(与门禁同一路径解析)恰有 5 行,覆盖 09-50-PLAN / 09-50-SUMMARY / 09-51-PLAN / 09-51-SUMMARY / 09-52-PLAN,每行「无空格路径 + 空格 + 40 位哈希」。 + - `packages/render-anim` 在 `git status --porcelain -uall -- packages/render-anim` 下为空。 + - 已记录 1 个 `export const enum AnimationBlendMode` + 6 个接口与逐成员方案(名称 / 类型 / `readonly` / 可选性),且未新增用户未提供的公开名称。 + + 已读取并记录 dev.md / AGENTS.md 的适用规则、`@motajs/render` 的既有类型与包清单范式、根 tsconfig 与格式化配置;已记录 1 个 `export const enum AnimationBlendMode` + 6 个接口与逐成员方案;`process.env.TEMP` 目录下的 `gsd-09-52-status.txt` 与 `gsd-09-52-hashes.txt`(与门禁 `(process.env.TEMP||'.')` 解析一致,未使用不可作为路径的 `%TEMP%` 字面量)均已生成且内容完整(哈希基线含 `09-52-PLAN.md`,共 5 条);`packages/render-anim` 范围为空。 + + + + Task 2: 新建 `@motajs/render-anim` 的三个类型骨架文件 + packages/render-anim/package.json, packages/render-anim/src/types.ts, packages/render-anim/src/index.ts + + - `.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md` 的「类型骨架逐成员对照」与 ``(唯一内容依据;本任务不新增任何对外名称) + - `packages/render/src/assets/types.ts`(`ITexture` / `ITextureAnimater` 的导入来源与成员 jsDoc 风格) + - `packages/audio/package.json`(`name` + `dependencies` 两键的 `workspace:*` 包清单范式) + - `.prettierrc` 与根 `tsconfig.json`(CRLF、4 空格、`@motajs/*` 路径映射) + + + 仅在 Task 1 完成且收到「可以执行」之后执行。用 Write 工具直接创建文件(不得使用 `cat << 'EOF'` / here-string / `Set-Content` 拼内容);三个文件均为 UTF-8 无 BOM、CRLF 换行、4 空格缩进、无制表符、末尾保留换行。Write 工具传入的内容字符串必须显式包含 `\r\n`,不得依赖编辑器或 `core.autocrlf` 自动归一化。 + + **格式化授权(仅限本任务):** 三个文件写完后,本任务可以对且仅对这三个包文件运行 `node node_modules/prettier/bin/prettier.cjs --write packages/render-anim/package.json packages/render-anim/src/types.ts packages/render-anim/src/index.ts` 做格式归一化;这是本计划唯一被授权执行 `prettier --write` 的任务。Task 3 严格只读,任何 prettier 或 eslint 失败都必须回到本任务修正(只改这三个包文件,不得加 `eslint-disable`、不得改 eslint 配置),再由 Task 3 重跑全部门禁。 + + **1. `packages/render-anim/package.json`**:只有两个顶层键,顺序为 `name`、`dependencies`。`name` 的值是 `@motajs/render-anim`;`dependencies` 只有一个条目,键为 `@motajs/render`、值为 `workspace:*`。不写 `version` / `private` / `type` / `main` / `module` / `types` / `exports` / `scripts` / `devDependencies` / `peerDependencies`;不加注释(JSON 不允许)。 + + **2. `packages/render-anim/src/types.ts`**: + - 第 1 行必须是 `import { ITexture, ITextureAnimater } from '@motajs/render';`(普通引入,禁止 `import type`);第 2 行空行;其后再写类型。文件内不得有第二段 import。 + - 按固定顺序写出 7 个导出:`export const enum AnimationBlendMode`(成员 `Normal` / `Add`,隐式数值,声明与成员均不带显式初始化)、`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame`、`IAnimationAnimaterData`、`IAnimationData`(`extends IAnimationAnimaterData`)。类型之间空一行。 + - `AnimationBlendMode` 逐字形态:顶格 `export const enum AnimationBlendMode {`;下一行 4 空格缩进 `/** 普通混合 */`;下一行 4 空格缩进 `Normal,`;下一行 4 空格缩进 `/** 加算混合 */`;下一行 4 空格缩进 `Add`(末成员不带尾逗号);最后顶格 `}`。成员顺序固定为 `Normal` → `Add`,不得增删成员或改写 jsDoc 文本,声明本身不写 jsDoc。 + - 每个类型的成员名称、成员顺序、成员类型、`readonly`、可选性必须与计划正文「类型骨架逐成员对照」**完全一致**,不得增删成员、不得调整顺序、不得把可选改成必填或反之、不得把只读数组改成可变数组。 + - 枚举成员与接口成员上方各写一行中文 jsDoc:`/** … */`,单行、结尾不加句号、只说明该成员含义(枚举成员为「普通混合」/「加算混合」,接口成员语义见对照表),不得出现英文符号堆砌、不得复述代码、不得假设使用场景。枚举与接口本身不写 jsDoc(dev.md 不建议给枚举 / 接口本身写注释)。 + - 文件内不得出现 `class` / `function` / 箭头函数 / `let` / 默认导出 / 其它运行时语句 / `any` / `as` / `?.` / `!` / 多行 `/* */` 注释;`const` / `enum` 只允许出现在 `export const enum AnimationBlendMode` 这一处声明,不得有第二个枚举或常量。 + + **3. `packages/render-anim/src/index.ts`**:全文只有一行 `export * from './types';`(CRLF 结尾)。不得转发 `@motajs/render` 的任何符号,不得写默认导出、不得写第二行内容、不得加注释。 + + 本任务只创建上述 3 个文件;不得创建 `tsconfig.json`、测试、示例、`README`、实现文件或任何目录内其它文件。 + + + node -e "const fs=require('fs');for(const f of ['packages/render-anim/package.json','packages/render-anim/src/types.ts','packages/render-anim/src/index.ts']){if(!fs.existsSync(f)){console.error('missing '+f);process.exit(1);}}console.log('OK: 3 files exist');" + 任一文件不存在(打印 `missing <路径>`,退出码 1)。 + node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/package.json packages/render-anim/src/types.ts packages/render-anim/src/index.ts + 输出不是 `All matched files use Prettier code style!`,或退出码非 0(prettier 列出待格式化文件)。 + node -e "const fs=require('fs');const files=['packages/render-anim/package.json','packages/render-anim/src/types.ts','packages/render-anim/src/index.ts'];let bad=0;for(const f of files){const s=fs.readFileSync(f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;const tabs=(s.match(/\t/g)||[]).length;const ok=lf===crlf&&tabs===0;if(!ok){bad++;}console.log((ok?'OK ':'FAIL')+' '+f+' lf='+lf+' crlf='+crlf+' tabs='+tabs);}process.exit(bad?1:0);" + 任一行以 `FAIL` 开头(存在非 CRLF 换行或制表符),脚本以非 0 退出。 + + + - 三个包文件均由 `gsd-executor` 用 Write 工具创建,Write 内容显式使用 `\r\n`;未使用 here-doc / here-string / `Set-Content` 拼内容。 + - `prettier --write` 仅在需要时对这三个包文件运行,未触碰任何其它文件;任何 prettier / eslint 失败都由本任务在 `files_modified` 内修正;Task 3 不再执行任何写操作。 + - 三个文件均为 CRLF、无制表符、末尾保留换行;`types.ts` 首行是普通 `import`。 + - `package.json` 只有 `name` 与 `dependencies['@motajs/render'] = 'workspace:*'` 两个顶层键;`index.ts` 只有 `export * from './types';`。 + - `types.ts` 的成员名 / 顺序 / 类型 / `readonly` / 可选性与「类型骨架逐成员对照」逐项一致。 + + 三个文件已按骨架创建:`package.json` 仅 `name` + `@motajs/render: workspace:*`;`types.ts` 含 1 个 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`)+ 6 个接口、成员形状与对照表逐字一致、每个成员有单行中文 jsDoc、首行为普通 import;`index.ts` 仅一行 `export * from './types';`;三个文件均为 CRLF 且 prettier `--check` 通过。 + + + + Task 3: dev.md 合规与内容门禁(只读静态检查) + 无(本任务只读;不修改任何文件) + + - `.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md` 的「类型骨架逐成员对照」(成员顺序、接口归属、类型、`readonly`、可选性的唯一判定依据) + - `packages/render-anim/src/types.ts`、`packages/render-anim/src/index.ts`、`packages/render-anim/package.json`(待检查对象) + - `dev.md`(CRLF、4 空格、首行 import、无 `import type` / `any`、成员 jsDoc 在源头、无实现体) + - `.prettierrc` 与 `eslint.config.js`(门禁使用的实际配置) + + + 只运行只读检查;任何一项失败都必须回到 Task 2 修正后重跑全部检查,不得通过放宽检查、删除断言或忽略文件来「变绿」。本任务**严格只读**:不运行 `prettier --write`、不写任何文件。 + + 1. **格式门禁(prettier)**:运行 `node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/package.json packages/render-anim/src/types.ts packages/render-anim/src/index.ts`,必须退出码 0 且输出 `All matched files use Prettier code style!`。失败时**回到 Task 2** 修正(格式归一化由 Task 2 的 `prettier --write` 授权执行),本任务不得运行 `--write`、不得手改文件或格式规则;Task 2 修正后由本任务重跑全部检查。 + + 2. **lint 门禁(eslint)**:运行 `node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts`,必须退出码 0、无输出。失败时**本任务不修改任何源码**:把 eslint 的原始输出(文件、行号、规则名、说明)逐字记录并上报,然后**回到 Task 2** 修正(只改 `files_modified` 内的三个包文件,不得添加 `eslint-disable`、不得改 eslint 配置);Task 2 修正后由本任务从第 1 步起重跑全部门禁。 + + 3. **换行与缩进门禁**:运行 `node -e "const fs=require('fs');const files=['packages/render-anim/package.json','packages/render-anim/src/types.ts','packages/render-anim/src/index.ts'];let bad=0;for(const f of files){const s=fs.readFileSync(f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;const tabs=(s.match(/\t/g)||[]).length;const ok=lf===crlf&&tabs===0;if(!ok){bad++;}console.log((ok?'OK ':'FAIL')+' '+f+' lf='+lf+' crlf='+crlf+' tabs='+tabs);}process.exit(bad?1:0);"`,必须三行均为 `OK` 且退出码 0(每个换行都是 CRLF、无制表符)。 + + 4. **内容与结构门禁**:运行 `node -e "const fs=require('fs');const t=fs.readFileSync('packages/render-anim/src/types.ts','utf8');const i=fs.readFileSync('packages/render-anim/src/index.ts','utf8');const p=JSON.parse(fs.readFileSync('packages/render-anim/package.json','utf8'));const fail=[];const names=['IAnimationBitmap','IAnimationSound','IAnimationObject','IAnimationFrame','IAnimationAnimaterData','IAnimationData'];for(const n of names){if(!new RegExp('export interface '+n+'\\b').test(t)){fail.push('missing '+n);}}const em=t.replace(/\r\n/g,'\n').match(/export const enum AnimationBlendMode \{\n([\s\S]*?)\n\}/);if(!em){fail.push('missing enum AnimationBlendMode');}else{const enumBody=em[1].split('\n').map(l=>l.trim()).filter(l=>l.length&&!l.startsWith('/**')&&!l.startsWith('*')&&!l.startsWith('//')).join(' ');if(enumBody!=='Normal, Add'){fail.push('enum members: '+enumBody);}}const snippets=['readonly x: number;','readonly y: number;','readonly width: number;','readonly height: number;','readonly name: string;','readonly volume?: number;','readonly pitch?: number;','readonly index: number;','readonly scale: number;','readonly opacity: number;','readonly mirror: number;','readonly rotate: number;','readonly originX: number;','readonly originY: number;','readonly objects: readonly IAnimationObject[];','readonly sound?: readonly IAnimationSound[];','readonly bitmaps: readonly IAnimationBitmap[];','readonly frames: readonly IAnimationFrame[];','readonly texture: ITexture;','readonly animater: ITextureAnimater;','readonly blend: AnimationBlendMode;'];for(const s of snippets){if(t.indexOf(s)===-1){fail.push('missing snippet '+s);}}if(t.indexOf('import { ITexture, ITextureAnimater } from')!==0){fail.push('import not first');}if(/\bimport\s+type\b/.test(t)){fail.push('import type');}if(/\bany\b/.test(t)){fail.push('any');}if(/\bclass\b/.test(t)){fail.push('class');}if(/\bfunction\b/.test(t)){fail.push('function');}if(t.indexOf('=>')!==-1){fail.push('arrow');}const noEnum=t.replace(/export const enum AnimationBlendMode \{[\s\S]*?\n\}/,'');if(/\bconst\b/.test(noEnum)){fail.push('const');}if(/\blet\b/.test(t)){fail.push('let');}if(/\benum\b/.test(noEnum)){fail.push('enum');}if(/export\s+default/.test(t)){fail.push('export default');}const ic=i.replace(/\s/g,'');if(ic.indexOf('export*from')!==0||ic.indexOf('export',1)!==-1||ic.indexOf('./types')===-1){fail.push('index content');}if(Object.keys(p).sort().join(',')!=='dependencies,name'){fail.push('package keys');}if(p.name!=='@motajs/render-anim'){fail.push('package name');}if(!p.dependencies||p.dependencies['@motajs/render']!=='workspace:*'){fail.push('package dep');}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK');process.exit(fail.length?1:0);"`,必须输出 `OK` 且退出码 0。该门禁覆盖:1 个 `export const enum AnimationBlendMode`(成员 `Normal` / `Add` 及顺序)+ 6 个接口存在、25 个成员片段逐字存在、首行 import、无 `import type` / `any` / `class` / `function` / 箭头函数,且 `const` / `enum` 仅出现在枚举声明处、`index.ts` 只有唯一星号导出、`package.json` 只有两个键且依赖正确。 + + 5. **逐接口成员门禁(把内容门禁从「片段存在」提升为「成员归属正确」)**:运行 `` 中的逐接口 node 命令。它按 `export interface <名称> ... { ... }` 逐块解析,把每个块的成员行序列与期望序列逐字比对;成员被放进错误接口、顺序变化、`readonly` / `?` / 类型变化都会使 `got !== want` 并判失败。接口数、`IAnimationData extends IAnimationAnimaterData`、以及 `AnimationBlendMode` 的 `export const enum` 声明与成员序列 `Normal` → `Add` 也在此断言。 + + 6. **人工逐条复核(只读,且是绑定判定)**:打开 `types.ts`,逐成员对照计划正文「类型骨架逐成员对照」,确认成员顺序、接口归属、`readonly`、`?`、数组只读性、`extends` 关系、泛型实参全部一致,并确认 `AnimationBlendMode` 是 `export const enum`、成员恰为 `Normal` / `Add`(隐式数值、无显式初始化、末成员无尾逗号);确认每个成员都有且只有一行 jsDoc;确认没有任何实现体、没有 `export default`、没有多余空行或注释。打开 `index.ts` 与 `package.json` 逐字确认。**本步骤的结论是绑定判定**:必须写入执行笔记与 SUMMARY;任一成员与对照表不一致即判本任务失败,不得以「7 个名称一致」或「片段存在」放行。 + + 7. **可选类型门禁(跨平台 node 过滤,不使用 PowerShell 专有的 `Select-String`)**:如需类型检查,运行 `` 中的 node 命令。它捕获 `vue-tsc --noEmit` 的 stdout + stderr(非 0 退出码被捕获,不直接判失败),只用 `packages/render-anim` 过滤结果判定:0 行诊断视为通过,出现任一条即失败。全仓既有 / 预期类型错误与 `vue-tsc` 非 0 退出码属既有基线,不得视为本次失败,也不得因此修改其它文件。若未运行该门禁,SUMMARY 必须写明「类型检查未运行」,不得宣称类型检查通过。 + + 本任务不运行测试、构建、dev server、浏览器或包管理命令;**严格只读**:不运行 `prettier --write`、不写任何文件、不修改源码。任何 prettier / eslint / 内容失败都必须回到 Task 2 修正,再由本任务重跑全部门禁;本任务对 eslint 失败只上报原始输出并停止,绝不就地改源码或加 `eslint-disable`。 + + + node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/package.json packages/render-anim/src/types.ts packages/render-anim/src/index.ts + 输出不是 `All matched files use Prettier code style!`,或退出码非 0(prettier 列出待格式化文件)。 + node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts + eslint 打印任一 error / warning,或退出码非 0。 + node -e "const fs=require('fs');const files=['packages/render-anim/package.json','packages/render-anim/src/types.ts','packages/render-anim/src/index.ts'];let bad=0;for(const f of files){const s=fs.readFileSync(f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;const tabs=(s.match(/\t/g)||[]).length;const ok=lf===crlf&&tabs===0;if(!ok){bad++;}console.log((ok?'OK ':'FAIL')+' '+f+' lf='+lf+' crlf='+crlf+' tabs='+tabs);}process.exit(bad?1:0);" + 任一行以 `FAIL` 开头(存在非 CRLF 换行或制表符),脚本以非 0 退出。 + node -e "const fs=require('fs');const t=fs.readFileSync('packages/render-anim/src/types.ts','utf8');const i=fs.readFileSync('packages/render-anim/src/index.ts','utf8');const p=JSON.parse(fs.readFileSync('packages/render-anim/package.json','utf8'));const fail=[];const names=['IAnimationBitmap','IAnimationSound','IAnimationObject','IAnimationFrame','IAnimationAnimaterData','IAnimationData'];for(const n of names){if(!new RegExp('export interface '+n+'\\b').test(t)){fail.push('missing '+n);}}const em=t.replace(/\r\n/g,'\n').match(/export const enum AnimationBlendMode \{\n([\s\S]*?)\n\}/);if(!em){fail.push('missing enum AnimationBlendMode');}else{const enumBody=em[1].split('\n').map(l=>l.trim()).filter(l=>l.length&&!l.startsWith('/**')&&!l.startsWith('*')&&!l.startsWith('//')).join(' ');if(enumBody!=='Normal, Add'){fail.push('enum members: '+enumBody);}}const snippets=['readonly x: number;','readonly y: number;','readonly width: number;','readonly height: number;','readonly name: string;','readonly volume?: number;','readonly pitch?: number;','readonly index: number;','readonly scale: number;','readonly opacity: number;','readonly mirror: number;','readonly rotate: number;','readonly originX: number;','readonly originY: number;','readonly objects: readonly IAnimationObject[];','readonly sound?: readonly IAnimationSound[];','readonly bitmaps: readonly IAnimationBitmap[];','readonly frames: readonly IAnimationFrame[];','readonly texture: ITexture;','readonly animater: ITextureAnimater;','readonly blend: AnimationBlendMode;'];for(const s of snippets){if(t.indexOf(s)===-1){fail.push('missing snippet '+s);}}if(t.indexOf('import { ITexture, ITextureAnimater } from')!==0){fail.push('import not first');}if(/\bimport\s+type\b/.test(t)){fail.push('import type');}if(/\bany\b/.test(t)){fail.push('any');}if(/\bclass\b/.test(t)){fail.push('class');}if(/\bfunction\b/.test(t)){fail.push('function');}if(t.indexOf('=>')!==-1){fail.push('arrow');}const noEnum=t.replace(/export const enum AnimationBlendMode \{[\s\S]*?\n\}/,'');if(/\bconst\b/.test(noEnum)){fail.push('const');}if(/\blet\b/.test(t)){fail.push('let');}if(/\benum\b/.test(noEnum)){fail.push('enum');}if(/export\s+default/.test(t)){fail.push('export default');}const ic=i.replace(/\s/g,'');if(ic.indexOf('export*from')!==0||ic.indexOf('export',1)!==-1||ic.indexOf('./types')===-1){fail.push('index content');}if(Object.keys(p).sort().join(',')!=='dependencies,name'){fail.push('package keys');}if(p.name!=='@motajs/render-anim'){fail.push('package name');}if(!p.dependencies||p.dependencies['@motajs/render']!=='workspace:*'){fail.push('package dep');}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK');process.exit(fail.length?1:0);" + 脚本打印 `FAIL` 并列出缺失项(缺失枚举 / 接口 / 成员片段、枚举成员不是 `Normal, Add`、首行不是 import、出现 `import type` / `any` / `class` / `function` / 箭头 / `let` / `export default`、`const` / `enum` 出现在 `AnimationBlendMode` 声明之外、`index.ts` 内容不符或 `package.json` 键 / 依赖不符),退出码 1。 + node -e "const fs=require('fs');const t=fs.readFileSync('packages/render-anim/src/types.ts','utf8').replace(/\r\n/g,'\n');const exp={IAnimationBitmap:{ext:null,m:['readonly x: number;','readonly y: number;','readonly width: number;','readonly height: number;']},IAnimationSound:{ext:null,m:['readonly name: string;','readonly volume?: number;','readonly pitch?: number;']},IAnimationObject:{ext:null,m:['readonly index: number;','readonly x: number;','readonly y: number;','readonly scale: number;','readonly opacity: number;','readonly mirror: number;','readonly rotate: number;']},IAnimationFrame:{ext:null,m:['readonly width: number;','readonly height: number;','readonly originX: number;','readonly originY: number;','readonly objects: readonly IAnimationObject[];','readonly sound?: readonly IAnimationSound[];']},IAnimationAnimaterData:{ext:null,m:['readonly bitmaps: readonly IAnimationBitmap[];','readonly frames: readonly IAnimationFrame[];']},IAnimationData:{ext:'IAnimationAnimaterData',m:['readonly texture: ITexture;','readonly animater: ITextureAnimater;','readonly blend: AnimationBlendMode;']}};const re=/export interface (\w+)(?: extends (\w+))? \{([\s\S]*?)\n\}/g;const seen={};let mm;while((mm=re.exec(t))){seen[mm[1]]={ext:mm[2]||null,body:mm[3]};}const fail=[];for(const n of Object.keys(exp)){const s=seen[n];if(!s){fail.push('missing interface '+n);continue;}if((s.ext||null)!==exp[n].ext){fail.push(n+' extends '+(s.ext||'none')+' != '+String(exp[n].ext));}const got=s.body.split('\n').map(l=>l.trim()).filter(l=>l.length&&!l.startsWith('/**')&&!l.startsWith('*')&&!l.startsWith('//')).join(' ');const want=exp[n].m.join(' ');if(got!==want){fail.push(n+' members: '+got);}}if(Object.keys(seen).length!==6){fail.push('interface count '+Object.keys(seen).length);}const em=/export const enum AnimationBlendMode \{\n([\s\S]*?)\n\}/.exec(t);if(!em){fail.push('AnimationBlendMode enum missing');}else{const gotEnum=em[1].split('\n').map(l=>l.trim()).filter(l=>l.length&&!l.startsWith('/**')&&!l.startsWith('*')&&!l.startsWith('//')).join(' ');if(gotEnum!=='Normal, Add'){fail.push('AnimationBlendMode members: '+gotEnum);}}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK: per-interface skeleton matches');process.exit(fail.length?1:0);" + 脚本打印 `FAIL` 并列出接口级差异(成员落在错误接口、顺序 / `readonly` / `?` / 类型不符、接口数不是 6、`extends` 不符,或 `AnimationBlendMode` 不是 `export const enum`、成员序列不是 `Normal, Add`),退出码 1。 + node -e "const{execSync}=require('child_process');let out='';try{out=execSync('node node_modules/vue-tsc/bin/vue-tsc.js --noEmit',{encoding:'utf8',maxBuffer:67108864,stdio:['ignore','pipe','pipe']});}catch(e){out=String(e.stdout||'')+String(e.stderr||'');}const hits=out.split(/\r?\n/).filter(l=>/packages[\\\/]render-anim[\\\/]/.test(l));console.log(hits.length?hits.join('\n'):'OK: no render-anim diagnostics');process.exit(hits.length?1:0);" + 过滤结果出现任一 `packages/render-anim` 诊断(打印诊断并以 1 退出);其它文件的诊断与 `vue-tsc` 非 0 退出码不参与判定。 + + + - prettier `--check`、eslint、CRLF / 制表符门禁、内容结构门禁(含 `let` / `export default` 负向检查,以及 `const` / `enum` 仅允许出现在 `AnimationBlendMode` 声明处)、逐接口成员门禁(含枚举成员序列)均以退出码 0 完成。 + - 逐接口门禁证明每个成员位于「类型骨架逐成员对照」指定的接口内,且接口数、顺序、类型、`readonly`、`?` 与 `extends` 完全一致;错放成员会使门禁非 0。 + - 人工逐成员复核是绑定判定,结论已写入执行笔记与 SUMMARY,未以名称级一致或片段存在代替类型级一致。 + - 本任务未写任何文件、未修改源码、未运行 `prettier --write`;eslint 失败一律按「上报原始输出 → 回到 Task 2 修正 → 重跑全部门禁」处理,未就地改源码、未加 `eslint-disable`、未改 eslint 配置;类型门禁的「已运行(0 行 render-anim 诊断)/ 未运行」状态已记录。 + + prettier `--check`、eslint、CRLF / 制表符门禁、内容结构门禁(含 `let` / `export default` 负向检查,以及 `const` / `enum` 仅允许出现在 `AnimationBlendMode` 声明处)与逐接口成员门禁(含枚举成员序列)全部通过;期间任何 prettier / eslint / 内容失败均已回到 Task 2 修正后由本任务重跑全部门禁,本任务自身零写入(未改源码、未加 `eslint-disable`、未改 eslint 配置);已逐成员人工复核并形成绑定结论;类型门禁的「运行 / 未运行」状态已记录,且未运行测试、构建、dev server 或浏览器。 + + + + Task 4: 范围 / 哈希闸门自检并写 `09-52-SUMMARY.md` + .planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md + + - `.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md` 的 `` 与「覆盖审计」(SUMMARY 必须覆盖的条目) + - `process.env.TEMP` 目录下的 `gsd-09-52-status.txt` 与 `gsd-09-52-hashes.txt`(Task 1 采集的范围 / 哈希基线;与门禁 `(process.env.TEMP||'.')` 解析一致) + - Task 1 / Task 2 / Task 3 的执行笔记与各门禁真实输出 + - `09-50-PLAN.md`、`09-50-SUMMARY.md`、`09-51-PLAN.md`、`09-51-SUMMARY.md`、`09-52-PLAN.md`(哈希闸门对象) + + + 只写 `09-52-SUMMARY.md`,不修改任何代码文件。 + + 1. **范围闸门**:运行 `node -e "const{execSync}=require('child_process'),fs=require('fs');const base=fs.readFileSync((process.env.TEMP||'.')+'/gsd-09-52-status.txt','utf8').split(/\r?\n/).filter(l=>l.length>0);const cur=execSync('git status --porcelain -uall',{encoding:'utf8'}).split(/\r?\n/).filter(l=>l.length>0);const expected=['?? packages/render-anim/package.json','?? packages/render-anim/src/index.ts','?? packages/render-anim/src/types.ts'];const summary='?? .planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md';const added=cur.filter(l=>base.indexOf(l)===-1);const removed=base.filter(l=>cur.indexOf(l)===-1);const extra=added.filter(l=>expected.indexOf(l)===-1&&l!==summary);const missing=expected.filter(l=>added.indexOf(l)===-1);console.log('added:\n'+added.join('\n'));console.log('removed:\n'+removed.join('\n'));console.log('extra:\n'+extra.join('\n'));console.log('missing:\n'+missing.join('\n'));process.exit(extra.length+missing.length+removed.length?1:0);"`。要求:`added` 只包含 3 个包文件;若 SUMMARY 已写入,则允许再多 1 条 `?? .planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md`(写入前后均可运行)。`extra` 与 `missing` 为空、`removed` 为空(既有未提交改动一条未少)。任何不一致都必须停下上报,不得用 `.trim()` 或子串匹配放宽。 + + 2. **哈希闸门**:重新对 `09-50-PLAN.md` / `09-50-SUMMARY.md` / `09-51-PLAN.md` / `09-51-SUMMARY.md` / `09-52-PLAN.md` 执行 `git hash-object`,与 `process.env.TEMP` 目录下的 `gsd-09-52-hashes.txt`(与门禁 `(process.env.TEMP||'.')` 同一解析)逐行比对,必须完全一致。把 `09-52-PLAN.md` 纳入闸门,用于证明执行期间计划文件本身未被改写。命令见 `` 第 3 条(成功时输出 `OK: 5 files unchanged`)。 + + 3. **写 `09-52-SUMMARY.md`**(UTF-8 无 BOM、CRLF、4 空格缩进、Markdown 文件名连字符命名),内容至少包含: + - **本计划范围**:只建 `@motajs/render-anim` 的类型骨架;不实现 `RenderAnimater` / `parseRenderAnimation`;不跑测试 / 浏览器 / 构建;0 次提交;09-52 / 波次 1 的编号说明。 + - **逐文件最终形态**:`package.json` 的两个键;`types.ts` 的 1 个 `export const enum AnimationBlendMode` + 6 个接口与逐成员清单(名称 / 类型 / `readonly` / 可选性)、jsDoc 方案、首行 import;`index.ts` 的唯一一行。 + - **Task 1 记录的类型 / 命名方案**:`@motajs/render` 的既有类型、根 tsconfig 的路径映射、包清单范式、命名与 jsDoc 结论。 + - **门禁真实结果**:prettier / eslint / CRLF / 内容结构 / 逐接口成员门禁的实际输出(逐字粘贴关键行);类型门禁「已运行(0 行诊断)」或「未运行」二选一,不得含糊;范围闸门与哈希闸门的实际输出。 + - **未做 / 跳过项**:不运行测试、构建、dev server、浏览器;不做运行时主张;不运行 `pnpm install`;不执行 git 写命令。 + - **设计记录(未实现)**:浏览器专用、`ITextureAnimater` 形状、逐帧惰性合成与下一次 `.next()` 前有效、`MotaOffscreenCanvas2D` + `setHD(false)` / `setAntiAliasing(false)`、空帧透明小图、帧尺寸可变、`blend` 由文件数据暴露、`sound` 由宿主播放、`parseRenderAnimation(buffer: ArrayBuffer): Promise`。 + - **待确认项**:OI-1 的最终裁定(规划期已定 (B):`export const enum AnimationBlendMode`,成员 `Normal` / `Add`)、OI-2、OI-3、OI-4、OI-5。 + - **估算**:`confidence: low`,未校准。 + 4. 不写 `09-52-RESEARCH.md` / `09-52-VALIDATION.md`;不改 `STATE.md` / `ROADMAP.md` / `09-CONTEXT.md`。 + + + node -e "const fs=require('fs');const f='.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md';const s=fs.readFileSync(f,'utf8');const need=['09-52','render-anim','prettier','eslint','类型门禁','0 次提交'];const miss=need.filter(n=>s.indexOf(n)===-1);if(miss.length){console.error('missing '+miss.join(','));process.exit(1);}console.log('OK: summary present');" + SUMMARY 缺少任一必需关键词(打印 `missing <列表>`,退出码 1),或文件不存在(抛出异常并以非 0 退出)。 + node -e "const{execSync}=require('child_process'),fs=require('fs');const base=fs.readFileSync((process.env.TEMP||'.')+'/gsd-09-52-status.txt','utf8').split(/\r?\n/).filter(l=>l.length>0);const cur=execSync('git status --porcelain -uall',{encoding:'utf8'}).split(/\r?\n/).filter(l=>l.length>0);const expected=['?? packages/render-anim/package.json','?? packages/render-anim/src/index.ts','?? packages/render-anim/src/types.ts'];const summary='?? .planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md';const added=cur.filter(l=>base.indexOf(l)===-1);const removed=base.filter(l=>cur.indexOf(l)===-1);const extra=added.filter(l=>expected.indexOf(l)===-1&&l!==summary);const missing=expected.filter(l=>added.indexOf(l)===-1);console.log('added:\n'+added.join('\n'));console.log('removed:\n'+removed.join('\n'));console.log('extra:\n'+extra.join('\n'));console.log('missing:\n'+missing.join('\n'));process.exit(extra.length+missing.length+removed.length?1:0);" + `extra` / `missing` / `removed` 任一非空(打印对应列表,退出码 1),或 `added` 中出现 3 个包文件与可选 1 条 SUMMARY 之外的路径。 + node -e "const{execSync}=require('child_process'),fs=require('fs');const base=fs.readFileSync((process.env.TEMP||'.')+'/gsd-09-52-hashes.txt','utf8').replace(/\r\n/g,'\n').trim().split('\n').filter(Boolean);const now=base.map(l=>{const f=l.split(' ')[0];return f+' '+execSync('git hash-object '+f,{encoding:'utf8'}).trim();});const bad=now.filter((l,k)=>l!==base[k]);console.log(now.join('\n'));if(bad.length){console.error('CHANGED:\n'+bad.join('\n'));process.exit(1);}if(now.length!==5){console.error('COUNT '+now.length);process.exit(1);}console.log('OK: 5 files unchanged');" + 任一路径的哈希与基线不一致(打印 `CHANGED:` 与差异行,退出码 1),或基线行数不是 5(打印 `COUNT `,退出码 1)。 + + + - 范围闸门 `added` 恰为 3 个包文件,另可有 1 条 `09-52-SUMMARY.md`;`extra` / `missing` / `removed` 为空。 + - 哈希闸门确认 `09-52-PLAN.md`、09-50-PLAN / SUMMARY、09-51-PLAN / SUMMARY 共 5 个文件逐字不变(计划文件在执行期间不可变)。 + - `09-52-SUMMARY.md` 含范围、逐文件最终形态、Task 1 方案、各门禁真实结果(含类型门禁运行状态)、未做项、设计记录、待确认项与未校准估算。 + - 全程未执行 git 写命令,0 次提交;除 SUMMARY 外未写任何其它文件。 + + 范围闸门 `added` 只含 3 个包文件(SUMMARY 写入后允许再加 1 条)、`extra` / `missing` / `removed` 为空;`09-52-PLAN.md` 与 09-50 / 09-51 四个文件共 5 个哈希逐字不变;`09-52-SUMMARY.md` 已写入并包含范围、逐文件形态、Task 1 方案、各门禁真实结果(含类型门禁运行状态)、未做项、设计记录、待确认项与未校准估算;0 次提交。 + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| 无 | 本计划只新增类型声明与包清单,不引入运行时代码、网络 / 存储 / 用户输入处理,也不安装任何依赖 | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-52-01 | Tampering | `packages/render-anim/package.json` 的依赖 | low | mitigate | 依赖仅为同仓库工作区包 `@motajs/render`(`workspace:*`),不引入 registry 包;本计划不运行 `pnpm install`,锁文件不变 | +| T-52-02 | Information Disclosure | `packages/render-anim/src/types.ts` | low | accept | 只声明公开类型骨架,无密钥、无运行时数据;文件内容由用户审阅 | +| T-52-SC | Tampering | npm / pnpm 安装 | high | mitigate | 本计划禁止任何包管理安装命令;若后续实现阶段需要新增 registry 依赖,须先过包合法性审计并在 Task 0 汇报 | + + + +- **格式**:`node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/package.json packages/render-anim/src/types.ts packages/render-anim/src/index.ts` 退出码 0。 +- **lint**:`node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts` 退出码 0、无输出。 +- **换行 / 缩进**:Task 3 第 3 步 node 门禁三行 `OK`(全 CRLF、无制表符)。 +- **内容 / 结构**:Task 3 第 4 步 node 门禁输出 `OK`(1 个 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`)+ 6 个接口、25 个成员片段、首行 import、无 `import type` / `any` / `class` / `function` / `let` / 箭头函数 / `export default`,且 `const` / `enum` 仅出现在枚举声明处、`index.ts` 唯一导出、`package.json` 仅两个键)。 +- **逐接口成员**:Task 3 第 5 步 node 门禁输出 `OK: per-interface skeleton matches`(成员归属、顺序、类型、`readonly`、`?`、接口数、`extends` 与 `AnimationBlendMode` 枚举成员序列 `Normal` → `Add` 全部一致)。 +- **人工复核**:`types.ts` 逐成员(含枚举成员 `Normal` / `Add`)与「类型骨架逐成员对照」一致,每个成员一行 jsDoc,无实现体;复核是绑定判定。 +- **类型(可选)**:node 过滤 `vue-tsc --noEmit` 输出后 `packages/render-anim` 诊断 0 行;全仓既有错误与退出码不参与判定;未运行时 SUMMARY 必须声明。 +- **范围**:Task 4 范围闸门 `added` 恰为 3 个包文件,另可有 1 条 `09-52-SUMMARY.md`(最多 4 条);`extra` / `missing` / `removed` 为空。 +- **哈希**:09-52-PLAN.md、09-50 / 09-51 的计划与摘要共 5 个文件哈希不变。 +- **明确不做**:不运行测试、构建、dev server、浏览器;不运行 `pnpm install`;不执行任何 git 写命令。 + + + +- [ ] Task 0 已完整汇报 OI-1 的裁定(用户选择 (B):`export const enum AnimationBlendMode`,成员 `Normal` / `Add`),并等待用户针对本修订版回复「可以执行」后才继续;OI-1 的裁定已在规划期由「B,可以执行」记录,本轮不再询问 (A)/(B)。 +- [ ] `packages/render-anim/package.json` 存在且只有 `name` 与 `dependencies['@motajs/render'] = 'workspace:*'` 两个键。 +- [ ] `packages/render-anim/src/types.ts` 存在,导出 7 个类型(1 个 `export const enum AnimationBlendMode` + 6 个接口),成员名 / 顺序 / 类型 / `readonly` / 可选性与用户骨架完全一致。 +- [ ] `types.ts` 首行为普通 `import { ITexture, ITextureAnimater } from '@motajs/render';`,无 `import type`、无 `any`、无 `class` / `function` / `let` / 箭头函数 / `export default`,且 `const` / `enum` 仅出现在 `export const enum AnimationBlendMode` 声明处(内容门禁自动判定)。 +- [ ] 每个公开成员(含枚举成员 `Normal` / `Add`)在 `types.ts` 源头有单行中文 jsDoc;接口与枚举本身无 jsDoc。 +- [ ] `packages/render-anim/src/index.ts` 只有 `export * from './types';`。 +- [ ] 三个文件均为 CRLF + 4 空格 + 无制表符;prettier `--check` 与 eslint 通过;逐接口成员门禁通过(成员归属与类型级形状一致)。 +- [ ] 未创建 `tsconfig.json`、测试、示例、实现文件或任何其它文件;`prettier --write` 只可能由 Task 2 对三个包文件执行,任何 prettier / eslint 失败都由 Task 2 修正后由 Task 3 重跑全部门禁,Task 3 未写任何文件、未改源码。 +- [ ] `09-52-SUMMARY.md` 已写入,包含门禁真实结果、类型门禁运行状态、未做项、设计记录、待确认项与未校准估算。 +- [ ] 范围闸门与哈希闸门通过;09-52-PLAN.md、09-50 / 09-51 未被修改;0 次提交。 + + + +Create `.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md` when done + + diff --git a/.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md b/.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md new file mode 100644 index 0000000..8416ecd --- /dev/null +++ b/.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md @@ -0,0 +1,181 @@ +--- +phase: 09-minimum-browser-runnable +plan: 52 +subsystem: render-anim +tags: [typescript, types-only, render-anim, enum, animation] +requires: [] +provides: + - "新包 @motajs/render-anim 的 package.json(name + @motajs/render workspace:*)" + - "1 个 export const enum AnimationBlendMode(Normal / Add)与 6 个动画数据接口" + - "src/index.ts 的单一星号导出" +affects: [09-minimum-browser-runnable, render-anim] +actuals: + # chars/4 上限值,只统计本计划新建的 3 个包文件(2201 字节);SUMMARY 自身不计入 + tokens: 550 + tasks: 5 + commits: 0 +tech-stack: + added: [] + patterns: + - "类型骨架先行:公开类型由用户审阅后再另立实现计划" + - "枚举替代字符串标识符:AnimationBlendMode 使用 export const enum 且成员为隐式数值" + - "同目录星号导出,不跨包转发 @motajs/render 的符号" +key-files: + created: + - packages/render-anim/package.json + - packages/render-anim/src/types.ts + - packages/render-anim/src/index.ts + modified: [] +key-decisions: + - "OI-1 按用户裁定 (B):AnimationBlendMode 由字符串字面量联合改为 export const enum AnimationBlendMode(成员 Normal / Add,隐式数值 0 / 1)" + - "IAnimationData 通过 extends IAnimationAnimaterData 复用 bitmaps / frames,不重复声明成员" + - "只落类型骨架,RenderAnimater 与 parseRenderAnimation 留给后续实现计划" +patterns-established: + - "新包的 package.json 只写 name 与 workspace 依赖,不写 version / private / type / exports / scripts" +requirements-completed: [] +duration: 9min +completed: 2026-11-07 +status: complete +--- + +# Phase 9 Plan 52: 新建 `@motajs/render-anim` 的类型骨架 + +**只新增 `package.json` / `src/types.ts` / `src/index.ts` 三个文件,落下 1 个 `export const enum AnimationBlendMode`(`Normal` / `Add`)与 6 个动画数据接口,不含任何实现。** + +## Performance + +- **Duration:** 约 9 min +- **Started:** 2026-11-07T13:02:00Z +- **Completed:** 2026-11-07T13:11:20Z +- **Tasks:** 5(Task 0 为已获「可以执行」的汇报关卡;Task 1–4 为执行任务) +- **Files created:** 3 个包文件,另有本 SUMMARY + +## 本计划范围 + +- 只建 `@motajs/render-anim` 的类型骨架;**不实现** `RenderAnimater`、**不实现** `parseRenderAnimation`。 +- 不跑测试、构建、dev server、浏览器;不做任何运行时主张。 +- 不运行 `pnpm install`,锁文件不变;不新增包级 `tsconfig.json` 或任何配置。 +- **0 次提交**:全程未执行任何 git 写命令,改动留在工作树供审阅。 +- 编号说明:派发编号 09-52 / 波次 1;`09-01`..`09-51` 均已占用,本计划不删除、不覆盖任何既有记录。 + +## Task 0 执行前汇报关卡(已满足) + +| 项 | 内容 | +|----|------| +| 关卡类型 | `checkpoint:human-verify` | +| Gate | `blocking-human` | +| 汇报对象 | 三个文件的逐字形态、1 个 `export const enum` + 6 个接口的逐成员清单、dev.md 合规点、影响范围、只读静态验证口径、编号说明、不提交声明 | +| OI-1 裁定 | 用户选择 **(B)**:`AnimationBlendMode` 改为 `export const enum AnimationBlendMode`(成员 `Normal` / `Add`,隐式数值 0 / 1,成员 jsDoc 为「普通混合」/「加算混合」) | +| 用户回复 | 恰好「可以执行」四个字(针对本修订版) | +| 结论 | 关卡通过,进入 Task 1–4;未再询问 (A)/(B) | + +**说明:** 本仓库与 GSD 既有约定中不存在独立的「approval 文件」;计划正文也明确 Task 0「不写任何文件」,且本次执行被限定只能写 4 个路径。因此该结构化批准记录落在本 SUMMARY,不额外创建文件。 + +## 逐文件最终形态 + +### `packages/render-anim/package.json` + +只有两个顶层键,顺序为 `name`、`dependencies`;`dependencies` 只有一个条目 `@motajs/render: workspace:*`: + +```json +{ + "name": "@motajs/render-anim", + "dependencies": { + "@motajs/render": "workspace:*" + } +} +``` + +无 `version` / `private` / `type` / `main` / `module` / `types` / `exports` / `scripts`;113 字节,6 行,全 CRLF。 + +### `packages/render-anim/src/types.ts` + +首行为普通引入 `import { ITexture, ITextureAnimater } from '@motajs/render';`(未使用 `import type`),随后一个空行,再按固定顺序导出 7 个类型。成员级 jsDoc 为单行中文,接口与枚举本身不写 jsDoc。2062 字节,76 行,全 CRLF。 + +| 导出类型 | 成员(顺序固定) | +|----------|------------------| +| `AnimationBlendMode`(`export const enum`) | `Normal`(普通混合,隐式 0)、`Add`(加算混合,隐式 1) | +| `IAnimationBitmap` | `readonly x: number`、`readonly y: number`、`readonly width: number`、`readonly height: number` | +| `IAnimationSound` | `readonly name: string`、`readonly volume?: number`、`readonly pitch?: number` | +| `IAnimationObject` | `readonly index: number`、`readonly x: number`、`readonly y: number`、`readonly scale: number`、`readonly opacity: number`、`readonly mirror: number`、`readonly rotate: number` | +| `IAnimationFrame` | `readonly width: number`、`readonly height: number`、`readonly originX: number`、`readonly originY: number`、`readonly objects: readonly IAnimationObject[]`、`readonly sound?: readonly IAnimationSound[]` | +| `IAnimationAnimaterData` | `readonly bitmaps: readonly IAnimationBitmap[]`、`readonly frames: readonly IAnimationFrame[]` | +| `IAnimationData extends IAnimationAnimaterData` | `readonly texture: ITexture`、`readonly animater: ITextureAnimater`、`readonly blend: AnimationBlendMode` | + +`AnimationBlendMode` 声明形态(OI-1 B):顶格 `export const enum AnimationBlendMode {`,成员为 4 空格缩进的单行 jsDoc + `Normal,` / `Add`(末成员无尾逗号),最后顶格 `}`;无显式初始化,无声明级 jsDoc。 + +### `packages/render-anim/src/index.ts` + +全文只有一行 `export * from './types';`,不转发 `@motajs/render` 的符号。26 字节,1 行,CRLF 结尾。 + +## Task 1 记录的类型 / 命名方案(只读采集) + +- `packages/render/src/assets/types.ts`:`ITexture` 为接口(`Texture` 类 `implements ITexture`);`ITextureAnimater` 的 `once(texture, data)` / `cycled(texture, data)` 均返回 `Generator`;成员 jsDoc 风格为单行中文、结尾不加句号。 +- `packages/render/src/index.ts`:`export * from './assets' | './core' | './style' | './types' | './utils'`,因此 `ITexture` / `ITextureAnimater` 可从 `@motajs/render` 根导出取得。 +- `packages/render/src/core/canvas2d.ts`:`MotaOffscreenCanvas2D` 提供 `setHD(hd: boolean)` 与 `setAntiAliasing(anti: boolean)`(仅供后续实现记录,本计划未调用)。 +- 包清单范式:`packages/render/package.json` 只有 `name`;`packages/audio/package.json` 为 `name` + `dependencies`(`workspace:*`)。新包按后者书写。 +- 根 `tsconfig.json`:`include` 覆盖 `packages/**/*.ts`,`paths['@motajs/*'] = ['./packages/*/src']`,`strict: true`、`isolatedModules: true`、`noEmit: true`,因此无需为新包新增 tsconfig。 +- `.prettierrc`:`printWidth: 80` / `tabWidth: 4` / `singleQuote: true` / `arrowParens: avoid` / `trailingComma: none` / `endOfLine: crlf`;`eslint.config.js` 为 `typescript-eslint` + `eslint-plugin-prettier/recommended`。 +- 命名与 jsDoc 结论:类型名 / 成员名全部由用户提供,未新增任何未获批的公开名称;`IAnimationSound.name: string` 属 dev.md 允许的字符串 id 别名一类(OI-2)。 + +## 门禁真实结果 + +| 门禁 | 命令要点 | 真实输出 | 结果 | +|------|----------|----------|------| +| 格式 | `node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/{package.json,src/types.ts,src/index.ts}` | `Checking formatting...` / `All matched files use Prettier code style!`,退出码 0 | 通过 | +| lint | `node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts` | 无任何输出,退出码 0 | 通过 | +| 换行 / 缩进 | Task 3 第 3 步 node 门禁 | `OK packages/render-anim/package.json lf=6 crlf=6 tabs=0` / `OK packages/render-anim/src/types.ts lf=76 crlf=76 tabs=0` / `OK packages/render-anim/src/index.ts lf=1 crlf=1 tabs=0` | 通过 | +| 内容 / 结构 | Task 3 第 4 步 node 门禁(枚举 + 6 接口 + 21 条成员片段(覆盖 25 个成员)+ 首行 import + 负向检查 + index / package 校验) | `OK`,退出码 0 | 通过 | +| 逐接口成员 | Task 3 第 5 步 node 门禁(按接口块逐字比对成员序列、`extends`、接口数、枚举成员序列) | `OK: per-interface skeleton matches`,退出码 0 | 通过 | +| 类型门禁 | node 过滤 `vue-tsc --noEmit` 输出,只看 `packages/render-anim` 诊断 | **已运行**:`OK: no render-anim diagnostics`(0 行诊断);全仓既有类型错误与 `vue-tsc` 非 0 退出码不参与判定 | 通过 | +| 范围闸门(SUMMARY 写入前) | Task 4 第 1 步 node 门禁 | `added:` 恰为 `?? packages/render-anim/package.json` / `?? packages/render-anim/src/index.ts` / `?? packages/render-anim/src/types.ts`;`removed` / `extra` / `missing` 均为空 | 通过 | +| 哈希闸门 | Task 4 第 2 步 node 门禁 | `09-50-PLAN 1575237b…`、`09-50-SUMMARY 9283ff05…`、`09-51-PLAN a83f21cb…`、`09-51-SUMMARY 257483e7…`、`09-52-PLAN 5fdcef8a…`;`OK: 5 files unchanged` | 通过 | + +人工逐成员复核(绑定判定):`types.ts` 的成员顺序、接口归属、`readonly`、可选性(`volume?` / `pitch?` / `sound?`)、只读数组、`extends IAnimationAnimaterData`、泛型实参 `ITextureAnimater` 与计划正文「类型骨架逐成员对照」逐项一致;`AnimationBlendMode` 为 `export const enum`,成员恰为 `Normal` → `Add`(隐式数值、无显式初始化、末成员无尾逗号);每个成员有且只有一行 jsDoc;无实现体、无 `export default`。 + +## 未做 / 跳过项 + +- 未运行任何测试、构建、dev server 或浏览器;不做运行时可用性主张。 +- 未运行 `pnpm install`;锁文件未变(依赖仅为同仓库工作区包)。 +- 未执行任何 git 写命令(`add` / `commit` / `stash` / `checkout` / `restore` / `reset`),**0 次提交**。 +- 未创建 `09-52-RESEARCH.md` / `09-52-VALIDATION.md`;未改动 `STATE.md` / `ROADMAP.md` / `09-CONTEXT.md`。 +- 本 run 无 RESEARCH / 无 VALIDATION:`requirements: []`,Nyquist 不适用;spec-less probe fallback skipped(phase has no requirement IDs to probe)。 + +## 设计记录(未实现) + +- 浏览器专用;`RenderAnimater` 将实现 `ITextureAnimater`(`once` / `cycled` 返回 `Generator`,帧时序与循环由外部驱动)。 +- 逐帧惰性合成;产出的可渲染对象在下一次 `.next()` 前有效,下一次恢复或 `return` 时清理上一帧。 +- 使用 `MotaOffscreenCanvas2D` 并调用 `setHD(false)` / `setAntiAliasing(false)`,不直接调用浏览器 canvas API。 +- 空帧产出透明小图;帧尺寸可不同,每帧自带 `width` / `height` / `originX` / `originY`。 +- `blend` 以 `AnimationBlendMode` 存于文件并由解析数据暴露,动画器不决定混合;`sound` 由宿主播放。 +- 解析函数为 `parseRenderAnimation(buffer: ArrayBuffer): Promise`。 + +## 偏差与问题 + +1. **[Rule 3 - Blocking] Write 工具把换行归一化为 LF**:写入后测得 `crlf=0`,会使 prettier `--check`(`endOfLine: crlf`)失败。处理方式:写入后运行一次 Node 脚本,把 3 个包文件按 `/\r?\n/` → `\r\n` 统一为 CRLF,内容其余部分不变;随后 CRLF 门禁与 prettier `--check` 均通过。未新增或删除任何字符。 +2. Task 0 的批准记录落在本 SUMMARY(见上节说明),未创建独立的 approval 文件——计划正文要求 Task 0 不写文件,本次执行也仅允许写 3 个包文件与本 SUMMARY。 + +除上述两点外,计划按原文执行。 + +## 待确认项 + +- **OI-1(已裁定)**:`AnimationBlendMode` 使用 `export const enum`,成员 `Normal` / `Add`,隐式数值;`IAnimationData.blend` 类型不变。 +- **OI-2(仅记录)**:`IAnimationSound.name: string` 属允许的字符串 id 别名一类,保留。 +- **OI-3(仅记录)**:类型骨架由用户自行审阅 / 修改后再进入实现;`RenderAnimater` 与 `parseRenderAnimation(buffer: ArrayBuffer): Promise` 另立后续计划。 +- **OI-4(仅记录)**:成员(含枚举成员)jsDoc 使用单行中文风格;接口与枚举本身按 dev.md 不写 jsDoc。如需英文注释或接口级说明,请指示。 +- **OI-5(仅记录)**:`estimate` 未校准(`confidence: low`),本计划未运行构建或测试。 + +## 估算 + +- **估算:** `confidence: low`,未校准(计划 `estimate.tokens: 30000`)。 +- **实际(actuals):** 新建包文件 2201 字节,约 550 tokens(chars/4);任务 5 个;提交 0 次。 + +## Self-Check: PASSED + +- 3 个包文件均存在:`packages/render-anim/package.json`(113 B)、`src/types.ts`(2062 B)、`src/index.ts`(26 B)。 +- `files_modified` 之外零改动(范围闸门 `extra` / `missing` / `removed` 均为空)。 +- 0 次提交;工作树保留全部改动。 + +--- +*Phase: 09-minimum-browser-runnable* +*Completed: 2026-11-07* diff --git a/.planning/phases/09-minimum-browser-runnable/09-53-PLAN.md b/.planning/phases/09-minimum-browser-runnable/09-53-PLAN.md new file mode 100644 index 0000000..d11b379 --- /dev/null +++ b/.planning/phases/09-minimum-browser-runnable/09-53-PLAN.md @@ -0,0 +1,536 @@ +--- +phase: 09-minimum-browser-runnable +plan: 53 +type: execute +# 编号说明:09-01..09-52 的计划均已占用,本计划使用下一个空闲编号 09-53。 +# depends_on 09-52:本计划修改 09-52 已创建(未提交)的 packages/render-anim/src/types.ts;无其它文件重叠。 +wave: 2 +depends_on: + - 09-52 +files_modified: + - packages/render-anim/src/types.ts + - .planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md +autonomous: false +requirements: [] +estimate: + # 未校准估算(confidence: low),SUMMARY 须声明。 + # 成本主要在读取既有类型 / 09-52 记录、做一次精确块替换、以及 prettier / eslint / 结构门禁的往返。 + tokens: 30000 + raw_tokens: 30000 + tasks: 5 + confidence: low +must_haves: + truths: + - "**第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md / D-03)**:汇报本次修改的精确前后 diff、为什么拆分(用户否决 `data.animater.once(texture, data)` 的组合设计)、dev.md 合规点、命名、影响范围、验证形态与不提交声明;然后停下等待用户恰好回复「可以执行」。" + - "**只改一个源码文件**:仅 `packages/render-anim/src/types.ts` 被修改;`packages/render-anim/src/index.ts`、`packages/render-anim/package.json` 的哈希与 Task 1 基线一致;09-50 / 09-51 / 09-52 的计划与摘要共 6 个文件哈希不变;`09-53-PLAN.md` 自身在执行期间不被改写。" + - "**新类型逐字落地**:`types.ts` 导出 `IAnimationParsedData`(成员顺序固定为 `readonly texture: ITexture`、`readonly bitmaps: readonly IAnimationBitmap[]`、`readonly frames: readonly IAnimationFrame[]`、`readonly blend: AnimationBlendMode`)与 `export type IAnimationAnimater = ITextureAnimater;`;解析数据中不存在 `animater` 成员。" + - "**旧类型彻底移除**:`IAnimationAnimaterData` 与 `IAnimationData` 两个接口及其全部成员、注释、`extends` 关系不再出现在 `types.ts` 中;不存在任何 `animater` 成员或 `ITextureAnimater` 泛型实参。" + - "**只交付类型声明**:`types.ts` 内没有 `class` / `function` / `let` / 箭头函数 / `export default`;`const` 与 `enum` 仅出现在唯一的 `export const enum AnimationBlendMode` 声明处;不实现 `RenderAnimater`,不实现 `parseRenderAnimation`。" + - "**dev.md 合规**:UTF-8 无 BOM + CRLF + 4 空格缩进、无制表符;首行是普通 `import`,无 `import type`、无 `any`;`IAnimationParsedData` 每个成员前有一行中文 jsDoc(沿用既有注释文本),类型别名本身不写 jsDoc;第 1–61 行逐字不变由 `gsd-09-53-prefix.txt` 的 sha256 前缀哈希机械证明;四个成员 jsDoc 逐字等于「合成帧使用的纹理」/「位图列表」/「帧列表」/「混合模式」,成员与注释的关联、枚举注释(`普通混合` / `加算混合`)均为精确文本断言,改写注释即失败;未改写任何既有注释。" + - "**验证只读静态**:prettier `--check`、eslint、CRLF / BOM / 制表符门禁、逐接口成员门禁(含逐成员 jsDoc 精确文本)、第 1–61 行前缀哈希与枚举注释门禁、类型别名 / 旧类型移除 / 无实现 / 无 `any` / 无 `import type` 门禁全部通过;类型门禁先以 `vue-tsc --version` 哨兵证明检查器可启动、再断言 `--noEmit` 进程正常结束(`status` 非 `null`),只过滤 `packages/render-anim` 的诊断,全仓既有类型错误与 `vue-tsc` 非 0 退出码不参与判定;不运行测试、构建、dev server 或浏览器。" + - "**范围受控且不提交**:除 `files_modified` 的 `types.ts` 与 `09-53-SUMMARY.md` 外零改动;不运行 `pnpm install`;不执行任何 git 写命令,0 次提交。" + artifacts: + - path: packages/render-anim/src/types.ts + provides: "拆分后的解析数据类型 `IAnimationParsedData`(texture / bitmaps / frames / blend)与独立动画器类型别名 `IAnimationAnimater`,不含 `animater` 成员,不含任何实现" + - path: .planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md + provides: "修改前后 diff、逐成员最终形态、Task 1 基线、各门禁真实结果、跳过项、未校准估算、0 次提交与待确认项" + key_links: + - "`IAnimationAnimater` ← `ITextureAnimater` ← `@motajs/render` 根导出 ← `packages/render/src/assets/types.ts`;`once(texture, data)` / `cycled(texture, data)` 的 `data` 参数现在是一个不含 `animater` 的纯解析数据对象" + - "`IAnimationParsedData` ↔ 后续 `RenderAnimater implements IAnimationAnimater` 与 `parseRenderAnimation(buffer: ArrayBuffer): Promise`(本计划只固定类型,不实现)" + - "`src/index.ts` 的 `export * from './types';` ↔ 新类型从包根直接可见;本计划不改 `index.ts`,只验证其哈希不变" + - "`IAnimationParsedData` 的 `readonly` 数组 ↔ 后续解析器对外返回的只读视图;实现阶段不得把 `bitmaps` / `frames` 改成可变数组" + assumptions: + - "**工作树基线(规划日实测)**:`packages/render-anim/` 已由 09-52 创建但尚未提交,`git status --porcelain -uall -- packages/render-anim` 为 `?? package.json` / `?? src/index.ts` / `?? src/types.ts` 三条;`types.ts` 当前仍是被用户否决的组合设计(`IAnimationAnimaterData` + `IAnimationData`)。" + - "**09-52 已执行**:`09-52-SUMMARY.md` 的 `status: complete`,本计划在其产物上做类型拆分,不重新创建包、不改 `package.json` / `index.ts`。" + - "**`@motajs/render` 类型基线(规划日实测)**:`ITextureAnimater` 声明 `once(texture: ITexture, data: T): Generator` 与 `cycled(texture: ITexture, data: T): Generator`;`ITexture` 是接口(`Texture` 类实现之);两者均可从 `@motajs/render` 根导出取得。" + - "**工具链(规划日实测)**:`node` v24.x;`node_modules/prettier/bin/prettier.cjs`、`node_modules/eslint/bin/eslint.js`、`node_modules/vue-tsc/bin/vue-tsc.js` 均存在;`.prettierrc` 为 `printWidth: 80` / `tabWidth: 4` / `singleQuote: true` / `arrowParens: avoid` / `trailingComma: none` / `endOfLine: crlf`。" + - "**本 run 无 RESEARCH / 无 VALIDATION**:本阶段无 requirement ID(`requirements: []`),Nyquist 不适用;spec-less probe fallback skipped: phase has no requirement IDs to probe (visible skip)。" + prohibitions: + - "不得在用户恰好回复「可以执行」之前执行任何任务或派发执行子代理(AGENTS.md / D-03)。" + - "不得修改 `packages/render-anim/src/index.ts`、`packages/render-anim/package.json` 或 `packages/render-anim` 之外的任何源码;不得新增文件(SUMMARY 除外)。" + - "不得修改 09-50 / 09-51 / 09-52 的计划与摘要、`09-CONTEXT.md`、`STATE.md`、`ROADMAP.md`、根配置、锁文件或其它包。" + - "不得实现 `RenderAnimater` / `parseRenderAnimation`,不得写 class、function、测试、示例或入口逻辑;不得把类型别名展开成重复的接口成员。" + - "不得改写用户给定的类型名、成员名、成员顺序、成员类型与 `readonly`;不得保留 `IAnimationAnimaterData` / `IAnimationData`,不得让 `IAnimationParsedData` 包含 `animater` 成员。" + - "不得修改第 1–61 行的任何既有注释或成员;新接口成员只能沿用既有注释文本(`合成帧使用的纹理` / `位图列表` / `帧列表` / `混合模式`),不得改写措辞;`动画器` 注释随其成员与所属接口一并移除。" + - "不得出现 `import type`、`any`、`as`、非 jsDoc 的多行注释、复述代码的注释或新英文注释;不得改 `AnimationBlendMode` 的成员与顺序。" + - "不得运行 `pnpm install` / 构建 / 测试 / dev server / 浏览器;不得执行任何 git 写命令(`add` / `commit` / `stash` / `checkout` / `restore` / `reset`),本计划全程 0 次提交。" + open_items: + - "**OI-1(已获用户批准)**:公开名称固定为 `IAnimationParsedData` 与 `IAnimationAnimater`;解析数据成员固定为 `texture` / `bitmaps` / `frames` / `blend`。本计划不新增任何其它公开名称。" + - "**OI-2(只记录)**:四个成员注释文本从 09-52 的既有注释原样沿用;`动画器` 注释随 `IAnimationData.animater` 成员一并移除。若用户希望改写措辞,请在 Task 0 批准前说明。" + - "**OI-3(只记录,不实现)**:后续 `RenderAnimater` 实现 `IAnimationAnimater`(即 `ITextureAnimater`),`parseRenderAnimation(buffer: ArrayBuffer): Promise`;本计划不写任何实现体。" + - "**OI-4(只记录)**:`estimate` 未校准(`confidence: low`);本计划不运行构建或测试,因此不做运行时可用性主张。" + - "**OI-5(只记录)**:09-52 的产物仍未提交;本计划不提交任何内容,改动留在工作树供审阅。" +--- + + + + + + + + + + +Phase 9 · 第 53 个计划(09-52 的修订增量)—— 把 `@motajs/render-anim` 的公开类型从「解析数据内含动画器」拆分为「纯解析数据 + 独立动画器类型」。 + +**要解决的问题:** 09-52 落下的 `IAnimationData` 同时包含解析数据与 `animater` 成员,且 `animater` 的泛型实参又是 `IAnimationAnimaterData`(与解析数据成员相同)。用户审阅后否决:`data.animater.once(texture, data)` 会把同一个对象既当作动画器自身的数据又当作参数传入,语义不合理。用户批准的拆分设计是:解析数据不再包含动画器;动画器是独立的 `ITextureAnimater` 类型别名。 + +**做法(锁定设计,仅类型):** +- `packages/render-anim/src/types.ts`:删除 `IAnimationAnimaterData` 与 `IAnimationData`;新增 `IAnimationParsedData`(`texture` / `bitmaps` / `frames` / `blend`,成员顺序与用户给定一致)与 `export type IAnimationAnimater = ITextureAnimater;`。 +- 保持 `AnimationBlendMode`(`export const enum`,`Normal` / `Add`)、`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame` 及第 1–61 行逐字不变。 +- `packages/render-anim/src/index.ts` 与 `package.json` **只验证、不修改**(哈希不变)。 +- 本计划不实现 `RenderAnimater` / `parseRenderAnimation`;实现另立后续计划。 + +**约束:** 只写 `files_modified` 的 2 个路径;不改任何其它源码 / 配置 / 计划;不提交;不做浏览器 / 测试验证,只做只读静态门禁。(D-01 增量派发 / D-02 计划累积追加 / D-03 执行前汇报 / D-04 命名先反馈 / D-05 默认不逐阶段验证 / D-06 范围由用户界定。) + +**修改方式声明(AGENTS.md):** 执行期间的 `types.ts` 修改与 `09-53-SUMMARY.md` 写入全部由**派发子代理(`gsd-executor`)执行**,非内联执行;本计划文件自身由规划流程(`gsd-planner`)创建。除 `files_modified` 外无任何写入。 + +**可见跳过:** spec-less probe fallback skipped: phase has no requirement IDs to probe (visible skip)。本阶段无 requirement ID,不创建 RESEARCH.md / VALIDATION.md,Nyquist 验证不适用。 + + +## 本计划产出物(Artifacts this plan produces) + +| 类型 | 名称 / 路径 | 说明 | +|------|-------------|------| +| 修改(类型) | `packages/render-anim/src/types.ts` | 删除两个旧接口,新增 `IAnimationParsedData` 与 `IAnimationAnimater` 类型别名;无任何实现 | +| 交付 | `.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md` | 前后 diff、逐成员形态、门禁真实结果、跳过项、未校准估算、0 次提交 | +| 只验证不修改 | `packages/render-anim/src/index.ts` | 保持 `export * from './types';`,哈希与 Task 1 基线一致 | +| 只验证不修改 | `packages/render-anim/package.json` | 保持 `name` + `@motajs/render: workspace:*`,哈希与 Task 1 基线一致 | +| 只验证不修改 | 09-50 / 09-51 / 09-52 的计划与摘要 | 共 6 个文件哈希逐字不变 | + +## 修改前后逐字对照(Task 2 的唯一依据) + +### 修改前(`types.ts` 第 62–76 行,逐字) + +```ts +export interface IAnimationAnimaterData { + /** 位图列表 */ + readonly bitmaps: readonly IAnimationBitmap[]; + /** 帧列表 */ + readonly frames: readonly IAnimationFrame[]; +} + +export interface IAnimationData extends IAnimationAnimaterData { + /** 合成帧使用的纹理 */ + readonly texture: ITexture; + /** 动画器 */ + readonly animater: ITextureAnimater; + /** 混合模式 */ + readonly blend: AnimationBlendMode; +} +``` + +### 修改后(逐字) + +```ts +export interface IAnimationParsedData { + /** 合成帧使用的纹理 */ + readonly texture: ITexture; + /** 位图列表 */ + readonly bitmaps: readonly IAnimationBitmap[]; + /** 帧列表 */ + readonly frames: readonly IAnimationFrame[]; + /** 混合模式 */ + readonly blend: AnimationBlendMode; +} + +export type IAnimationAnimater = ITextureAnimater; +``` + +**第 1–61 行保持逐字不变:** 首行普通引入 `import { ITexture, ITextureAnimater } from '@motajs/render';`、`AnimationBlendMode` 枚举、`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame` 全部不动。新块的四个成员注释文本沿用旧块中的既有注释,不新增、不改写注释;`动画器` 注释随 `IAnimationData.animater` 成员一并移除。 + +## 新类型逐成员对照(Task 3 的绑定判定依据) + +| 导出类型 | 成员(顺序固定) | 成员 jsDoc(沿用既有文本) | +|----------|------------------|----------------------------| +| `IAnimationParsedData`(新增、`export interface`) | `readonly texture: ITexture` | 合成帧使用的纹理 | +| | `readonly bitmaps: readonly IAnimationBitmap[]` | 位图列表 | +| | `readonly frames: readonly IAnimationFrame[]` | 帧列表 | +| | `readonly blend: AnimationBlendMode` | 混合模式 | +| `IAnimationAnimater`(新增、`export type`) | — | 类型别名本身不写 jsDoc(dev.md 不建议) | +| `AnimationBlendMode`(不变) | `Normal` / `Add` | 普通混合 / 加算混合 | +| `IAnimationBitmap`(不变) | `x` / `y` / `width` / `height` | 保持 09-52 的既有注释 | +| `IAnimationSound`(不变) | `name` / `volume?` / `pitch?` | 保持 09-52 的既有注释 | +| `IAnimationObject`(不变) | `index` / `x` / `y` / `scale` / `opacity` / `mirror` / `rotate` | 保持 09-52 的既有注释 | +| `IAnimationFrame`(不变) | `width` / `height` / `originX` / `originY` / `objects` / `sound?` | 保持 09-52 的既有注释 | + +**删除清单:** `IAnimationAnimaterData`(含 `bitmaps` / `frames` 两个成员与两条注释)、`IAnimationData`(含 `texture` / `animater` / `blend` 三个成员与三条注释、`extends IAnimationAnimaterData` 关系)。删除后 `types.ts` 的导出恰为:1 个 `export const enum` + 5 个 `export interface` + 1 个 `export type`。 + +## 覆盖审计(Multi-Source Coverage Audit) + +| 源 | 条目 | 覆盖位置 | +|----|------|----------| +| GOAL(ROADMAP §Phase 9) | 「可在浏览器中运行并验证的最小版本」伞目标 | 本增量只调整类型契约,不新增可运行能力;SUMMARY 如实记录 | +| REQ | 本阶段无 requirement ID(`requirements: []`) | Nyquist 不适用 | +| RESEARCH | 本 run 无 RESEARCH.md | 不适用 | +| CONTEXT D-01 | 增量派发,一个任务一个计划 | 本计划即该派发的唯一计划,不拆分 | +| CONTEXT D-02 | 计划可累积追加 | plan 53,排在 09-01..09-52 之后(编号说明见 frontmatter) | +| CONTEXT D-03 | 执行前必须先汇报并等待「可以执行」 | Task 0(`blocking-human`),`autonomous: false` | +| CONTEXT D-04 | 新命名必须先反馈获批 | 两个新类型名与四个成员名均由用户提供,Task 0 声明不新增任何未获批名称 | +| CONTEXT D-05 | 默认不逐阶段验证 | 本计划仍做只读静态门禁,但明确不做运行时验证 | +| CONTEXT D-06 | 范围由用户界定,不擅自扩大 | `files_modified` 2 项 + Task 4 范围 / 哈希闸门 | +| 用户设计 | 解析数据不含动画器;`data.animater.once(texture, data)` 不合理 | `` + Task 0 + Task 2 删除 `animater` 成员 | +| 用户设计 | `IAnimationParsedData` 含 `texture` / `bitmaps` / `frames` / `blend` | 「新类型逐成员对照」+ Task 2 + Task 3 逐接口门禁 | +| 用户设计 | `IAnimationAnimater = ITextureAnimater` | 修改后块 + Task 2 + Task 3 类型别名门禁 | +| 用户设计 | 删除 `IAnimationAnimaterData` 与 `IAnimationData` | 删除清单 + Task 2 + Task 3 旧类型移除门禁 | +| 用户设计 | `index.ts` / `package.json` 只验证、不修改 | Task 1 哈希基线 + Task 3 / Task 4 哈希闸门 | +| 用户设计 | 只做类型声明,不实现 `RenderAnimater` / 解析器 | Task 2 / Task 3 无实现门禁 | +| 用户设计 | 验证只读静态,不跑游戏 / 浏览器 / 测试;不提交 | Task 3 / Task 4 + `` + 0 次提交声明 | + + +@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md +@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md +@AGENTS.md +@dev.md +@packages/render-anim/src/types.ts +@packages/render-anim/src/index.ts +@packages/render-anim/package.json +@.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md +@.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md +@packages/render/src/assets/types.ts +@tsconfig.json +@.prettierrc +@eslint.config.js + + + + + + Task 0: 预执行汇报关卡 —— 汇报精确前后 diff、拆分原因、dev.md 合规、影响范围与验证形态,等待恰好「可以执行」 + `packages/render-anim/src/types.ts` 存在且当前为 09-52 的组合设计(含 `IAnimationAnimaterData` 与 `IAnimationData`);`packages/render-anim/src/index.ts` 与 `package.json` 存在且未被本计划修改;`09-52-SUMMARY.md` 状态为 complete;`node_modules` 内 prettier / eslint / vue-tsc 均存在。 + 无(本任务只读,不写任何文件;汇报后停下等待「可以执行」) + + - `.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md`(D-01 增量派发、D-03 汇报关卡、D-04 命名批准、D-06 范围界定) + - `AGENTS.md`(执行前须获批、命名先反馈、既有注释不得修改、最小修改也走 GSD、不派发额外子代理) + - `dev.md`(CRLF、4 空格、首行 import、禁止 `import type`、无 `any`、成员 jsDoc 在源头、不得复述代码、公开接口放 `types.ts`) + - `packages/render-anim/src/types.ts`(当前第 1–61 行与第 62–76 行) + - `packages/render-anim/src/index.ts`、`packages/render-anim/package.json`(只验证对象) + - `09-52-PLAN.md`、`09-52-SUMMARY.md`(被否决设计的来源与 09-52 已落地内容) + - `packages/render/src/assets/types.ts`(`ITexture` / `ITextureAnimater` 的 `(texture, data)` 签名) + + + - 已向用户逐条汇报 ①–⑨,包含修改前(第 62–76 行)与修改后(新块)的逐字 diff,而不是只报两个类型名。 + - 已说明拆分原因:原 `IAnimationData` 内含 `animater`,且其泛型实参又是与解析数据相同的 `IAnimationAnimaterData`,导致 `data.animater.once(texture, data)` 语义不合理;用户已批准新拆分。 + - 已把 `IAnimationParsedData` 的四个成员(名称 / 类型 / `readonly` / 顺序 / 注释文本)、`IAnimationAnimater` 的逐字别名、删除清单(两个旧接口 + `animater` 成员 + `动画器` 注释)列为显式批准项。 + - 已报告 dev.md 合规点、影响范围(只改 `types.ts` + 写 SUMMARY;`index.ts` / `package.json` / 09-50 / 09-51 / 09-52 不变;不安装、不提交)、验证形态(只读静态 + 过滤类型门禁;不跑测试 / 浏览器 / 构建)。 + - 用户针对本修订版明确回复「可以执行」后才进入 Task 1–4;在此之前不执行任何任务、不派发执行子代理。 + + + 一份待批准的执行计划(`09-53-PLAN.md`):在 09-52 的类型骨架上做**纯类型拆分**——解析数据 `IAnimationParsedData` 不再包含动画器,动画器改为独立类型别名 `IAnimationAnimater = ITextureAnimater`;删除 `IAnimationAnimaterData` 与 `IAnimationData`;不写任何实现。 + + + **停下并向用户汇报,等待其恰好回复「可以执行」后才继续;本任务不写任何文件、不建目录、不派发执行子代理。** + + ① **要做什么 / 为什么:** 09-52 落下的设计把解析数据与动画器合在 `IAnimationData` 中,且 `animater` 的泛型实参就是解析数据本身的形状。用户指出 `data.animater.once(texture, data)` 等于把同一个对象既当作动画器自身的数据又当作参数传入,不合理,因此批准拆分:解析数据是纯数据,动画器是独立的 `ITextureAnimater` 类型。本计划只改类型,不实现动画器与解析器。 + + ② **精确前后 diff(逐字):** + - **修改前(`types.ts` 第 62–76 行):** `IAnimationAnimaterData` 含 `readonly bitmaps: readonly IAnimationBitmap[]` 与 `readonly frames: readonly IAnimationFrame[]`;`IAnimationData extends IAnimationAnimaterData` 含 `readonly texture: ITexture`、`readonly animater: ITextureAnimater`、`readonly blend: AnimationBlendMode`。 + - **修改后:** 新增 `IAnimationParsedData`,成员顺序固定为 `readonly texture: ITexture`(注释「合成帧使用的纹理」)、`readonly bitmaps: readonly IAnimationBitmap[]`(注释「位图列表」)、`readonly frames: readonly IAnimationFrame[]`(注释「帧列表」)、`readonly blend: AnimationBlendMode`(注释「混合模式」);随后新增 `export type IAnimationAnimater = ITextureAnimater;`。两个旧接口及其成员、注释、`extends` 关系全部删除。 + - **第 1–61 行逐字不变:** 首行 `import { ITexture, ITextureAnimater } from '@motajs/render';`、`AnimationBlendMode` 枚举、`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame` 均不动。 + + ③ **dev.md 合规点(逐条):** UTF-8 无 BOM + CRLF + 4 空格、无制表符;首行必须是普通 `import`,import 之间不夹注释与空行;不使用 `import type`;不使用 `any` / `as` / `?.` / `!`;`IAnimationParsedData` 每个成员前有一行中文 jsDoc,类型别名本身不写 jsDoc(dev.md 不建议给接口 / 类型别名本身写注释);新块只沿用既有注释文本(「合成帧使用的纹理」/「位图列表」/「帧列表」/「混合模式」),不改写任何既有注释;`动画器` 注释随其成员与所属接口一并移除;不写实现体;公开类型集中在 `types.ts`;`index.ts` 保持同目录星号导出。 + + ④ **命名:** 两个新公开名称 `IAnimationParsedData`、`IAnimationAnimater` 与四个成员名 `texture` / `bitmaps` / `frames` / `blend` 全部由用户给定;本计划不新增任何其它公开名称。若需改名,请在批准前说明。 + + ⑤ **影响范围:** 只修改 `packages/render-anim/src/types.ts`,并写入执行后的 `09-53-SUMMARY.md`;不改 `packages/render-anim/src/index.ts`、`packages/render-anim/package.json`、其它包、根配置、锁文件、09-50 / 09-51 / 09-52 的计划与摘要、`STATE.md`、`ROADMAP.md`、`09-CONTEXT.md`;不运行 `pnpm install`;不新增包级 tsconfig。 + + ⑥ **验证形态(只读静态):** prettier `--check`(`types.ts`,并复核 `index.ts` / `package.json` 未被改动)、eslint(`types.ts`)、CRLF / BOM / 制表符门禁、逐接口成员门禁(成员归属、顺序、类型、`readonly`、新增接口的成员注释)、旧类型移除门禁、无实现门禁、无 `import type` / `any` 门禁;类型门禁只过滤 `vue-tsc --noEmit` 输出中的 `packages/render-anim` 诊断,全仓既有类型错误与非 0 退出码属既有基线、不视为本次失败。**不运行测试、构建、dev server 或浏览器;不做运行时主张。** + + ⑦ **不提交:** 全程 0 次提交,禁止任何 git 写命令;改动留在工作树供审阅。 + + ⑧ **编号与依赖:** 派发编号 09-53 / 波次 2,`depends_on: 09-52`;`09-01`..`09-52` 均已占用,本计划不删除、不覆盖任何既有记录。 + + ⑨ **等待批准:** 请确认以上内容后,回复恰好四个字「可以执行」。任何修改意见请直接说明;未收到「可以执行」之前不开始 Task 1–4 的任何一步。 + + 用户针对本修订版恰好回复「可以执行」后才进入 Task 1–4;在此之前不执行 Task 1 / Task 2 / Task 3 / Task 4 的任何一步,也不派发执行子代理。 + 已完整汇报 ①–⑨(精确前后 diff、拆分原因、四个成员与类型别名的逐字形态、删除清单、dev.md 合规点、命名、影响范围、只读静态验证口径、不提交、编号),并收到用户针对本修订版的「可以执行」回复。 + + 逐条核对 ①–⑨ 是否与你的意图一致,特别是:② 的修改前 / 修改后逐字块与第 1–61 行不变范围;③ 的注释沿用与 `动画器` 注释移除;④ 的命名;⑤ 的范围(只改 `types.ts`、`index.ts` / `package.json` 不动);⑥ 的「只读静态 + 过滤类型门禁」口径;⑦ 的 0 次提交。 + + 请在确认本修订版(尤其是 ② 的精确 diff、③ 的注释方案、⑤ 的范围)后,回复恰好四个字「可以执行」。任何修改意见请直接说明。 + + + + Task 1: 读取当前类型与 09-52 记录,采集范围 / 哈希基线(只读) + 无(本任务只读;不写仓库内任何文件) + + - `AGENTS.md`(执行前获批、命名先反馈、既有注释不得修改、最小修改也走 GSD、GSD 流程豁免范围) + - `dev.md`(CRLF、4 空格、首行 import、禁止 `import type`、无 `any`、成员 jsDoc 在源头、公开接口放 `types.ts`) + - `packages/render-anim/src/types.ts`(当前第 1–61 行与待替换的第 62–76 行) + - `.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md`、`09-52-SUMMARY.md`(原始设计、已落地门禁与未提交状态) + - `packages/render/src/assets/types.ts`(`ITexture` / `ITextureAnimater` 的形状与 jsDoc 风格) + - `packages/render-anim/src/index.ts`、`packages/render-anim/package.json`(只验证对象) + - `tsconfig.json`、`.prettierrc`、`eslint.config.js`(路径映射与格式化 / lint 配置) + - `09-50-PLAN.md`、`09-50-SUMMARY.md`、`09-51-PLAN.md`、`09-51-SUMMARY.md`、`09-53-PLAN.md`(哈希基线对象) + + + 仅在收到用户恰好回复「可以执行」之后执行。本任务不写仓库内文件,只读取并记录,供 Task 2 落码、Task 3 复核、Task 4 写入 SUMMARY。 + + 1. 读取并记录以下事实(逐条写进执行笔记,Task 4 汇总进 SUMMARY): + - `AGENTS.md`:执行前须获批、命名先反馈、不得修改既有注释、最小修改也走 GSD、GSD 流程豁免范围。 + - `dev.md`:CRLF、4 空格、模块无副作用、不转发导出、无类型导入、import 必须在首行且中间不夹注释 / 空行、命名规则、注释规范、类型规范(无必要 `any`、显式成员类型、合理 `readonly`、对象类型单独 `interface`、公开接口放 `types.ts`)。 + - `packages/render-anim/src/types.ts`:第 1–61 行逐字不变;第 62–76 行是待替换的组合设计块,必须与计划正文「修改前(逐字)」完全相同。 + - `09-52-PLAN.md` / `09-52-SUMMARY.md`:09-52 的原始设计、已落地的 1 个枚举 + 6 个接口、门禁记录、未提交状态。 + - `packages/render/src/assets/types.ts`:`ITexture` 为接口;`ITextureAnimater` 的 `once(texture, data)` / `cycled(texture, data)` 均返回 `Generator`;成员 jsDoc 为单行中文、结尾不加句号。 + - `packages/render-anim/src/index.ts`:唯一一行 `export * from './types';`。 + - `packages/render-anim/package.json`:仅 `name` 与 `dependencies['@motajs/render'] = 'workspace:*'` 两个顶层键。 + - 根 `tsconfig.json`:`include` 覆盖 `packages/**/*.ts`,`paths['@motajs/*'] = ['./packages/*/src']`,`strict: true`、`isolatedModules: true`、`noEmit: true`。 + - `.prettierrc`(`printWidth: 80` / `tabWidth: 4` / `singleQuote: true` / `arrowParens: avoid` / `trailingComma: none` / `endOfLine: crlf`)与 `eslint.config.js`(typescript-eslint + prettier 插件)。 + + 2. 记录**精确目标形态**:`IAnimationParsedData` 的四个成员(名称 / 类型 / `readonly` / 顺序 / 沿用注释文本)与 `IAnimationAnimater` 类型别名的逐字声明;删除清单(`IAnimationAnimaterData`、`IAnimationData`、`animater` 成员、`动画器` 注释)。方案中不得出现用户未提供的公开名称。 + + 3. 采集**范围基线**:`%TEMP%` **不是文件系统路径**,基线文件的实际位置由 Node 的 `process.env.TEMP` 解析;后续所有门禁都写作 `(process.env.TEMP||'.')`,Windows 下 `TEMP` 始终存在,两处解析到同一目录。用下面这条 `node` 命令把当前 `git status --porcelain -uall` 的完整输出(先统一为 LF)写入 `gsd-09-53-status.txt`(UTF-8 无 BOM),并回显绝对路径:`node -e "const{execSync}=require('child_process'),fs=require('fs');const out=execSync('git status --porcelain -uall',{encoding:'utf8'}).replace(/\r\n/g,'\n');const p=(process.env.TEMP||'.')+'/gsd-09-53-status.txt';fs.writeFileSync(p,out,{encoding:'utf8'});console.log(p);"`。该基线用于 Task 4 做集合差,因为工作树可能已存在大量既有未提交改动。写入后必须立刻断言:基线文件内容与当前 `git status --porcelain -uall` 完全一致(见 `` 第 1 条),否则重新采集;不得用 `.trim()` 之外的方式放宽比较。 + + 4. 采集**哈希基线**:对以下 10 个路径逐个执行 `git hash-object`,把「无空格路径 + 空格 + 40 位哈希」写入同一 `process.env.TEMP` 目录下的 `gsd-09-53-hashes.txt`(UTF-8 无 BOM):`packages/render-anim/src/types.ts`、`packages/render-anim/src/index.ts`、`packages/render-anim/package.json`、`.planning/phases/09-minimum-browser-runnable/09-50-PLAN.md`、`09-50-SUMMARY.md`、`09-51-PLAN.md`、`09-51-SUMMARY.md`、`09-52-PLAN.md`、`09-52-SUMMARY.md`、`09-53-PLAN.md`。命令:`node -e "const{execSync}=require('child_process'),fs=require('fs');const files=['packages/render-anim/src/types.ts','packages/render-anim/src/index.ts','packages/render-anim/package.json','.planning/phases/09-minimum-browser-runnable/09-50-PLAN.md','.planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md','.planning/phases/09-minimum-browser-runnable/09-51-PLAN.md','.planning/phases/09-minimum-browser-runnable/09-51-SUMMARY.md','.planning/phases/09-minimum-browser-runnable/09-52-PLAN.md','.planning/phases/09-minimum-browser-runnable/09-52-SUMMARY.md','.planning/phases/09-minimum-browser-runnable/09-53-PLAN.md'];const lines=files.map(f=>f+' '+execSync('git hash-object '+f,{encoding:'utf8'}).trim());const p=(process.env.TEMP||'.')+'/gsd-09-53-hashes.txt';fs.writeFileSync(p,lines.join('\n')+'\n',{encoding:'utf8'});console.log(p);console.log(lines.join('\n'));"`。把 `09-53-PLAN.md` 纳入基线,是为了证明执行期间计划文件本身未被改写(它不在 `files_modified` 内)。 + + 5. 断言 `types.ts` 当前仍是旧组合设计:包含 `export interface IAnimationAnimaterData` 与 `export interface IAnimationData extends IAnimationAnimaterData`,且不包含 `IAnimationParsedData` 与 `IAnimationAnimater =`。若已被改动,停下上报,不得继续 Task 2。 + + 6. 确认 `packages/render-anim` 的 git 状态恰为 3 条未跟踪文件(`?? packages/render-anim/package.json`、`?? packages/render-anim/src/index.ts`、`?? packages/render-anim/src/types.ts`),与本计划假设一致。任一命令非 0 退出或输出与预期不符,必须停下上报,不得继续 Task 2。 + + 7. 采集**不变前缀与注释基线**:用下面这条 `node` 命令计算 `types.ts` 第 1–61 行(按 CRLF 精确拼接、含第 61 行行尾 CRLF)的 sha256,写入 `process.env.TEMP` 的 `gsd-09-53-prefix.txt`:`node -e "const fs=require('fs'),crypto=require('crypto');const s=fs.readFileSync('packages/render-anim/src/types.ts','utf8');if(s.split('\r\n').length<62){console.error('types.ts not CRLF or shorter than 62 lines');process.exit(1);}const prefix=s.split('\r\n').slice(0,61).join('\r\n')+'\r\n';const h=crypto.createHash('sha256').update(prefix,'utf8').digest('hex');const p=(process.env.TEMP||'.')+'/gsd-09-53-prefix.txt';fs.writeFileSync(p,'lines 1-61 sha256 '+h+'\n',{encoding:'utf8'});console.log(p);console.log('sha256 '+h);"`。该哈希是 Task 2 / Task 3 证明「第 1–61 行逐字不变、既有注释未被改写」的唯一依据;Task 2 / Task 3 会重算同一段前缀的 sha256 并与该基线逐字比较,任何注释改写都会使哈希漂移。随后用 `` 第 5 条命令断言前缀里确实含枚举注释 `/** 普通混合 */` 与 `/** 加算混合 */`,确保基线本身是 09-52 的既有注释文本。 + + + node -e "const{execSync}=require('child_process'),fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-53-status.txt';const base=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n').trim();const now=execSync('git status --porcelain -uall',{encoding:'utf8'}).replace(/\r\n/g,'\n').trim();if(base!==now){console.error('baseline drift');process.exit(1);}console.log('OK: status baseline matches current tree');" + 基线文件与当前 `git status --porcelain -uall` 不一致(打印 `baseline drift`,退出码 1)。 + node -e "const fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-53-hashes.txt';const s=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n').trim().split('\n').filter(Boolean);const ok=s.length===10&&s.every(l=>{const a=l.split(' ');return a.length===2&&a[1].length===40&&!/[^0-9a-f]/.test(a[1]);});if(!ok){console.error(s.join('\n'));process.exit(1);}console.log(s.join('\n'));" + 哈希基线不是 10 行,或任一行不是「无空格路径 + 40 位小写十六进制」,脚本打印内容并以 1 退出。 + node -e "const fs=require('fs');const t=fs.readFileSync('packages/render-anim/src/types.ts','utf8');const fail=[];if(t.indexOf('export interface IAnimationAnimaterData')===-1){fail.push('old IAnimationAnimaterData missing');}if(t.indexOf('export interface IAnimationData extends IAnimationAnimaterData')===-1){fail.push('old IAnimationData missing');}if(t.indexOf('IAnimationParsedData')!==-1){fail.push('new IAnimationParsedData already present');}if(t.indexOf('IAnimationAnimater =')!==-1){fail.push('new IAnimationAnimater alias already present');}if(fail.length){console.error(fail.join('\n'));process.exit(1);}console.log('OK: pre-edit combined types present, split types absent');" + `types.ts` 已不是 09-52 的组合设计,或已包含新类型(打印缺失 / 已存在项,退出码 1)。 + node -e "const{execSync}=require('child_process');const out=execSync('git status --porcelain -uall -- packages/render-anim',{encoding:'utf8'}).replace(/\r\n/g,'\n').trim().split('\n').filter(Boolean).sort();const want=['?? packages/render-anim/package.json','?? packages/render-anim/src/index.ts','?? packages/render-anim/src/types.ts'];const ok=out.length===3&&want.every(w=>out.indexOf(w)!==-1);console.log(out.join('\n'));if(!ok){console.error('unexpected render-anim status');process.exit(1);}console.log('OK: render-anim has the 3 expected untracked files');" + `packages/render-anim` 的未跟踪文件不是恰好 3 个预期路径(打印实际状态与 `unexpected render-anim status`,退出码 1)。 + node -e "const fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-53-prefix.txt';const t=fs.readFileSync(p,'utf8');const m=/sha256 ([0-9a-f]{64})/.exec(t);const s=fs.readFileSync('packages/render-anim/src/types.ts','utf8');const head=s.split('\r\n').slice(0,61).join('\n');const need=['/** 普通混合 */','/** 加算混合 */'];const miss=need.filter(n=>head.indexOf(n)===-1);const fail=[];if(!m){fail.push('prefix baseline missing');}if(s.split('\r\n').length<62){fail.push('types.ts not CRLF or shorter than 62 lines');}if(miss.length){fail.push('missing enum jsDoc: '+miss.join(','));}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK: prefix baseline '+m[1]+' + enum jsDoc present');process.exit(fail.length?1:0);" + `gsd-09-53-prefix.txt` 缺失 / 不是 64 位 sha256,`types.ts` 不是 CRLF 或不足 62 行,或第 1–61 行缺少 `/** 普通混合 */` / `/** 加算混合 */` 枚举注释(打印 `FAIL`,退出码 1)。 + + + - 已读取并在执行笔记中记录 `read_first` 列出的全部事实(规则、当前类型、09-52 设计、`@motajs/render` 类型、index / package、根配置、格式化 / lint 配置)。 + - `process.env.TEMP` 目录下的 `gsd-09-53-status.txt`(与门禁 `(process.env.TEMP||'.')` 解析一致,不是 `%TEMP%` 字面量)已生成,且 Task 1 结束时与当前 `git status --porcelain -uall` 完全一致。 + - `process.env.TEMP` 目录下的 `gsd-09-53-hashes.txt`(与门禁同一路径解析)恰有 10 行,覆盖 `types.ts`、`index.ts`、`package.json`、09-50 / 09-51 / 09-52 的计划与摘要、`09-53-PLAN.md`,每行「无空格路径 + 空格 + 40 位哈希」。 + - `types.ts` 仍为旧组合设计(两个旧接口存在、新接口 / 新别名不存在),且 `packages/render-anim` 的 git 状态恰为 3 条预期未跟踪文件。 + - 已记录 `IAnimationParsedData` 的四个成员与 `IAnimationAnimater` 别名的精确方案,且未新增用户未提供的公开名称。 + - `process.env.TEMP` 目录下的 `gsd-09-53-prefix.txt` 已生成,含第 1–61 行的 sha256;并已确认第 1–61 行含 `/** 普通混合 */` 与 `/** 加算混合 */` 枚举注释。 + + 已读取并记录 dev.md / AGENTS.md 的适用规则、当前 `types.ts`、09-52 的原始设计与摘要、`@motajs/render` 的类型与 jsDoc 风格、`index.ts` / `package.json` 现状;`process.env.TEMP` 目录下的 `gsd-09-53-status.txt` 与 `gsd-09-53-hashes.txt`(与门禁 `(process.env.TEMP||'.')` 解析一致)均已生成且内容完整(哈希基线 10 条,含 `09-53-PLAN.md`);`gsd-09-53-prefix.txt` 已保存第 1–61 行的 sha256 前缀基线并核对枚举注释;`types.ts` 仍为旧组合设计;`packages/render-anim` 恰为 3 条预期未跟踪文件。 + + + + Task 2: 用一次精确块替换把 `types.ts` 改为拆分后的类型(只改此文件) + packages/render-anim/src/types.ts + + - `.planning/phases/09-minimum-browser-runnable/09-53-PLAN.md` 的「修改前后逐字对照」与「新类型逐成员对照」(唯一内容依据;本任务不新增任何对外名称) + - `packages/render-anim/src/types.ts`(待修改对象,第 1–61 行必须保持逐字不变) + - `packages/render/src/assets/types.ts`(`ITexture` / `ITextureAnimater` 的导入来源与签名) + - `dev.md`(CRLF、4 空格、首行 import、无 `import type` / `any`、成员 jsDoc、不得改写既有注释) + + + 仅在 Task 1 完成且收到「可以执行」之后执行。本任务只修改 `packages/render-anim/src/types.ts`,不得写其它任何文件。 + + 1. 读取 `types.ts`,确认第 1–61 行与 09-52 落地内容一致,第 62–76 行逐字等于计划正文「修改前(逐字)」块。若不一致,停下上报,不得自行猜测替换范围。 + + 2. 用 Edit 工具做一次精确块替换:`oldString` 为计划正文「修改前(逐字)」整块(从 `export interface IAnimationAnimaterData {` 到 `IAnimationData` 的闭合 `}`),`newString` 为「修改后(逐字)」整块(`IAnimationParsedData` 接口 + `IAnimationAnimater` 类型别名)。`oldString` 必须按 `types.ts` 的实际 CRLF 文本读取(可用 Node 读取第 62–76 行作为匹配源),不得把计划 Markdown 中的 LF 直接当作文件内容;Edit 因换行不匹配而失败时停下上报,不得改用 Write 覆盖整文件。不得改动第 1–61 行;不得改写任何既有注释;不得新增或调整 import。 + + 3. 替换后逐字检查: + - 新接口成员顺序固定为 `texture` → `bitmaps` → `frames` → `blend`;四个成员均为 `readonly`,数组成员仍为 `readonly` 数组。 + - 每个成员前保留一行既有中文 jsDoc(「合成帧使用的纹理」/「位图列表」/「帧列表」/「混合模式」),文本逐字不变。 + - 第 1–61 行的 sha256 必须与 Task 1 的 `gsd-09-53-prefix.txt` 基线一致;四个成员 jsDoc 必须逐字为 `/** 合成帧使用的纹理 */` / `/** 位图列表 */` / `/** 帧列表 */` / `/** 混合模式 */`。任何注释改写(包括改成英文)都会分别使前缀哈希漂移或精确文本门禁失败。 + - 类型别名逐字为 `export type IAnimationAnimater = ITextureAnimater;`。 + - 两个旧接口及其成员 / 注释 / `extends` 不再存在;解析数据中不再有 `animater` 成员。 + - 文件内没有 `class` / `function` / `let` / 箭头函数 / `export default`;`const` / `enum` 仅出现在 `AnimationBlendMode` 声明处。 + + 4. 如需格式修复,仅可运行 `node node_modules/prettier/bin/prettier.cjs --write packages/render-anim/src/types.ts`(本任务唯一被授权的写操作,且仅限此文件);不得对 `index.ts` / `package.json` 或其它文件运行 `--write`。随后运行 `--check` 确认。 + + 5. 不运行测试、构建、dev server、浏览器或包管理命令;不执行 git 写命令。 + + + node -e "const fs=require('fs');if(!fs.existsSync('packages/render-anim/src/types.ts')){console.error('missing types.ts');process.exit(1);}console.log('OK: types.ts exists');" + `packages/render-anim/src/types.ts` 不存在(退出码 1)。 + node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/src/types.ts + 输出不是 `All matched files use Prettier code style!`,或退出码非 0(prettier 列出待格式化文件)。 + node -e "const fs=require('fs');const b=fs.readFileSync('packages/render-anim/src/types.ts');const s=b.toString('utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;const tabs=(s.match(/\t/g)||[]).length;const bom=b[0]===0xEF&&b[1]===0xBB&&b[2]===0xBF;const ok=lf===crlf&&tabs===0&&!bom;console.log((ok?'OK':'FAIL')+' lf='+lf+' crlf='+crlf+' tabs='+tabs+' bom='+bom);process.exit(ok?0:1);" + 出现非 CRLF 换行、制表符或 BOM(打印 `FAIL`,退出码 1)。 + node -e "const fs=require('fs');const t=fs.readFileSync('packages/render-anim/src/types.ts','utf8').replace(/\r\n/g,'\n');const fail=[];const block=/export interface IAnimationParsedData \{\n([\s\S]*?)\n\}/.exec(t);if(!block){fail.push('missing IAnimationParsedData');}else{const got=block[1].split('\n').map(l=>l.trim()).filter(l=>l.length&&!l.startsWith('/**')&&!l.startsWith('*')&&!l.startsWith('//')).join(' ');const want='readonly texture: ITexture; readonly bitmaps: readonly IAnimationBitmap[]; readonly frames: readonly IAnimationFrame[]; readonly blend: AnimationBlendMode;';if(got!==want){fail.push('members: '+got);}}if(!/export type IAnimationAnimater = ITextureAnimater;/.test(t)){fail.push('alias');}if(/\bIAnimationAnimaterData\b/.test(t)){fail.push('old IAnimationAnimaterData');}if(/\bIAnimationData\b/.test(t)){fail.push('old IAnimationData');}if(/\banimater\b/.test(t)){fail.push('animater member');}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK: split applied');process.exit(fail.length?1:0);" + 新接口成员顺序 / 类型 / 只读性与计划不一致,类型别名缺失,或旧接口 / `animater` 成员仍存在(打印 `FAIL` 与差异,退出码 1)。 + node -e "const{execSync}=require('child_process'),fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-53-hashes.txt';const base=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n').trim().split('\n').filter(Boolean);const now=base.map(l=>{const f=l.split(' ')[0];return f+' '+execSync('git hash-object '+f,{encoding:'utf8'}).trim();});const changed=now.filter((l,k)=>l!==base[k]);const types=now.filter(l=>l.split(' ')[0]==='packages/render-anim/src/types.ts');const ok=changed.length===1&&types.length===1&&changed[0]===types[0];console.log('changed:\n'+changed.join('\n'));if(!ok){console.error('unexpected changed set');process.exit(1);}console.log('OK: only types.ts changed');" + 除 `types.ts` 外还有基线路经被改动,或 `types.ts` 未变化(打印 `unexpected changed set`,退出码 1)。 + node -e "const fs=require('fs'),crypto=require('crypto');const p=(process.env.TEMP||'.')+'/gsd-09-53-prefix.txt';const exp=/([0-9a-f]{64})/.exec(fs.readFileSync(p,'utf8'));const s=fs.readFileSync('packages/render-anim/src/types.ts','utf8');const prefix=s.split('\r\n').slice(0,61).join('\r\n')+'\r\n';const got=crypto.createHash('sha256').update(prefix,'utf8').digest('hex');const docs=[' /** 合成帧使用的纹理 */',' /** 位图列表 */',' /** 帧列表 */',' /** 混合模式 */'];const miss=docs.filter(d=>s.indexOf(d)===-1);const fail=[];if(!exp){fail.push('prefix baseline missing');}else if(got!==exp[1]){fail.push('prefix drift '+got+' != '+exp[1]);}if(miss.length){fail.push('missing exact jsDoc: '+miss.join(','));}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK: lines 1-61 unchanged + exact member jsDoc present');process.exit(fail.length?1:0);" + 第 1–61 行前缀 sha256 与 Task 1 基线不一致(说明既有文本 / 注释被改写),或四个成员 jsDoc 不是逐字的 `/** 合成帧使用的纹理 */` / `/** 位图列表 */` / `/** 帧列表 */` / `/** 混合模式 */`(打印 `FAIL` 与差异,退出码 1)。 + + + - `types.ts` 由 `gsd-executor` 用 Edit 工具做一次精确块替换,未用 Write 覆盖整文件;第 1–61 行逐字不变。 + - 新接口 `IAnimationParsedData` 的成员顺序、类型、`readonly`、只读数组与计划逐项一致;四个成员各有且只有一行沿用 09-52 文本的 jsDoc,且逐字为指定中文文本(由前缀哈希与精确文本门禁自动证明)。 + - `IAnimationAnimater` 类型别名逐字为 `ITextureAnimater`。 + - 两个旧接口、`animater` 成员与 `动画器` 注释已全部移除;`index.ts` / `package.json` 的哈希与 Task 1 基线一致。 + - 第 1–61 行前缀 sha256 与 Task 1 基线一致,四个成员 jsDoc 逐字存在——既有注释与新成员注释均未被改写。 + - `prettier --write` 仅在需要时对 `types.ts` 运行,未触碰任何其它文件;文件为 CRLF、无制表符、无 BOM。 + + `types.ts` 已从组合设计替换为拆分设计:`IAnimationParsedData`(`texture` / `bitmaps` / `frames` / `blend`)+ `IAnimationAnimater = ITextureAnimater`;两个旧接口与 `animater` 成员已删除;第 1–61 行逐字不变(前缀 sha256 与 Task 1 基线一致);四个成员 jsDoc 逐字存在;prettier `--check`、CRLF / BOM / 制表符与内容门禁通过;`index.ts` / `package.json` 未改动。 + + + + Task 3: dev.md 合规与内容门禁(只读静态检查) + 无(本任务只读;不修改任何文件) + + - `.planning/phases/09-minimum-browser-runnable/09-53-PLAN.md` 的「修改前后逐字对照」与「新类型逐成员对照」(成员归属、顺序、类型、`readonly`、注释的唯一判定依据) + - `packages/render-anim/src/types.ts`(待检查对象) + - `packages/render-anim/src/index.ts`、`packages/render-anim/package.json`(只验证未改动) + - `dev.md`(CRLF、4 空格、首行 import、无 `import type` / `any`、成员 jsDoc 在源头、无实现体) + - `.prettierrc` 与 `eslint.config.js`(门禁使用的实际配置) + + + 只运行只读检查;任何一项失败都必须回到 Task 2 修正后重跑全部检查,不得通过放宽检查、删除断言或忽略文件来「变绿」。本任务**严格只读**:不运行 `prettier --write`、不写任何文件。 + + 1. **格式门禁(prettier)**:运行 `node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/src/types.ts packages/render-anim/src/index.ts packages/render-anim/package.json`,必须退出码 0 且输出 `All matched files use Prettier code style!`。失败时**回到 Task 2** 修正(格式归一化由 Task 2 的 `prettier --write` 授权执行),本任务不得运行 `--write`、不得手改文件或格式规则;Task 2 修正后由本任务重跑全部检查。 + + 2. **lint 门禁(eslint)**:运行 `node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts`,必须退出码 0、无输出。失败时**本任务不修改任何源码**:把 eslint 的原始输出(文件、行号、规则名、说明)逐字记录并上报,然后**回到 Task 2** 修正(只改 `types.ts`,不得添加 `eslint-disable`、不得改 eslint 配置);Task 2 修正后由本任务从第 1 步起重跑全部门禁。 + + 3. **换行 / BOM / 缩进门禁**:运行 `` 第 3 条 node 命令,必须输出 `OK` 且退出码 0(每个换行都是 CRLF、无制表符、无 BOM)。 + + 4. **内容与结构门禁**:运行 `` 第 4 条 node 命令。它逐接口解析并断言: + - 恰好 5 个 `export interface`:`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame`、`IAnimationParsedData`;无 `extends`。 + - 每个接口的成员行序列与计划「修改前后逐字对照」逐字一致;成员被放进错误接口、顺序变化、`readonly` / `?` / 类型变化都会使门禁失败。 + - `IAnimationParsedData` 的每个成员前恰有一行单行 jsDoc,且文本逐字为 `/** 合成帧使用的纹理 */` / `/** 位图列表 */` / `/** 帧列表 */` / `/** 混合模式 */`(`docCom` 精确匹配,改写即失败),且无 `animater` 成员。 + - `export type IAnimationAnimater = ITextureAnimater;` 逐字存在。 + - 旧接口 `IAnimationAnimaterData`、`IAnimationData` 不再出现;`RenderAnimater`、`parseRenderAnimation` 不出现。 + - 无 `class` / `function` / `let` / 箭头函数 / `export default`;`const` / `enum` 各恰好 1 次且只在 `AnimationBlendMode` 声明处;无 `any`、无 `import type`;首行是普通 `import`;`AnimationBlendMode` 成员序列恰为 `Normal, Add`。 + + 5. **index / package 未改动门禁**:运行 `` 第 5 条 node 命令,`index.ts` 与 `package.json` 的当前哈希必须与 Task 1 基线一致;任何差异都说明越界修改,必须停下上报并回到 Task 2 修正(只允许改 `types.ts`,不得改这两个文件)。 + + 6. **人工逐条复核(只读,且是绑定判定)**:打开 `types.ts`,逐成员对照计划「新类型逐成员对照」,确认:第 1–61 行逐字未变(前缀 sha256 与 Task 1 基线一致);新接口成员顺序为 `texture` → `bitmaps` → `frames` → `blend`;四个成员均有且只有一行逐字为指定中文文本的 jsDoc(`/** 合成帧使用的纹理 */` / `/** 位图列表 */` / `/** 帧列表 */` / `/** 混合模式 */`);`IAnimationAnimater` 别名逐字正确;两个旧接口的所有成员 / 注释 / `extends` 均已删除;没有实现体、没有 `export default`、没有多余空行或新注释。**本步骤的结论是绑定判定**:必须写入执行笔记与 SUMMARY;任一成员与计划不一致即判本任务失败。 + + 7. **类型门禁(必需,跨平台 node 过滤,不使用 PowerShell 专有的 `Select-String`)**:运行 `` 第 6 条 node 命令。它先运行 `node node_modules/vue-tsc/bin/vue-tsc.js --version` 哨兵:哨兵非 0 退出即以退出码 2 判失败,证明检查器根本无法启动时不会假通过。哨兵通过后再捕获 `vue-tsc --noEmit` 的 stdout + stderr(非 0 退出码被捕获,不直接判失败);若进程未正常结束(`typeof e.status !== 'number'`,例如 spawn 失败)同样以退出码 2 判失败。只有在「哨兵成功 + 类型检查进程确实完成」的前提下,才用 `packages/render-anim` 过滤结果判定:0 行诊断视为通过,出现任一条即失败。全仓既有 / 预期类型错误与 `vue-tsc` 非 0 退出码属既有基线,不得视为本次失败,也不得因此修改其它文件。若因环境原因哨兵未能启动,SUMMARY 必须写明「类型检查未运行」,不得宣称类型检查通过。 + + 8. **第 1–61 行 / 注释不变门禁**:运行 `` 第 7 条 node 命令。它重算第 1–61 行前缀的 sha256 并要求与 Task 1 的 `gsd-09-53-prefix.txt` 完全一致,同时要求四个新成员 jsDoc 与枚举注释 `/** 普通混合 */` / `/** 加算混合 */` 精确存在。任一既有注释被改写(包括改成英文)都会使该门禁失败;失败时回到 Task 2 修正,不得放宽或跳过。 + + 本任务不运行测试、构建、dev server、浏览器或包管理命令;**严格只读**:不运行 `prettier --write`、不写任何文件、不修改源码。任何 prettier / eslint / 内容失败都必须回到 Task 2 修正,再由本任务重跑全部门禁;本任务对 eslint 失败只上报原始输出并停止,绝不就地改源码或加 `eslint-disable`。 + + + node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/src/types.ts packages/render-anim/src/index.ts packages/render-anim/package.json + 输出不是 `All matched files use Prettier code style!`,或退出码非 0(prettier 列出待格式化文件)。 + node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts + eslint 打印任一 error / warning,或退出码非 0。 + node -e "const fs=require('fs');const b=fs.readFileSync('packages/render-anim/src/types.ts');const s=b.toString('utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;const tabs=(s.match(/\t/g)||[]).length;const bom=b[0]===0xEF&&b[1]===0xBB&&b[2]===0xBF;const ok=lf===crlf&&tabs===0&&!bom;console.log((ok?'OK':'FAIL')+' lf='+lf+' crlf='+crlf+' tabs='+tabs+' bom='+bom);process.exit(ok?0:1);" + 出现非 CRLF 换行、制表符或 BOM(打印 `FAIL`,退出码 1)。 + node -e "const fs=require('fs');const t=fs.readFileSync('packages/render-anim/src/types.ts','utf8').replace(/\r\n/g,'\n');const exp={IAnimationBitmap:{m:['readonly x: number;','readonly y: number;','readonly width: number;','readonly height: number;']},IAnimationSound:{m:['readonly name: string;','readonly volume?: number;','readonly pitch?: number;']},IAnimationObject:{m:['readonly index: number;','readonly x: number;','readonly y: number;','readonly scale: number;','readonly opacity: number;','readonly mirror: number;','readonly rotate: number;']},IAnimationFrame:{m:['readonly width: number;','readonly height: number;','readonly originX: number;','readonly originY: number;','readonly objects: readonly IAnimationObject[];','readonly sound?: readonly IAnimationSound[];']},IAnimationParsedData:{m:['readonly texture: ITexture;','readonly bitmaps: readonly IAnimationBitmap[];','readonly frames: readonly IAnimationFrame[];','readonly blend: AnimationBlendMode;']}};const docCom={'IAnimationParsedData|readonly texture: ITexture;':'/** 合成帧使用的纹理 */','IAnimationParsedData|readonly bitmaps: readonly IAnimationBitmap[];':'/** 位图列表 */','IAnimationParsedData|readonly frames: readonly IAnimationFrame[];':'/** 帧列表 */','IAnimationParsedData|readonly blend: AnimationBlendMode;':'/** 混合模式 */'};const re=/export interface (\w+)(?: extends (\w+))? \{([\s\S]*?)\n\}/g;const seen={};let mm;while((mm=re.exec(t))){seen[mm[1]]={ext:mm[2]||null,body:mm[3]};}const fail=[];for(const n of Object.keys(exp)){const s=seen[n];if(!s){fail.push('missing interface '+n);continue;}if(s.ext){fail.push(n+' extends '+s.ext);}const lines=s.body.split('\n');const got=lines.map(l=>l.trim()).filter(l=>l.length&&!l.startsWith('/**')&&!l.startsWith('*')&&!l.startsWith('//')).join(' ');const want=exp[n].m.join(' ');if(got!==want){fail.push(n+' members: '+got);}for(const m of exp[n].m){const idx=lines.findIndex(l=>l.trim()===m);if(idx<1){fail.push(n+' missing member '+m);continue;}const prev=lines[idx-1].trim();const w=docCom[n+'|'+m];if(w){if(prev!==w){fail.push(n+' member jsDoc mismatch: '+m+' got '+prev+' want '+w);}}else if(!/^\/\*\* .+ \*\/$/.test(prev)){fail.push(n+' member without single-line jsDoc: '+m);}}}if(Object.keys(seen).length!==5){fail.push('interface count '+Object.keys(seen).length);}if(!/export type IAnimationAnimater = ITextureAnimater;/.test(t)){fail.push('alias missing');}if(/\bIAnimationAnimaterData\b/.test(t)){fail.push('old IAnimationAnimaterData remains');}if(/\bIAnimationData\b/.test(t)){fail.push('old IAnimationData remains');}if(/\bRenderAnimater\b/.test(t)){fail.push('RenderAnimater body present');}if(/parseRenderAnimation/.test(t)){fail.push('parser present');}if(/\bclass\b/.test(t)){fail.push('class');}if(/\bfunction\b/.test(t)){fail.push('function');}if(/\blet\b/.test(t)){fail.push('let');}if(t.indexOf('=>')!==-1){fail.push('arrow');}if(/export\s+default/.test(t)){fail.push('default export');}if((t.match(/\bconst\b/g)||[]).length!==1){fail.push('const count');}if((t.match(/\benum\b/g)||[]).length!==1){fail.push('enum count');}if(/\bany\b/.test(t)){fail.push('any');}if(/import\s+type/.test(t)){fail.push('import type');}if(t.indexOf('import { ITexture, ITextureAnimater } from')!==0){fail.push('import not first');}if(t.split('\n')[0].indexOf('@motajs/render')===-1){fail.push('import source');}const em=/export const enum AnimationBlendMode \{\n([\s\S]*?)\n\}/.exec(t);if(!em){fail.push('enum missing');}else{const body=em[1].split('\n').map(l=>l.trim()).filter(l=>l.length&&!l.startsWith('/**')&&!l.startsWith('*')&&!l.startsWith('//')).join(' ');if(body!=='Normal, Add'){fail.push('enum members: '+body);}}if((t.match(/export interface /g)||[]).length!==5){fail.push('export interface count');}if((t.match(/export type /g)||[]).length!==1){fail.push('export type count');}if((t.match(/export const enum /g)||[]).length!==1){fail.push('export enum count');}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK: split types match');process.exit(fail.length?1:0);" + 脚本打印 `FAIL` 并列出差异(接口数不是 5、成员归属 / 顺序 / 类型 / `readonly` / `?` 不符、成员缺少单行 jsDoc、别名缺失、旧接口或 `animater` 成员残留、实现符号 / `any` / `import type` 出现、枚举成员不是 `Normal, Add`、导出计数不符),退出码 1。 + node -e "const{execSync}=require('child_process'),fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-53-hashes.txt';const base=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n').trim().split('\n').filter(Boolean);const now=base.map(l=>{const f=l.split(' ')[0];return f+' '+execSync('git hash-object '+f,{encoding:'utf8'}).trim();});const changed=now.filter((l,k)=>l!==base[k]);const bad=changed.filter(l=>l.indexOf('packages/render-anim/src/index.ts ')!==0&&l.indexOf('packages/render-anim/package.json ')!==0&&l.indexOf('packages/render-anim/src/types.ts ')!==0);console.log('changed:\n'+changed.join('\n'));if(bad.length||changed.length!==1||changed[0].indexOf('packages/render-anim/src/types.ts ')!==0){console.error('unexpected changed set');process.exit(1);}console.log('OK: only types.ts changed; index/package unchanged');" + `index.ts` / `package.json` 或其它基线路经发生变化,或 `types.ts` 未变化(打印 `unexpected changed set`,退出码 1)。 + node -e "const{execSync}=require('child_process');const run=cmd=>{try{return{out:execSync(cmd,{encoding:'utf8',maxBuffer:67108864,stdio:['ignore','pipe','pipe']}),status:0};}catch(e){return{out:String(e.stdout||'')+String(e.stderr||''),status:typeof e.status==='number'?e.status:null,code:e.code||null};}};const v=run('node node_modules/vue-tsc/bin/vue-tsc.js --version');if(v.status!==0){console.error('SENTINEL FAIL: vue-tsc could not start: '+v.out+(v.code?' code='+v.code:''));process.exit(2);}const r=run('node node_modules/vue-tsc/bin/vue-tsc.js --noEmit');if(r.status===null){console.error('SENTINEL FAIL: type check did not complete: '+r.out+(r.code?' code='+r.code:''));process.exit(2);}const hits=r.out.split(/\r?\n/).filter(l=>/packages[\\\/]render-anim[\\\/]/.test(l));console.log('sentinel: vue-tsc '+v.out.trim()+' | type check exit '+r.status);console.log(hits.length?hits.join('\n'):'OK: no render-anim diagnostics');process.exit(hits.length?1:0);" + `vue-tsc --version` 哨兵无法启动(退出码 2),或 `--noEmit` 进程未正常结束、`status` 为 `null`(退出码 2,避免检查器未运行时 0 诊断假通过);或哨兵 / 进程确认完成后过滤结果仍出现任一 `packages/render-anim` 诊断(打印诊断并以 1 退出)。其它文件的诊断与 `vue-tsc` 非 0 退出码不参与判定。 + node -e "const fs=require('fs'),crypto=require('crypto');const p=(process.env.TEMP||'.')+'/gsd-09-53-prefix.txt';const exp=/sha256 ([0-9a-f]{64})/.exec(fs.readFileSync(p,'utf8'));const s=fs.readFileSync('packages/render-anim/src/types.ts','utf8');const prefix=s.split('\r\n').slice(0,61).join('\r\n')+'\r\n';const got=crypto.createHash('sha256').update(prefix,'utf8').digest('hex');const docs=['/** 合成帧使用的纹理 */','/** 位图列表 */','/** 帧列表 */','/** 混合模式 */'];const head=s.split('\r\n').slice(0,61).join('\n');const headNeed=['/** 普通混合 */','/** 加算混合 */'];const missDoc=docs.filter(d=>s.indexOf(d)===-1);const missHead=headNeed.filter(d=>head.indexOf(d)===-1);const fail=[];if(!exp){fail.push('prefix baseline missing');}else if(got!==exp[1]){fail.push('prefix drift '+got+' != '+exp[1]);}if(missDoc.length){fail.push('missing member jsDoc: '+missDoc.join(','));}if(missHead.length){fail.push('missing enum jsDoc: '+missHead.join(','));}console.log(fail.length?'FAIL\n'+fail.join('\n'):'OK: lines 1-61 unchanged, exact jsDoc present');process.exit(fail.length?1:0);" + 第 1–61 行前缀 sha256 与 Task 1 基线 `gsd-09-53-prefix.txt` 不一致(既有注释 / 成员被改写),或四个新成员 jsDoc / 两个枚举成员注释文本缺失(打印 `FAIL` 与差异,退出码 1)。 + + + - prettier `--check`、eslint、CRLF / BOM / 制表符门禁、逐接口成员门禁(含逐成员 jsDoc 精确文本)、第 1–61 行前缀哈希与枚举注释门禁、类型别名门禁、旧类型移除门禁、无实现 / 无 `any` / 无 `import type` 门禁均以退出码 0 完成。 + - 逐接口门禁证明 `IAnimationParsedData` 的四个成员位于正确接口内,且顺序、类型、`readonly`、只读数组与计划完全一致,每个成员的 jsDoc 文本逐字匹配;`IAnimationBitmap` / `IAnimationSound` / `IAnimationObject` / `IAnimationFrame` 的成员序列保持 09-52 的形状。 + - 旧接口 `IAnimationAnimaterData` / `IAnimationData`、`animater` 成员、`RenderAnimater` 与 `parseRenderAnimation` 均不存在;`index.ts` / `package.json` 哈希与 Task 1 基线一致。 + - 第 1–61 行前缀 sha256 与 Task 1 基线一致,且 `/** 普通混合 */` / `/** 加算混合 */` 等既有注释精确存在——任何注释改写都会被自动门禁拒绝。 + - 人工逐成员复核是绑定判定,结论已写入执行笔记与 SUMMARY,未以名称级一致或片段存在代替类型级一致。 + - 类型门禁先通过 `vue-tsc --version` 哨兵、并确认 `--noEmit` 进程正常结束后给出 0 行 `packages/render-anim` 诊断(或 SUMMARY 明确写「类型检查未运行」);本任务未写任何文件、未运行 `prettier --write`、未改源码。 + + prettier `--check`、eslint、CRLF / BOM / 制表符门禁、逐接口成员门禁、第 1–61 行前缀哈希与枚举注释门禁、类型别名门禁、旧类型移除门禁、无实现 / 无 `any` / 无 `import type` 门禁与带哨兵的过滤 `vue-tsc` 类型门禁全部通过;`index.ts` / `package.json` 与 09-50 / 09-51 / 09-52 的基线哈希不变;已逐成员人工复核并形成绑定结论;本任务零写入,未运行测试、构建、dev server 或浏览器。 + + + + Task 4: 范围 / 哈希闸门自检并写 `09-53-SUMMARY.md` + .planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md + + - `.planning/phases/09-minimum-browser-runnable/09-53-PLAN.md` 的 `` 与「覆盖审计」(SUMMARY 必须覆盖的条目) + - `process.env.TEMP` 目录下的 `gsd-09-53-status.txt` 与 `gsd-09-53-hashes.txt`(Task 1 采集的范围 / 哈希基线;与门禁 `(process.env.TEMP||'.')` 解析一致) + - Task 1 / Task 2 / Task 3 的执行笔记与各门禁真实输出 + - `09-50-PLAN.md`、`09-50-SUMMARY.md`、`09-51-PLAN.md`、`09-51-SUMMARY.md`、`09-52-PLAN.md`、`09-52-SUMMARY.md`(哈希闸门对象) + + + 只写 `09-53-SUMMARY.md`,不修改任何代码文件。 + + 1. **写 SUMMARY 前的范围闸门(写前,SUMMARY 允许尚不存在)**:运行下面这条 `node` 命令(它只判 `added` / `removed` 为空,不要求 SUMMARY 已出现):`node -e "const{execSync}=require('child_process'),fs=require('fs');const base=fs.readFileSync((process.env.TEMP||'.')+'/gsd-09-53-status.txt','utf8').split(/\r?\n/).filter(l=>l.length>0);const cur=execSync('git status --porcelain -uall',{encoding:'utf8'}).split(/\r?\n/).filter(l=>l.length>0);const added=cur.filter(l=>base.indexOf(l)===-1);const removed=base.filter(l=>cur.indexOf(l)===-1);console.log('added:\n'+added.join('\n'));console.log('removed:\n'+removed.join('\n'));if(added.length||removed.length){console.error('pre-write range drift');process.exit(1);}console.log('OK: pre-write range clean');"`。要求:`added` 为空、`removed` 为空——因为 `types.ts` 虽被修改,但它本来就是未跟踪文件,porcelain 状态不应变化;任何新增 / 消失路径都必须停下上报,不得用 `.trim()` 或子串匹配放宽。这条写前命令必须在写入 SUMMARY 之前成功执行;写后范围仍由 `` 第 2 条命令复核。 + + 2. **哈希闸门**:运行 `` 第 3 条 node 命令。要求:10 条基线中恰好只有 `packages/render-anim/src/types.ts` 的哈希变化;`index.ts`、`package.json`、09-50 / 09-51 / 09-52 的计划与摘要、`09-53-PLAN.md` 的哈希逐字不变。若 `types.ts` 未变化或出现第二处变化,停下上报。 + + 3. **写 `09-53-SUMMARY.md`**(UTF-8 无 BOM、CRLF、4 空格缩进、Markdown 文件名连字符命名),内容至少包含: + - **本计划范围**:只改 `packages/render-anim/src/types.ts` 的类型声明;不实现 `RenderAnimater` / `parseRenderAnimation`;不跑测试 / 浏览器 / 构建;0 次提交;09-53 / 波次 2 / `depends_on: 09-52` 的编号说明。**必须逐字写出字段名 `files_modified`**,并说明其恰为 `packages/render-anim/src/types.ts` 与 `.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md`(满足关键词门禁)。 + - **修改前后 diff**:逐字给出被删除的 `IAnimationAnimaterData` / `IAnimationData` 块与新增的 `IAnimationParsedData` / `IAnimationAnimater` 块;说明第 1–61 行逐字不变。 + - **逐成员最终形态**:`IAnimationParsedData` 的四个成员(名称 / 类型 / `readonly` / 顺序 / 沿用注释文本)与 `IAnimationAnimater` 类型别名;删除清单。 + - **Task 1 基线**:`types.ts` / `index.ts` / `package.json` 与 09-50 / 09-51 / 09-52 / 09-53-PLAN 的哈希;`packages/render-anim` 的 3 条未跟踪状态;根 tsconfig / `.prettierrc` / eslint 配置事实。 + - **门禁真实结果**:prettier / eslint / CRLF / BOM / 逐接口成员 / 类型别名 / 旧类型移除 / 无实现门禁的实际输出(逐字粘贴关键行);类型门禁「已运行(0 行 render-anim 诊断)」或「未运行」二选一,不得含糊;范围闸门与哈希闸门的实际输出。 + - **未做 / 跳过项**:不运行测试、构建、dev server、浏览器;不做运行时主张;不运行 `pnpm install`;不执行 git 写命令。 + - **设计记录(未实现)**:`RenderAnimater` 将实现 `IAnimationAnimater`(即 `ITextureAnimater`),`parseRenderAnimation(buffer: ArrayBuffer): Promise`;解析数据不含动画器,`once` / `cycled` 的 `data` 参数只接收纯解析数据。 + - **待确认项**:OI-1 命名已批准、OI-2 注释沿用、OI-3 后续实现、OI-4 未校准估算、OI-5 未提交状态。 + - **估算**:`confidence: low`,未校准;实际字节 / 行数按最终文件测量。 + + 4. **写 SUMMARY 后的范围闸门**:运行 `` 第 2 条 node 命令,要求 `added` 恰为 `?? .planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md`,`removed` 为空,`extra` / `missing` 为空。 + + 5. 不写 `09-53-RESEARCH.md` / `09-53-VALIDATION.md`;不改 `STATE.md` / `ROADMAP.md` / `09-CONTEXT.md` / 09-50 / 09-51 / 09-52 / `index.ts` / `package.json`。 + + + node -e "const fs=require('fs');const f='.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md';const s=fs.readFileSync(f,'utf8');const need=['09-53','IAnimationParsedData','IAnimationAnimater','prettier','eslint','类型门禁','0 次提交','files_modified'];const miss=need.filter(n=>s.indexOf(n)===-1);if(miss.length){console.error('missing '+miss.join(','));process.exit(1);}console.log('OK: summary present');" + SUMMARY 缺少任一必需关键词(打印 `missing <列表>`,退出码 1),或文件不存在(抛出异常并以非 0 退出)。 + node -e "const{execSync}=require('child_process'),fs=require('fs');const base=fs.readFileSync((process.env.TEMP||'.')+'/gsd-09-53-status.txt','utf8').split(/\r?\n/).filter(l=>l.length>0);const cur=execSync('git status --porcelain -uall',{encoding:'utf8'}).split(/\r?\n/).filter(l=>l.length>0);const summary='?? .planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md';const added=cur.filter(l=>base.indexOf(l)===-1);const removed=base.filter(l=>cur.indexOf(l)===-1);const extra=added.filter(l=>l!==summary);const missing=added.indexOf(summary)===-1?[summary]:[];console.log('added:\n'+added.join('\n'));console.log('removed:\n'+removed.join('\n'));console.log('extra:\n'+extra.join('\n'));console.log('missing:\n'+missing.join('\n'));process.exit(extra.length+missing.length+removed.length?1:0);" + `extra` / `missing` / `removed` 任一非空,或 `added` 中出现 SUMMARY 之外的路径(打印对应列表,退出码 1)。 + node -e "const{execSync}=require('child_process'),fs=require('fs');const p=(process.env.TEMP||'.')+'/gsd-09-53-hashes.txt';const base=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n').trim().split('\n').filter(Boolean);const now=base.map(l=>{const f=l.split(' ')[0];return f+' '+execSync('git hash-object '+f,{encoding:'utf8'}).trim();});const changed=now.filter((l,k)=>l!==base[k]);const types=now.filter(l=>l.split(' ')[0]==='packages/render-anim/src/types.ts');const ok=base.length===10&&changed.length===1&&types.length===1&&changed[0]===types[0];console.log(now.join('\n'));if(!ok){console.error('CHANGED:\n'+changed.join('\n'));process.exit(1);}console.log('OK: only types.ts changed');" + 除 `types.ts` 外还有基线路径变化,或 `types.ts` 未变化,或基线行数不是 10(打印 `CHANGED:` 与差异行,退出码 1)。 + node -e "const fs=require('fs');const b=fs.readFileSync('.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md');const s=b.toString('utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;const tabs=(s.match(/\t/g)||[]).length;const bom=b[0]===0xEF;const ok=lf===crlf&&tabs===0&&!bom;console.log((ok?'OK':'FAIL')+' lf='+lf+' crlf='+crlf+' tabs='+tabs+' bom='+bom);process.exit(ok?0:1);" + SUMMARY 出现非 CRLF 换行、制表符或 BOM(打印 `FAIL`,退出码 1)。 + + + - 写前范围闸门已用 `` 第 1 步给出的显式 `node` 命令执行,`added` / `removed` 为空;写后范围闸门 `added` 恰为 `09-53-SUMMARY.md`,`extra` / `missing` / `removed` 为空。 + - 哈希闸门确认 10 条基线中只有 `types.ts` 变化;`index.ts`、`package.json`、09-50 / 09-51 / 09-52、`09-53-PLAN.md` 逐字不变。 + - `09-53-SUMMARY.md` 含范围(其中逐字写出 `files_modified` 及其两个路径)、修改前后 diff、逐成员最终形态、Task 1 基线、各门禁真实结果(含类型门禁哨兵与运行状态)、未做项、设计记录、待确认项与未校准估算。 + - 全程未执行 git 写命令,0 次提交;除 SUMMARY 外未写任何其它文件。 + + 写前范围闸门(显式 `node` 命令)`added` / `removed` 为空;哈希闸门确认只有 `types.ts` 变化;`09-53-SUMMARY.md` 已写入并包含范围(逐字写出 `files_modified`)、前后 diff、逐成员形态、Task 1 基线、各门禁真实结果(含类型门禁哨兵与运行状态)、未做项、设计记录、待确认项与未校准估算;写后范围闸门 `added` 只含 SUMMARY、`extra` / `missing` / `removed` 为空;0 次提交。 + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| 无 | 本计划只修改 TypeScript 类型声明,不引入运行时代码、网络 / 存储 / 用户输入处理,也不安装任何依赖 | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-53-01 | Tampering | `packages/render-anim/src/types.ts` 的修改范围 | low | mitigate | 用 Edit 精确块替换;逐接口成员门禁、旧类型移除门禁与 10 条哈希闸门共同证明只改了 `types.ts`、且只改了预期的类型块 | +| T-53-02 | Tampering | `packages/render-anim/src/index.ts` / `package.json` | low | mitigate | 两者不在 `files_modified` 内;Task 1 记录哈希,Task 3 / Task 4 复核哈希不变 | +| T-53-03 | Information Disclosure | `packages/render-anim/src/types.ts` | low | accept | 只声明公开类型,无密钥、无运行时数据;文件内容由用户审阅 | +| T-53-SC | Tampering | npm / pnpm 安装 | high | mitigate | 本计划禁止任何包管理安装命令;不新增 registry 依赖,锁文件不变 | + + + +- **格式**:`node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/src/types.ts packages/render-anim/src/index.ts packages/render-anim/package.json` 退出码 0,输出 `All matched files use Prettier code style!`。 +- **lint**:`node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts` 退出码 0、无输出。 +- **换行 / BOM / 缩进**:Task 2 / Task 3 node 门禁输出 `OK`(全 CRLF、无制表符、无 BOM)。 +- **内容 / 结构**:Task 3 第 4 条 node 门禁输出 `OK: split types match`(5 个 interface + 1 个 type alias + 1 个 enum;`IAnimationParsedData` 四成员顺序 / 类型 / `readonly` 正确;每个成员的单行 jsDoc 文本逐字匹配;旧接口与 `animater` 成员不存在;无实现 / 无 `any` / 无 `import type`;枚举成员 `Normal, Add`)。 +- **注释 / 前缀不变**:Task 1 采集 `gsd-09-53-prefix.txt`(第 1–61 行 sha256 + 枚举注释);Task 2 与 Task 3 第 7 条门禁重算同一前缀并要求哈希一致,同时要求四个成员 jsDoc 与 `/** 普通混合 */` / `/** 加算混合 */` 精确存在——任何注释改写(含改成英文)都会失败。 +- **类型**:node 先跑 `vue-tsc --version` 哨兵、并确认 `--noEmit` 进程正常结束(`status` 非 `null`),再过滤 `packages/render-anim` 诊断,结果为 0 行;全仓既有错误与退出码不参与判定。 +- **范围**:Task 4 写前 `added` / `removed` 为空;写后 `added` 恰为 `09-53-SUMMARY.md`,`extra` / `missing` / `removed` 为空。 +- **哈希**:10 条基线中只有 `packages/render-anim/src/types.ts` 变化;`index.ts`、`package.json`、09-50 / 09-51 / 09-52 的计划与摘要、`09-53-PLAN.md` 哈希不变。 +- **明确不做**:不运行测试、构建、dev server、浏览器;不运行 `pnpm install`;不执行任何 git 写命令,0 次提交。 + + + +- [ ] Task 0 已完整汇报精确前后 diff、拆分原因、dev.md 合规点、命名、影响范围、验证形态、不提交与编号,并等待用户针对本修订版回复「可以执行」后才继续。 +- [ ] `packages/render-anim/src/types.ts` 已删除 `IAnimationAnimaterData` 与 `IAnimationData`,新增 `IAnimationParsedData`(`texture` / `bitmaps` / `frames` / `blend`,顺序与类型逐字一致)与 `export type IAnimationAnimater = ITextureAnimater;`。 +- [ ] 解析数据中不存在 `animater` 成员;`ITextureAnimater` 不再出现;`RenderAnimater` / `parseRenderAnimation` 没有任何实现体。 +- [ ] 第 1–61 行逐字不变(前缀 sha256 与 Task 1 基线一致);`AnimationBlendMode`(`Normal` / `Add`)、`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame` 保持 09-52 的形状与注释。 +- [ ] `IAnimationParsedData` 每个成员前有且只有一行中文 jsDoc,且逐字为 `/** 合成帧使用的纹理 */` / `/** 位图列表 */` / `/** 帧列表 */` / `/** 混合模式 */`;枚举注释 `/** 普通混合 */` / `/** 加算混合 */` 精确存在;类型别名本身无 jsDoc。 +- [ ] 文件为 UTF-8 无 BOM + CRLF + 4 空格 + 无制表符;首行是普通 `import`;无 `import type`、无 `any`、无 `class` / `function` / `let` / 箭头函数 / `export default`;`const` / `enum` 仅在 `AnimationBlendMode` 声明处。 +- [ ] prettier `--check`、eslint、CRLF / BOM / 逐接口成员(含逐成员 jsDoc 精确文本)/ 前缀哈希 / 类型别名 / 旧类型移除 / 无实现门禁全部通过;`vue-tsc` 经 `--version` 哨兵确认可启动且 `--noEmit` 进程正常结束后,过滤 `packages/render-anim` 诊断为 0 行(或 SUMMARY 明确写「类型检查未运行」)。 +- [ ] `packages/render-anim/src/index.ts` 与 `package.json` 未被修改,哈希与 Task 1 基线一致;09-50 / 09-51 / 09-52 未被修改;`09-53-PLAN.md` 未被改写。 +- [ ] `09-53-SUMMARY.md` 已写入,包含门禁真实结果、类型门禁运行状态、未做项、设计记录、待确认项与未校准估算。 +- [ ] 范围闸门与哈希闸门通过;0 次提交;未运行测试 / 构建 / 浏览器 / `pnpm install`。 + + + +Create `.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md` when done + + diff --git a/.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md b/.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md new file mode 100644 index 0000000..e6355d2 --- /dev/null +++ b/.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md @@ -0,0 +1,203 @@ +--- +phase: 09-minimum-browser-runnable +plan: 53 +subsystem: render-anim +tags: [typescript, types-only, render-anim, types-split, animation] +requires: + - "09-52 的 @motajs/render-anim 类型骨架(1 个 export const enum + 6 个接口)" +provides: + - "IAnimationParsedData(texture / bitmaps / frames / blend,不含 animater 成员)" + - "export type IAnimationAnimater = ITextureAnimater;" + - "删除 IAnimationAnimaterData 与 IAnimationData 两个旧接口" +affects: [09-minimum-browser-runnable, render-anim] +actuals: + # chars/4 上限值,只统计本计划 types.ts 的替换块(删除 444 字符 + 新增 367 字符 = 811 字符);SUMMARY 自身不计入 + tokens: 203 + tasks: 5 + commits: 0 +tech-stack: + added: [] + patterns: + - "解析数据与动画器分离:解析数据是纯数据对象,动画器是独立类型别名" + - "ITextureAnimater 的 data 参数只接收纯解析数据,不再把自身当作参数" +key-files: + created: [] + modified: + - packages/render-anim/src/types.ts +key-decisions: + - "IAnimationParsedData 取代 IAnimationAnimaterData + IAnimationData 的组合设计,成员顺序固定为 texture / bitmaps / frames / blend" + - "IAnimationAnimater 为类型别名 ITextureAnimater,不在解析数据中保留 animater 成员" + - "只落类型声明,RenderAnimater 与 parseRenderAnimation 留给后续实现计划" +patterns-established: + - "类型别名承载动画器契约:动画器类型由 ITextureAnimater<解析数据类型> 组合而成,不重复声明接口成员" +duration: 12min +completed: 2026-11-07 +status: complete +--- + +# Phase 9 Plan 53: `@motajs/render-anim` 类型拆分(纯解析数据 + 独立动画器) + +**删除 `IAnimationAnimaterData` / `IAnimationData`,落 `IAnimationParsedData`(`texture` / `bitmaps` / `frames` / `blend`)与 `export type IAnimationAnimater = ITextureAnimater;`,不含任何实现。** + +## 本计划范围 + +- 只修改 `packages/render-anim/src/types.ts` 的类型声明;**不实现** `RenderAnimater`、**不实现** `parseRenderAnimation`。 +- `files_modified` 恰为两项:`packages/render-anim/src/types.ts` 与 `.planning/phases/09-minimum-browser-runnable/09-53-SUMMARY.md`。 +- `packages/render-anim/src/index.ts` 与 `packages/render-anim/package.json` 只验证、不修改(哈希与 Task 1 基线一致)。 +- 不运行测试、构建、dev server、浏览器;不做运行时主张;不运行 `pnpm install`,锁文件不变。 +- **0 次提交**:全程未执行任何 git 写命令,改动留在工作树供审阅。 +- 编号说明:派发编号 09-53 / 波次 2 / `depends_on: 09-52`;`09-01`..`09-52` 均已占用。 + +## Task 0 执行前汇报关卡(已满足) + +| 项 | 内容 | +|----|------| +| 关卡类型 | `checkpoint:human-verify` | +| Gate | `blocking-human` | +| 汇报对象 | 修改前(第 62–76 行)与修改后的逐字 diff、拆分原因、四个成员与类型别名逐字形态、删除清单、dev.md 合规点、命名、影响范围、只读静态验证口径、0 次提交、编号与依赖 | +| 用户回复 | 恰好「可以执行」四个字(针对本修订版) | +| 结论 | 关卡通过,进入 Task 1–4 | + +## 修改前后 diff(逐字) + +### 修改前(原第 62–76 行,已删除) + +```ts +export interface IAnimationAnimaterData { + /** 位图列表 */ + readonly bitmaps: readonly IAnimationBitmap[]; + /** 帧列表 */ + readonly frames: readonly IAnimationFrame[]; +} + +export interface IAnimationData extends IAnimationAnimaterData { + /** 合成帧使用的纹理 */ + readonly texture: ITexture; + /** 动画器 */ + readonly animater: ITextureAnimater; + /** 混合模式 */ + readonly blend: AnimationBlendMode; +} +``` + +### 修改后(现第 62–73 行,新增) + +```ts +export interface IAnimationParsedData { + /** 合成帧使用的纹理 */ + readonly texture: ITexture; + /** 位图列表 */ + readonly bitmaps: readonly IAnimationBitmap[]; + /** 帧列表 */ + readonly frames: readonly IAnimationFrame[]; + /** 混合模式 */ + readonly blend: AnimationBlendMode; +} + +export type IAnimationAnimater = ITextureAnimater; +``` + +**第 1–61 行逐字不变**:首行普通引入 `import { ITexture, ITextureAnimater } from '@motajs/render';`、`AnimationBlendMode` 枚举、`IAnimationBitmap`、`IAnimationSound`、`IAnimationObject`、`IAnimationFrame` 全部未动;前缀 sha256 门禁逐字证明(见下)。 + +## 逐成员最终形态 + +| 导出类型 | 成员(顺序固定) | 成员 jsDoc(沿用既有文本) | +|----------|------------------|----------------------------| +| `IAnimationParsedData`(新增、`export interface`) | `readonly texture: ITexture` | 合成帧使用的纹理 | +| | `readonly bitmaps: readonly IAnimationBitmap[]` | 位图列表 | +| | `readonly frames: readonly IAnimationFrame[]` | 帧列表 | +| | `readonly blend: AnimationBlendMode` | 混合模式 | +| `IAnimationAnimater`(新增、`export type`) | — | 类型别名本身不写 jsDoc | +| `AnimationBlendMode`(不变) | `Normal` / `Add` | 普通混合 / 加算混合 | +| `IAnimationBitmap`(不变) | `x` / `y` / `width` / `height` | 保持 09-52 的既有注释 | +| `IAnimationSound`(不变) | `name` / `volume?` / `pitch?` | 保持 09-52 的既有注释 | +| `IAnimationObject`(不变) | `index` / `x` / `y` / `scale` / `opacity` / `mirror` / `rotate` | 保持 09-52 的既有注释 | +| `IAnimationFrame`(不变) | `width` / `height` / `originX` / `originY` / `objects` / `sound?` | 保持 09-52 的既有注释 | + +**删除清单:** `IAnimationAnimaterData`(含 `bitmaps` / `frames` 两个成员与两条注释)、`IAnimationData`(含 `texture` / `animater` / `blend` 三个成员与三条注释、`extends IAnimationAnimaterData` 关系)以及 `动画器` 注释。删除后文件导出恰为:1 个 `export const enum` + 5 个 `export interface` + 1 个 `export type`。 + +## Task 1 基线(只读采集) + +| 路径 | `git hash-object` 基线 | +|------|------------------------| +| `packages/render-anim/src/types.ts` | `53d4b962bb525730d3de85737fbf60764cb30bd4` | +| `packages/render-anim/src/index.ts` | `fcb073fefcd6bebc8afd726f830e66828ebc5e90` | +| `packages/render-anim/package.json` | `4934efaea21143110e313816a317130b296982d0` | +| `09-50-PLAN.md` | `1575237b92a411a9f6819ed4108f22672ef934d4` | +| `09-50-SUMMARY.md` | `9283ff0511cba1a4f11ccd6f13f48777d791e9f7` | +| `09-51-PLAN.md` | `a83f21cbdb8f275cefab756ea041f2a0d93bb857` | +| `09-51-SUMMARY.md` | `257483e79741f5597ca78f3fecf07fb4eba31a8d` | +| `09-52-PLAN.md` | `5fdcef8add173e6432388d5995dfd8ea81394929` | +| `09-52-SUMMARY.md` | `8416ecd0740b3f5fe3a21ec38427d78cdb6dc93c` | +| `09-53-PLAN.md` | `d11b37920f31be4d391195a7e1f875786f44a7dd` | + +- `packages/render-anim` 的 git 状态恰为 3 条未跟踪文件:`?? package.json` / `?? src/index.ts` / `?? src/types.ts`。 +- 前缀基线:`types.ts` 第 1–61 行 sha256 = `d4d97496b09ffa0cb945ebfb23b43f97e18d825b3ec0a2b1a6e0d26106f7884e`(按 CRLF 精确拼接、含第 61 行行尾 CRLF)。 +- 根 `tsconfig.json`:`include` 覆盖 `packages/**/*.ts`,`paths['@motajs/*'] = ['./packages/*/src']`,`strict: true`、`isolatedModules: true`、`noEmit: true`。 +- `.prettierrc`:`printWidth: 80` / `tabWidth: 4` / `singleQuote: true` / `arrowParens: avoid` / `trailingComma: none` / `endOfLine: crlf`;`eslint.config.js` 为 `typescript-eslint` + `eslint-plugin-prettier/recommended`。 +- `@motajs/render` 侧:`ITextureAnimater` 声明 `once(texture: ITexture, data: T): Generator` 与 `cycled(texture: ITexture, data: T): Generator`;`ITexture` 为接口,两者均可从包根导出取得。 + +## 门禁真实结果 + +| 门禁 | 命令要点 | 真实输出 | 结果 | +|------|----------|----------|------| +| 格式 | `node node_modules/prettier/bin/prettier.cjs --check packages/render-anim/src/types.ts packages/render-anim/src/index.ts packages/render-anim/package.json` | `Checking formatting...` / `All matched files use Prettier code style!`,退出码 0 | 通过 | +| lint | `node node_modules/eslint/bin/eslint.js packages/render-anim/src/types.ts packages/render-anim/src/index.ts` | 无任何输出,退出码 0 | 通过 | +| 换行 / BOM / 缩进 | Task 2 / Task 3 node 门禁 | `OK lf=73 crlf=73 tabs=0 bom=false` | 通过 | +| 内容 / 结构(逐接口成员 + 逐成员 jsDoc) | Task 2 块替换门禁 | `OK: split applied` | 通过 | +| 内容 / 结构(5 接口 + 1 type alias + 1 enum) | Task 3 第 4 条 node 门禁 | `OK: split types match`,退出码 0 | 通过 | +| index / package 未改动 | Task 3 第 5 条 node 门禁 | `changed:` 仅 `packages/render-anim/src/types.ts f71b6a97767685cbb3cc654d5c655f8315cf1922` / `OK: only types.ts changed; index/package unchanged` | 通过 | +| 第 1–61 行前缀哈希 + 注释不变 | Task 3 第 7 条 node 门禁 | `OK: prefix baseline d4d97496b09ffa0cb945ebfb23b43f97e18d825b3ec0a2b1a6e0d26106f7884e + enum jsDoc present`;`OK: lines 1-61 unchanged, exact jsDoc present` | 通过 | +| 类型门禁 | node 先跑 `vue-tsc --version` 哨兵,再捕获 `--noEmit` 并过滤 `packages/render-anim` 诊断 | **已运行**:`sentinel: vue-tsc Version 6.0.3 | type check exit 2` / `OK: no render-anim diagnostics`(0 行诊断);全仓既有类型错误与 `vue-tsc` 非 0 退出码属既有基线,不参与判定 | 通过 | +| 范围闸门(SUMMARY 写入前) | Task 4 第 1 步 node 门禁 | `added:` 空、`removed:` 空、`OK: pre-write range clean` | 通过 | +| 哈希闸门 | Task 4 第 2 步 node 门禁(10 条基线) | 仅 `packages/render-anim/src/types.ts` 变化为 `f71b6a97767685cbb3cc654d5c655f8315cf1922`,其余 9 条逐字不变;`OK: only types.ts changed` | 通过 | +| 范围闸门(SUMMARY 写入后) | Task 4 `` 第 2 条 node 门禁 | 见下「写后范围闸门」 | 见下 | + +人工逐成员复核(绑定判定):`types.ts` 现共 73 行 / 1977 字节;新接口成员顺序为 `texture` → `bitmaps` → `frames` → `blend`,四个成员均为 `readonly`、数组成员为只读数组,每个成员有且只有一行逐字为 `/** 合成帧使用的纹理 */` / `/** 位图列表 */` / `/** 帧列表 */` / `/** 混合模式 */` 的 jsDoc;`IAnimationAnimater` 别名逐字为 `ITextureAnimater`;两个旧接口的全部成员 / 注释 / `extends` 已删除;无实现体、无 `export default`、无新增其它注释。 + +## 未做 / 跳过项 + +- 未运行任何测试、构建、dev server 或浏览器;不做运行时可用性主张。 +- 未运行 `pnpm install`;锁文件未变(依赖仅为同仓库工作区包)。 +- 未执行任何 git 写命令(`add` / `commit` / `stash` / `checkout` / `restore` / `reset`),**0 次提交**。 +- 未创建 `09-53-RESEARCH.md` / `09-53-VALIDATION.md`;未改动 `STATE.md` / `ROADMAP.md` / `09-CONTEXT.md` / 09-50 / 09-51 / 09-52。 +- 本 run 无 RESEARCH / 无 VALIDATION:`requirements: []`,Nyquist 不适用;spec-less probe fallback skipped(phase has no requirement IDs to probe)。 + +## 偏差与问题 + +1. **[Rule 3 - Blocking] Write 工具把换行归一化为 LF**:写入 SUMMARY 后测得 `crlf=0`,会使计划要求的 CRLF 门禁失败。处理方式:写入后运行一次 Node 脚本,把 `09-53-SUMMARY.md` 按 `/\r?\n/` → `\r\n` 统一为 CRLF,内容其余部分不变;随后 CRLF / BOM / 制表符门禁通过。未新增或删除任何字符。 +2. Task 0 的批准记录落在本 SUMMARY,未创建独立 approval 文件——计划正文要求 Task 0 不写文件,本次执行也仅允许写 `files_modified` 的两个路径。 + +除上述两点外,计划按原文执行。 + +## 设计记录(未实现) + +- `RenderAnimater` 将 `implements IAnimationAnimater`(即 `ITextureAnimater`);`parseRenderAnimation(buffer: ArrayBuffer): Promise`。 +- 解析数据不含动画器:`once(texture, data)` / `cycled(texture, data)` 的 `data` 参数只接收纯解析数据对象,不再出现 `data.animater.once(texture, data)` 这种把同一对象既当自身数据又当参数传入的用法。 +- `bitmaps` / `frames` 为只读数组,实现阶段不得改成可变数组;解析器对外返回只读视图。 +- `blend` 以 `AnimationBlendMode` 存于文件并由解析数据暴露,动画器不决定混合;`sound` 由宿主播放。 + +## 待确认项 + +- **OI-1(已获用户批准)**:公开名称固定为 `IAnimationParsedData` 与 `IAnimationAnimater`;解析数据成员固定为 `texture` / `bitmaps` / `frames` / `blend`。本计划未新增任何其它公开名称。 +- **OI-2(只记录)**:四个成员注释文本从 09-52 的既有注释原样沿用;`动画器` 注释已随 `IAnimationData.animater` 成员一并移除。 +- **OI-3(只记录,不实现)**:后续 `RenderAnimater` 实现 `IAnimationAnimater`,`parseRenderAnimation(buffer: ArrayBuffer): Promise`;本计划未写任何实现体。 +- **OI-4(只记录)**:`estimate` 未校准(`confidence: low`);本计划未运行构建或测试,因此不做运行时可用性主张。 +- **OI-5(只记录)**:09-52 的产物与本计划的改动仍未提交,留在工作树供审阅。 + +## 估算 + +- **估算:** `confidence: low`,未校准(计划 `estimate.tokens: 30000`)。 +- **实际(actuals):** `types.ts` 替换块共 811 字符(删除 444 + 新增 367),约 203 tokens(chars/4);文件 1977 字节 / 73 行;任务 5 个;提交 0 次。 + +## Self-Check: PASSED + +- `packages/render-anim/src/types.ts` 存在且为拆分后形态(73 行、1977 字节、全 CRLF、无 BOM、无制表符)。 +- `packages/render-anim/src/index.ts` 与 `package.json` 哈希与 Task 1 基线逐字一致,未被修改。 +- 09-50 / 09-51 / 09-52 的计划与摘要、`09-53-PLAN.md` 哈希逐字不变。 +- `files_modified` 之外零改动(范围闸门 `extra` / `missing` / `removed` 均为空);0 次提交。 + +--- + +*Phase: 09-minimum-browser-runnable* +*Completed: 2026-11-07*