incanto 0.43.0 → 0.44.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-frame.mjs +55 -11
- package/dist/3d.d.ts +2 -40
- package/dist/3d.js +3 -2
- package/dist/{create-game-B3vBWgVD.js → create-game-D90pyPMx.js} +12 -77
- package/dist/debug.d.ts +44 -3
- package/dist/debug.js +562 -4
- package/dist/frame-report-Ct8XgsmV.d.ts +80 -0
- package/dist/frame-report-Lr3VO24R.js +197 -0
- package/dist/index.js +1 -1
- package/dist/react.js +1 -1
- package/dist/{src-CwYxzZKl.js → src-B3n06SsL.js} +1 -1
- package/dist/vite.d.ts +15 -1
- package/dist/vite.js +48 -5
- package/editor/assets/{agent8-Cl3qFuBB.js → agent8-MgX6kYWH.js} +1 -1
- package/editor/assets/debug-Czri9cgX.js +3 -0
- package/editor/assets/{index-Dk2ZlO68.js → index-D9MX3UHF.js} +49 -49
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/skills/incanto-verifying-your-game.md +60 -4
- 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
- package/editor/assets/debug-u3yLCciO.js +0 -3
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/3d/frame-report.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What the frame looks like, as something worth saying.
|
|
4
|
+
*
|
|
5
|
+
* The pixels are not the deliverable. A 1280x720 frame is 2.8 million numbers:
|
|
6
|
+
* unreadable by a person, unaffordable for a model (roughly a thousand tokens
|
|
7
|
+
* as an image, roughly a million as text). The deliverable is the JUDGEMENT —
|
|
8
|
+
* black screen, one flat colour, washed out — and the numbers behind it, small
|
|
9
|
+
* enough to print and compare.
|
|
10
|
+
*
|
|
11
|
+
* Same principle as `framing`'s "lit by: NOTHING": a report is a sentence
|
|
12
|
+
* before it is a number, because a number needs a reader who already knows what
|
|
13
|
+
* to compare it against.
|
|
14
|
+
*
|
|
15
|
+
* Pure arithmetic over an RGBA buffer — no three, no DOM, no GPU. Whoever has
|
|
16
|
+
* the pixels calls this; in practice that is the browser holding the canvas.
|
|
17
|
+
*/
|
|
18
|
+
/** Per-cell mean colour, `[r, g, b]` 0..255. */
|
|
19
|
+
type GridCell = [number, number, number];
|
|
20
|
+
interface FrameStats {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
/** Every pixel below the black threshold — the render produced nothing. */
|
|
24
|
+
black: boolean;
|
|
25
|
+
/** One colour everywhere: a clear-colour fill, or a camera inside geometry. */
|
|
26
|
+
uniform: boolean;
|
|
27
|
+
/** 0..1 mean relative luminance. */
|
|
28
|
+
meanLuma: number;
|
|
29
|
+
/** Coarse mean colour per cell, rows top to bottom. */
|
|
30
|
+
grid: GridCell[][];
|
|
31
|
+
}
|
|
32
|
+
interface FrameStatsOptions {
|
|
33
|
+
/** Cells across and down. Default 16x9 — 144 cells, printable. */
|
|
34
|
+
grid?: [number, number];
|
|
35
|
+
}
|
|
36
|
+
declare function frameStats(pixels: Uint8Array | Uint8ClampedArray, width: number, height: number, opts?: FrameStatsOptions): FrameStats;
|
|
37
|
+
/** The report a person or an agent reads. Verdict first, numbers after. */
|
|
38
|
+
declare function frameText(stats: FrameStats): string;
|
|
39
|
+
/**
|
|
40
|
+
* Comparison resolution. Fine enough that a one-pixel line moves a cell by
|
|
41
|
+
* several levels at 720p, small enough (6912 bytes) to hold and to send.
|
|
42
|
+
*/
|
|
43
|
+
declare const SIGNATURE_GRID: readonly [number, number];
|
|
44
|
+
interface FrameSignature {
|
|
45
|
+
/** The frame this was made from — a different size is not comparable. */
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
cols: number;
|
|
49
|
+
rows: number;
|
|
50
|
+
/** base64 of `cols * rows * 3` bytes: cell means, rows top to bottom. */
|
|
51
|
+
data: string;
|
|
52
|
+
}
|
|
53
|
+
interface FrameDiff {
|
|
54
|
+
/** False when the two cannot be subtracted at all; see `reason`. */
|
|
55
|
+
comparable: boolean;
|
|
56
|
+
reason?: string;
|
|
57
|
+
/** 0..1 fraction of cells that moved past the threshold. */
|
|
58
|
+
changed: number;
|
|
59
|
+
/** Largest single-channel move, 0..255. */
|
|
60
|
+
maxDelta: number;
|
|
61
|
+
/** Mean single-channel move across the frame, 0..255. */
|
|
62
|
+
meanDelta: number;
|
|
63
|
+
/** Where the change is, in pixels of the compared frame. */
|
|
64
|
+
box: {
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
w: number;
|
|
68
|
+
h: number;
|
|
69
|
+
} | null;
|
|
70
|
+
/** A thin full-length band is worth naming: that is what a seam looks like. */
|
|
71
|
+
shape: "vertical band" | "horizontal band" | null;
|
|
72
|
+
}
|
|
73
|
+
declare function frameSignature(pixels: Uint8Array | Uint8ClampedArray, width: number, height: number, grid?: readonly [number, number]): FrameSignature;
|
|
74
|
+
declare function diffSignatures(before: FrameSignature, after: FrameSignature, opts?: {
|
|
75
|
+
threshold?: number;
|
|
76
|
+
}): FrameDiff;
|
|
77
|
+
/** The comparison a person or an agent reads. */
|
|
78
|
+
declare function diffText(diff: FrameDiff): string;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { GridCell as a, diffText as c, frameText as d, FrameStatsOptions as i, frameSignature as l, FrameSignature as n, SIGNATURE_GRID as o, FrameStats as r, diffSignatures as s, FrameDiff as t, frameStats as u };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
//#region src/3d/frame-report.ts
|
|
2
|
+
/** Rec. 709 relative luminance of an 8-bit triple, 0..1. */
|
|
3
|
+
function luma(r, g, b) {
|
|
4
|
+
return (.2126 * r + .7152 * g + .0722 * b) / 255;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A pixel this dark is indistinguishable from "nothing was drawn". Not zero:
|
|
8
|
+
* a real render of a night scene still lands a few levels above black, and
|
|
9
|
+
* calling that a failure would make the check useless where it matters most.
|
|
10
|
+
*/
|
|
11
|
+
const BLACK_LEVEL = .012;
|
|
12
|
+
/** Cell-to-cell luminance range below this: the frame is one flat colour. */
|
|
13
|
+
const UNIFORM_EPSILON = .004;
|
|
14
|
+
/**
|
|
15
|
+
* Mean colour of every cell of a `cols` x `rows` box filter, as a flat
|
|
16
|
+
* `cols * rows * 3` byte array, rows top to bottom.
|
|
17
|
+
*
|
|
18
|
+
* The one place a frame is reduced. Both the printable report and the
|
|
19
|
+
* comparison signature are this at different resolutions, so a boundary that
|
|
20
|
+
* moves moves for both.
|
|
21
|
+
*/
|
|
22
|
+
function cellMeans(pixels, width, height, cols, rows) {
|
|
23
|
+
const out = new Uint8Array(cols * rows * 3);
|
|
24
|
+
for (let row = 0; row < rows; row++) for (let col = 0; col < cols; col++) {
|
|
25
|
+
const x0 = Math.floor(col * width / cols);
|
|
26
|
+
const x1 = Math.max(x0 + 1, Math.floor((col + 1) * width / cols));
|
|
27
|
+
const y0 = Math.floor(row * height / rows);
|
|
28
|
+
const y1 = Math.max(y0 + 1, Math.floor((row + 1) * height / rows));
|
|
29
|
+
let r = 0;
|
|
30
|
+
let g = 0;
|
|
31
|
+
let b = 0;
|
|
32
|
+
let n = 0;
|
|
33
|
+
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) {
|
|
34
|
+
const i = (y * width + x) * 4;
|
|
35
|
+
r += pixels[i] ?? 0;
|
|
36
|
+
g += pixels[i + 1] ?? 0;
|
|
37
|
+
b += pixels[i + 2] ?? 0;
|
|
38
|
+
n += 1;
|
|
39
|
+
}
|
|
40
|
+
const o = (row * cols + col) * 3;
|
|
41
|
+
out[o] = Math.round(r / n);
|
|
42
|
+
out[o + 1] = Math.round(g / n);
|
|
43
|
+
out[o + 2] = Math.round(b / n);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function frameStats(pixels, width, height, opts = {}) {
|
|
48
|
+
const [cols, rows] = opts.grid ?? [16, 9];
|
|
49
|
+
const means = cellMeans(pixels, width, height, cols, rows);
|
|
50
|
+
const grid = [];
|
|
51
|
+
let total = 0;
|
|
52
|
+
let maxLuma = 0;
|
|
53
|
+
let minLuma = 1;
|
|
54
|
+
for (let row = 0; row < rows; row++) {
|
|
55
|
+
const line = [];
|
|
56
|
+
for (let col = 0; col < cols; col++) {
|
|
57
|
+
const o = (row * cols + col) * 3;
|
|
58
|
+
const cell = [
|
|
59
|
+
means[o],
|
|
60
|
+
means[o + 1],
|
|
61
|
+
means[o + 2]
|
|
62
|
+
];
|
|
63
|
+
const cellLuma = luma(cell[0], cell[1], cell[2]);
|
|
64
|
+
total += cellLuma;
|
|
65
|
+
maxLuma = Math.max(maxLuma, cellLuma);
|
|
66
|
+
minLuma = Math.min(minLuma, cellLuma);
|
|
67
|
+
line.push(cell);
|
|
68
|
+
}
|
|
69
|
+
grid.push(line);
|
|
70
|
+
}
|
|
71
|
+
const cells = cols * rows;
|
|
72
|
+
const meanLuma = total / cells;
|
|
73
|
+
return {
|
|
74
|
+
width,
|
|
75
|
+
height,
|
|
76
|
+
black: maxLuma <= BLACK_LEVEL,
|
|
77
|
+
uniform: maxLuma - minLuma < UNIFORM_EPSILON,
|
|
78
|
+
meanLuma: Math.round(meanLuma * 1e3) / 1e3,
|
|
79
|
+
grid
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** The report a person or an agent reads. Verdict first, numbers after. */
|
|
83
|
+
function frameText(stats) {
|
|
84
|
+
const lines = [];
|
|
85
|
+
if (stats.black) lines.push("BLACK SCREEN — nothing was drawn (no light, no camera, or nothing in view)");
|
|
86
|
+
else if (stats.uniform) lines.push("one flat colour — the camera may be inside geometry, or only the sky is drawn");
|
|
87
|
+
lines.push(`frame ${stats.width}×${stats.height} · luminance ${stats.meanLuma.toFixed(2)}`);
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Comparison resolution. Fine enough that a one-pixel line moves a cell by
|
|
92
|
+
* several levels at 720p, small enough (6912 bytes) to hold and to send.
|
|
93
|
+
*/
|
|
94
|
+
const SIGNATURE_GRID = [64, 36];
|
|
95
|
+
/**
|
|
96
|
+
* Below this, a cell has not changed. Compression, dithering and the last bit
|
|
97
|
+
* of a tonemap all wander by a level or two between two captures of the same
|
|
98
|
+
* still frame; calling that a change would bury the real one.
|
|
99
|
+
*/
|
|
100
|
+
const DIFF_THRESHOLD = 3;
|
|
101
|
+
function frameSignature(pixels, width, height, grid = SIGNATURE_GRID) {
|
|
102
|
+
const [cols, rows] = grid;
|
|
103
|
+
return {
|
|
104
|
+
width,
|
|
105
|
+
height,
|
|
106
|
+
cols,
|
|
107
|
+
rows,
|
|
108
|
+
data: toBase64(cellMeans(pixels, width, height, cols, rows))
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function diffSignatures(before, after, opts = {}) {
|
|
112
|
+
const none = {
|
|
113
|
+
changed: 0,
|
|
114
|
+
maxDelta: 0,
|
|
115
|
+
meanDelta: 0,
|
|
116
|
+
box: null,
|
|
117
|
+
shape: null
|
|
118
|
+
};
|
|
119
|
+
if (before.width !== after.width || before.height !== after.height) return {
|
|
120
|
+
comparable: false,
|
|
121
|
+
reason: `frame size changed: ${before.width}×${before.height} → ${after.width}×${after.height}`,
|
|
122
|
+
...none
|
|
123
|
+
};
|
|
124
|
+
if (before.cols !== after.cols || before.rows !== after.rows) return {
|
|
125
|
+
comparable: false,
|
|
126
|
+
reason: "signature grid changed",
|
|
127
|
+
...none
|
|
128
|
+
};
|
|
129
|
+
const threshold = opts.threshold ?? DIFF_THRESHOLD;
|
|
130
|
+
const a = fromBase64(before.data);
|
|
131
|
+
const b = fromBase64(after.data);
|
|
132
|
+
const { cols, rows } = after;
|
|
133
|
+
let changedCells = 0;
|
|
134
|
+
let maxDelta = 0;
|
|
135
|
+
let totalDelta = 0;
|
|
136
|
+
let minCol = cols;
|
|
137
|
+
let maxCol = -1;
|
|
138
|
+
let minRow = rows;
|
|
139
|
+
let maxRow = -1;
|
|
140
|
+
for (let row = 0; row < rows; row++) for (let col = 0; col < cols; col++) {
|
|
141
|
+
const o = (row * cols + col) * 3;
|
|
142
|
+
const delta = Math.max(Math.abs((a[o] ?? 0) - (b[o] ?? 0)), Math.abs((a[o + 1] ?? 0) - (b[o + 1] ?? 0)), Math.abs((a[o + 2] ?? 0) - (b[o + 2] ?? 0)));
|
|
143
|
+
totalDelta += delta;
|
|
144
|
+
maxDelta = Math.max(maxDelta, delta);
|
|
145
|
+
if (delta >= threshold) {
|
|
146
|
+
changedCells += 1;
|
|
147
|
+
minCol = Math.min(minCol, col);
|
|
148
|
+
maxCol = Math.max(maxCol, col);
|
|
149
|
+
minRow = Math.min(minRow, row);
|
|
150
|
+
maxRow = Math.max(maxRow, row);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const cells = cols * rows;
|
|
154
|
+
const box = maxCol < 0 ? null : {
|
|
155
|
+
x: Math.floor(minCol * after.width / cols),
|
|
156
|
+
y: Math.floor(minRow * after.height / rows),
|
|
157
|
+
w: Math.ceil((maxCol + 1 - minCol) * after.width / cols),
|
|
158
|
+
h: Math.ceil((maxRow + 1 - minRow) * after.height / rows)
|
|
159
|
+
};
|
|
160
|
+
const spanCols = maxCol < 0 ? 0 : maxCol + 1 - minCol;
|
|
161
|
+
const spanRows = maxRow < 0 ? 0 : maxRow + 1 - minRow;
|
|
162
|
+
return {
|
|
163
|
+
comparable: true,
|
|
164
|
+
changed: changedCells / cells,
|
|
165
|
+
maxDelta,
|
|
166
|
+
meanDelta: Math.round(totalDelta / cells * 10) / 10,
|
|
167
|
+
box,
|
|
168
|
+
shape: spanCols > 0 && spanCols <= 2 && spanRows >= rows / 2 ? "vertical band" : spanRows > 0 && spanRows <= 2 && spanCols >= cols / 2 ? "horizontal band" : null
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** The comparison a person or an agent reads. */
|
|
172
|
+
function diffText(diff) {
|
|
173
|
+
if (!diff.comparable) return `cannot compare — ${diff.reason ?? "unknown reason"}`;
|
|
174
|
+
if (diff.changed === 0) return `identical — nothing moved (largest cell shift ${diff.maxDelta}/255)`;
|
|
175
|
+
const lines = [`changed ${(diff.changed * 100).toFixed(1)}% of the frame · largest shift ${diff.maxDelta}/255, mean ${diff.meanDelta}`];
|
|
176
|
+
if (diff.box) {
|
|
177
|
+
const { x, y, w, h } = diff.box;
|
|
178
|
+
lines.push(`region x ${x}..${x + w}, y ${y}..${y + h}${diff.shape ? ` — a ${diff.shape}` : ""}`);
|
|
179
|
+
}
|
|
180
|
+
if (diff.changed > .9) lines.push("the whole frame moved — if the scene is animating, pause it first (debug ☰ → Time → Pause) so the comparison is of your change, not of time passing");
|
|
181
|
+
return lines.join("\n");
|
|
182
|
+
}
|
|
183
|
+
/** Portable in a browser and in node; both have had `btoa`/`atob` for years. */
|
|
184
|
+
function toBase64(bytes) {
|
|
185
|
+
let binary = "";
|
|
186
|
+
const CHUNK = 32768;
|
|
187
|
+
for (let i = 0; i < bytes.length; i += CHUNK) binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
188
|
+
return btoa(binary);
|
|
189
|
+
}
|
|
190
|
+
function fromBase64(text) {
|
|
191
|
+
const binary = atob(text);
|
|
192
|
+
const out = new Uint8Array(binary.length);
|
|
193
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { frameStats as a, frameSignature as i, diffSignatures as n, frameText as o, diffText as r, SIGNATURE_GRID as t };
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.
|
|
|
7
7
|
import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-IyWCGe4q.js";
|
|
8
8
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
9
9
|
import { i as applyParticlePreset, n as PARTICLE_PRESETS, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
|
|
10
|
-
import { a as findPath, i as preloadUrls, n as newUid, o as gridFromRows, r as assetUrls, t as VERSION } from "./src-
|
|
10
|
+
import { a as findPath, i as preloadUrls, n as newUid, o as gridFromRows, r as assetUrls, t as VERSION } from "./src-B3n06SsL.js";
|
|
11
11
|
import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-BoNg_MnF.js";
|
|
12
12
|
import { t as duplicateNode } from "./duplicate-CRtihGmC.js";
|
|
13
13
|
export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveOrderGroups, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
|
package/dist/react.js
CHANGED
|
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
|
|
|
156
156
|
pointer: latest.pointer,
|
|
157
157
|
...keyboard !== void 0 ? { keyboard } : {}
|
|
158
158
|
};
|
|
159
|
-
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-
|
|
159
|
+
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-D90pyPMx.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-DFRbG0Bj.js").then((n) => n.n)).createGame2D(o)))(opts);
|
|
160
160
|
if (disposed) {
|
|
161
161
|
next.dispose();
|
|
162
162
|
return;
|
|
@@ -161,6 +161,6 @@ function newUid() {
|
|
|
161
161
|
//#endregion
|
|
162
162
|
//#region src/index.ts
|
|
163
163
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
164
|
-
const VERSION = "0.
|
|
164
|
+
const VERSION = "0.44.0";
|
|
165
165
|
//#endregion
|
|
166
166
|
export { findPath as a, preloadUrls as i, newUid as n, gridFromRows as o, assetUrls as r, VERSION as t };
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { n as FrameSignature } from "./frame-report-Ct8XgsmV.js";
|
|
2
|
+
|
|
1
3
|
//#region src/vite/discover.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* The listening TCP ports in `/proc/net/tcp` (and `tcp6`) format.
|
|
@@ -25,7 +27,7 @@ interface FrameHost {
|
|
|
25
27
|
root?: string;
|
|
26
28
|
};
|
|
27
29
|
middlewares: {
|
|
28
|
-
use(path: string, handler: (req:
|
|
30
|
+
use(path: string, handler: (req: FrameReq, res: FrameRes) => void): void;
|
|
29
31
|
};
|
|
30
32
|
/** Vite's HMR channel. `ws` on v5, `hot` on v6+ — both are accepted. */
|
|
31
33
|
ws?: FrameChannel;
|
|
@@ -44,10 +46,22 @@ interface FrameRes {
|
|
|
44
46
|
setHeader(name: string, value: string): void;
|
|
45
47
|
end(body?: string): void;
|
|
46
48
|
}
|
|
49
|
+
/** Connect strips the mount path off `url`; `originalUrl` keeps the query. */
|
|
50
|
+
interface FrameReq {
|
|
51
|
+
url?: string;
|
|
52
|
+
originalUrl?: string;
|
|
53
|
+
}
|
|
47
54
|
interface FrameEndpointOptions {
|
|
48
55
|
/** Overridden in tests; defaults to the real clock. */
|
|
49
56
|
now?: () => number;
|
|
50
57
|
timeoutMs?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Remembered frames, by label. Lives in the dev server's MEMORY on purpose:
|
|
60
|
+
* a baseline is only valid for the browser and the window that made it, so it
|
|
61
|
+
* should die with the server rather than linger in the project as a file
|
|
62
|
+
* nobody asked for.
|
|
63
|
+
*/
|
|
64
|
+
store?: Map<string, FrameSignature>;
|
|
51
65
|
}
|
|
52
66
|
/**
|
|
53
67
|
* Wire `/__incanto/ping` and `/__incanto/frame` onto a dev server.
|
package/dist/vite.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { t as VERSION } from "./src-
|
|
1
|
+
import { t as VERSION } from "./src-B3n06SsL.js";
|
|
2
|
+
import { n as diffSignatures } from "./frame-report-Lr3VO24R.js";
|
|
2
3
|
import { s as validateScene } from "./test-BMgiiD5i.js";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
@@ -18,6 +19,8 @@ import { basename, dirname, join, normalize, relative, resolve, sep } from "node
|
|
|
18
19
|
*/
|
|
19
20
|
/** How long to wait for a page to answer before calling it absent. */
|
|
20
21
|
const CLIENT_TIMEOUT_MS = 2e3;
|
|
22
|
+
/** Where a remembered frame goes when nobody named one. */
|
|
23
|
+
const DEFAULT_LABEL = "last";
|
|
21
24
|
/**
|
|
22
25
|
* Wire `/__incanto/ping` and `/__incanto/frame` onto a dev server.
|
|
23
26
|
*
|
|
@@ -28,6 +31,7 @@ const CLIENT_TIMEOUT_MS = 2e3;
|
|
|
28
31
|
function serveFrameEndpoints(host, version, opts = {}) {
|
|
29
32
|
const channel = host.hot ?? host.ws;
|
|
30
33
|
const timeoutMs = opts.timeoutMs ?? CLIENT_TIMEOUT_MS;
|
|
34
|
+
const store = opts.store ?? /* @__PURE__ */ new Map();
|
|
31
35
|
let nextId = 1;
|
|
32
36
|
const waiting = /* @__PURE__ */ new Map();
|
|
33
37
|
channel?.on("incanto:frame-report", (data) => {
|
|
@@ -48,11 +52,16 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
48
52
|
frame: Boolean(channel)
|
|
49
53
|
});
|
|
50
54
|
});
|
|
51
|
-
host.middlewares.use("/__incanto/frame", (
|
|
55
|
+
host.middlewares.use("/__incanto/frame", (req, res) => {
|
|
52
56
|
if (!channel) {
|
|
53
57
|
json$1(res, 503, { error: "no-hmr-channel" });
|
|
54
58
|
return;
|
|
55
59
|
}
|
|
60
|
+
const params = query(req);
|
|
61
|
+
const remember = params.get("remember");
|
|
62
|
+
const diff = params.get("diff");
|
|
63
|
+
const threshold = Number(params.get("threshold"));
|
|
64
|
+
const grid = parseGrid(params.get("grid"));
|
|
56
65
|
const id = nextId++;
|
|
57
66
|
const timer = setTimeout(() => {
|
|
58
67
|
waiting.delete(id);
|
|
@@ -60,16 +69,50 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
60
69
|
}, timeoutMs);
|
|
61
70
|
waiting.set(id, (value) => {
|
|
62
71
|
clearTimeout(timer);
|
|
63
|
-
if (value.ok)
|
|
64
|
-
|
|
72
|
+
if (!value.ok) {
|
|
73
|
+
json$1(res, 500, { error: "capture-failed" });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const full = value.report;
|
|
77
|
+
const signature = full?.signature;
|
|
78
|
+
const { signature: _drop, ...report } = full ?? {};
|
|
79
|
+
const body = { report };
|
|
80
|
+
if (diff !== null) {
|
|
81
|
+
const before = store.get(diff || DEFAULT_LABEL);
|
|
82
|
+
if (!signature) body.diffError = "no-signature";
|
|
83
|
+
else if (!before) body.diffError = "no-baseline";
|
|
84
|
+
else body.diff = diffSignatures(before, signature, { threshold: threshold > 0 ? threshold : void 0 });
|
|
85
|
+
}
|
|
86
|
+
if (remember !== null) if (!signature) body.rememberError = "no-signature";
|
|
87
|
+
else {
|
|
88
|
+
store.set(remember || DEFAULT_LABEL, signature);
|
|
89
|
+
body.remembered = remember || DEFAULT_LABEL;
|
|
90
|
+
}
|
|
91
|
+
json$1(res, 200, body);
|
|
65
92
|
});
|
|
66
93
|
channel.send({
|
|
67
94
|
type: "custom",
|
|
68
95
|
event: "incanto:frame-request",
|
|
69
|
-
data: {
|
|
96
|
+
data: {
|
|
97
|
+
id,
|
|
98
|
+
grid
|
|
99
|
+
}
|
|
70
100
|
});
|
|
71
101
|
});
|
|
72
102
|
}
|
|
103
|
+
function query(req) {
|
|
104
|
+
const url = req?.originalUrl ?? req?.url ?? "";
|
|
105
|
+
const q = url.indexOf("?");
|
|
106
|
+
return new URLSearchParams(q >= 0 ? url.slice(q + 1) : "");
|
|
107
|
+
}
|
|
108
|
+
/** `16x9` → `[16, 9]`. Anything else is not a grid and is ignored. */
|
|
109
|
+
function parseGrid(text) {
|
|
110
|
+
const m = text?.match(/^(\d+)x(\d+)$/i);
|
|
111
|
+
if (!m) return void 0;
|
|
112
|
+
const cols = Number(m[1]);
|
|
113
|
+
const rows = Number(m[2]);
|
|
114
|
+
return cols > 0 && rows > 0 ? [cols, rows] : void 0;
|
|
115
|
+
}
|
|
73
116
|
function json$1(res, status, body) {
|
|
74
117
|
res.statusCode = status;
|
|
75
118
|
res.setHeader("content-type", "application/json");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./index-
|
|
1
|
+
import{n as e}from"./index-D9MX3UHF.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};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{i as e,r as t,t as n}from"./index-D9MX3UHF.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){for(let[n,r]of Object.entries(t))e.style[n]=r}var v=`rgba(18, 20, 26, 0.92)`,y=`1px solid rgba(255,255,255,0.14)`,b=`12px ui-monospace, SFMono-Regular, Menlo, monospace`,x=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`),_(this.el,{position:`absolute`,background:v,border:y,borderRadius:`8px`,color:`rgba(255,255,255,0.88)`,font:b,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,_(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`,_(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`),_(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let o=e.createElement(`div`);o.textContent=`◢`,_(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,_(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)}},S=4,C=`#6ee7dc`,w=`rgba(18,20,26,0.94)`,T=`12px ui-monospace, Menlo, monospace`;function E(e){let{doc:t,container:n}=e,r=t.createElement(`div`);_(r,{position:`absolute`,inset:`0`,cursor:`crosshair`,zIndex:`80`,pointerEvents:`auto`,userSelect:`none`,touchAction:`none`});let i=t.createElement(`div`);_(i,{position:`absolute`,inset:`0`,background:`rgba(10,14,20,0.35)`,pointerEvents:`none`}),r.appendChild(i);let a=t.createElement(`div`);_(a,{position:`absolute`,outline:`1px solid ${C}`,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`);_(o,{position:`absolute`,padding:`2px 6px`,borderRadius:`4px`,background:w,color:C,font:T,pointerEvents:`none`,display:`none`,whiteSpace:`nowrap`}),r.appendChild(o);let s=t.createElement(`div`);s.textContent=`drag to select · Esc to cancel`,_(s,{position:`absolute`,top:`10px`,left:`50%`,transform:`translateX(-50%)`,padding:`4px 10px`,borderRadius:`6px`,background:w,color:`rgba(255,255,255,0.85)`,font:T,pointerEvents:`none`,whiteSpace:`nowrap`}),r.appendChild(s);let d=null,g=`idle`,v={x:0,y:0},y={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];_(n,{position:`absolute`,left:`${r*100}%`,top:`${i*100}%`,width:`10px`,height:`10px`,marginLeft:`-5px`,marginTop:`-5px`,borderRadius:`2px`,background:C,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`);_(x,{position:`absolute`,display:`none`,gap:`6px`,padding:`6px`,borderRadius:`8px`,background:w,border:`1px solid rgba(255,255,255,0.18)`,font:T,pointerEvents:`auto`,whiteSpace:`nowrap`,boxShadow:`0 4px 16px rgba(0,0,0,0.45)`});let E=(e,n,r)=>{let i=t.createElement(`div`);return i.textContent=e,_(i,{padding:`5px 10px`,borderRadius:`5px`,cursor:`pointer`,color:n?`#08121a`:`rgba(255,255,255,0.85)`,background:n?C:`rgba(255,255,255,0.08)`,userSelect:`none`}),i.addEventListener(`pointerdown`,e=>{D(e),r()}),i};x.appendChild(E(`Copy to clipboard`,!0,()=>N())),x.appendChild(E(`Cancel`,!1,()=>I())),r.appendChild(x);function D(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){D(e),v=O(e),g=t,t===`create`?d={x:v.x,y:v.y,w:0,h:0}:d&&(y=d),_(x,{display:`none`}),_(a,{cursor:t===`move`?`grabbing`:`crosshair`});let n=e.pointerId;n!==void 0&&r.setPointerCapture?.(n),A()}function A(){if(!d){_(a,{display:`none`}),_(o,{display:`none`}),_(i,{display:`block`});return}_(i,{display:`none`}),_(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;_(o,{display:`block`,left:`${Math.max(2,d.x)}px`,top:`${n>=2?n:d.y+4}px`})}function j(){if(!d||d.w<S||d.h<S)return;_(x,{display:`flex`});let e=b(),t=x.offsetWidth||200,n=x.offsetHeight||36,r=h(d,t,n,e.w,e.h);_(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:v.x,y0:v.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(y,t.x-v.x,t.y-v.y,n.w,n.h):p(y,g,t.x-v.x,t.y-v.y,n.w,n.h);A()});let M=()=>{g!==`idle`&&(g===`create`&&d&&(d.w<S||d.h<S)&&(d=null),g=`idle`,_(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 D=96,O=200,k=8192;function A(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function j(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,O).map(e=>JSON.stringify(e)),n=e.length-O;return t.join(`
|
|
2
|
+
`)+(n>0?`\n… ${n} more`:``)}return t.length>k?`${t.slice(0,k)} … (${t.length-k} more chars)`:t}function M(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var N=2;function P(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 L(e,r,n,t.statsSource,t.actions,t.frameSource)}var F=300,I=[`log`,`info`,`warn`,`error`,`debug`],L=class{engine;container;doc;statsSource;actions;frameSource;panels=new Map;cleanups=[];menuButton;dropdown=null;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`,_(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.menuButton.addEventListener(`click`,()=>this.toggleDropdown()),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(B).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 x(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 I){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(B).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.dropdown?.remove(),this.dropdown=null,this.menuButton.remove()}menuLabel(e,t){let n=e===`colliders`&&this.colliderMode!==`off`?` · ${this.colliderMode}`:``;return`${this.isOpen(e)?`✓ `:``}${t}${n}`}toggleDropdown(){if(this.dropdown){this.dropdown.remove(),this.dropdown=null;return}let e=this.doc.createElement(`div`);_(e,{position:`absolute`,top:`34px`,left:`8px`,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),_(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.dropdown?.remove(),this.dropdown=null}),e.appendChild(r)}for(let t of this.actions){let n=this.doc.createElement(`div`);n.textContent=t.label,_(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.dropdown?.remove(),this.dropdown=null,t.run()}),e.appendChild(n)}this.container.appendChild(e),this.dropdown=e}openStatsChip(){if(this.statsChip)return;let e=this.doc.createElement(`div`);_(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 ${z(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;R(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`);_(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?`▸`:`▾`:`·`,_(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,_(c,{whiteSpace:`nowrap`}),o.appendChild(c);let l=this.doc.createElement(`span`);if(l.textContent=` ${n.typeName}`,_(l,{opacity:`0.45`,whiteSpace:`nowrap`,fontSize:`10px`}),o.appendChild(l),i&&a){let t=this.doc.createElement(`span`);t.textContent=` (${e.children.length})`,_(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`);_(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;R(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`,_(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}`:``}`,_(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(`, `)}`,_(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`);_(c,{display:`flex`,gap:`6px`,alignItems:`center`,margin:`2px 0`});let l=this.doc.createElement(`div`);l.textContent=r,_(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),_(i,H(`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}),_(e,H(`110px`)),c.appendChild(e)}else if(typeof a==`string`){let e=this.doc.createElement(`input`);e.type=`text`,e.value=a,this.trackEditing(e),_(e,H(`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(M(a)){let o=this.doc.createElement(`div`);_(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<=D){let e=this.doc.createElement(`div`);e.textContent=o,_(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?`▾`:`▸`} ${A(a)}`,_(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=j(a,o),_(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>F&&this.logRows.splice(0,this.logRows.length-F),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(()=>{}),E({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,_(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,N)),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;R(e.body),this.timeEls=null;let t=this.doc.createElement(`div`);_(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(N),n.step=`0.05`,n.value=String(Math.min(this.engine.timeScale,N)),_(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`);_(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),_(i,H(`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}×`,_(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`);_(a,{display:`flex`,gap:`8px`});let o=this.doc.createElement(`div`);_(o,V()),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`,_(s,V()),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,N)),e.box.value=String(Math.round(t*1e3)/1e3)}renderLogs(){let e=this.panels.get(`logs`);if(!e)return;R(e.body);let t=this.doc.createElement(`div`);_(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}`,_(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`,_(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}`,_(n,{color:r[t.level],whiteSpace:`pre-wrap`}),e.body.appendChild(n)}}};function R(e){for(let t of[...e.children])t.remove()}function z(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function B(e){if(typeof e==`string`)return e;if(e instanceof Error){let t=e.stack?.split(`
|
|
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 V(){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 H(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{P as attachDebugOverlay};
|