支持 Phaser 4 与 three.js 运行画面素材点选 #612

Closed
opened 2026-10-04 13:48:08 +08:00 by suzmii · 4 comments
Member

背景与问题

工作台运行画面已有「点选素材」入口:点中预览页元素后往对话插入一条 runtime-region 引用(对话里是 @ 芯片)。但改动前它是纯 DOM 命中,AGC 的默认玩法链路上画面内容全部画在 <canvas> 里,点选对它们等于空操作。

改动前链路(按代码):

  1. 注入桥 apps/ai-game-creator-shell/src-tauri/src/preview.rs(PREVIEW_FIT_BRIDGE_SCRIPT,经 /__genarrative/local-preview-fit.js 注入到 </body> 之前):handleInspectMove / handleInspectClick 只取 event.target;inspectElementSummary(element) 只读三件事——元素矩形、data-resource-id / data-asset-id / data-genarrative-resource-id、元素内 img|video|audio 的 src。
  2. 宿主侧 src/view/project-development/index.tsx 的 handleRuntimeInspectSelection:用 selection.sourcePath 的文件名到 manifest.assets[].localPath 里匹配素材 id,再与 selection.resourceIds 合并后派发 runtime-region 引用。
  3. 因此点 Phaser 4 / three.js 画面时:target 是整块 <canvas>,没有 data-*,也没有 src → sourcePath 与 resourceIds 全空。结果是一条不带素材的区域引用(芯片文本退化成 @canvas),Rust 侧不会报错(agc_runtime_region_reference 只在 resourceIds 指向不存在的素材时才拒绝),所以是静默失效。
  4. 无法只靠 DOM 侧补救:Phaser 4.2.1 不把 Game 挂到 window,canvas 上也没有反向引用;three.js 同理。引擎对象、纹理、相机都不在 DOM 可达范围内,运行画面点选必须新增引擎级握手。

影响范围:新项目默认引擎是 Phaser 4.2.1(src-tauri/src/main.rs 的 DEFAULT_GAME_SCRIPT_JS、template-library/v1/phaser-2d-starter),3D 起步模板是 three.js(template-library/v1/threejs-3d-starter)——即两条主力链路上「点选素材」都拿不到素材。

本需求分两个阶段:阶段一给运行画面装引擎级点选(已实现,PR 待评审);阶段二把线索的产出写进生成期契约(玩法 skill / Runtime Prompt)并重新发布模板产物,让新项目开箱即用。

目标与验收判据

阶段一:运行画面引擎档点选

  1. Phaser 4 项目:点中由已登记素材渲染的精灵 / 图集帧 / Tilemap tile,对话里插入的引用芯片带该素材显示名,且引用载荷的 resourceIds 含对应 manifest 素材 id。
  2. three.js 项目:点中贴了已登记贴图的 Mesh / Sprite(含 GLB 内嵌纹理的模型),同上。
  3. 无引擎句柄或未命中素材时:不报错、不伪造 id,退化为现状语义的区域引用(label 取引擎/画面侧可得的名字,退无可退时保留 canvas)。
  4. DOM 点选(HTML HUD、<img>、data-resource-id)行为逐字不变,现有 resourceReferenceInput.test.tsx 的 inspect 解析用例不回归。
  5. 真机 smoke:Phaser 4 与 three.js 各一条——模板项目 + 一张生成素材 → 画面渲染 → 点选 → 引用芯片带素材名,附截图/录制。

阶段二:生成期契约落位

  1. 新生成的 Phaser 4 项目与 three.js 项目,不改任何代码即可点出已登记素材:芯片显示该素材名,载荷 resourceIds 命中该素材 id。
  2. 未标注对象、点空白仍退化为不带素材的区域引用:零报错、零伪造 id、引用可插入可发送。
  3. 模板重新发布后补跑一次真机 smoke(Phaser + three 各一条,含芯片与载荷证据),证据进里程碑。

阶段一:运行画面引擎档点选(已实现,PR 待评审)

落地摘要(原「方案」「落地改动清单」压缩):

  • 桥脚本搬出 Rust 字符串:PREVIEW_FIT_BRIDGE_SCRIPT 落到 src-tauri/resources/preview/local-preview-fit.js,preview.rs 改 include_str!;注入、去重扫描、/__genarrative/local-preview-fit.js 路由与响应逐字不变(行为零变化)。
  • 引擎握手契约:只认玩法显式发布的只读句柄 window.__GENARRATIVE_PREVIEW_GAME__({ engine: 'phaser', game } / { engine: 'three', THREE, scene, camera, renderer }),不通过扫描全局对象猜引擎;桥在 move / click 时惰性读取。
  • Phaser 4 适配:game.input.activePointer + scene.input.hitTestPointer,按 pointer.camera.renderList 索引降序取真实最上层(hitTestPointer 的返回顺序是输入注册顺序,不是叠放顺序);setInteractive() 未覆盖的对象用 scene.children.list(含 depth + 入序)几何兜底;只取当前激活场景;矩形按相机换算屏幕矩形。
  • three.js 适配:renderer.domElement → NDC → Raycaster.setFromCamera + intersectObjects(scene.children, true) 取最近命中;矩形用 Box3 八角投影取 min/max。
  • 身份来源优先级:玩法显式标注(genarrativeResourceId / genarrativeResourcePath)优先于从贴图地址推断出的文件名;两者都取不到时按退化条款处理。
  • 宿主四档匹配与芯片名归一化:新增纯函数 runtimeInspectResourceMatch.ts(显式 resourceIds → sourcePath 与 localPath 的精确后缀 → 文件名 → 序列帧 imageSequenceFrames[].imageSrc),结果去重且只保留 manifest 现有 id;唯一命中且标签由文件名派生时归一化为资源显示名,与 @ 候选 / 资源卡「引用」/ 拖拽引用逐字一致。
  • 脚手架与起步模板发布句柄:DEFAULT_GAME_SCRIPT_JS 与三个起步模板(phaser-2d-starter、threejs-3d-starter、blank-3d-scene)创建引擎实例后发布只读句柄。
  • Phaser 装载方式:脚手架与 phaser-2d-starter 改用 loader: { imageLoadType: 'HTMLImageElement' },让贴图元素保留原始素材地址(默认 XHR 装载只剩会话内 blob 地址,自动档读不回素材路径)。
  • overlay 与协议:高亮命中对象的屏幕矩形(取不到时退回整块 canvas),move 与 click 共用同一解析函数;runtime-region 引用契约、genarrative.local-preview-inspect.v1 消息版本号、字段净化与数量上限、Rust resourceIds 校验口径均不变。

证据(阶段一):

  • npm run agc:typecheck EXIT=0(check:tests:types / tsc / skill-pack 指纹 / 包布局声明 / check-config 全过)。
  • 前端 4 个用例文件 95 passed(runtimeInspectEngines / runtimeInspectResourceMatch / localGamePreviewFrame / resourceReferenceInput);tests/appSurface.test.ts 197 passed / 9 skipped。
  • cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- preview 44 passed;同路径 -- scaffold 5 passed / 1 既有 #[ignore](需 npm 装依赖 + Provider,非失败)。
  • 真实 Chromium 端到端(真实 Phaser 4.2.1 / three 0.184 + 真实桥脚本 local-preview-fit.js,被测 sha256 6276ac48…917a):已登记贴图对象 → resourceIds=["hero"]、芯片 @hero;显式标注且与上一个重叠的对象 → ["enemy-tag"]、芯片 @enemy(显式优先 + 取最上层);非交互精灵 → 几何兜底命中;点空白 → @canvas + []、零报错;DOM 档芯片名与 id 逐字不变。高亮矩形与期望差:Phaser 0px、three 最大 ≈0.0006px。
  • Tauri 真机链路未跑:预览脚本注入、去重扫描与 /__genarrative/local-preview-fit.js 路由由 cargo test … -- preview 覆盖;浏览器证据用的是同一份桥脚本文件 + 真实引擎,不含 Rust 预览服务器那一环。
  • 模板 zip 与线上索引本轮未重新发布(只改本地模板源),发布属阶段二。

阶段二:生成期契约落位

为什么需要

阶段一让引擎档识别靠玩法侧给线索;缺线索就只能退化为「不带素材的区域引用」。三种拿不到素材身份的情况:

  1. Phaser 默认 XHR 装载:贴图元素上的地址是 blob:,读不回原始素材路径。历史项目与未改装载方式的生成物都属于这类。
  2. GLB 内嵌纹理的模型:three.js 侧取不到贴图 URL,只能靠显式标注。
  3. 对象既没标注、也取不到贴图地址(程序化绘制、图形拼接)。

也就是说:桥和宿主已经准备好了,缺的是「生成期让玩法把线索写出来」。

要改什么

  1. AGC 玩法 skill 与 Runtime Prompt 强制三条
    • 游戏启动后发布只读引擎句柄 window.__GENARRATIVE_PREVIEW_GAME__:Phaser → { engine: 'phaser', game };three.js → { engine: 'three', THREE, scene, camera, renderer }。
    • 用已登记素材渲染的对象必须显式标注素材身份:Phaser setData('genarrativeResourceId' | 'genarrativeResourcePath');three.js object.userData.genarrativeResourceId | genarrativeResourcePath。
    • 素材以稳定路径进 dist:贴图 URL 与显式路径都要能对回 manifest 的 localPath(推荐直接用 assets/... 这类与 manifest 同源的相对路径);不要靠 import 产物名——Vite 的 hash 文件名会让自动档失配。
    • agc-client-projection 的投影契约补一句「素材身份 = manifest 素材 id / localPath,不用文件名猜」。
  2. Phaser 装载方式写进脚手架约定:loader: { imageLoadType: 'HTMLImageElement' }。阶段一已在 DEFAULT_GAME_SCRIPT_JS 与 phaser-2d-starter 落地,还需写进 skill,让后续生成物保持一致(否则贴图退化成 blob,自动档失效)。
  3. 发布动作:用 scripts/agc-template-library-publish.mjs 重新产出 template.zip 与线上索引(本轮只改本地模板源,没有发布会话);需要同步线上索引版本号与灰度策略。

验收判据

  • 新生成的 Phaser 4 项目与 three.js 项目,不改任何代码即可点出已登记素材:芯片显示该素材名,载荷 resourceIds 命中该素材 id。
  • 未标注对象、点空白仍退化为不带素材的区域引用:零报错、零伪造 id、引用可插入可发送。
  • 模板发布后补跑一次真机 smoke(Phaser + three 各一条,含芯片与载荷证据),证据进里程碑。

非目标

  • 不做运行期资源使用关系持久化,不重建 project/asset_usage.rs 那套 slot 绑定/代码注入/侧车方案(2026-09-13 已整体撤回)。
  • 不改 runtime-region 引用契约与 Rust 的 resourceIds 校验口径,不改 manifest、版本绑定、替换链路。
  • 不做像素级遮罩/颜色拾取(不截图逐像素比对推断素材)。
  • 不覆盖 Cocos / Godot / Unity(各自走编辑器桥接链路,另立需求)。
  • DOM 点选语义、enabled/disabled/cancelled 协议、parseLocalGamePreviewInspectMessage 的现有字段语义均不变。
  • 历史项目不做迁移、不做兼容分支:能力说明里如实标注「改动前生成的项目不自动具备」,需重新生成或人工补句柄与标注。

风险与未决

  1. Phaser 4 是重写版本:已核 4.2.1 的 hitTestPointer / Texture#getSourceImage / TextureSource#image 存在;hitTestPointer 的返回顺序不是叠放顺序(实测),实现按 renderList 索引取最上层。相机世界↔屏幕换算与图集帧 frame.name 的取值仍需按 4.2.1 实测校准(不照抄 Phaser 3 经验),不稳定的分支降级为「命中对象但只给点标记矩形」。
  2. 契约依赖模型执行:句柄发布与对象标注写在 skill + 脚手架里,但生成代码由模型自由编写,不能 100% 保证;因此保留自动档 +「不命中就退化为无素材区域引用」,并如实写明限制。
  3. Vite hash 产物名会让自动档失配——这是把「稳定路径进 dist」写进 skill 的原因;若实测中 agent 仍普遍用 import 引用素材,则显式档成为唯一可靠档,需要评估是否加交付校验(会与存量项目冲突,倾向只告警不阻断)。
  4. GLB 内嵌纹理拿不到 URL(ImageBitmap),必须走显式档;若模型漏标,只能「不命中」。
  5. three.js 场景递归 intersectObjects 在复杂场景下每帧成本:仅在 inspect 模式开着时按需执行(move 节流),不做常驻监听。
  6. 阶段二依赖阶段一合入(否则句柄与桥的识别口径不成立);模板重新发布会改变新项目产物,需要线上索引版本号与灰度策略配合。

关联

  • 阶段一实现:本 issue 的 PR(待评审)——src-tauri/src/preview.rs 与 src-tauri/resources/preview/local-preview-fit.js(注入桥与引擎档)、src/features/project-workspace/runtimeInspectResourceMatch.ts、src/view/project-development/index.tsx、src-tauri/src/main.rs 与 template-library/v1/{phaser-2d-starter,threejs-3d-starter,blank-3d-scene}、tests/runtimeInspectEngines.test.ts、tests/runtimeInspectResourceMatch.test.ts。
  • 协议与净化:src/features/project-workspace/LocalGamePreviewFrame.tsx(genarrative.local-preview-inspect.v1 字段净化与数量上限)。
  • 文档口径:docs/【功能说明】AGC聊天素材引用-2026-09-08.md「运行画面素材点选(2026-10-04)」已同步行为合同、退化条款与边界(贴图地址不可得、只认显式句柄);PRD §3.4 已同步。
  • 撤回记录:docs/project-memory/shared-memory/decision-log.md 2026-09-13 条(运行期资源观察方案整体撤回,边界明确"不做运行画面点选"——本需求是点选→对话引用,不是持久化使用关系,与该边界不冲突)。
  • 全部阶段在本 issue 与同一 PR 内完成。
## 背景与问题 工作台运行画面已有「点选素材」入口:点中预览页元素后往对话插入一条 `runtime-region` 引用(对话里是 `@` 芯片)。但改动前它是**纯 DOM 命中**,AGC 的默认玩法链路上画面内容全部画在 `<canvas>` 里,点选对它们等于空操作。 改动前链路(按代码): 1. 注入桥 `apps/ai-game-creator-shell/src-tauri/src/preview.rs`(`PREVIEW_FIT_BRIDGE_SCRIPT`,经 `/__genarrative/local-preview-fit.js` 注入到 `</body>` 之前):`handleInspectMove` / `handleInspectClick` 只取 `event.target`;`inspectElementSummary(element)` 只读三件事——元素矩形、`data-resource-id` / `data-asset-id` / `data-genarrative-resource-id`、元素内 `img|video|audio` 的 `src`。 2. 宿主侧 `src/view/project-development/index.tsx` 的 `handleRuntimeInspectSelection`:用 `selection.sourcePath` 的**文件名**到 `manifest.assets[].localPath` 里匹配素材 id,再与 `selection.resourceIds` 合并后派发 `runtime-region` 引用。 3. 因此点 Phaser 4 / three.js 画面时:`target` 是整块 `<canvas>`,没有 `data-*`,也没有 `src` → `sourcePath` 与 `resourceIds` 全空。结果是一条不带素材的区域引用(芯片文本退化成 `@canvas`),Rust 侧不会报错(`agc_runtime_region_reference` 只在 `resourceIds` 指向不存在的素材时才拒绝),所以是**静默失效**。 4. 无法只靠 DOM 侧补救:Phaser 4.2.1 不把 `Game` 挂到 `window`,canvas 上也没有反向引用;three.js 同理。引擎对象、纹理、相机都不在 DOM 可达范围内,运行画面点选必须新增引擎级握手。 影响范围:新项目默认引擎是 Phaser 4.2.1(`src-tauri/src/main.rs` 的 `DEFAULT_GAME_SCRIPT_JS`、`template-library/v1/phaser-2d-starter`),3D 起步模板是 three.js(`template-library/v1/threejs-3d-starter`)——即两条主力链路上「点选素材」都拿不到素材。 本需求分两个阶段:**阶段一**给运行画面装引擎级点选(已实现,PR 待评审);**阶段二**把线索的产出写进生成期契约(玩法 skill / Runtime Prompt)并重新发布模板产物,让新项目开箱即用。 ## 目标与验收判据 ### 阶段一:运行画面引擎档点选 1. Phaser 4 项目:点中由**已登记素材**渲染的精灵 / 图集帧 / Tilemap tile,对话里插入的引用芯片带该素材显示名,且引用载荷的 `resourceIds` 含对应 manifest 素材 id。 2. three.js 项目:点中贴了已登记贴图的 Mesh / Sprite(含 GLB 内嵌纹理的模型),同上。 3. 无引擎句柄或未命中素材时:不报错、不伪造 id,退化为现状语义的区域引用(`label` 取引擎/画面侧可得的名字,退无可退时保留 `canvas`)。 4. DOM 点选(HTML HUD、`<img>`、`data-resource-id`)行为逐字不变,现有 `resourceReferenceInput.test.tsx` 的 inspect 解析用例不回归。 5. 真机 smoke:Phaser 4 与 three.js 各一条——模板项目 + 一张生成素材 → 画面渲染 → 点选 → 引用芯片带素材名,附截图/录制。 ### 阶段二:生成期契约落位 1. 新生成的 Phaser 4 项目与 three.js 项目,**不改任何代码**即可点出已登记素材:芯片显示该素材名,载荷 `resourceIds` 命中该素材 id。 2. 未标注对象、点空白仍退化为不带素材的区域引用:零报错、零伪造 id、引用可插入可发送。 3. 模板重新发布后补跑一次真机 smoke(Phaser + three 各一条,含芯片与载荷证据),证据进里程碑。 ## 阶段一:运行画面引擎档点选(已实现,PR 待评审) 落地摘要(原「方案」「落地改动清单」压缩): - **桥脚本搬出 Rust 字符串**:`PREVIEW_FIT_BRIDGE_SCRIPT` 落到 `src-tauri/resources/preview/local-preview-fit.js`,`preview.rs` 改 `include_str!`;注入、去重扫描、`/__genarrative/local-preview-fit.js` 路由与响应逐字不变(行为零变化)。 - **引擎握手契约**:只认玩法显式发布的只读句柄 `window.__GENARRATIVE_PREVIEW_GAME__`(`{ engine: 'phaser', game }` / `{ engine: 'three', THREE, scene, camera, renderer }`),不通过扫描全局对象猜引擎;桥在 move / click 时惰性读取。 - **Phaser 4 适配**:`game.input.activePointer` + `scene.input.hitTestPointer`,按 `pointer.camera.renderList` 索引降序取真实最上层(`hitTestPointer` 的返回顺序是输入注册顺序,不是叠放顺序);`setInteractive()` 未覆盖的对象用 `scene.children.list`(含 `depth` + 入序)几何兜底;只取当前激活场景;矩形按相机换算屏幕矩形。 - **three.js 适配**:`renderer.domElement` → NDC → `Raycaster.setFromCamera` + `intersectObjects(scene.children, true)` 取最近命中;矩形用 `Box3` 八角投影取 min/max。 - **身份来源优先级**:玩法显式标注(`genarrativeResourceId` / `genarrativeResourcePath`)优先于从贴图地址推断出的文件名;两者都取不到时按退化条款处理。 - **宿主四档匹配与芯片名归一化**:新增纯函数 `runtimeInspectResourceMatch.ts`(显式 `resourceIds` → `sourcePath` 与 `localPath` 的精确后缀 → 文件名 → 序列帧 `imageSequenceFrames[].imageSrc`),结果去重且只保留 manifest 现有 id;唯一命中且标签由文件名派生时归一化为资源显示名,与 `@` 候选 / 资源卡「引用」/ 拖拽引用逐字一致。 - **脚手架与起步模板发布句柄**:`DEFAULT_GAME_SCRIPT_JS` 与三个起步模板(`phaser-2d-starter`、`threejs-3d-starter`、`blank-3d-scene`)创建引擎实例后发布只读句柄。 - **Phaser 装载方式**:脚手架与 `phaser-2d-starter` 改用 `loader: { imageLoadType: 'HTMLImageElement' }`,让贴图元素保留原始素材地址(默认 XHR 装载只剩会话内 blob 地址,自动档读不回素材路径)。 - **overlay 与协议**:高亮命中对象的屏幕矩形(取不到时退回整块 canvas),move 与 click 共用同一解析函数;`runtime-region` 引用契约、`genarrative.local-preview-inspect.v1` 消息版本号、字段净化与数量上限、Rust `resourceIds` 校验口径均不变。 证据(阶段一): - `npm run agc:typecheck` EXIT=0(`check:tests:types` / `tsc` / skill-pack 指纹 / 包布局声明 / `check-config` 全过)。 - 前端 4 个用例文件 **95 passed**(`runtimeInspectEngines` / `runtimeInspectResourceMatch` / `localGamePreviewFrame` / `resourceReferenceInput`);`tests/appSurface.test.ts` **197 passed / 9 skipped**。 - `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- preview` **44 passed**;同路径 `-- scaffold` **5 passed / 1 既有 `#[ignore]`**(需 npm 装依赖 + Provider,非失败)。 - **真实 Chromium 端到端**(真实 Phaser 4.2.1 / three 0.184 + 真实桥脚本 `local-preview-fit.js`,被测 sha256 `6276ac48…917a`):已登记贴图对象 → `resourceIds=["hero"]`、芯片 `@hero`;显式标注且与上一个重叠的对象 → `["enemy-tag"]`、芯片 `@enemy`(显式优先 + 取最上层);非交互精灵 → 几何兜底命中;点空白 → `@canvas` + `[]`、零报错;DOM 档芯片名与 id 逐字不变。高亮矩形与期望差:Phaser 0px、three 最大 ≈0.0006px。 - **Tauri 真机链路未跑**:预览脚本注入、去重扫描与 `/__genarrative/local-preview-fit.js` 路由由 `cargo test … -- preview` 覆盖;浏览器证据用的是同一份桥脚本文件 + 真实引擎,不含 Rust 预览服务器那一环。 - 模板 zip 与线上索引本轮**未重新发布**(只改本地模板源),发布属阶段二。 ## 阶段二:生成期契约落位 ### 为什么需要 阶段一让引擎档识别靠玩法侧给线索;缺线索就只能退化为「不带素材的区域引用」。三种拿不到素材身份的情况: 1. **Phaser 默认 XHR 装载**:贴图元素上的地址是 `blob:`,读不回原始素材路径。历史项目与未改装载方式的生成物都属于这类。 2. **GLB 内嵌纹理的模型**:three.js 侧取不到贴图 URL,只能靠显式标注。 3. **对象既没标注、也取不到贴图地址**(程序化绘制、图形拼接)。 也就是说:桥和宿主已经准备好了,缺的是「生成期让玩法把线索写出来」。 ### 要改什么 1. **AGC 玩法 skill 与 Runtime Prompt 强制三条** - 游戏启动后发布只读引擎句柄 `window.__GENARRATIVE_PREVIEW_GAME__`:Phaser → `{ engine: 'phaser', game }`;three.js → `{ engine: 'three', THREE, scene, camera, renderer }`。 - 用已登记素材渲染的对象必须显式标注素材身份:Phaser `setData('genarrativeResourceId' | 'genarrativeResourcePath')`;three.js `object.userData.genarrativeResourceId | genarrativeResourcePath`。 - 素材以稳定路径进 dist:贴图 URL 与显式路径都要能对回 manifest 的 `localPath`(推荐直接用 `assets/...` 这类与 manifest 同源的相对路径);不要靠 `import` 产物名——Vite 的 hash 文件名会让自动档失配。 - `agc-client-projection` 的投影契约补一句「素材身份 = manifest 素材 id / localPath,不用文件名猜」。 2. **Phaser 装载方式写进脚手架约定**:`loader: { imageLoadType: 'HTMLImageElement' }`。阶段一已在 `DEFAULT_GAME_SCRIPT_JS` 与 `phaser-2d-starter` 落地,还需写进 skill,让后续生成物保持一致(否则贴图退化成 blob,自动档失效)。 3. **发布动作**:用 `scripts/agc-template-library-publish.mjs` 重新产出 `template.zip` 与线上索引(本轮只改本地模板源,没有发布会话);需要同步线上索引版本号与灰度策略。 ### 验收判据 - 新生成的 Phaser 4 项目与 three.js 项目,不改任何代码即可点出已登记素材:芯片显示该素材名,载荷 `resourceIds` 命中该素材 id。 - 未标注对象、点空白仍退化为不带素材的区域引用:零报错、零伪造 id、引用可插入可发送。 - 模板发布后补跑一次真机 smoke(Phaser + three 各一条,含芯片与载荷证据),证据进里程碑。 ## 非目标 - 不做运行期资源使用关系持久化,不重建 `project/asset_usage.rs` 那套 slot 绑定/代码注入/侧车方案(2026-09-13 已整体撤回)。 - 不改 `runtime-region` 引用契约与 Rust 的 `resourceIds` 校验口径,不改 manifest、版本绑定、替换链路。 - 不做像素级遮罩/颜色拾取(不截图逐像素比对推断素材)。 - 不覆盖 Cocos / Godot / Unity(各自走编辑器桥接链路,另立需求)。 - DOM 点选语义、`enabled/disabled/cancelled` 协议、`parseLocalGamePreviewInspectMessage` 的现有字段语义均不变。 - 历史项目不做迁移、不做兼容分支:能力说明里如实标注「改动前生成的项目不自动具备」,需重新生成或人工补句柄与标注。 ## 风险与未决 1. Phaser 4 是重写版本:已核 4.2.1 的 `hitTestPointer` / `Texture#getSourceImage` / `TextureSource#image` 存在;`hitTestPointer` 的返回顺序不是叠放顺序(实测),实现按 `renderList` 索引取最上层。相机世界↔屏幕换算与图集帧 `frame.name` 的取值仍需按 4.2.1 实测校准(不照抄 Phaser 3 经验),不稳定的分支降级为「命中对象但只给点标记矩形」。 2. 契约依赖模型执行:句柄发布与对象标注写在 skill + 脚手架里,但生成代码由模型自由编写,不能 100% 保证;因此保留自动档 +「不命中就退化为无素材区域引用」,并如实写明限制。 3. Vite hash 产物名会让自动档失配——这是把「稳定路径进 dist」写进 skill 的原因;若实测中 agent 仍普遍用 `import` 引用素材,则显式档成为唯一可靠档,需要评估是否加交付校验(会与存量项目冲突,倾向只告警不阻断)。 4. GLB 内嵌纹理拿不到 URL(`ImageBitmap`),必须走显式档;若模型漏标,只能「不命中」。 5. three.js 场景递归 `intersectObjects` 在复杂场景下每帧成本:仅在 inspect 模式开着时按需执行(move 节流),不做常驻监听。 6. 阶段二依赖阶段一合入(否则句柄与桥的识别口径不成立);模板重新发布会改变新项目产物,需要线上索引版本号与灰度策略配合。 ## 关联 - 阶段一实现:本 issue 的 PR(待评审)——`src-tauri/src/preview.rs` 与 `src-tauri/resources/preview/local-preview-fit.js`(注入桥与引擎档)、`src/features/project-workspace/runtimeInspectResourceMatch.ts`、`src/view/project-development/index.tsx`、`src-tauri/src/main.rs` 与 `template-library/v1/{phaser-2d-starter,threejs-3d-starter,blank-3d-scene}`、`tests/runtimeInspectEngines.test.ts`、`tests/runtimeInspectResourceMatch.test.ts`。 - 协议与净化:`src/features/project-workspace/LocalGamePreviewFrame.tsx`(`genarrative.local-preview-inspect.v1` 字段净化与数量上限)。 - 文档口径:`docs/【功能说明】AGC聊天素材引用-2026-09-08.md`「运行画面素材点选(2026-10-04)」已同步行为合同、退化条款与边界(贴图地址不可得、只认显式句柄);PRD §3.4 已同步。 - 撤回记录:`docs/project-memory/shared-memory/decision-log.md` 2026-09-13 条(运行期资源观察方案整体撤回,边界明确"不做运行画面点选"——本需求是**点选→对话引用**,不是持久化使用关系,与该边界不冲突)。 - 全部阶段在本 issue 与同一 PR 内完成。
suzmii added the Kind/Feature
Priority
High
2
labels 2026-10-04 13:48:08 +08:00
suzmii self-assigned this 2026-10-04 13:48:08 +08:00
Author
Member

阶段一已推送,PR:#624

  • 结论:canvas 玩法(Phaser 4 / three.js)上点选已能落成带素材 id 的 runtime-region 引用;
    未标注对象 / 点空白退化为不带素材的区域引用,零报错、零伪造 id;DOM 点选语义与消息协议逐字不变。
  • 证据(合并 master 后的顶端 b2307a5c5 上复跑):npm run agc:typecheck EXIT=0;
    前端四文件 95 passed、appSurface.test.ts 197 passed / 9 skipped;
    cargo test … -- preview 44 passed、-- scaffold 5 passed / 1 既有 #[ignore];
    真实 Chromium 端到端(真实 Phaser 4.2.1 / three 0.184 + 真实桥脚本 sha256 6276ac48…917a)
    与高亮矩形差值(Phaser 0px / three ≈0.0006px)见 PR 正文与截图。
  • 未覆盖:Tauri 真机链路(由 Rust 定向测试覆盖注入与路由)、多场景叠加、非默认相机、高亮真机目视。
  • 阶段二(生成期契约:AGC 玩法 skill / Runtime Prompt 强制「发布句柄 + 素材身份标注 + 素材以稳定路径进 dist」,
    模板 template.zip 与线上索引重新发布,新项目开箱即用验收)在本 PR 续推,不另立 issue。
阶段一已推送,PR:https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/624 - **结论**:canvas 玩法(Phaser 4 / three.js)上点选已能落成带素材 id 的 `runtime-region` 引用; 未标注对象 / 点空白退化为不带素材的区域引用,零报错、零伪造 id;DOM 点选语义与消息协议逐字不变。 - **证据**(合并 `master` 后的顶端 `b2307a5c5` 上复跑):`npm run agc:typecheck` EXIT=0; 前端四文件 95 passed、`appSurface.test.ts` 197 passed / 9 skipped; `cargo test … -- preview` 44 passed、`-- scaffold` 5 passed / 1 既有 `#[ignore]`; 真实 Chromium 端到端(真实 Phaser 4.2.1 / three 0.184 + 真实桥脚本 sha256 `6276ac48…917a`) 与高亮矩形差值(Phaser 0px / three ≈0.0006px)见 PR 正文与截图。 - **未覆盖**:Tauri 真机链路(由 Rust 定向测试覆盖注入与路由)、多场景叠加、非默认相机、高亮真机目视。 - **阶段二**(生成期契约:AGC 玩法 skill / Runtime Prompt 强制「发布句柄 + 素材身份标注 + 素材以稳定路径进 dist」, 模板 `template.zip` 与线上索引重新发布,新项目开箱即用验收)**在本 PR 续推,不另立 issue**。
Author
Member

阶段二(生成期契约落位)已随同一 PR #624 落盘:提交 0aed0faa9(普通推送 b2307a5c5..0aed0faa9)。

  • 主源 agc-web-game-development/SKILL.md 新增「运行画面点选契约」四条:发布只读引擎句柄、Phaser 用 loader.imageLoadType='HTMLImageElement'、素材渲染对象标注 manifest 身份(localAssetId / localPath)、素材进 dist 走与 localPath 对齐的稳定路径;game-quality-checklist.md 与 agc-game-production-workflow/SKILL.md 只补指针,不复制正文。
  • 指纹 skill-pack:sync 到 2026-08-26.38;npm run agc:typecheck(含指纹校验)、skill-pack:test(3 passed)、check-doc-index(244 份)、check:encoding(5272 文件)、git diff --check 全绿。
  • 未合并主线;模板 zip 与线上索引未重新发布(沿用阶段一边界)。

顺带更正 #612 描述里的一处口径:点选结果的芯片名归一化不是「仅当标签等于素材文件名词干」这一条,完整口径是——引擎档(载荷 elementTag='canvas')一旦唯一命中 manifest 素材,直接用 resourceDisplayName(asset) 统一显示名,不看 label 形状;DOM 档才只在标签恰好等于 sourcePath 文件名词干(大小写不敏感)时做同一次归一化,其余原样保留。实现见 apps/ai-game-creator-shell/src/features/project-workspace/runtimeInspectResourceMatch.ts 的 resolveRuntimeInspectLabel。

阶段二(生成期契约落位)已随同一 PR #624 落盘:提交 `0aed0faa9`(普通推送 `b2307a5c5..0aed0faa9`)。 - 主源 `agc-web-game-development/SKILL.md` 新增「运行画面点选契约」四条:发布只读引擎句柄、Phaser 用 `loader.imageLoadType='HTMLImageElement'`、素材渲染对象标注 manifest 身份(`localAssetId` / `localPath`)、素材进 dist 走与 `localPath` 对齐的稳定路径;`game-quality-checklist.md` 与 `agc-game-production-workflow/SKILL.md` 只补指针,不复制正文。 - 指纹 `skill-pack:sync` 到 `2026-08-26.38`;`npm run agc:typecheck`(含指纹校验)、`skill-pack:test`(3 passed)、`check-doc-index`(244 份)、`check:encoding`(5272 文件)、`git diff --check` 全绿。 - 未合并主线;模板 zip 与线上索引未重新发布(沿用阶段一边界)。 顺带更正 #612 描述里的一处口径:点选结果的芯片名归一化不是「仅当标签等于素材文件名词干」这一条,完整口径是——**引擎档**(载荷 `elementTag='canvas'`)一旦唯一命中 manifest 素材,直接用 `resourceDisplayName(asset)` 统一显示名,不看 label 形状;**DOM 档**才只在标签恰好等于 `sourcePath` 文件名词干(大小写不敏感)时做同一次归一化,其余原样保留。实现见 `apps/ai-game-creator-shell/src/features/project-workspace/runtimeInspectResourceMatch.ts` 的 `resolveRuntimeInspectLabel`。
Author
Member

阶段一 / 阶段二现状与本轮真机复验结论:

  • 阶段一(引擎档识别):桥、宿主匹配、脚手架句柄均在本 PR;本轮真机复验通过——点中 three 对象芯片为对象名/几何类型(@CylinderGeometry),不再一律 canvas;点空白回落 @canvas、零报错。
  • 阶段二(生成期契约):已写入 agc-web-game-development 的「运行画面点选契约」(发布只读引擎句柄 / Phaser 用 Image 装载 / 对象标注素材身份 / 素材 dist 稳定路径)+ 两个 three 模板瘦身句柄,skill-pack 指纹 2026-08-26.39。
  • 本轮另修 BUG2(点选后预览自己缩放):根因是高亮框被尺寸上报计入内容尺寸 → 宿主改 iframe 尺寸 → 游戏 resize 重算相机形成振荡;已最小修复 + 5 例回归用例,真机四项指标(iframe src/style/rect、尺寸上报、预览内 resize、相机 aspect)点选前后无变化。

仍未完成:模板产物(template.zip 与线上索引)未重新发布;BUG1(运行页刷新按钮)由另一工位在独立分支处理、未包含在本 PR;本 PR 未合并。

阶段一 / 阶段二现状与本轮真机复验结论: - 阶段一(引擎档识别):桥、宿主匹配、脚手架句柄均在本 PR;本轮真机复验通过——点中 three 对象芯片为对象名/几何类型(`@CylinderGeometry`),不再一律 `canvas`;点空白回落 `@canvas`、零报错。 - 阶段二(生成期契约):已写入 `agc-web-game-development` 的「运行画面点选契约」(发布只读引擎句柄 / Phaser 用 Image 装载 / 对象标注素材身份 / 素材 dist 稳定路径)+ 两个 three 模板瘦身句柄,skill-pack 指纹 `2026-08-26.39`。 - 本轮另修 BUG2(点选后预览自己缩放):根因是高亮框被尺寸上报计入内容尺寸 → 宿主改 iframe 尺寸 → 游戏 `resize` 重算相机形成振荡;已最小修复 + 5 例回归用例,真机四项指标(iframe `src/style/rect`、尺寸上报、预览内 `resize`、相机 aspect)点选前后无变化。 仍未完成:模板产物(`template.zip` 与线上索引)未重新发布;BUG1(运行页刷新按钮)由另一工位在独立分支处理、未包含在本 PR;本 PR 未合并。
Author
Member

阶段一 / 阶段二:已合并 (#624)

  • merge 提交:96859666ff631b73460a8643aa6a9b10f2d44a2d(merged_at=2026-10-05T19:16:03+08:00,merged_by=suzmii);origin/master 顶端 96859666f,df221f4eb 已是其祖先。
  • CI(head df221f4eb):8/8 success(Rust crates / Rust lane 1-2 / Frontend / Backend / web tests / Repository checks / Native shell)。
  • 合并后最小门禁:master 临时 worktree 上 runtimeInspectEngines + localPreviewInspectSizeStability = 38 passed;cargo test -- preview = 44 passed / 0 failed。
  • 真机复验证据在 PR 正文「### 真机复验」小节 + 三张附件(对象档芯片为 @CylinderGeometry 而非 canvas、空白档 @canvas、高亮裁剪在画布内、点选前后 iframe src/style/rect 与尺寸上报不变)。

仍未做:模板产物重新发布(阶段二第 3 条)、GLB 显式标注真机路径、多场景叠加/非默认相机真机目视。

## 阶段一 / 阶段二:已合并 (#624) - merge 提交:`96859666ff631b73460a8643aa6a9b10f2d44a2d`(`merged_at=2026-10-05T19:16:03+08:00`,`merged_by=suzmii`);`origin/master` 顶端 `96859666f`,`df221f4eb` 已是其祖先。 - CI(head `df221f4eb`):**8/8 success**(Rust crates / Rust lane 1-2 / Frontend / Backend / web tests / Repository checks / Native shell)。 - 合并后最小门禁:master 临时 worktree 上 `runtimeInspectEngines + localPreviewInspectSizeStability` = 38 passed;`cargo test -- preview` = 44 passed / 0 failed。 - 真机复验证据在 PR 正文「### 真机复验」小节 + 三张附件(对象档芯片为 `@CylinderGeometry` 而非 `canvas`、空白档 `@canvas`、高亮裁剪在画布内、点选前后 iframe `src/style/rect` 与尺寸上报不变)。 仍未做:模板产物重新发布(阶段二第 3 条)、GLB 显式标注真机路径、多场景叠加/非默认相机真机目视。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GenarrativeAI/Genarrative#612