修运行画面点选后预览自己缩放:高亮框不再参与尺寸上报,命中矩形裁剪到画布
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
- 根因:高亮框(body 下 data-genarrative-preview-inspect,position:fixed)被尺寸上报的 measureContentBounds 遍历计入内容尺寸;three 投影盒超出画布时把内容尺寸撑到 3756×2643(视口 900×560),宿主改 iframe 尺寸触发游戏 resize 重算相机,形成自激振荡 - 桥:measureContentBounds 的 TreeWalker 对 data-genarrative-preview-fit / data-genarrative-preview-inspect 返回 FILTER_REJECT(桥自己的节点不参与内容测量) - 桥:新增 clipInspectRect,引擎档(three 投影盒、Phaser 世界包围盒)命中矩形统一裁剪到画布可见范围;与画布无交集时退化为指针点矩形(不出现 0/负尺寸) - 测试:新增 localPreviewInspectSizeStability.test.ts(5 例:桥节点不参与测量、同尺寸普通节点仍计入的对照组、进入检查模式与 hover 后尺寸与宿主适配布局不变、投影盒超出画布时载荷被裁剪、与画布无交集时退化为 1×1);runtimeInspectEngines 的字段换算用例改到画布内的等价参数(原矩形落在画布外,裁剪后按新规则退化为指针点矩形) - 文档:pitfalls 记一条「高亮框被尺寸上报当成页面内容」的根因、口径与回归
This commit is contained in:
@@ -170,9 +170,24 @@
|
||||
heightBaseline: height.baseline,
|
||||
};
|
||||
};
|
||||
// 桥自己的节点(尺寸上报的 style、点选高亮框)不属于页面内容:高亮框还是 `position: fixed`
|
||||
// 且跟随命中对象,一旦被 `measureContentBounds` 当成固有尺寸,宿主就会据此改 iframe 尺寸、
|
||||
// 游戏 resize 重算相机——实测表现为「点选后画面自己缩放」。
|
||||
const isPreviewBridgeNode = (element) => {
|
||||
const dataset = element && element.dataset;
|
||||
if (!dataset) return false;
|
||||
return (
|
||||
dataset.genarrativePreviewFit !== undefined
|
||||
|| dataset.genarrativePreviewInspect !== undefined
|
||||
);
|
||||
};
|
||||
const measureContentBounds = (body, viewportWidth, viewportHeight) => {
|
||||
if (!body) return { width: 0, height: 0, truncated: false };
|
||||
const walker = document.createTreeWalker(body, NodeFilter.SHOW_ELEMENT);
|
||||
const walker = document.createTreeWalker(body, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode: (node) => (
|
||||
isPreviewBridgeNode(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT
|
||||
),
|
||||
});
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let widthBaseline = 0;
|
||||
@@ -499,6 +514,19 @@
|
||||
width: 1,
|
||||
height: 1,
|
||||
});
|
||||
// 命中矩形裁剪到画布可见范围:引擎侧的世界 / 投影包围盒可能远超画布,高亮框与载荷都不该出现
|
||||
// 比画面还大的矩形(同时它也是「点选后画面自己缩放」的输入之一,见 measureContentBounds 注释)。
|
||||
// 与画布无交集时返回 null,由调用方退化成指针点矩形。
|
||||
const clipInspectRect = (rect, canvasRect) => {
|
||||
if (!rect || !canvasRect) return null;
|
||||
const left = Math.max(rect.left, canvasRect.left);
|
||||
const top = Math.max(rect.top, canvasRect.top);
|
||||
const right = Math.min(rect.left + rect.width, canvasRect.left + canvasRect.width);
|
||||
const bottom = Math.min(rect.top + rect.height, canvasRect.top + canvasRect.height);
|
||||
if (![left, top, right, bottom].every(Number.isFinite)) return null;
|
||||
if (right <= left || bottom <= top) return null;
|
||||
return { left, top, width: Math.max(1, right - left), height: Math.max(1, bottom - top) };
|
||||
};
|
||||
const engineInspectSelection = (identity, rect) => ({
|
||||
label: identity.label || 'canvas',
|
||||
elementTag: 'canvas',
|
||||
@@ -835,7 +863,8 @@
|
||||
} catch {
|
||||
boundsRect = null;
|
||||
}
|
||||
const rect = boundsRect || inspectPointerRect(clientX, clientY);
|
||||
const canvasRect = canvasElement.getBoundingClientRect();
|
||||
const rect = clipInspectRect(boundsRect, canvasRect) || inspectPointerRect(clientX, clientY);
|
||||
return { selection: engineInspectSelection(phaserResourceIdentity(gameObject), rect), rect };
|
||||
};
|
||||
const phaserInspectTarget = (handle, canvasElement, clientX, clientY) => {
|
||||
@@ -1016,7 +1045,8 @@
|
||||
const hit = Array.isArray(hits) && hits.length > 0 ? hits[0] : null;
|
||||
const object = hit && hit.object;
|
||||
if (!object) return null;
|
||||
const rect = threeBoxScreenRect(three, object, camera, domRect) || inspectPointerRect(clientX, clientY);
|
||||
const rect = clipInspectRect(threeBoxScreenRect(three, object, camera, domRect), domRect)
|
||||
|| inspectPointerRect(clientX, clientY);
|
||||
return { selection: engineInspectSelection(threeResourceIdentity(object), rect), rect };
|
||||
};
|
||||
const resolveInspectEngineSelection = (canvasElement, clientX, clientY) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -710,8 +710,8 @@ describe('运行画面点选:Phaser 4 档', () => {
|
||||
data: { genarrativeResourceId: 'local-asset:plain' },
|
||||
});
|
||||
const camera = createPhaserCamera({
|
||||
scrollX: 10,
|
||||
scrollY: 20,
|
||||
scrollX: -400,
|
||||
scrollY: -300,
|
||||
zoom: 2,
|
||||
x: 3,
|
||||
y: 4,
|
||||
@@ -733,8 +733,9 @@ describe('运行画面点选:Phaser 4 档', () => {
|
||||
sendInspectAction('enable');
|
||||
clickAt(canvas, 50, 40);
|
||||
|
||||
// 世界 (10,20)-(110,70) 经 origin=0.5 / zoom=2 / scroll=(10,20) / camera=(3,4)
|
||||
// → 屏幕 (-397,-296)-( -197,-196):宽 200、高 100。
|
||||
// 相机字段换算(origin=0.5 / zoom=2 / scroll=(-400,-300) / camera=(3,4)):
|
||||
// 世界 (10,20)-(110,70) → 屏幕 (423,344)-(623,444),宽 200、高 100(世界尺寸的 2 倍)。
|
||||
// 矩形落在画布内,画布裁剪不改变它。
|
||||
expect(selectedSelection()).toMatchObject({
|
||||
width: 200,
|
||||
height: 100,
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。
|
||||
|
||||
## 2026-10-05 运行画面点选的高亮框被尺寸上报当成页面内容:点选后预览自己缩放
|
||||
|
||||
- **现象**:three 项目里点选 3D 对象后预览画面自己缩放、视角异常(相机 aspect 与重新适配的距离都变了),点选结束后视口仍停在放大后的尺寸。实测夹具里是**自激振荡**:10 秒内 1459 条尺寸消息、1457 次 iframe 尺寸/缩放变更、1454 次 `resize`,相机 aspect 在 1.607143 ↔ 1.421112 之间来回变。
|
||||
- **原因**:高亮框是 `document.body` 下的 `div[data-genarrative-preview-inspect]`(`position: fixed`、尺寸随命中对象),而尺寸上报的 `measureContentBounds` 用 `createTreeWalker(body, SHOW_ELEMENT)` 遍历所有元素并把 `rect.right/bottom` 计入内容尺寸。three 档的高亮矩形来自 `Box3` 八角投影,物体贴近相机时投影盒远超画布(实测 6607×4721、left/top 为负)→ 上报内容被撑到 3756×2643(视口只有 900×560)→ 宿主 `resolveLocalGamePreviewFitLayout` 把 iframe 改成 3756×2643 + scale 0.2119 → 游戏 `resize` 重算相机 → 视口变大又让投影盒更大,如此循环。
|
||||
- **结论(现行口径)**:① 桥自己的节点一律不进内容尺寸测量——`measureContentBounds` 的 TreeWalker 用 `acceptNode` 对 `data-genarrative-preview-fit` / `data-genarrative-preview-inspect` 返回 `FILTER_REJECT`;以后新增任何桥注入的 DOM 节点都要带上这两个标记之一,否则会重新引入这条反馈。② 引擎档命中矩形统一裁剪到画布可见范围(`clipInspectRect`;与画布无交集时退化为指针点矩形),不再出现比画面还大的高亮框。
|
||||
- **回归**:`apps/ai-game-creator-shell/tests/localPreviewInspectSizeStability.test.ts`(5 例:桥节点不参与测量、同尺寸普通节点仍计入的对照组、进入检查模式与 hover 后尺寸与宿主适配布局不变、投影盒超出画布时载荷被裁剪、与画布无交集时退化为 1×1)。
|
||||
- **关联**:`resources/preview/local-preview-fit.js`(`measureContentBounds` / `clipInspectRect` / `threeInspectTarget` / `phaserSelection`)、`features/project-workspace/LocalGamePreviewFrame.tsx`(`resolveLocalGamePreviewFitLayout`)。
|
||||
|
||||
## 2026-10-05 把整个 `THREE` 命名空间塞进预览句柄会让 tree-shaking 失效
|
||||
|
||||
- **现象**:three 项目的运行画面点选句柄写成 `{ engine: 'three', THREE, scene, camera, renderer }` 时,同一个 Vite 构建的产物从 517,682 B 涨到 730,831 B(+213,149 B ≈ +41%)。
|
||||
|
||||
Reference in New Issue
Block a user