支持 Phaser 4 与 three.js 运行画面素材点选 #612
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景与问题
工作台运行画面已有「点选素材」入口:点中预览页元素后往对话插入一条
runtime-region引用(对话里是@芯片)。但改动前它是纯 DOM 命中,AGC 的默认玩法链路上画面内容全部画在<canvas>里,点选对它们等于空操作。改动前链路(按代码):
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。src/view/project-development/index.tsx的handleRuntimeInspectSelection:用selection.sourcePath的文件名到manifest.assets[].localPath里匹配素材 id,再与selection.resourceIds合并后派发runtime-region引用。target是整块<canvas>,没有data-*,也没有src→sourcePath与resourceIds全空。结果是一条不带素材的区域引用(芯片文本退化成@canvas),Rust 侧不会报错(agc_runtime_region_reference只在resourceIds指向不存在的素材时才拒绝),所以是静默失效。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)并重新发布模板产物,让新项目开箱即用。
目标与验收判据
阶段一:运行画面引擎档点选
resourceIds含对应 manifest 素材 id。label取引擎/画面侧可得的名字,退无可退时保留canvas)。<img>、data-resource-id)行为逐字不变,现有resourceReferenceInput.test.tsx的 inspect 解析用例不回归。阶段二:生成期契约落位
resourceIds命中该素材 id。阶段一:运行画面引擎档点选(已实现,PR 待评审)
落地摘要(原「方案」「落地改动清单」压缩):
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 时惰性读取。game.input.activePointer+scene.input.hitTestPointer,按pointer.camera.renderList索引降序取真实最上层(hitTestPointer的返回顺序是输入注册顺序,不是叠放顺序);setInteractive()未覆盖的对象用scene.children.list(含depth+ 入序)几何兜底;只取当前激活场景;矩形按相机换算屏幕矩形。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-2d-starter改用loader: { imageLoadType: 'HTMLImageElement' },让贴图元素保留原始素材地址(默认 XHR 装载只剩会话内 blob 地址,自动档读不回素材路径)。runtime-region引用契约、genarrative.local-preview-inspect.v1消息版本号、字段净化与数量上限、RustresourceIds校验口径均不变。证据(阶段一):
npm run agc:typecheckEXIT=0(check:tests:types/tsc/ skill-pack 指纹 / 包布局声明 /check-config全过)。runtimeInspectEngines/runtimeInspectResourceMatch/localGamePreviewFrame/resourceReferenceInput);tests/appSurface.test.ts197 passed / 9 skipped。cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- preview44 passed;同路径-- scaffold5 passed / 1 既有#[ignore](需 npm 装依赖 + Provider,非失败)。local-preview-fit.js,被测 sha2566276ac48…917a):已登记贴图对象 →resourceIds=["hero"]、芯片@hero;显式标注且与上一个重叠的对象 →["enemy-tag"]、芯片@enemy(显式优先 + 取最上层);非交互精灵 → 几何兜底命中;点空白 →@canvas+[]、零报错;DOM 档芯片名与 id 逐字不变。高亮矩形与期望差:Phaser 0px、three 最大 ≈0.0006px。/__genarrative/local-preview-fit.js路由由cargo test … -- preview覆盖;浏览器证据用的是同一份桥脚本文件 + 真实引擎,不含 Rust 预览服务器那一环。阶段二:生成期契约落位
为什么需要
阶段一让引擎档识别靠玩法侧给线索;缺线索就只能退化为「不带素材的区域引用」。三种拿不到素材身份的情况:
blob:,读不回原始素材路径。历史项目与未改装载方式的生成物都属于这类。也就是说:桥和宿主已经准备好了,缺的是「生成期让玩法把线索写出来」。
要改什么
window.__GENARRATIVE_PREVIEW_GAME__:Phaser →{ engine: 'phaser', game };three.js →{ engine: 'three', THREE, scene, camera, renderer }。setData('genarrativeResourceId' | 'genarrativeResourcePath');three.jsobject.userData.genarrativeResourceId | genarrativeResourcePath。localPath(推荐直接用assets/...这类与 manifest 同源的相对路径);不要靠import产物名——Vite 的 hash 文件名会让自动档失配。agc-client-projection的投影契约补一句「素材身份 = manifest 素材 id / localPath,不用文件名猜」。loader: { imageLoadType: 'HTMLImageElement' }。阶段一已在DEFAULT_GAME_SCRIPT_JS与phaser-2d-starter落地,还需写进 skill,让后续生成物保持一致(否则贴图退化成 blob,自动档失效)。scripts/agc-template-library-publish.mjs重新产出template.zip与线上索引(本轮只改本地模板源,没有发布会话);需要同步线上索引版本号与灰度策略。验收判据
resourceIds命中该素材 id。非目标
project/asset_usage.rs那套 slot 绑定/代码注入/侧车方案(2026-09-13 已整体撤回)。runtime-region引用契约与 Rust 的resourceIds校验口径,不改 manifest、版本绑定、替换链路。enabled/disabled/cancelled协议、parseLocalGamePreviewInspectMessage的现有字段语义均不变。风险与未决
hitTestPointer/Texture#getSourceImage/TextureSource#image存在;hitTestPointer的返回顺序不是叠放顺序(实测),实现按renderList索引取最上层。相机世界↔屏幕换算与图集帧frame.name的取值仍需按 4.2.1 实测校准(不照抄 Phaser 3 经验),不稳定的分支降级为「命中对象但只给点标记矩形」。import引用素材,则显式档成为唯一可靠档,需要评估是否加交付校验(会与存量项目冲突,倾向只告警不阻断)。ImageBitmap),必须走显式档;若模型漏标,只能「不命中」。intersectObjects在复杂场景下每帧成本:仅在 inspect 模式开着时按需执行(move 节流),不做常驻监听。关联
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.md2026-09-13 条(运行期资源观察方案整体撤回,边界明确"不做运行画面点选"——本需求是点选→对话引用,不是持久化使用关系,与该边界不冲突)。阶段一已推送,PR:#624
runtime-region引用;未标注对象 / 点空白退化为不带素材的区域引用,零报错、零伪造 id;DOM 点选语义与消息协议逐字不变。
master后的顶端b2307a5c5上复跑):npm run agc:typecheckEXIT=0;前端四文件 95 passed、
appSurface.test.ts197 passed / 9 skipped;cargo test … -- preview44 passed、-- scaffold5 passed / 1 既有#[ignore];真实 Chromium 端到端(真实 Phaser 4.2.1 / three 0.184 + 真实桥脚本 sha256
6276ac48…917a)与高亮矩形差值(Phaser 0px / three ≈0.0006px)见 PR 正文与截图。
模板
template.zip与线上索引重新发布,新项目开箱即用验收)在本 PR 续推,不另立 issue。阶段二(生成期契约落位)已随同一 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全绿。顺带更正 #612 描述里的一处口径:点选结果的芯片名归一化不是「仅当标签等于素材文件名词干」这一条,完整口径是——引擎档(载荷
elementTag='canvas')一旦唯一命中 manifest 素材,直接用resourceDisplayName(asset)统一显示名,不看 label 形状;DOM 档才只在标签恰好等于sourcePath文件名词干(大小写不敏感)时做同一次归一化,其余原样保留。实现见apps/ai-game-creator-shell/src/features/project-workspace/runtimeInspectResourceMatch.ts的resolveRuntimeInspectLabel。阶段一 / 阶段二现状与本轮真机复验结论:
@CylinderGeometry),不再一律canvas;点空白回落@canvas、零报错。agc-web-game-development的「运行画面点选契约」(发布只读引擎句柄 / Phaser 用 Image 装载 / 对象标注素材身份 / 素材 dist 稳定路径)+ 两个 three 模板瘦身句柄,skill-pack 指纹2026-08-26.39。resize重算相机形成振荡;已最小修复 + 5 例回归用例,真机四项指标(iframesrc/style/rect、尺寸上报、预览内resize、相机 aspect)点选前后无变化。仍未完成:模板产物(
template.zip与线上索引)未重新发布;BUG1(运行页刷新按钮)由另一工位在独立分支处理、未包含在本 PR;本 PR 未合并。阶段一 / 阶段二:已合并 (#624)
96859666ff631b73460a8643aa6a9b10f2d44a2d(merged_at=2026-10-05T19:16:03+08:00,merged_by=suzmii);origin/master顶端96859666f,df221f4eb已是其祖先。df221f4eb):8/8 success(Rust crates / Rust lane 1-2 / Frontend / Backend / web tests / Repository checks / Native shell)。runtimeInspectEngines + localPreviewInspectSizeStability= 38 passed;cargo test -- preview= 44 passed / 0 failed。@CylinderGeometry而非canvas、空白档@canvas、高亮裁剪在画布内、点选前后 iframesrc/style/rect与尺寸上报不变)。仍未做:模板产物重新发布(阶段二第 3 条)、GLB 显式标注真机路径、多场景叠加/非默认相机真机目视。