incanto 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/incanto-model.mjs +13 -1
- package/bin/incanto-play.mjs +42 -17
- package/bin/incanto-playtest.mjs +7 -0
- package/bin/incanto-verify.mjs +10 -2
- package/dist/2d.d.ts +21 -12
- package/dist/2d.js +57 -3
- package/dist/3d.d.ts +38 -1
- package/dist/3d.js +114 -4
- package/dist/{create-game-CniOiWzN.js → create-game-BLDjy_PW.js} +5 -5
- package/dist/{create-game-akjPkFv5.js → create-game-DqqxEax1.js} +5 -5
- package/dist/{duplicate-CRtihGmC.js → duplicate-CI9WF_bg.js} +1 -1
- package/dist/{environment-presets-D1b0ydTS.js → environment-presets-XFuqu5jv.js} +15 -20
- package/dist/{gameplay-BQOeAid6.js → gameplay-DbaI313d.js} +96 -11
- package/dist/gameplay.d.ts +17 -0
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +40 -2
- package/dist/index.js +7 -7
- package/dist/{loader-r49nDwB4.js → loader-DwazzlQb.js} +36 -6
- package/dist/net.js +3 -3
- package/dist/{physics-2d-BmgXBNDB.js → physics-2d-_9VBOHn6.js} +4 -4
- package/dist/{physics-3d-CSoGjM8P.js → physics-3d-DrpF5hcG.js} +9 -9
- package/dist/react.js +1 -1
- package/dist/{register-R2JTnIMw.js → register-BNPZYJmd.js} +23 -22
- package/dist/{register-D651it1J.js → register-CB11yp21.js} +2 -2
- package/dist/{register-BSXV8T9F.js → register-uvaZj1KX.js} +62 -2
- package/dist/{replay-CAphXMyM.d.ts → replay-DYdy1wb0.d.ts} +5 -1
- package/dist/{replay-DilbZgQI.js → replay-j-m6lJ4W.js} +23 -3
- package/dist/sprite-animation-CMr6f1K2.d.ts +44 -0
- package/dist/{particle-sim-Bw7hB93B.js → sprite-animation-D_p28jwU.js} +63 -1
- package/dist/{src-Bl1Kire1.js → src-B3HrKuAi.js} +1 -1
- package/dist/{test-E4-otKqK.js → test-D16igj4C.js} +245 -38
- package/dist/test.d.ts +39 -7
- package/dist/test.js +2 -2
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-_007gPF8.js → agent8-t3kl5q9K.js} +1 -1
- package/editor/assets/{debug-0DI_MJaq.js → debug-Bu3eeAlO.js} +1 -1
- package/editor/assets/{index-B-6eYZEi.js → index-Df5g8ofT.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +10 -3
- package/skills/incanto-3d-character.md +13 -2
- package/skills/incanto-3d-models.md +40 -0
- package/skills/incanto-assets.md +15 -0
- package/skills/incanto-building-2d-games.md +57 -5
- package/skills/incanto-building-3d-games.md +7 -0
- package/skills/incanto-gameplay-behaviors.md +29 -1
- package/skills/incanto-hud.md +39 -0
- package/skills/incanto-node-reference.md +5 -3
- package/skills/incanto-physics-and-input.md +1 -1
- package/skills/incanto-playtesting.md +22 -4
- package/skills/incanto-verifying-your-game.md +14 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { n as loadScene, w as registerBehavior } from "./loader-
|
|
2
|
-
import { h as Engine } from "./register-
|
|
1
|
+
import { n as loadScene, s as resolveViewport, w as registerBehavior } from "./loader-DwazzlQb.js";
|
|
2
|
+
import { h as Engine } from "./register-uvaZj1KX.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import { n as startRecording } from "./replay-
|
|
4
|
+
import { n as startRecording } from "./replay-j-m6lJ4W.js";
|
|
5
5
|
import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
6
6
|
import { i as getNodeSchema, s as mergeStaticProps } from "./registry-IyWCGe4q.js";
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
-
import { t as registerNodes2D } from "./register-
|
|
9
|
-
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-
|
|
10
|
-
import { t as registerNodesNet } from "./register-
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-DbaI313d.js";
|
|
8
|
+
import { t as registerNodes2D } from "./register-BNPZYJmd.js";
|
|
9
|
+
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-XFuqu5jv.js";
|
|
10
|
+
import { t as registerNodesNet } from "./register-CB11yp21.js";
|
|
11
11
|
import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from "three";
|
|
12
12
|
//#region src/test/framing.ts
|
|
13
13
|
/**
|
|
@@ -38,6 +38,15 @@ const LIGHT_TYPES = new Set([
|
|
|
38
38
|
"OmniLight3D",
|
|
39
39
|
"SpotLight3D"
|
|
40
40
|
]);
|
|
41
|
+
/** Screen-space subtrees: they ignore the camera, so framing has nothing to say. */
|
|
42
|
+
const SCREEN_SPACE = new Set(["UILayer", "HudLayer"]);
|
|
43
|
+
/** Depth a 2D collider is given on the axis it does not have. */
|
|
44
|
+
const FLAT_DEPTH = 1e3;
|
|
45
|
+
/** How far two colliders must actually interpenetrate to be a finding. */
|
|
46
|
+
const TOUCH_EPS_2D = 1;
|
|
47
|
+
const TOUCH_EPS_3D = .01;
|
|
48
|
+
/** What a 2D scene shows when it declares neither a viewport nor a canvas. */
|
|
49
|
+
const DEFAULT_VIEW_2D = [960, 540];
|
|
41
50
|
/**
|
|
42
51
|
* World transform for every node, composed from the tree.
|
|
43
52
|
*
|
|
@@ -50,7 +59,11 @@ function place(scene) {
|
|
|
50
59
|
const type = node.constructor.typeName ?? "Node";
|
|
51
60
|
const n = node;
|
|
52
61
|
const local = new Matrix4();
|
|
53
|
-
if (Array.isArray(n.position))
|
|
62
|
+
if (Array.isArray(n.position)) {
|
|
63
|
+
const r = n.rotation;
|
|
64
|
+
const euler = typeof r === "number" ? new Euler(0, 0, r * DEG) : new Euler((r?.[0] ?? 0) * DEG, (r?.[1] ?? 0) * DEG, (r?.[2] ?? 0) * DEG, "XYZ");
|
|
65
|
+
local.compose(new Vector3(n.position[0] ?? 0, n.position[1] ?? 0, n.position[2] ?? 0), new Quaternion().setFromEuler(euler), new Vector3(n.scale?.[0] ?? 1, n.scale?.[1] ?? 1, n.scale?.[2] ?? 1));
|
|
66
|
+
}
|
|
54
67
|
const matrix = new Matrix4().multiplyMatrices(parent, local);
|
|
55
68
|
out.push({
|
|
56
69
|
node,
|
|
@@ -58,6 +71,7 @@ function place(scene) {
|
|
|
58
71
|
matrix,
|
|
59
72
|
position: new Vector3().setFromMatrixPosition(matrix)
|
|
60
73
|
});
|
|
74
|
+
if (SCREEN_SPACE.has(type)) return;
|
|
61
75
|
for (const child of node.children) walk(child, matrix);
|
|
62
76
|
};
|
|
63
77
|
walk(scene.root, new Matrix4());
|
|
@@ -74,6 +88,54 @@ function draws(node) {
|
|
|
74
88
|
return proto._createObject3D !== base._createObject3D;
|
|
75
89
|
}
|
|
76
90
|
/**
|
|
91
|
+
* A 2D node draws something if its class puts pixels on its backing object.
|
|
92
|
+
*
|
|
93
|
+
* NOT `_createObject2D` — the 3D test's counterpart, and wrong here. 2D
|
|
94
|
+
* drawables all inherit the plain container and hang a quad off it in
|
|
95
|
+
* `_syncObject2D`, so the create hook is identical for a sprite and for a bare
|
|
96
|
+
* `Node2D`, and testing it reports a scene full of sprites as drawing nothing.
|
|
97
|
+
*/
|
|
98
|
+
function draws2D(node) {
|
|
99
|
+
const proto = Object.getPrototypeOf(node);
|
|
100
|
+
let base = proto;
|
|
101
|
+
while (base && base.constructor?.typeName !== "Node2D") {
|
|
102
|
+
base = Object.getPrototypeOf(base);
|
|
103
|
+
if (!base) return false;
|
|
104
|
+
}
|
|
105
|
+
return proto._syncObject2D !== base._syncObject2D;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A 2D node's half-size in pixels, and where its box hangs off the origin.
|
|
109
|
+
*
|
|
110
|
+
* Most 2D nodes are centred on their origin, but a `TileMap2D` hangs cell (0,0)
|
|
111
|
+
* by its TOP-LEFT corner — a whole level reported as a point at its corner is
|
|
112
|
+
* how you conclude the map is off screen when you are standing on it.
|
|
113
|
+
*/
|
|
114
|
+
function box2D(node) {
|
|
115
|
+
if ((node.constructor.typeName ?? "") === "TileMap2D") {
|
|
116
|
+
const map = node;
|
|
117
|
+
const rows = Array.isArray(map.cells) ? map.cells : [];
|
|
118
|
+
const cols = rows.reduce((n, r) => Math.max(n, String(r).length), 0);
|
|
119
|
+
const ts = map.tileSize ?? 32;
|
|
120
|
+
if (rows.length === 0 || cols === 0) return null;
|
|
121
|
+
const half = [cols * ts / 2, rows.length * ts / 2];
|
|
122
|
+
return {
|
|
123
|
+
half,
|
|
124
|
+
centre: half
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const size = node.size;
|
|
128
|
+
if (Array.isArray(size) && typeof size[0] === "number" && typeof size[1] === "number") return {
|
|
129
|
+
half: [Math.abs(size[0]) / 2, Math.abs(size[1]) / 2],
|
|
130
|
+
centre: [0, 0]
|
|
131
|
+
};
|
|
132
|
+
const half = halfExtents(node);
|
|
133
|
+
return half ? {
|
|
134
|
+
half: [half.x, half.y],
|
|
135
|
+
centre: [0, 0]
|
|
136
|
+
} : null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
77
139
|
* How big the node is on screen, from whatever it declares.
|
|
78
140
|
*
|
|
79
141
|
* `size` first (a MeshInstance3D's box, a Terrain3D's ground), then a collider.
|
|
@@ -118,10 +180,110 @@ function halfExtents(node) {
|
|
|
118
180
|
const r = collider.radius ?? .5;
|
|
119
181
|
return new Vector3(r, (collider.height ?? 1) / 2 + r, r);
|
|
120
182
|
}
|
|
183
|
+
if (shape === "rect") {
|
|
184
|
+
const size = collider.size ?? [1, 1];
|
|
185
|
+
return new Vector3((size[0] ?? 1) / 2, (size[1] ?? 1) / 2, .5);
|
|
186
|
+
}
|
|
187
|
+
if (shape === "circle") {
|
|
188
|
+
const r = collider.radius ?? .5;
|
|
189
|
+
return new Vector3(r, r, .5);
|
|
190
|
+
}
|
|
121
191
|
return null;
|
|
122
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* The same question in 2D: an orthographic window of design pixels, centred on
|
|
195
|
+
* the Camera2D and CLAMPED by its limits.
|
|
196
|
+
*
|
|
197
|
+
* The clamp is not a detail — it is the whole point of `limits`, and a report
|
|
198
|
+
* that skips it describes a view the renderer never draws. A following camera
|
|
199
|
+
* sits at the player's y, which in a 544 px world is hundreds of pixels past
|
|
200
|
+
* anything a 540 px window can show.
|
|
201
|
+
*
|
|
202
|
+
* With no camera at all a 2D scene still draws: the documented default view is
|
|
203
|
+
* `(0,0)`–`(w,h)`, so "no camera" is not "nothing is framed" the way it is in 3D.
|
|
204
|
+
*/
|
|
205
|
+
function describeFraming2D(scene, placed, opts) {
|
|
206
|
+
const design = resolveViewport(scene.viewport)?.design;
|
|
207
|
+
const [vw, vh] = opts.viewport ?? design ?? DEFAULT_VIEW_2D;
|
|
208
|
+
const cameras = placed.filter((p) => p.type === "Camera2D");
|
|
209
|
+
const chosen = cameras.find((p) => p.node.current === true) ?? cameras[0] ?? null;
|
|
210
|
+
let camera = null;
|
|
211
|
+
let view = {
|
|
212
|
+
minX: 0,
|
|
213
|
+
minY: 0,
|
|
214
|
+
maxX: vw,
|
|
215
|
+
maxY: vh
|
|
216
|
+
};
|
|
217
|
+
if (chosen) {
|
|
218
|
+
const cam = chosen.node;
|
|
219
|
+
const zoom = Math.max(.01, cam.zoom ?? 1);
|
|
220
|
+
const halfW = vw / (2 * zoom);
|
|
221
|
+
const halfH = vh / (2 * zoom);
|
|
222
|
+
let cx = chosen.position.x;
|
|
223
|
+
let cy = chosen.position.y;
|
|
224
|
+
const limits = cam.limits ?? [];
|
|
225
|
+
if (limits.length === 4) {
|
|
226
|
+
const [minX, minY, maxX, maxY] = limits;
|
|
227
|
+
cx = clampCentred(cx, minX + halfW, maxX - halfW);
|
|
228
|
+
cy = clampCentred(cy, minY + halfH, maxY - halfH);
|
|
229
|
+
}
|
|
230
|
+
view = {
|
|
231
|
+
minX: cx - halfW,
|
|
232
|
+
minY: cy - halfH,
|
|
233
|
+
maxX: cx + halfW,
|
|
234
|
+
maxY: cy + halfH
|
|
235
|
+
};
|
|
236
|
+
camera = {
|
|
237
|
+
path: chosen.node.getPath(),
|
|
238
|
+
at: [
|
|
239
|
+
round(cx),
|
|
240
|
+
round(cy),
|
|
241
|
+
0
|
|
242
|
+
],
|
|
243
|
+
view: [vw, vh],
|
|
244
|
+
zoom
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const wanted = opts.types ? new Set(opts.types) : null;
|
|
248
|
+
const centre = [(view.minX + view.maxX) / 2, (view.minY + view.maxY) / 2];
|
|
249
|
+
const entries = [];
|
|
250
|
+
for (const p of placed) {
|
|
251
|
+
if (p.type === "Camera2D") continue;
|
|
252
|
+
if (!(wanted ? wanted.has(p.type) : draws2D(p.node))) continue;
|
|
253
|
+
const b = box2D(p.node);
|
|
254
|
+
const cx = p.position.x + (b?.centre[0] ?? 0);
|
|
255
|
+
const cy = p.position.y + (b?.centre[1] ?? 0);
|
|
256
|
+
const hx = b?.half[0] ?? 0;
|
|
257
|
+
const hy = b?.half[1] ?? 0;
|
|
258
|
+
const onScreen = cx + hx >= view.minX && cx - hx <= view.maxX && cy + hy >= view.minY && cy - hy <= view.maxY;
|
|
259
|
+
entries.push({
|
|
260
|
+
path: p.node.getPath(),
|
|
261
|
+
type: p.type,
|
|
262
|
+
at: [
|
|
263
|
+
round(p.position.x),
|
|
264
|
+
round(p.position.y),
|
|
265
|
+
0
|
|
266
|
+
],
|
|
267
|
+
where: onScreen ? "onScreen" : "offscreen",
|
|
268
|
+
screen: [round((cx - centre[0]) / ((view.maxX - view.minX) / 2)), round((cy - centre[1]) / ((view.maxY - view.minY) / 2))],
|
|
269
|
+
distance: round(Math.hypot(cx - centre[0], cy - centre[1]))
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
dimension: "2d",
|
|
274
|
+
camera,
|
|
275
|
+
entries,
|
|
276
|
+
lights: [],
|
|
277
|
+
overlaps: findOverlaps(placed, TOUCH_EPS_2D)
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function clampCentred(v, lo, hi) {
|
|
281
|
+
if (lo > hi) return (lo + hi) / 2;
|
|
282
|
+
return Math.min(hi, Math.max(lo, v));
|
|
283
|
+
}
|
|
123
284
|
function describeFraming(scene, opts = {}) {
|
|
124
285
|
const placed = place(scene);
|
|
286
|
+
if (scene.dimension === "2d") return describeFraming2D(scene, placed, opts);
|
|
125
287
|
const cameras = placed.filter((p) => CAMERA_TYPES.has(p.type));
|
|
126
288
|
const chosen = cameras.find((p) => p.node.current === true) ?? cameras[0] ?? null;
|
|
127
289
|
const lights = [];
|
|
@@ -185,10 +347,11 @@ function describeFraming(scene, opts = {}) {
|
|
|
185
347
|
}
|
|
186
348
|
}
|
|
187
349
|
return {
|
|
350
|
+
dimension: "3d",
|
|
188
351
|
camera,
|
|
189
352
|
entries,
|
|
190
353
|
lights,
|
|
191
|
-
overlaps: findOverlaps(placed)
|
|
354
|
+
overlaps: findOverlaps(placed, TOUCH_EPS_3D)
|
|
192
355
|
};
|
|
193
356
|
}
|
|
194
357
|
function cornersOnScreen(box, cam) {
|
|
@@ -207,55 +370,98 @@ function cornersOnScreen(box, cam) {
|
|
|
207
370
|
* no loaded model — and a prop buried inside a platform is the thing you were
|
|
208
371
|
* trying to see anyway.
|
|
209
372
|
*/
|
|
210
|
-
function findOverlaps(placed) {
|
|
211
|
-
const
|
|
373
|
+
function findOverlaps(placed, epsilon) {
|
|
374
|
+
const shapes = [];
|
|
212
375
|
for (const p of placed) {
|
|
213
|
-
const
|
|
214
|
-
if (!
|
|
215
|
-
|
|
376
|
+
const shape = shapeOf(p.node, p.position);
|
|
377
|
+
if (!shape) continue;
|
|
378
|
+
shapes.push({
|
|
216
379
|
path: p.node.getPath(),
|
|
217
|
-
|
|
380
|
+
internal: p.node.name.startsWith("__"),
|
|
381
|
+
shape
|
|
218
382
|
});
|
|
219
383
|
}
|
|
220
384
|
const out = [];
|
|
221
|
-
for (let i = 0; i <
|
|
222
|
-
const a =
|
|
223
|
-
const b =
|
|
385
|
+
for (let i = 0; i < shapes.length; i++) for (let j = i + 1; j < shapes.length; j++) {
|
|
386
|
+
const a = shapes[i];
|
|
387
|
+
const b = shapes[j];
|
|
224
388
|
if (!a || !b) continue;
|
|
225
389
|
if (a.path.startsWith(`${b.path}/`) || b.path.startsWith(`${a.path}/`)) continue;
|
|
226
|
-
if (a.
|
|
390
|
+
if (a.internal && b.internal) continue;
|
|
391
|
+
if (penetrates(a.shape, b.shape, epsilon)) out.push({
|
|
227
392
|
a: a.path,
|
|
228
393
|
b: b.path
|
|
229
394
|
});
|
|
230
395
|
}
|
|
231
396
|
return out;
|
|
232
397
|
}
|
|
398
|
+
function shapeOf(node, centre) {
|
|
399
|
+
const collider = node.collider;
|
|
400
|
+
const kind = collider?.shape;
|
|
401
|
+
if (kind === "sphere" || kind === "circle") return {
|
|
402
|
+
kind: "ball",
|
|
403
|
+
centre,
|
|
404
|
+
radius: collider?.radius ?? .5
|
|
405
|
+
};
|
|
406
|
+
const half = halfExtents(node);
|
|
407
|
+
if (!half) return null;
|
|
408
|
+
return {
|
|
409
|
+
kind: "box",
|
|
410
|
+
centre,
|
|
411
|
+
half: kind === "rect" ? new Vector3(half.x, half.y, FLAT_DEPTH) : half
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/** True when the two shapes share more than `eps` of space, in every axis. */
|
|
415
|
+
function penetrates(a, b, eps) {
|
|
416
|
+
if (a.kind === "ball" && b.kind === "ball") return a.centre.distanceTo(b.centre) < a.radius + b.radius - eps;
|
|
417
|
+
if (a.kind === "ball" || b.kind === "ball") {
|
|
418
|
+
const ball = a.kind === "ball" ? a : b;
|
|
419
|
+
const box = a.kind === "ball" ? b : a;
|
|
420
|
+
return new Vector3(Math.max(0, Math.abs(ball.centre.x - box.centre.x) - box.half.x), Math.max(0, Math.abs(ball.centre.y - box.centre.y) - box.half.y), Math.max(0, Math.abs(ball.centre.z - box.centre.z) - box.half.z)).length() < ball.radius - eps;
|
|
421
|
+
}
|
|
422
|
+
return Math.abs(a.centre.x - b.centre.x) < a.half.x + b.half.x - eps && Math.abs(a.centre.y - b.centre.y) < a.half.y + b.half.y - eps && Math.abs(a.centre.z - b.centre.z) < a.half.z + b.half.z - eps;
|
|
423
|
+
}
|
|
233
424
|
function round(n) {
|
|
234
425
|
return Math.round(n * 1e3) / 1e3;
|
|
235
426
|
}
|
|
236
427
|
/** The report as something to read in a terminal. */
|
|
237
428
|
function framingText(report) {
|
|
238
429
|
const lines = [];
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
430
|
+
const is2d = report.dimension === "2d";
|
|
431
|
+
const unit = is2d ? "px" : "m";
|
|
432
|
+
const c = report.camera;
|
|
433
|
+
if (c && is2d) {
|
|
434
|
+
const [vw = 0, vh = 0] = c.view ?? [];
|
|
435
|
+
const zoom = c.zoom === 1 ? "" : ` zoom ${c.zoom}`;
|
|
436
|
+
lines.push(`camera ${c.path} centred [${c.at[0]}, ${c.at[1]}] showing ${vw}×${vh}px${zoom}`);
|
|
437
|
+
} else if (c) lines.push(`camera ${c.path} at [${c.at.map(round).join(", ")}] looking [${(c.forward ?? []).join(", ")}] fov ${c.fovDeg}`);
|
|
438
|
+
else if (is2d) lines.push("camera: NONE — the view is (0,0)–(design), which may be intentional");
|
|
439
|
+
else lines.push("camera: NONE — this scene frames nothing (no Camera3D)");
|
|
440
|
+
if (!is2d) lines.push(report.lights.length ? `lit by: ${report.lights.join(", ")}` : "lit by: NOTHING — this scene renders black");
|
|
245
441
|
const on = report.entries.filter((e) => e.where === "onScreen");
|
|
246
442
|
const off = report.entries.filter((e) => e.where === "offscreen");
|
|
247
443
|
const behind = report.entries.filter((e) => e.where === "behind");
|
|
248
|
-
lines.push(`${on.length} on screen, ${off.length} off screen, ${behind.length} behind the camera`);
|
|
444
|
+
lines.push(is2d ? `${on.length} in view, ${off.length} outside it` : `${on.length} on screen, ${off.length} off screen, ${behind.length} behind the camera`);
|
|
249
445
|
for (const e of report.entries) {
|
|
250
|
-
const at = `[${e.at.map(round).join(", ")}]`;
|
|
446
|
+
const at = `[${(is2d ? e.at.slice(0, 2) : e.at).map(round).join(", ")}]`;
|
|
251
447
|
const screen = e.screen ? ` screen [${e.screen.join(", ")}]` : "";
|
|
252
|
-
lines.push(` ${e.where.padEnd(9)} ${e.path} (${e.type}) ${at}${screen} ${e.distance}
|
|
448
|
+
lines.push(` ${e.where.padEnd(9)} ${e.path} (${e.type}) ${at}${screen} ${e.distance}${unit}`);
|
|
253
449
|
}
|
|
254
450
|
for (const o of report.overlaps) lines.push(` overlap ${o.a} ∩ ${o.b}`);
|
|
255
451
|
return lines.join("\n");
|
|
256
452
|
}
|
|
257
453
|
//#endregion
|
|
258
454
|
//#region src/test/playtest.ts
|
|
455
|
+
/**
|
|
456
|
+
* Out of the world, in pixels — about two screens under the spawn.
|
|
457
|
+
*
|
|
458
|
+
* Generous on purpose: a player who has actually left the level is falling
|
|
459
|
+
* forever, so a far line costs a few frames and never mistakes a long drop
|
|
460
|
+
* down a shaft for one.
|
|
461
|
+
*/
|
|
462
|
+
const FALL_2D_PX = 1e3;
|
|
463
|
+
/** "Reached it", in pixels — one 32 px tile, next to a ~34 px character. */
|
|
464
|
+
const REACH_2D_PX = 32;
|
|
259
465
|
const CONTROLLERS = new Set(["CharacterController2D", "CharacterController3D"]);
|
|
260
466
|
/**
|
|
261
467
|
* Who the driver is playing as.
|
|
@@ -499,8 +705,9 @@ async function runOnce(json, seed, opts) {
|
|
|
499
705
|
0,
|
|
500
706
|
0
|
|
501
707
|
];
|
|
502
|
-
const
|
|
503
|
-
const
|
|
708
|
+
const down = scene.dimension === "2d" ? 1 : -1;
|
|
709
|
+
const floor = opts.fallBelow ?? start[1] + down * (down > 0 ? FALL_2D_PX : 50);
|
|
710
|
+
const radius = opts.reachRadius ?? (down > 0 ? REACH_2D_PX : 2);
|
|
504
711
|
const recorder = startRecording(engine);
|
|
505
712
|
const rand = () => engine.rng.next();
|
|
506
713
|
const driver = new Driver(engine, rand);
|
|
@@ -527,7 +734,7 @@ async function runOnce(json, seed, opts) {
|
|
|
527
734
|
outcome = "error";
|
|
528
735
|
break;
|
|
529
736
|
}
|
|
530
|
-
if (player && at[1]
|
|
737
|
+
if (player && (at[1] - floor) * down > 0) {
|
|
531
738
|
outcome = "fell";
|
|
532
739
|
break;
|
|
533
740
|
}
|
|
@@ -584,7 +791,8 @@ async function playtest(json, opts = {}) {
|
|
|
584
791
|
signals: [...new Set(signals)],
|
|
585
792
|
actions,
|
|
586
793
|
inertActions: [],
|
|
587
|
-
declaresWin
|
|
794
|
+
declaresWin,
|
|
795
|
+
seconds: opts.seconds ?? 60
|
|
588
796
|
};
|
|
589
797
|
}
|
|
590
798
|
function pct(n, total) {
|
|
@@ -605,8 +813,7 @@ function playtestText(report) {
|
|
|
605
813
|
const { runs, targets, signals } = report;
|
|
606
814
|
const total = runs.length;
|
|
607
815
|
const lines = [];
|
|
608
|
-
|
|
609
|
-
lines.push(`${total} runs × up to ${secs}s`);
|
|
816
|
+
lines.push(`${total} runs × up to ${report.seconds}s`);
|
|
610
817
|
lines.push("");
|
|
611
818
|
const won = runs.filter((r) => r.outcome === "won");
|
|
612
819
|
if (won.length > 0) lines.push(` ✓ reached "won" in ${pct(won.length, total)} median ${Math.round(median(won.map((r) => r.timeMs)) / 1e3)}s`);
|
|
@@ -1414,10 +1621,10 @@ async function runScript(json, opts) {
|
|
|
1414
1621
|
engine.setScene(scene);
|
|
1415
1622
|
const physics = opts.physics ?? "auto";
|
|
1416
1623
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
1417
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
1624
|
+
const { enablePhysics2D } = await import("./physics-2d-_9VBOHn6.js").then((n) => n.r);
|
|
1418
1625
|
await enablePhysics2D(engine);
|
|
1419
1626
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
1420
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
1627
|
+
const { enablePhysics3D } = await import("./physics-3d-DrpF5hcG.js").then((n) => n.r);
|
|
1421
1628
|
await enablePhysics3D(engine);
|
|
1422
1629
|
}
|
|
1423
1630
|
const failures = [];
|
|
@@ -1530,10 +1737,10 @@ async function createPlaySession(json, opts = {}) {
|
|
|
1530
1737
|
engine.setScene(scene);
|
|
1531
1738
|
const physics = opts.physics ?? "auto";
|
|
1532
1739
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
1533
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
1740
|
+
const { enablePhysics2D } = await import("./physics-2d-_9VBOHn6.js").then((n) => n.r);
|
|
1534
1741
|
await enablePhysics2D(engine);
|
|
1535
1742
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
1536
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
1743
|
+
const { enablePhysics3D } = await import("./physics-3d-DrpF5hcG.js").then((n) => n.r);
|
|
1537
1744
|
await enablePhysics3D(engine);
|
|
1538
1745
|
}
|
|
1539
1746
|
const stepMs = 1e3 / (opts.fixedHz ?? 60);
|
package/dist/test.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Et as Node, P as Scene, b as Engine, kt as LogEntry, n as BehaviorCtor } from "./behavior-62q0HWBO.js";
|
|
2
2
|
import { c as JsonValue, i as SceneJson } from "./schema-CFeioQRE.js";
|
|
3
3
|
import { t as LoadSceneOptions } from "./loader-CeyU_bm1.js";
|
|
4
|
-
import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-
|
|
4
|
+
import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-DYdy1wb0.js";
|
|
5
5
|
|
|
6
6
|
//#region src/test/facing.d.ts
|
|
7
7
|
interface FacingOptions {
|
|
@@ -52,12 +52,23 @@ interface FramingEntry {
|
|
|
52
52
|
distance: number;
|
|
53
53
|
}
|
|
54
54
|
interface FramingReport {
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Which report this is. The question is the same — can the camera see it —
|
|
57
|
+
* but the machinery and the UNITS are not: 3D projects a perspective frustum
|
|
58
|
+
* and measures metres, 2D clips an orthographic window and measures pixels.
|
|
59
|
+
*/
|
|
60
|
+
dimension: "2d" | "3d";
|
|
61
|
+
/**
|
|
62
|
+
* 3D: null when the scene declares no camera — nothing frames anything.
|
|
63
|
+
* 2D: null means the documented default view, `(0,0)`–`(design)`.
|
|
64
|
+
*/
|
|
56
65
|
camera: {
|
|
57
66
|
path: string;
|
|
58
|
-
at: [number, number, number]; /**
|
|
59
|
-
forward
|
|
60
|
-
fovDeg
|
|
67
|
+
at: [number, number, number]; /** 3D: the unit vector it points along. Absent in 2D. */
|
|
68
|
+
forward?: [number, number, number]; /** 3D only. */
|
|
69
|
+
fovDeg?: number; /** 2D only: world pixels visible, after `zoom`. */
|
|
70
|
+
view?: [number, number]; /** 2D only. */
|
|
71
|
+
zoom?: number;
|
|
61
72
|
} | null;
|
|
62
73
|
entries: FramingEntry[];
|
|
63
74
|
/**
|
|
@@ -76,6 +87,11 @@ interface FramingOptions {
|
|
|
76
87
|
aspect?: number;
|
|
77
88
|
/** Report only these node types (default: everything that draws). */
|
|
78
89
|
types?: readonly string[];
|
|
90
|
+
/**
|
|
91
|
+
* 2D only: the window in world pixels. Defaults to the scene's
|
|
92
|
+
* `viewport.design`, then 960×540 — headless there is no canvas to ask.
|
|
93
|
+
*/
|
|
94
|
+
viewport?: readonly [number, number];
|
|
79
95
|
}
|
|
80
96
|
declare function describeFraming(scene: Scene, opts?: FramingOptions): FramingReport;
|
|
81
97
|
/** The report as something to read in a terminal. */
|
|
@@ -133,9 +149,16 @@ interface PlaytestOptions {
|
|
|
133
149
|
behaviors?: Record<string, BehaviorCtor>;
|
|
134
150
|
stubMissingBehaviors?: boolean;
|
|
135
151
|
resolveScene?: LoadSceneOptions["resolveScene"];
|
|
136
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* The y past which the player has left the world, in the SCENE's own
|
|
154
|
+
* direction: 3D counts down (−y), 2D counts down the screen (+y).
|
|
155
|
+
* Default: 50 m under the start in 3D, 1000 px under it in 2D.
|
|
156
|
+
*/
|
|
137
157
|
fallBelow?: number;
|
|
138
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* How close counts as "reached", in the scene's own units. Default 2 (metres)
|
|
160
|
+
* in 3D, 32 (pixels — one tile) in 2D.
|
|
161
|
+
*/
|
|
139
162
|
reachRadius?: number;
|
|
140
163
|
}
|
|
141
164
|
interface PlaytestRun {
|
|
@@ -174,6 +197,15 @@ interface PlaytestReport {
|
|
|
174
197
|
* author looking for a bug that was never there.
|
|
175
198
|
*/
|
|
176
199
|
declaresWin: boolean;
|
|
200
|
+
/**
|
|
201
|
+
* The per-run clock the caller asked for, in simulated seconds.
|
|
202
|
+
*
|
|
203
|
+
* The header used to read this off the FIRST run's length, which is the one
|
|
204
|
+
* number guaranteed to be wrong when something ends runs early: a 60-second
|
|
205
|
+
* playtest whose every run died at a second announced itself as "20 runs ×
|
|
206
|
+
* up to 1s", hiding the very fact it was reporting.
|
|
207
|
+
*/
|
|
208
|
+
seconds: number;
|
|
177
209
|
}
|
|
178
210
|
/**
|
|
179
211
|
* Who the driver is playing as.
|
package/dist/test.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { r as auditScene } from "./replay-
|
|
2
|
-
import { _ as playtestText, a as registerAllNodes, c as ladderText, d as feelText, f as facingReport, g as playtest, h as findPlayer, i as findFloatingProps, l as ladderVerdict, m as failingReplays, n as createPlaySession, o as runScript, p as facingText, r as describeCapture, s as validateScene, t as captureScene, u as feelReport, v as describeFraming, y as framingText } from "./test-
|
|
1
|
+
import { r as auditScene } from "./replay-j-m6lJ4W.js";
|
|
2
|
+
import { _ as playtestText, a as registerAllNodes, c as ladderText, d as feelText, f as facingReport, g as playtest, h as findPlayer, i as findFloatingProps, l as ladderVerdict, m as failingReplays, n as createPlaySession, o as runScript, p as facingText, r as describeCapture, s as validateScene, t as captureScene, u as feelReport, v as describeFraming, y as framingText } from "./test-D16igj4C.js";
|
|
3
3
|
export { auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, ladderText, ladderVerdict, playtest, playtestText, registerAllNodes, runScript, validateScene };
|
package/dist/vite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as VERSION } from "./src-
|
|
1
|
+
import { t as VERSION } from "./src-B3HrKuAi.js";
|
|
2
2
|
import { n as diffSignatures } from "./frame-report-njybhZon.js";
|
|
3
|
-
import { s as validateScene } from "./test-
|
|
3
|
+
import { s as validateScene } from "./test-D16igj4C.js";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
6
6
|
//#region src/vite/frame-endpoint.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./index-
|
|
1
|
+
import{n as e}from"./index-Df5g8ofT.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{i as e,r as t,t as n}from"./index-
|
|
1
|
+
import{i as e,r as t,t as n}from"./index-Df5g8ofT.js";function r(e,t,n,r=1){let i=Math.min(e.x0,e.x1)*r,a=Math.max(e.x0,e.x1)*r,o=Math.min(e.y0,e.y1)*r,s=Math.max(e.y0,e.y1)*r,c=Math.max(0,Math.min(Math.round(i),t-1)),l=Math.max(0,Math.min(Math.round(o),n-1));return{x:c,y:l,w:Math.max(1,Math.min(Math.round(a-i),t-c)),h:Math.max(1,Math.min(Math.round(s-o),n-l))}}function i(e,t,n,r){let{x:i,y:a,w:o,h:s}=r,c=new Uint8ClampedArray(o*s*4);for(let n=0;n<s;n++){let r=((a+n)*t+i)*4,s=n*o*4;c.set(e.subarray(r,r+o*4),s)}return{pixels:c,width:o,height:s}}function a(e){if(!e)return`// no scene loaded`;let t=e.source??e;return JSON.stringify(t,null,2)}async function o(e){let t=globalThis.navigator;if(!t?.clipboard?.writeText)return!1;try{return await t.clipboard.writeText(e),!0}catch{return!1}}async function s(e,t){let n=globalThis,r=n.navigator?.clipboard?.write;if(!r||!n.ImageData||!n.ClipboardItem)return!1;try{let i=e.createElement(`canvas`);i.width=t.width,i.height=t.height;let a=i.getContext(`2d`);if(!a)return!1;a.putImageData(new n.ImageData(t.pixels,t.width,t.height),0,0);let o=await new Promise(e=>i.toBlob(e,`image/png`));return o?(await r.call(n.navigator?.clipboard,[new n.ClipboardItem({"image/png":o})]),!0):!1}catch{return!1}}var c=[`nw`,`n`,`ne`,`e`,`se`,`s`,`sw`,`w`],l={nw:[0,0],n:[.5,0],ne:[1,0],e:[1,.5],se:[1,1],s:[.5,1],sw:[0,1],w:[0,.5]},u={nw:`nwse-resize`,se:`nwse-resize`,ne:`nesw-resize`,sw:`nesw-resize`,n:`ns-resize`,s:`ns-resize`,e:`ew-resize`,w:`ew-resize`},d=(e,t,n)=>Math.max(t,Math.min(n,e));function f(e){return{x:Math.min(e.x0,e.x1),y:Math.min(e.y0,e.y1),w:Math.abs(e.x1-e.x0),h:Math.abs(e.y1-e.y0)}}function p(e,t,n,r,i,a){let o=e.x,s=e.y,c=e.x+e.w,l=e.y+e.h;return t.includes(`w`)&&(o=d(o+n,0,i)),t.includes(`e`)&&(c=d(c+n,0,i)),t.includes(`n`)&&(s=d(s+r,0,a)),t.includes(`s`)&&(l=d(l+r,0,a)),{x:Math.min(o,c),y:Math.min(s,l),w:Math.max(1,Math.abs(c-o)),h:Math.max(1,Math.abs(l-s))}}function m(e,t,n,r,i){return{...e,x:d(e.x+t,0,Math.max(0,r-e.w)),y:d(e.y+n,0,Math.max(0,i-e.h))}}function h(e,t,n,r,i,a=8){let o=d(e.x+e.w/2-t/2,a,Math.max(a,r-t-a)),s=e.y+e.h+a,c=e.y-n-a;return{x:o,y:s+n<=i-a?s:c>=a?c:d(s,a,Math.max(a,i-n-a))}}function g(e,t,n,r,i,a,o=180,s=120){let c=Math.max(o,n),l=Math.max(s,r);return{x:Math.min(Math.max(0,e),Math.max(0,i-c)),y:Math.min(Math.max(0,t),Math.max(0,a-l)),w:c,h:l}}function _(e,t,n,r,i,a=4){let o=Math.min(Math.max(0,e.x),Math.max(0,r-t)),s=e.y+e.h+a,c=e.y-n-a;return s+n<=i?{x:o,y:s}:c>=0?{x:o,y:c}:{x:o,y:Math.min(Math.max(0,s),Math.max(0,i-n))}}function v(e,t,n){let r=e=>{e.key===`Escape`&&n()},i=e=>{let r=e.target;if(r){for(let e of t)if(e&&(e===r||e.contains?.(r)))return;n()}};return e.addEventListener?.(`keydown`,r),e.addEventListener?.(`pointerdown`,i),()=>{e.removeEventListener?.(`keydown`,r),e.removeEventListener?.(`pointerdown`,i)}}function y(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var b=`rgba(18, 20, 26, 0.92)`,x=`1px solid rgba(255,255,255,0.14)`,S=`12px ui-monospace, SFMono-Regular, Menlo, monospace`,C=class{host;el;body;onClose=()=>{};x;y;w;h;constructor(e,t,n,r){this.host=t,this.x=r.x,this.y=r.y,this.w=r.w,this.h=r.h,this.el=e.createElement(`div`),y(this.el,{position:`absolute`,background:b,border:x,borderRadius:`8px`,color:`rgba(255,255,255,0.88)`,font:S,display:`flex`,flexDirection:`column`,overflow:`hidden`,zIndex:`40`,pointerEvents:`auto`,boxShadow:`0 8px 28px rgba(0,0,0,0.45)`});let i=e.createElement(`div`);i.textContent=n,y(i,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(i);let a=e.createElement(`div`);a.textContent=`×`,a.title=`close`,y(a,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),a.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(a),this.body=e.createElement(`div`),y(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let o=e.createElement(`div`);o.textContent=`◢`,y(o,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(o),this.wireDrag(i,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(o,(e,t)=>{this.w+=e,this.h+=t,this.layout()}),this.layout(),t.appendChild(this.el)}remove(){this.el.remove()}layout(){let e=this.host.getBoundingClientRect(),t=g(this.x,this.y,this.w,this.h,e.width,e.height);this.x=t.x,this.y=t.y,this.w=t.w,this.h=t.h,y(this.el,{left:`${t.x}px`,top:`${t.y}px`,width:`${t.w}px`,height:`${t.h}px`})}wireDrag(e,t){let n=null,r=0,i=0;e.addEventListener(`pointerdown`,t=>{n=t.pointerId,r=t.clientX,i=t.clientY,e.setPointerCapture?.(t.pointerId)}),e.addEventListener(`pointermove`,e=>{e.pointerId===n&&(t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY)});let a=e=>{e.pointerId===n&&(n=null)};e.addEventListener(`pointerup`,a),e.addEventListener(`pointercancel`,a)}},w=4,T=`#6ee7dc`,E=`rgba(18,20,26,0.94)`,D=`12px ui-monospace, Menlo, monospace`;function O(e){let{doc:t,container:n}=e,r=t.createElement(`div`);y(r,{position:`absolute`,inset:`0`,cursor:`crosshair`,zIndex:`80`,pointerEvents:`auto`,userSelect:`none`,touchAction:`none`});let i=t.createElement(`div`);y(i,{position:`absolute`,inset:`0`,background:`rgba(10,14,20,0.35)`,pointerEvents:`none`}),r.appendChild(i);let a=t.createElement(`div`);y(a,{position:`absolute`,outline:`1px solid ${T}`,boxShadow:`0 0 0 9999px rgba(10,14,20,0.35)`,cursor:`grab`,display:`none`,pointerEvents:`auto`}),r.appendChild(a);let o=t.createElement(`div`);y(o,{position:`absolute`,padding:`2px 6px`,borderRadius:`4px`,background:E,color:T,font:D,pointerEvents:`none`,display:`none`,whiteSpace:`nowrap`}),r.appendChild(o);let s=t.createElement(`div`);s.textContent=`drag to select · Esc to cancel`,y(s,{position:`absolute`,top:`10px`,left:`50%`,transform:`translateX(-50%)`,padding:`4px 10px`,borderRadius:`6px`,background:E,color:`rgba(255,255,255,0.85)`,font:D,pointerEvents:`none`,whiteSpace:`nowrap`}),r.appendChild(s);let d=null,g=`idle`,_={x:0,y:0},v={x:0,y:0,w:0,h:0},b=()=>({w:n.clientWidth||1,h:n.clientHeight||1});for(let e of c){let n=t.createElement(`div`),[r,i]=l[e];y(n,{position:`absolute`,left:`${r*100}%`,top:`${i*100}%`,width:`10px`,height:`10px`,marginLeft:`-5px`,marginTop:`-5px`,borderRadius:`2px`,background:T,border:`1px solid rgba(10,14,20,0.75)`,cursor:u[e],pointerEvents:`auto`}),n.addEventListener(`pointerdown`,t=>{k(t,e)}),a.appendChild(n)}let x=t.createElement(`div`);y(x,{position:`absolute`,display:`none`,gap:`6px`,padding:`6px`,borderRadius:`8px`,background:E,border:`1px solid rgba(255,255,255,0.18)`,font:D,pointerEvents:`auto`,whiteSpace:`nowrap`,boxShadow:`0 4px 16px rgba(0,0,0,0.45)`});let S=(e,n,r)=>{let i=t.createElement(`div`);return i.textContent=e,y(i,{padding:`5px 10px`,borderRadius:`5px`,cursor:`pointer`,color:n?`#08121a`:`rgba(255,255,255,0.85)`,background:n?T:`rgba(255,255,255,0.08)`,userSelect:`none`}),i.addEventListener(`pointerdown`,e=>{C(e),r()}),i};x.appendChild(S(`Copy to clipboard`,!0,()=>N())),x.appendChild(S(`Cancel`,!1,()=>I())),r.appendChild(x);function C(e){e.preventDefault?.(),e.stopPropagation?.()}function O(e){let t=e,r=n.getBoundingClientRect?.()??{left:0,top:0};return{x:(t.clientX??0)-r.left,y:(t.clientY??0)-r.top}}function k(e,t){C(e),_=O(e),g=t,t===`create`?d={x:_.x,y:_.y,w:0,h:0}:d&&(v=d),y(x,{display:`none`}),y(a,{cursor:t===`move`?`grabbing`:`crosshair`});let n=e.pointerId;n!==void 0&&r.setPointerCapture?.(n),A()}function A(){if(!d){y(a,{display:`none`}),y(o,{display:`none`}),y(i,{display:`block`});return}y(i,{display:`none`}),y(a,{display:`block`,left:`${d.x}px`,top:`${d.y}px`,width:`${d.w}px`,height:`${d.h}px`});let t=e.scale();o.textContent=`${Math.round(d.w*t)}×${Math.round(d.h*t)}`;let n=d.y-24;y(o,{display:`block`,left:`${Math.max(2,d.x)}px`,top:`${n>=2?n:d.y+4}px`})}function j(){if(!d||d.w<w||d.h<w)return;y(x,{display:`flex`});let e=b(),t=x.offsetWidth||200,n=x.offsetHeight||36,r=h(d,t,n,e.w,e.h);y(x,{left:`${r.x}px`,top:`${r.y}px`})}r.addEventListener(`pointerdown`,e=>{k(e,`create`)}),a.addEventListener(`pointerdown`,e=>{k(e,`move`)}),r.addEventListener(`pointermove`,e=>{if(g===`idle`||!d)return;let t=O(e),n=b();if(g===`create`){let e=f({x0:_.x,y0:_.y,x1:t.x,y1:t.y});d={x:Math.max(0,Math.min(e.x,n.w)),y:Math.max(0,Math.min(e.y,n.h)),w:Math.min(e.w,n.w-Math.max(0,Math.min(e.x,n.w))),h:Math.min(e.h,n.h-Math.max(0,Math.min(e.y,n.h)))}}else d=g===`move`?m(v,t.x-_.x,t.y-_.y,n.w,n.h):p(v,g,t.x-_.x,t.y-_.y,n.w,n.h);A()});let M=()=>{g!==`idle`&&(g===`create`&&d&&(d.w<w||d.h<w)&&(d=null),g=`idle`,y(a,{cursor:`grab`}),s.textContent=d?`drag the handles or the middle · Enter to copy · Esc to cancel`:`drag to select · Esc to cancel`,A(),j())};r.addEventListener(`pointerup`,M),r.addEventListener(`pointercancel`,M);function N(){let t=d;I(),t&&e.onCopy(t)}let P=e=>{let t=e.key;t===`Escape`?I():t===`Enter`&&d&&N()};t.addEventListener?.(`keydown`,P);let F=!1;function I(){F||(F=!0,t.removeEventListener?.(`keydown`,P),r.remove(),e.onClose())}return n.appendChild(r),{element:r,close:I}}var k=96,A=200,j=8192;function M(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function N(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,A).map(e=>JSON.stringify(e)),n=e.length-A;return t.join(`
|
|
2
2
|
`)+(n>0?`\n… ${n} more`:``)}return t.length>j?`${t.slice(0,j)} … (${t.length-j} more chars)`:t}function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var F=2,I=4;function L(e,t={}){let n=t.doc??(typeof document<`u`?document:null);if(!n)return null;let r=t.container??(typeof document<`u`?document.body:null);return!r||typeof r.appendChild!=`function`?null:new B(e,r,n,t.statsSource,t.actions,t.frameSource)}var R=300,z=[`log`,`info`,`warn`,`error`,`debug`],B=class{engine;container;doc;statsSource;actions;frameSource;panels=new Map;cleanups=[];menuButton;dropdown=null;dismissDropdown=null;menuPos={x:8,y:8};selected=null;collapsedFlags=new Map;statsChip=null;logRows=[];levelEnabled={debug:!0,info:!0,warn:!0,error:!0};consoleCapture=!1;consolePatched=[];frame=0;editing=0;detailOpen=new WeakMap;resumeScale=1;timeEls=null;hovering=!1;constructor(e,t,n,r,i=[],a){this.engine=e,this.container=t,this.doc=n,this.statsSource=r,this.actions=i,this.frameSource=a,this.menuButton=n.createElement(`div`),this.menuButton.textContent=`☰ debug`,y(this.menuButton,{position:`absolute`,top:`8px`,left:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,cursor:`pointer`,userSelect:`none`,zIndex:`50`,pointerEvents:`auto`}),this.bindMenuDrag(),t.style.position||(t.style.position=`relative`),t.appendChild(this.menuButton),this.cleanups.push(e.log.added.connect(e=>{this.pushLog({level:e.level,source:`engine`,text:e.parts.map(U).join(` `)})})),this.cleanups.push(e.sceneChanged.connect(()=>{this.selected=null,this.applyColliderScope(),this.renderExplorer(),this.renderInspector()})),this.cleanups.push(e.updated.connect(()=>{this.frame+=1,this.frame%30==0&&(this.renderExplorer(),this.renderStats(),this.editing===0&&!this.hovering&&this.renderInspector(),this.syncTime())}))}isOpen(e){return e===`copyScene`||e===`captureRegion`?!1:e===`colliders`?this.colliderMode!==`off`:e===`stats`?this.statsChip!==null:this.panels.has(e)}colliderMode=`off`;setColliders(e){this.colliderMode=e,this.applyColliderScope()}applyColliderScope(){let e=this.colliderMode;for(let t of n(`2d`).concat(n(`3d`)))t.debugDraw=e!==`off`,t.debugScope=e===`selected`?this.selected:null}open(e){if(e===`stats`){this.openStatsChip();return}if(this.panels.has(e)){e===`explorer`&&this.renderExplorer(),e===`inspector`&&this.renderInspector(),e===`logs`&&this.renderLogs(),e===`time`&&this.renderTime();return}let t=new C(this.doc,this.container,{explorer:`Explorer`,inspector:`Inspector`,logs:`Logs`,time:`Time`}[e],{explorer:{x:12,y:44,w:240,h:320},inspector:{x:264,y:44,w:280,h:320},logs:{x:12,y:380,w:532,h:200},time:{x:556,y:44,w:260,h:150}}[e]);t.onClose=()=>this.close(e),e===`inspector`&&(t.body.addEventListener(`pointerenter`,()=>{this.hovering=!0}),t.body.addEventListener(`pointerleave`,()=>{this.hovering=!1})),this.panels.set(e,t),e===`explorer`&&this.renderExplorer(),e===`inspector`&&this.renderInspector(),e===`logs`&&this.renderLogs(),e===`time`&&this.renderTime()}close(e){if(e===`inspector`&&(this.hovering=!1),e===`stats`){this.statsChip?.remove(),this.statsChip=null;return}let t=this.panels.get(e);t&&(t.remove(),this.panels.delete(e))}toggle(e){if(e===`copyScene`){this.copyScene();return}if(e===`captureRegion`){this.captureRegion();return}if(e===`colliders`){this.setColliders({off:`all`,all:`selected`,selected:`off`}[this.colliderMode]);return}this.isOpen(e)?this.close(e):this.open(e)}setLevelEnabled(e,t){this.levelEnabled[e]=t,this.renderLogs()}setConsoleCapture(e){if(e!==this.consoleCapture)if(this.consoleCapture=e,e){this.renderLogs();let e=console;for(let t of z){let n=e[t];e[t]=(...e)=>{n.apply(console,e);let r=t===`log`?`info`:t;this.pushLog({level:r,source:`console`,text:e.map(U).join(` `)})},this.consolePatched.push(()=>{e[t]=n})}}else{for(let e of this.consolePatched)e();this.consolePatched=[],this.renderLogs()}}dispose(){this.engine.debugSelection=null,this.setColliders(`off`),this.setConsoleCapture(!1);for(let e of this.cleanups)e();for(let e of[...this.panels.keys()])this.close(e);this.close(`stats`),this.closeDropdown(),this.menuButton.remove()}menuLabel(e,t){let n=e===`colliders`&&this.colliderMode!==`off`?` · ${this.colliderMode}`:``;return`${this.isOpen(e)?`✓ `:``}${t}${n}`}bindMenuDrag(){let e=null,t=!1;this.menuButton.addEventListener(`pointerdown`,n=>{let r=n;e={x:r.clientX??0,y:r.clientY??0,bx:this.menuPos.x,by:this.menuPos.y},t=!1,r.pointerId!==void 0&&this.menuButton.setPointerCapture?.(r.pointerId)}),this.menuButton.addEventListener(`pointermove`,n=>{if(!e)return;let r=n,i=(r.clientX??0)-e.x,a=(r.clientY??0)-e.y;!t&&Math.hypot(i,a)<I||(t||(t=!0,this.closeDropdown(),y(this.menuButton,{cursor:`grabbing`})),this.moveMenuButton(e.bx+i,e.by+a))});let n=()=>{if(!e)return;let n=t;e=null,t=!1,y(this.menuButton,{cursor:`pointer`}),n||this.toggleDropdown()};this.menuButton.addEventListener(`pointerup`,n),this.menuButton.addEventListener(`pointercancel`,n)}moveMenuButton(e,t){let n=this.menuButton,r=g(e,t,n.offsetWidth??0,n.offsetHeight??0,this.container.clientWidth||0,this.container.clientHeight||0,0,0);this.menuPos={x:r.x,y:r.y},y(this.menuButton,{left:`${r.x}px`,top:`${r.y}px`})}closeDropdown(){this.dismissDropdown?.(),this.dismissDropdown=null,this.dropdown?.remove(),this.dropdown=null}toggleDropdown(){if(this.dropdown){this.closeDropdown();return}let e=this.doc.createElement(`div`);y(e,{position:`absolute`,background:`rgba(18,20,26,0.95)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,font:`12px ui-monospace, Menlo, monospace`,color:`rgba(255,255,255,0.85)`,zIndex:`60`,pointerEvents:`auto`,overflow:`hidden`});for(let[t,n]of[[`explorer`,`Explorer`],[`inspector`,`Inspector`],[`logs`,`Logs`],[`time`,`Time`],[`copyScene`,`Copy scene JSON`],[`captureRegion`,`Capture region`],[`stats`,`Stats`],[`colliders`,`Colliders`]]){let r=this.doc.createElement(`div`);r.textContent=this.menuLabel(t,n),y(r,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`}),r.addEventListener(`click`,()=>{if(this.toggle(t),t===`colliders`){r.textContent=this.menuLabel(t,n);return}this.closeDropdown()}),e.appendChild(r)}for(let t of this.actions){let n=this.doc.createElement(`div`);n.textContent=t.label,y(n,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`,borderTop:`1px solid rgba(255,255,255,0.14)`,color:`rgba(158,232,220,0.95)`}),n.addEventListener(`click`,()=>{this.closeDropdown(),t.run()}),e.appendChild(n)}this.container.appendChild(e),this.dropdown=e;let t=e,n=this.menuButton,r=_({x:this.menuPos.x,y:this.menuPos.y,w:n.offsetWidth??0,h:n.offsetHeight??26},t.offsetWidth??0,t.offsetHeight??0,this.container.clientWidth||0,this.container.clientHeight||0);y(e,{left:`${r.x}px`,top:`${r.y}px`}),this.dismissDropdown=v(this.doc,[e,this.menuButton],()=>this.closeDropdown())}openStatsChip(){if(this.statsChip)return;let e=this.doc.createElement(`div`);y(e,{position:`absolute`,top:`8px`,right:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,textAlign:`right`,whiteSpace:`pre`,userSelect:`none`,pointerEvents:`none`,zIndex:`70`}),this.container.appendChild(e),this.statsChip=e,this.renderStats()}renderStats(){let e=this.statsChip;if(!e)return;let t=this.engine.stats(),n=this.statsSource?.()??{},r=[`nodes ${t.nodes}`,...n.triangles===void 0?[]:[`tris ${H(n.triangles)}`],...n.drawCalls===void 0?[]:[`calls ${n.drawCalls}`]].join(` · `);e.textContent=`${Math.round(t.fps)} fps · ${t.frameMs.toFixed(1)} ms\n${r}`}renderExplorer(){let e=this.panels.get(`explorer`);if(!e)return;let t=e.body.scrollTop;V(e.body);let n=this.engine.scene?.root;if(!n){e.body.scrollTop=t;return}let r=(e,t)=>{let n=e.constructor,i=e.children.length>0,a=this.collapsedFlags.get(e)===!0,o=this.doc.createElement(`div`);y(o,{display:`flex`,alignItems:`center`,cursor:`pointer`,padding:`1px 2px`,borderRadius:`3px`,background:e===this.selected?`rgba(110,160,255,0.25)`:`transparent`});let s=this.doc.createElement(`span`);s.textContent=i?a?`▸`:`▾`:`·`,y(s,{width:`14px`,flex:`none`,textAlign:`center`,opacity:i?`0.85`:`0.25`,userSelect:`none`}),i&&s.addEventListener(`click`,t=>{t.stopPropagation?.(),this.collapsedFlags.set(e,!a),this.renderExplorer()}),o.appendChild(s);let c=this.doc.createElement(`span`);c.textContent=e.name,y(c,{whiteSpace:`nowrap`}),o.appendChild(c);let l=this.doc.createElement(`span`);if(l.textContent=` ${n.typeName}`,y(l,{opacity:`0.45`,whiteSpace:`nowrap`,fontSize:`10px`}),o.appendChild(l),i&&a){let t=this.doc.createElement(`span`);t.textContent=` (${e.children.length})`,y(t,{opacity:`0.35`,fontSize:`10px`}),o.appendChild(t)}if(o.addEventListener(`click`,()=>{this.selected=e,this.engine.debugSelection=e,this.applyColliderScope(),this.open(`inspector`),this.renderExplorer(),this.renderInspector()}),t.appendChild(o),i&&!a){let n=this.doc.createElement(`div`);y(n,{marginLeft:`8px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});for(let t of e.children)r(t,n);t.appendChild(n)}};r(n,e.body),e.body.scrollTop=t}renderInspector(){let e=this.panels.get(`inspector`);if(!e)return;let n=e.body.scrollTop;V(e.body);let r=this.selected;if(r&&r.tree===null&&(this.selected=null,this.engine.debugSelection=null,this.applyColliderScope(),r=null),!r){let t=this.doc.createElement(`div`);t.textContent=`select a node in the Explorer`,y(t,{opacity:`0.6`}),e.body.appendChild(t);return}let i=r.constructor,a=this.doc.createElement(`div`);if(a.textContent=`${r.getPath()} · ${i.typeName}${r.uid?` · ${r.uid}`:``}`,y(a,{fontWeight:`700`,marginBottom:`6px`,whiteSpace:`pre-wrap`}),e.body.appendChild(a),r.groups.size>0){let t=this.doc.createElement(`div`);t.textContent=`groups: ${[...r.groups].join(`, `)}`,y(t,{opacity:`0.7`,marginBottom:`6px`}),e.body.appendChild(t)}let o=t(i),s=r;for(let t of Object.keys(o)){let n=o[t];this.renderValueRow(e.body,r,t,{read:()=>s[t],write:e=>{s[t]=e},options:n?.options,variants:n?.variants})}e.body.scrollTop=n}renderValueRow(t,n,r,i){let a=i.read(),o=i.options,s={get[r](){return i.read()},set[r](e){i.write(e)}},c=this.doc.createElement(`div`);y(c,{display:`flex`,gap:`6px`,alignItems:`center`,margin:`2px 0`});let l=this.doc.createElement(`div`);l.textContent=r,y(l,{minWidth:`84px`,opacity:`0.75`}),c.appendChild(l);let u=(e,t)=>{let i=this.doc.createElement(`input`);return i.type=`number`,i.value=String(e),y(i,G(`70px`)),this.trackEditing(i),i.addEventListener(`change`,()=>{let e=Number(i.value);Number.isFinite(e)?t(e):i.value=String(n[r])}),i};if(typeof a==`number`)c.appendChild(u(a,e=>{s[r]=e}));else if(typeof a==`boolean`){let e=this.doc.createElement(`input`);e.type=`checkbox`,e.checked=a,this.trackEditing(e),e.addEventListener(`change`,()=>{s[r]=e.checked}),c.appendChild(e)}else if(typeof a==`string`&&o&&o.length>0){let e=this.doc.createElement(`select`);for(let t of o.includes(a)?o:[a,...o]){let n=this.doc.createElement(`option`);n.value=t,n.textContent=t,t===a&&(n.selected=!0),e.appendChild(n)}e.value=a,this.trackEditing(e),e.addEventListener(`change`,()=>{s[r]=e.value}),y(e,G(`110px`)),c.appendChild(e)}else if(typeof a==`string`){let e=this.doc.createElement(`input`);e.type=`text`,e.value=a,this.trackEditing(e),y(e,G(`140px`)),e.addEventListener(`change`,()=>{s[r]=e.value}),c.appendChild(e)}else if(Array.isArray(a)&&a.length<=8&&a.every(e=>typeof e==`number`))for(let e=0;e<a.length;e++)c.appendChild(u(a[e],t=>{let n=[...s[r]];n[e]=t,s[r]=n}));else if(P(a)){let o=this.doc.createElement(`div`);y(o,{marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});let s=i.variants?.tag,l=Object.keys(a);s&&!l.includes(s)&&l.unshift(s);let u=i.path??r;for(let t of l){let r=t===s;this.renderValueRow(o,n,t,{path:`${u}.${t}`,read:()=>i.read()[t]??(r?``:null),write:n=>{if(r&&i.variants){let t=i.variants.byTag[String(n)];if(t!==void 0){i.write(e(t));return}}i.write({...i.read(),[t]:n})},options:r&&i.variants?Object.keys(i.variants.byTag):void 0})}t.appendChild(c),t.appendChild(o);return}else{let o=JSON.stringify(e(a));if(o.length<=k){let e=this.doc.createElement(`div`);e.textContent=o,y(e,{opacity:`0.65`,whiteSpace:`pre-wrap`,wordBreak:`break-all`}),c.appendChild(e)}else{let e=i.path??r,s=this.detailOpen.get(n)?.has(e)??!1,l=this.doc.createElement(`div`);if(l.textContent=`${s?`▾`:`▸`} ${M(a)}`,y(l,{opacity:`0.75`,cursor:`pointer`,userSelect:`none`}),l.addEventListener(`click`,()=>{let t=this.detailOpen.get(n);t||(t=new Set,this.detailOpen.set(n,t)),s?t.delete(e):t.add(e),this.renderInspector()}),c.appendChild(l),t.appendChild(c),s){let e=this.doc.createElement(`div`);e.textContent=N(a,o),y(e,{opacity:`0.6`,whiteSpace:`pre-wrap`,wordBreak:`break-all`,marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`}),t.appendChild(e)}return}}t.appendChild(c)}trackEditing(e){e.addEventListener(`focus`,()=>{this.editing+=1}),e.addEventListener(`blur`,()=>{this.editing=Math.max(0,this.editing-1)})}pushLog(e){this.logRows.push(e),this.logRows.length>R&&this.logRows.splice(0,this.logRows.length-R),this.renderLogs()}async copyScene(){let e=await o(a(this.engine.scene??null));return this.toast(e?`scene JSON copied`:`could not reach the clipboard`),e}captureRegion(){this.capture||=(this.captureScale=globalThis.devicePixelRatio??1,this.frameSource?.().then(e=>{this.captureScale=e.width/Math.max(1,this.container.clientWidth||e.width)}).catch(()=>{}),O({doc:this.doc,container:this.container,scale:()=>this.captureScale,onCopy:e=>void this.copyRegion({x0:e.x,y0:e.y,x1:e.x+e.w,y1:e.y+e.h}),onClose:()=>{this.capture=null}}))}capture=null;captureScale=1;async copyRegion(e){let t=this.frameSource;if(!t)return this.toast(`no renderer to capture from`),!1;try{let n=await t(),a=n.width/Math.max(1,this.container.clientWidth||n.width),o=r(e,n.width,n.height,a),c=i(n.pixels,n.width,n.height,o),l=await s(this.doc,c);return this.toast(l?`copied ${c.width}×${c.height}`:`could not copy the image`),l}catch(e){return this.toast(`capture failed: ${e instanceof Error?e.message:String(e)}`),!1}}toast(e){let t=this.doc.createElement(`div`);t.textContent=e,y(t,{position:`absolute`,bottom:`16px`,left:`50%`,transform:`translateX(-50%)`,padding:`6px 12px`,borderRadius:`6px`,background:`rgba(18,20,26,0.92)`,color:`rgba(255,255,255,0.9)`,font:`12px ui-monospace, Menlo, monospace`,zIndex:`90`,pointerEvents:`none`}),this.container.appendChild(t),setTimeout(()=>t.remove(),1800)}setTimeScale(e){Number.isFinite(e)&&(this.engine.timeScale=Math.max(0,e),this.syncTime())}setPaused(e){e?(this.engine.timeScale>0&&(this.resumeScale=this.engine.timeScale),this.engine.timeScale=0):this.engine.timeScale=this.resumeScale>0?this.resumeScale:1,this.syncTime()}get paused(){return this.engine.timeScale===0}nextFrame(){this.paused||this.setPaused(!0),this.engine.step(),this.syncTime()}syncTime(){let e=this.timeEls;if(!e)return;let t=this.engine.timeScale,n=String(Math.round(t*1e3)/1e3);this.editing===0&&(e.slider.value=String(Math.min(t,F)),e.box.value=n),e.readout.textContent=`timeScale ${n}${this.paused?` · paused`:``}`,e.pause.textContent=this.paused?`▶ Resume`:`⏸ Pause`}renderTime(){let e=this.panels.get(`time`);if(!e)return;V(e.body),this.timeEls=null;let t=this.doc.createElement(`div`);y(t,{marginBottom:`6px`,opacity:`0.85`}),e.body.appendChild(t);let n=this.doc.createElement(`input`);n.type=`range`,n.min=`0`,n.max=String(F),n.step=`0.05`,n.value=String(Math.min(this.engine.timeScale,F)),y(n,{width:`100%`,marginBottom:`6px`}),this.trackEditing(n),n.addEventListener(`input`,()=>this.setTimeScale(Number(n.value))),e.body.appendChild(n);let r=this.doc.createElement(`div`);y(r,{display:`flex`,gap:`6px`,alignItems:`center`,marginBottom:`8px`});let i=this.doc.createElement(`input`);i.type=`number`,i.min=`0`,i.step=`0.05`,i.value=String(this.engine.timeScale),y(i,G(`72px`)),this.trackEditing(i),i.addEventListener(`change`,()=>{let e=Number(i.value);Number.isFinite(e)&&this.setTimeScale(e),this.syncTimeAfterEdit()}),r.appendChild(i);for(let e of[.25,.5,1,2]){let t=this.doc.createElement(`div`);t.textContent=`${e}×`,y(t,{cursor:`pointer`,opacity:`0.7`,padding:`2px 4px`}),t.addEventListener(`click`,()=>this.setTimeScale(e)),r.appendChild(t)}e.body.appendChild(r);let a=this.doc.createElement(`div`);y(a,{display:`flex`,gap:`8px`});let o=this.doc.createElement(`div`);y(o,W()),o.addEventListener(`click`,()=>this.setPaused(!this.paused)),a.appendChild(o);let s=this.doc.createElement(`div`);s.textContent=`⏭ Next frame`,s.title=`Pauses, then advances one fixed step`,y(s,W()),s.addEventListener(`click`,()=>this.nextFrame()),a.appendChild(s),e.body.appendChild(a),this.timeEls={slider:n,box:i,readout:t,pause:o},this.syncTime()}syncTimeAfterEdit(){let e=this.timeEls;if(!e)return;let t=this.engine.timeScale;e.slider.value=String(Math.min(t,F)),e.box.value=String(Math.round(t*1e3)/1e3)}renderLogs(){let e=this.panels.get(`logs`);if(!e)return;V(e.body);let t=this.doc.createElement(`div`);y(t,{display:`flex`,gap:`8px`,marginBottom:`4px`,flexWrap:`wrap`});for(let e of[`debug`,`info`,`warn`,`error`]){let n=this.doc.createElement(`div`);n.textContent=`${this.levelEnabled[e]?`✓`:`·`}${e}`,y(n,{cursor:`pointer`,opacity:this.levelEnabled[e]?`1`:`0.45`}),n.addEventListener(`click`,()=>this.setLevelEnabled(e,!this.levelEnabled[e])),t.appendChild(n)}let n=this.doc.createElement(`div`);n.textContent=`${this.consoleCapture?`✓`:`·`}console`,y(n,{cursor:`pointer`,marginLeft:`auto`}),n.addEventListener(`click`,()=>this.setConsoleCapture(!this.consoleCapture)),t.appendChild(n),e.body.appendChild(t);let r={debug:`rgba(255,255,255,0.5)`,info:`rgba(255,255,255,0.85)`,warn:`#ffc861`,error:`#ff6b6b`};for(let t of this.logRows){if(!this.levelEnabled[t.level])continue;let n=this.doc.createElement(`div`);n.textContent=`[${t.source===`console`?`console`:t.level}] ${t.text}`,y(n,{color:r[t.level],whiteSpace:`pre-wrap`}),e.body.appendChild(n)}}};function V(e){for(let t of[...e.children])t.remove()}function H(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function U(e){if(typeof e==`string`)return e;if(e instanceof Error){let t=e.stack?.split(`
|
|
3
3
|
`)[1]?.trim();return`${e.name}: ${e.message}${t?` (${t})`:``}`}try{let t=JSON.stringify(e);return t===`{}`||t===void 0?String(e):t}catch{return String(e)}}function W(){return{background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.2)`,borderRadius:`4px`,padding:`4px 8px`,cursor:`pointer`,userSelect:`none`}}function G(e){return{width:e,background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.2)`,borderRadius:`4px`,color:`inherit`,font:`inherit`,padding:`2px 4px`}}export{L as attachDebugOverlay};
|