AGC 运行画面点选:canvas 玩法上识别 Phaser 4 / three.js 的已登记素材(#612) #624
Reference in New Issue
Block a user
Delete Branch "feat/agc-runtime-picking-engines"
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?
标题:AGC 运行画面点选:canvas 玩法上识别 Phaser 4 / three.js 的已登记素材(#612)
关联
#612
背景与问题
运行画面「点选素材」今天的命中路径只有 DOM:读元素上的
data-resource-id/data-asset-id/data-genarrative-resource-id,再退一步读媒体src。canvas 玩法里这些线索一个都没有,于是点中精灵或 Mesh 时
resourceIds/sourcePath全空 —— 点选“看起来没反应”,引用只能退化成不带素材的区域引用,芯片名落到元素/画面名
(
canvas、甚至贴图内部 key)。本 PR 让引擎画面上的点选也能落到 manifest 的素材 id。落地方案
PREVIEW_FIT_BRIDGE_SCRIPT落到src-tauri/resources/preview/local-preview-fit.js,preview.rs改include_str!。注入、去重扫描、
/__genarrative/local-preview-fit.js路由与响应逐字不变(行为零变化)。handleInspectMove/handleInspectClick共同部分抽成resolveInspectTarget(clientX, clientY)→{ selection, rect };DOM 分支等价搬运。window.__GENARRATIVE_PREVIEW_GAME__(
{ engine: 'phaser', game }/{ engine: 'three', THREE, scene, camera, renderer }),不通过扫描全局对象猜引擎。
game.input.activePointer+scene.input.hitTestPointer,按pointer.camera.renderList索引降序取最上层;setInteractive()未覆盖的对象用scene.children.list(含depth)+getBounds()做几何兜底;矩形按camera.matrixCombined换算屏幕矩形。renderer.domElement→ NDC →Raycaster.setFromCamera+intersectObjects(scene.children, true)取最近命中;矩形用Box3八角投影取 min/max。genarrativeResourceId/genarrativeResourcePath)优先,其次才是从贴图地址推断出的文件名。
runtimeInspectResourceMatch.ts):显式resourceIds→
sourcePath与localPath的精确后缀(按/、\分段,段边界判定)→ 文件名(现有口径)→ 序列帧
imageSequenceFrames[].imageSrc同权参与后两档;结果去重且只保留manifest 现有 id(未知值丢弃,宁可少给不可给错)。芯片名在唯一命中时归一化为
resourceDisplayName(asset),与@候选 / 资源卡「引用」/ 拖拽引用逐字一致。DEFAULT_GAME_SCRIPT_JS与三个起步模板(phaser-2d-starter、threejs-3d-starter、blank-3d-scene)发布只读引擎句柄;Phaser 侧改用 Image 标签装载(
loader: { imageLoadType: 'HTMLImageElement' }),让贴图保留原始素材地址。前后对照
data-*、无src)resourceIds=[])resourceIds=["hero"],芯片@heroresourceIds=["enemy-tag"],芯片@enemyuserData标注 Mesh["hero"]/["enemy-tag"]["hero"](显式路径)@canvas+resourceIds=[],零报错data-resource-id、媒体src)data-*@HUD 区域/@img)说明:上表「改前」列是按改动前的桥只有 DOM 分支推得(未单独跑改动前版本),
「改后」列是真实 Chromium 实测值。
变更清单
src-tauri/resources/preview/local-preview-fit.js(新增,1074 行)、src-tauri/src/preview.rs(改include_str!,-508/+1)、tests/runtimeInspectEngines.test.ts(新增 1277 行)、.prettierignore。src/features/project-workspace/runtimeInspectResourceMatch.ts(新增 173 行)、src/view/project-development/index.tsx(+17/-14)、tests/runtimeInspectResourceMatch.test.ts(新增 278 行)。src-tauri/src/main.rs(DEFAULT_GAME_SCRIPT_JS)、template-library/v1/{phaser-2d-starter,threejs-3d-starter,blank-3d-scene}。shared-memory/pitfalls.md、实施计划、里程碑。验证证据
npm run agc:typecheck:EXIT=0(check:tests:types/tsc/ skill-pack 指纹 /包布局声明 /
check-config全过)。runtimeInspectEngines(27)、runtimeInspectResourceMatch(11)、localGamePreviewFrame(14)、resourceReferenceInput(43)。tests/appSurface.test.ts:197 passed / 9 skipped。cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- preview:43 passed / 0 failed。
-- scaffold:4 passed / 1 既有#[ignore](需 npm 装依赖 + Provider,非失败)。node scripts/check-doc-index.mjs通过;npm run check:encoding通过(5237 files);git diff --check输出为空。local-preview-fit.js,被测 sha2566276ac48…917a):resourceIds=["hero"]、芯片@hero;["enemy-tag"]、芯片@enemy(显式优先 + 取最上层);setData('genarrativeResourcePath'))→ 几何兜底命中["hero"];@canvas+[]、零报错;DOM 档芯片名与 id 行为逐字不变。DOM 档仍是元素矩形。
真机复验(2026-10-05,客户端从本分支源码编译)
@CylinderGeometry(titleCylinderGeometry · 运行区域),不再是canvas;@canvas(退化档),控制台 0 报错;(84,200,746,458)右边/下边恰好贴画布 830×658 的边);src、style、getBoundingClientRect逐值相同(830×658、scale(1))、尺寸上报全程仅 1 条、预览页内resize计数 0;engine='three'、three键为[Raycaster, Vector2, Vector3, Box3]且均为 function、scene/camera/renderer齐;pointerup → focusShape的既有反应(玩法对所有点击都如此),与检查模式无关;桥只监听click,不吞pointerdown/pointerup。边界与不做项
不写资源侧车、不改 manifest。
runtime-region引用契约、genarrative.local-preview-inspect.v1消息版本号、LocalGamePreviewFrame的解析与净化、Rustdirect_codex_user_item的resourceIds校验口径。已知限制 / 未覆盖
blob:,读不回原始素材路径 → 这类项目必须由玩法显式标注素材身份,否则按退化路径处理(不带素材的区域引用)。脚手架与
phaser-2d-starter已改用 Image 标签装载;历史项目仍是 blob。
/__genarrative/local-preview-fit.js路由由
cargo test … -- preview覆盖;本 PR 的浏览器证据用的是同一份桥脚本文件 +真实引擎,但不含 Rust 预览服务器那一环。
scroll/zoom/rotation)、高亮矩形的真机目视、GLB 内嵌纹理模型走显式标注路径的真机验证。
评审提示
.prettierignore新增src-tauri/resources/preview/:桥脚本是随包注入浏览器的资源,按字节搬运自 Rust 内联字符串,禁止 prettier 二次改写(会改变注入字节)。
template-library只改本地源);生成期契约落位(玩法 skill / Runtime Prompt 强制标注 + 重新发布模板产物)另立里程碑。
elementTag === 'canvas')唯一命中即归一化为 manifest 显示名;DOM 档仅在 label 恰为该
sourcePath的文件名词干时才归一化,其余原样保留。截图
three.js 场景:贴图 Mesh 自动档(
@hero)、userData显式档(@enemy)、点空白(@canvas)、DOM HUD(@HUD 区域)。Phaser(
loader.imageLoadType='HTMLImageElement'):自动档@hero、显式档@enemy、非交互精灵几何兜底@hero、点空白@canvas、DOM HUD 不变。Phaser(默认 XHR):同一个贴图精灵退化为
@heroUpper(resourceIds=[])——已知限制(贴图地址是blob:)的证据。合并前检查清单
已验证(本轮 merge
origin/master后复跑,全部在合并后的顶端b2307a5c5上)npm run agc:typecheck→ EXIT=0(check:tests:types/tsc/ skill-pack 指纹 / 包布局声明 /check-config全过)npx vitest run四文件:runtimeInspectEngines(27)+runtimeInspectResourceMatch(11)+localGamePreviewFrame(14)+resourceReferenceInput(43)→ 4 passed / 95 passednpx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts→ 197 passed / 9 skippedcargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- preview→ 44 passed / 0 failed-- scaffold→ 5 passed / 0 failed / 1 既有#[ignore](需 npm 装依赖 + Provider)node scripts/check-doc-index.mjs、npm run check:encoding(5272 files)、git diff --check→ 全通过git status --porcelain→ 空真实浏览器(Chromium)端到端冒烟
真实 Phaser 4.2.1 / three 0.184 + 真实桥脚本
apps/ai-game-creator-shell/src-tauri/resources/preview/local-preview-fit.js,被测版本 sha256
6276ac48…917a(合并前后一致)。芯片名与载荷实测值见正文「验证证据」,截图见正文「截图」:已登记贴图对象
["hero"]、显式标注对象["enemy-tag"]、非交互精灵经几何兜底命中
["hero"]、点空白@canvas+[]零报错、DOM 档芯片名不被改写、高亮矩形与期望逐值相减 Phaser 0px / three ≈0.0006px。
未覆盖(评审请勿当成已验)
Tauri 真机链路(预览服务器注入、
/__genarrative/local-preview-fit.js路由、去重扫描由cargo test … -- preview覆盖)、多场景叠加、非默认相机(
scroll/zoom/rotation)、高亮矩形的真机目视。范围
本 PR 对应 #612 阶段一:桥脚本搬迁 + canvas 引擎档识别 + 宿主四档匹配与芯片名归一化 + 脚手架/模板发布句柄与装载方式。
阶段二(生成期契约:AGC 玩法 skill / Runtime Prompt 强制「发布句柄 + 用已登记素材渲染的对象标注素材身份 + 素材以稳定路径进 dist」、
以及模板
template.zip与线上索引重新发布)在同一 PR 续推,验收判据见正文「评审提示」。评审提示
请勿合并前保持分支与
master同步:本分支已 merge 过一次origin/master(合并提交b2307a5c5);若
master再前进请先同步再合,避免评审看到 tip-to-tip 大 diff。阶段二:生成期契约落位(同一 PR 续推)
阶段一让运行画面能点出素材,但可用性依赖玩法满足三条约定(发布句柄、Phaser 用 Image 装载、对象标注素材身份);阶段二把它们写进生成期契约,新生成的 Phaser 4 / three.js 项目不改代码即可点出素材。提交
0aed0faa9。落地清单
resources/agc-skills/agc-web-game-development/SKILL.md(主源,唯一写全契约处){ engine:'phaser', game }/{ engine:'three', THREE, scene, camera, renderer },只服务点选与调试)② Phaser 固定loader: { imageLoadType: 'HTMLImageElement' }(默认 XHR 装载只剩会话内临时地址)③ 素材渲染对象标注 manifest 身份(PhasersetData('genarrativeResourceId'|'genarrativeResourcePath', …),three.jsmesh.userData.…,值只取agc_list_registered_assets的localAssetId/localPath)④ 素材进 dist 走与localPath对齐的稳定路径(public/原样拷贝,不用import的 hash 产物名);并写明不满足时不报错、不伪造 id、退化为不带素材的区域引用(= 验收面)agc-web-game-development/references/game-quality-checklist.mdagc-game-production-workflow/SKILL.mdresources/agc-skills/manifest.jsonskill-pack:sync:version2026-08-26.37→2026-08-26.38,agc-web-game-development与agc-game-production-workflow的 sha256 更新skill_pack.rs逐文件include_bytes!,因此未新增 skill 文件、未改任何 Rust。验证(工作树根,提交前最后一次运行)
npm run agc:typechecknpm --prefix apps/ai-game-creator-shell run skill-pack:testnode scripts/check-doc-index.mjsnpm run check:encodingEncoding check passed for 5272 file(s).git diff --checkgit status --porcelain边界确认
preview.rs、resources/preview/local-preview-fit.js(sha256 仍为6276ac48…917a)、宿主匹配runtimeInspectResourceMatch.ts、index.tsx、脚手架与三个模板源均未改动。git push为普通推送(b2307a5c5..0aed0faa9),未 force。- 桥:three 句柄改读 `handle.three || handle.THREE`(旧整命名空间形态仍兼容),四个构造器缺任一即按未命中退化 - 桥:three label 链补 `object.name` → `geometry.type` → `object.type`,Phaser 补 `name` → `type`(取值超 40 字符丢弃);命中对象但取不到素材身份时芯片不再一律是 `@canvas` - 契约:agc-web-game-development 的「运行画面点选契约」改用瘦身句柄 `three: { Raycaster, Vector2, Vector3, Box3 }`,并写明整命名空间会破坏 tree-shaking(同一 Vite 构建 517,682 B → 730,831 B,+41%);skill-pack 指纹同步到 2026-08-26.39 - 模板:threejs-3d-starter 与 blank-3d-scene 的句柄源改成瘦身形态 - 测试:runtimeInspectEngines 新增 6 例(瘦身句柄命中、旧命名空间兼容、name / geometry.type / object.type 兜底、缺构造器退化),共 33 例 - 文档:功能说明补 label 兜底与瘦身句柄的验收行和边界;pitfalls 记一条 tree-shaking 口径;实施计划阶段二进展补该轮结论可见性补强 + three 句柄瘦身(针对实测「点选还是只能选到 canvas」)
提交
19d179bde(普通推送7d205c8da..19d179bde,无 force)。1. label 兜底:命中对象但取不到素材身份时不再一律
@canvasobject.name = 'Crate'、无材质贴图、无 userDatacanvasCrate,矩形 143×143(对象区域)SphereGeometrycanvasSphereGeometry,矩形 227×227canvasobject.name→object.typeobject.name→geometry.type→object.type;Phaser 在texture.key之后补name→type。取值 trim 后超过 40 字符一律丢弃,避免把程序化命名塞进芯片。userData.genarrativeResourceId仍是resourceIds: ["local-asset:probe"],label 只作为无身份时的兜底。2. three 句柄瘦身(tree-shaking)
window.__GENARRATIVE_PREVIEW_GAME__ = { engine: 'three', three: { Raycaster, Vector2, Vector3, Box3 }, scene, camera, renderer }。THREE命名空间 730,831 B vs 只传 4 个构造器 517,682 B → +213,149 B ≈ +41%。handle.three || handle.THREE两种形态都读(旧项目不回归);四个构造器缺任一即按未命中退化。threejs-3d-starter与blank-3d-scene的句柄源改成瘦身形态;契约文本(agc-web-game-development「运行画面点选契约」)写明「不要传整个three命名空间」及原因,skill-pack指纹同步到2026-08-26.39。3. 重要:改完必须重启 AGC 客户端
桥脚本是
include_str!编译期内嵌进客户端的(sha2566276ac48…917a→877dbba8…a831)。不重启客户端,预览页拿到的仍是旧桥,label 兜底与瘦身句柄都不会生效。4. 验证
npx vitest run runtimeInspectEngines / runtimeInspectResourceMatch / localGamePreviewFrame / resourceReferenceInputruntimeInspectEngines33 例,新增 6 例)npm run agc:typecheck2026-08-26.39、包布局、check-config)node scripts/check-doc-index.mjsnpm run check:encodingEncoding check passed for 5272 file(s).git diff --check/git status --porcelain真实浏览器证据(真实 Chromium + three 0.184 + 真实桥脚本,临时夹具与本地 http 服务已删除):slim 句柄命中、
Crate/SphereGeometry/LegacyMesh(旧命名空间句柄)三种芯片名 + 命中对象区域矩形。5. 边界
runtimeInspectEngines例数 27 → 33(功能说明与里程碑的证据表;同一 PR 内保持同一口径)。BUG 修复:点选 3D 对象后预览画面自己缩放(B2)
提交
7ad412f0e(普通推送8d1980488..7ad412f0e)。1. 根因因果链(实证得出,非推测)
resources/preview/local-preview-fit.js:422inspectOverlay()→:427document.body.appendChild(overlay),样式position: fixed,尺寸 = 命中矩形。local-preview-fit.js:336contentBounds = measureContentBounds(body, …)→:186document.createTreeWalker(body, NodeFilter.SHOW_ELEMENT),对每个rect.width>0||height>0的元素取rect.right + scrollX/rect.bottom + scrollY计入上限。Box3八角投影(:1048一带),物体贴近相机时投影盒远超画布:实测高亮框 rect =[-2852, -2078, 6607, 4721]。LocalGamePreviewFrame.tsx:183 resolveLocalGamePreviewFitLayout→:443把 iframe 设为3756×2643+scale(0.2119)。resize→ 相机aspect/重新适配距离变化 → 视口又变大 → 自激振荡;点选结束后视口停在放大状态(相机 aspect 停在 1.421112 而不是 1.607143)。2. 修复(两处,均在桥内,不改点选语义与消息字段)
local-preview-fit.js:176 isPreviewBridgeNode+:186measureContentBounds的 TreeWalkeracceptNode:对data-genarrative-preview-fit/data-genarrative-preview-inspect返回FILTER_REJECT—— 桥自己的节点一律不参与内容尺寸测量。local-preview-fit.js:520 clipInspectRect:引擎档命中矩形统一裁剪到画布可见范围(:867Phaser、:1048three);与画布无交集时退化为指针点矩形(width/height至少 1)。DOM 档语义与消息协议未改。3. 前后对照(真实 Chromium + 真实 three 0.184 + 真实桥脚本 + 按宿主
resolveLocalGamePreviewFitLayout逐行复刻的宿主页)夹具:900×560 容器 / iframe,页面是「全窗口 canvas + resize 里改 aspect 并重新适配」的 three 游戏,贴近相机的大圆柱(投影盒超出画布);动作 = ① 进入检查模式 ② hover 出现高亮框 ③ 点中对象。
3756×2643 @900×560×728 与3756×2643 @3756×2643×728(振荡)300×150/300×150@900×560/900×560@900×560900x560@1↔3756x2643@0.2119)900x560@1.0000)resize事件aspectzoom恒 1、位置不变document滚动尺寸[-2852, -2078, 6607, 4721][0, 0, 904, 564](= 画布 900×560 + overlay 自身 2px 边框)(夹具与本地 http 服务已删除;三种动作的快照分别记录在夹具里,evidence 为上方数字。)
4. 用例(新增 5 例,回归可抓)
apps/ai-game-creator-shell/tests/localPreviewInspectSizeStability.test.ts:尺寸上报把桥自己的节点排除在内容测量之外(同样大小的普通节点仍会被计入)——含对照组(4000×3000 普通节点必须撑大上报),证明用例对「内容尺寸被撑大」敏感;进入检查模式与 hover 出现高亮框后:尺寸上报与宿主适配布局都不变——同一用例里断言resolveLocalGamePreviewFitLayout(真实宿主函数)在点选前后都返回900×560@1;three 投影盒超出画布时:载荷矩形被裁剪到画布内,width/height 不为 0;投影盒与画布完全无交集时退化为指针点矩形,不产生 0 或负尺寸;Phaser 世界包围盒超出画布时同样裁剪到画布内。敏感性验证:把这 5 例跑在修复前的桥(
git show HEAD^:...local-preview-fit.js)上 → 5 failed;跑在修复后 → 5 passed。既有套件:
runtimeInspectEngines(33)+ 新增(5)+localGamePreviewFrame(14)+resourceReferenceInput(43)= 95 passed;appSurface.test.ts197 passed / 9 skipped;npm run agc:typecheckexit 0(tsc 0 error);npm run check:encoding通过(5273 文件);git diff --checkclean;提交后git status --porcelain为空。5. 边界 / 未覆盖
agc-dev-real以 exit 0 结束),且此前登录态过期未登录,因此「真机里点选后画面不缩放」尚未实测;已请求重启并登录后补测。真机复验完成(三张图已附在正文的「### 真机复验」小节)
isPreviewBridgeNode/clipInspectRect),CDPEdg/154.0.4258.53。@CylinderGeometry(titleCylinderGeometry · 运行区域,非canvas);点空白天空 →@canvas退化、控制台 0 报错;命中高亮为对象级矩形且超出画布时被裁剪到画布内(实测(84,200,746,458))。src、style、getBoundingClientRect逐值相同(830×658、scale(1))、尺寸上报全程仅 1 条、预览页内resize计数 0。@TetrahedronGeometry)+ 本轮「点中对象」「点空白」两张。pointerup → focusShape的既有反应(玩法对所有点击都如此),与检查模式无关;桥只监听click,不吞pointerdown/pointerup。待用户决定合并(本 PR 未合并)。
合并前同步:已并入
origin/master并解决全部冲突(未合并 PR)origin/master:21c79f54a;本分支985b8f9fa(behind 25 / ahead 14)。364b1aa34(两父:本分支 + origin/master,使用 merge 而非 rebase,评审过的提交链未变)。985b8f9fa..364b1aa34。3 处冲突的解决
agc-game-production-workflow/SKILL.mdagc-web-game-development」指针),第 5 条用 master 的新构建验证口径agc-skills/manifest.jsonagc:skill-pack:sync按合并后内容重新生成 version 与指纹 →2026-08-26.41,skill-pack:check通过pitfalls.md另 2 个自动合并但已人工核对:
agc-web-game-development/SKILL.md:本分支「运行画面点选契约」小节(18 行)与 master 内容都在(git diff origin/master仅这 18 行新增)。index.tsx:handleRuntimeInspectSelection回调体与合并前逐字一致,master 新增的命名任务相关代码都在。关键不变量
9513020cfffa79128fb6355d44cdaabe666eefecbd96b7e807b15d2c4601bf76→ 真机截图与点选证据对应字节仍然成立。git diff origin/master HEAD只含本 PR 的改动集合(21 文件,3930 insertions / 532 deletions;大头是preview.rs的 raw string −508 行 ↔ 新增local-preview-fit.js)。门禁(合并后复跑)
npm run agc:typecheck:EXIT=0(tsc 0 error;[skill-pack] OK(version=2026-08-26.41))runtimeInspectEngines33 /localPreviewInspectSizeStability5 /runtimeInspectResourceMatch11 /localGamePreviewFrame14 /resourceReferenceInput43)appSurface.test.ts:200 passed / 9 skipped(master 新增 3 例)cargo test … -- preview:44 passed / 0 failed;-- scaffold:6 passed / 1 ignored(均为--bin形式;因客户端在跑占用 exe,且当前随包plugins是带编辑器 feature 的档位,故按仓库口径带--features=cocos-editor-execute,unity-editor-execute,godot-editor-execute)node scripts/check-doc-index.mjs通过(244 份);npm run check:encoding通过(5275 files);git diff --check空;git status --porcelain空当前状态
state=open、merged=false、mergeable=true、head364b1aa34。pending(Project CI 8 个检查 "Waiting to run",含 Backend tests / Repository checks / Rust lanes),未等待结果。✅ 已合并(merge 完成)
96859666ff631b73460a8643aa6a9b10f2d44a2d(short96859666f),merged_at=2026-10-05T19:16:03+08:00,merged_by=suzmii(由用户执行;我未调用 merge 接口)。fa42cdb13(master) +df221f4eb(本分支)),未 squash/rebase、分支未删。origin/master顶端:96859666f;git merge-base --is-ancestor df221f4eb origin/master退出码 0(本分支内容已在 master)。CI(head
df221f4eb,8/8 success)合并后最小门禁
npx vitest run runtimeInspectEngines + localPreviewInspectSizeStability:在 origin/master 的临时 worktree 上跑 → 38 passed(33 + 5)。cargo test … --bin … -- preview:44 passed / 0 failed(EXIT=0)。等价性依据:git diff df221f4eb 96859666f -- apps/ai-game-creator-shell/src-tauri为空、master 额外改动未触及shared-contracts,AGC Rust 侧逐字节一致。真机证据
见本 PR 正文「### 真机复验(2026-10-05,客户端从本分支源码编译)」小节与三张附件(用户四面体点选 / 点中对象高亮+芯片 / 点空白回落 canvas)。
仍未做(后续里程碑/工位)
template.zip与线上索引);