incanto 0.46.0 → 0.47.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-check.mjs +5 -1
- package/bin/incanto-feel.mjs +5 -1
- package/bin/incanto-frame.mjs +57 -6
- package/bin/incanto-model.mjs +79 -10
- package/bin/incanto-playtest.mjs +5 -1
- package/bin/incanto-verify.mjs +214 -0
- package/dist/3d.d.ts +89 -4
- package/dist/3d.js +90 -3
- package/dist/{create-game-BtCNdkjI.js → create-game-D16MVIPO.js} +21 -2
- package/dist/{frame-report-Ct8XgsmV.d.ts → frame-report-DZ70IY26.d.ts} +25 -0
- package/dist/{frame-report-Lr3VO24R.js → frame-report-njybhZon.js} +130 -3
- package/dist/index.js +1 -1
- package/dist/react.js +1 -1
- package/dist/{src-B43b6bZo.js → src-Ca3oV1fe.js} +1 -1
- package/dist/{test-BboGNQr-.js → test-E4-otKqK.js} +51 -4
- package/dist/test.d.ts +55 -1
- package/dist/test.js +2 -2
- package/dist/vite.d.ts +13 -2
- package/dist/vite.js +27 -9
- package/editor/assets/{agent8-Dl5ZZLu-.js → agent8-_007gPF8.js} +1 -1
- package/editor/assets/{debug-j9Pi6RvO.js → debug-0DI_MJaq.js} +1 -1
- package/editor/assets/{index-Y-93awAL.js → index-B-6eYZEi.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +3 -2
- package/skills/incanto-3d-models.md +30 -0
- package/skills/incanto-verifying-your-game.md +60 -1
- 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/dist/3d.js
CHANGED
|
@@ -1,9 +1,96 @@
|
|
|
1
1
|
import { A as Water3D, F as PhysicsBody3D, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D, V as WATER_MAX_RIPPLES, j as WATER_CUTOUT_MAX } from "./gameplay-BQOeAid6.js";
|
|
2
2
|
import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-D1b0ydTS.js";
|
|
3
|
-
import { a as Environment3D, c as parseEnvironment3D, d as AssetStore3D, i as syncTree, l as sunDirectionFromElevationAzimuth, o as setEnvironment3D, r as Renderer3D, s as horizonColorFromSky, t as createGame3D, u as sunDirectionFromSky } from "./create-game-
|
|
4
|
-
import { a as
|
|
3
|
+
import { a as Environment3D, c as parseEnvironment3D, d as AssetStore3D, i as syncTree, l as sunDirectionFromElevationAzimuth, o as setEnvironment3D, r as Renderer3D, s as horizonColorFromSky, t as createGame3D, u as sunDirectionFromSky } from "./create-game-D16MVIPO.js";
|
|
4
|
+
import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-njybhZon.js";
|
|
5
5
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
6
6
|
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-CSoGjM8P.js";
|
|
7
|
+
//#region src/3d/model-verdict.ts
|
|
8
|
+
/** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
|
|
9
|
+
const MIXAMO = /^mixamorig[:_]?/i;
|
|
10
|
+
/**
|
|
11
|
+
* Anything outside this is not a character at this scale, and the fix is one
|
|
12
|
+
* prop. Wide on purpose: a 2.4m ogre is a design choice, a 40m one is
|
|
13
|
+
* centimetres.
|
|
14
|
+
*/
|
|
15
|
+
const CHARACTER_HEIGHT = {
|
|
16
|
+
min: .4,
|
|
17
|
+
max: 4
|
|
18
|
+
};
|
|
19
|
+
/** What `targetHeight` gets set to when a model is plainly the wrong scale. */
|
|
20
|
+
const DEFAULT_TARGET_HEIGHT = 1.7;
|
|
21
|
+
/** Fraction of the model's own height that its origin may drift before it matters. */
|
|
22
|
+
const ORIGIN_TOLERANCE = .25;
|
|
23
|
+
function rigOf(facts) {
|
|
24
|
+
if (facts.vrm && facts.vrm.humanBones > 0) return "vrm-humanoid";
|
|
25
|
+
if (facts.nodes.some((n) => MIXAMO.test(n.name))) return "mixamorig";
|
|
26
|
+
return facts.skins > 0 ? "other" : "none";
|
|
27
|
+
}
|
|
28
|
+
function modelVerdict(facts, target) {
|
|
29
|
+
const rig = rigOf(facts);
|
|
30
|
+
const kind = facts.meshes === 0 && facts.animations.length > 0 ? "animation" : "model";
|
|
31
|
+
const height = facts.bbox ? facts.bbox.size[1] : 0;
|
|
32
|
+
const offset = facts.bbox ? [
|
|
33
|
+
facts.bbox.center[0],
|
|
34
|
+
facts.bbox.min[1],
|
|
35
|
+
facts.bbox.center[2]
|
|
36
|
+
] : [
|
|
37
|
+
0,
|
|
38
|
+
0,
|
|
39
|
+
0
|
|
40
|
+
];
|
|
41
|
+
const slack = Math.max(height * ORIGIN_TOLERANCE, .05);
|
|
42
|
+
const grounded = !facts.bbox || Math.abs(offset[0]) <= slack && Math.abs(offset[1]) <= slack && Math.abs(offset[2]) <= slack;
|
|
43
|
+
const oddSize = height > 0 && (height < CHARACTER_HEIGHT.min || height > CHARACTER_HEIGHT.max);
|
|
44
|
+
const props = { model: `$${target?.key ?? "model"}` };
|
|
45
|
+
if (oddSize) props.targetHeight = DEFAULT_TARGET_HEIGHT;
|
|
46
|
+
return {
|
|
47
|
+
kind,
|
|
48
|
+
rig,
|
|
49
|
+
takesLibraryClips: kind === "model" && (rig === "mixamorig" || rig === "vrm-humanoid"),
|
|
50
|
+
height,
|
|
51
|
+
originOffset: offset,
|
|
52
|
+
groundedAtOrigin: grounded,
|
|
53
|
+
embeddedClips: facts.animations.map((a) => a.name),
|
|
54
|
+
sceneJson: target ? {
|
|
55
|
+
assets: { [target.key]: {
|
|
56
|
+
type: kind,
|
|
57
|
+
url: target.url
|
|
58
|
+
} },
|
|
59
|
+
node: kind === "animation" ? null : {
|
|
60
|
+
name: titleCase(target.key),
|
|
61
|
+
type: "ModelInstance3D",
|
|
62
|
+
props
|
|
63
|
+
}
|
|
64
|
+
} : null
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const titleCase = (key) => key.split(/[-_\s]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") || "Model";
|
|
68
|
+
const round = (n) => String(Math.round(n * 100) / 100);
|
|
69
|
+
/** The verdict as sentences: what to do, and what will bite if you do not. */
|
|
70
|
+
function verdictText(v) {
|
|
71
|
+
const lines = [];
|
|
72
|
+
if (v.kind === "animation") {
|
|
73
|
+
lines.push("an animation CLIP — no mesh of its own. It plays ON a character: declare it as {\"type\":\"animation\"} and point a ModelInstance3D's `animation` at it. Used as a `model` it draws nothing at all, with no error");
|
|
74
|
+
if (v.embeddedClips.length > 0) lines.push(`clip: ${v.embeddedClips.join(", ")}`);
|
|
75
|
+
return lines.join("\n");
|
|
76
|
+
}
|
|
77
|
+
if (v.rig === "mixamorig") lines.push("mixamorig rig — the 3d/animations clips play on this as they are");
|
|
78
|
+
else if (v.rig === "vrm-humanoid") lines.push("VRM humanoid — clips are retargeted onto it automatically");
|
|
79
|
+
else if (v.rig === "other") lines.push("skinned, but NOT a mixamorig rig — the 3d/animations clips will not bind to it (bones are matched by name; a mismatch keeps the bind pose and only warns)");
|
|
80
|
+
else lines.push("no skeleton — a prop, not a character; `animation` does nothing here");
|
|
81
|
+
if (v.height > 0) {
|
|
82
|
+
const odd = v.height < CHARACTER_HEIGHT.min || v.height > CHARACTER_HEIGHT.max;
|
|
83
|
+
lines.push(`stands ${round(v.height)} units tall` + (odd ? ` — not a character's size; set targetHeight (${DEFAULT_TARGET_HEIGHT}) to fix it` : ""));
|
|
84
|
+
}
|
|
85
|
+
if (!v.groundedAtOrigin) {
|
|
86
|
+
const [x, y, z] = v.originOffset;
|
|
87
|
+
const where = Math.abs(y) > Math.abs(x) && Math.abs(y) > Math.abs(z) ? `${round(Math.abs(y))} units ${y > 0 ? "above" : "below"} the origin` : `off-centre by [${round(x)}, ${round(y)}, ${round(z)}]`;
|
|
88
|
+
lines.push(`does NOT stand on its own origin — it sits ${where}. A node at [0,0,0] puts it there too; offset the node, or expect an empty-looking frame`);
|
|
89
|
+
}
|
|
90
|
+
if (v.embeddedClips.length > 0) lines.push(`clips in the file: ${v.embeddedClips.join(", ")} (play by name, no asset needed)`);
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
7
94
|
//#region src/3d/terrain-nav.ts
|
|
8
95
|
function buildTerrainNav(terrain, opts) {
|
|
9
96
|
const cellSize = opts?.cellSize ?? 2;
|
|
@@ -115,4 +202,4 @@ function createNavDebugNode(nav, opts) {
|
|
|
115
202
|
return node;
|
|
116
203
|
}
|
|
117
204
|
//#endregion
|
|
118
|
-
export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
205
|
+
export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
|
|
@@ -7,7 +7,7 @@ import { a as openBundledEditor, i as devServerLibrary, n as pauseWhenHidden, o
|
|
|
7
7
|
import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-BQOeAid6.js";
|
|
8
8
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
9
9
|
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-D1b0ydTS.js";
|
|
10
|
-
import { a as
|
|
10
|
+
import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-njybhZon.js";
|
|
11
11
|
import { n as enablePhysics3D } from "./physics-3d-CSoGjM8P.js";
|
|
12
12
|
import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
|
|
13
13
|
import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
|
|
@@ -1545,6 +1545,12 @@ function prune(obj, visited) {
|
|
|
1545
1545
|
//#endregion
|
|
1546
1546
|
//#region src/3d/renderer.ts
|
|
1547
1547
|
/**
|
|
1548
|
+
* How long a capture waits for the game's own loop before drawing its own
|
|
1549
|
+
* frame. Long enough that a running game answers from its normal loop (16ms),
|
|
1550
|
+
* short enough that a paused or occluded tab still answers promptly.
|
|
1551
|
+
*/
|
|
1552
|
+
const CAPTURE_NUDGE_MS = 120;
|
|
1553
|
+
/**
|
|
1548
1554
|
* WebGL presentation layer: subscribes to `engine.updated`, mirrors the active
|
|
1549
1555
|
* scene's node tree onto a three.js scene, applies the scene `environment`
|
|
1550
1556
|
* header (ambient/background/sky/fog/shadows/exposure — see Environment3D),
|
|
@@ -1890,6 +1896,14 @@ var Renderer3D = class {
|
|
|
1890
1896
|
resolve,
|
|
1891
1897
|
reject
|
|
1892
1898
|
});
|
|
1899
|
+
setTimeout(() => {
|
|
1900
|
+
if (this.pendingCaptures.length > 0) try {
|
|
1901
|
+
this.render();
|
|
1902
|
+
} catch (error) {
|
|
1903
|
+
const e = error instanceof Error ? error : new Error(String(error));
|
|
1904
|
+
for (const w of this.pendingCaptures.splice(0)) w.reject(e);
|
|
1905
|
+
}
|
|
1906
|
+
}, CAPTURE_NUDGE_MS);
|
|
1893
1907
|
});
|
|
1894
1908
|
}
|
|
1895
1909
|
pendingCaptures = [];
|
|
@@ -2446,11 +2460,16 @@ async function createGame3D(opts) {
|
|
|
2446
2460
|
const onFrameRequest = (data) => {
|
|
2447
2461
|
const request = data;
|
|
2448
2462
|
const id = request?.id;
|
|
2463
|
+
hot.send("incanto:frame-ack", { id });
|
|
2449
2464
|
captureFramePixels().then((shot) => hot.send("incanto:frame-report", {
|
|
2450
2465
|
id,
|
|
2451
2466
|
report: {
|
|
2452
2467
|
...frameStats(shot.pixels, shot.width, shot.height, { grid: request?.grid }),
|
|
2453
|
-
signature: frameSignature(shot.pixels, shot.width, shot.height)
|
|
2468
|
+
signature: frameSignature(shot.pixels, shot.width, shot.height),
|
|
2469
|
+
...request?.image ? { image: frameImage(shot.pixels, shot.width, shot.height, (w, h) => Object.assign(document.createElement("canvas"), {
|
|
2470
|
+
width: w,
|
|
2471
|
+
height: h
|
|
2472
|
+
}), request.image) } : {}
|
|
2454
2473
|
}
|
|
2455
2474
|
})).catch((error) => hot.send("incanto:frame-report", {
|
|
2456
2475
|
id,
|
|
@@ -28,6 +28,31 @@ interface FrameStats {
|
|
|
28
28
|
meanLuma: number;
|
|
29
29
|
/** Coarse mean colour per cell, rows top to bottom. */
|
|
30
30
|
grid: GridCell[][];
|
|
31
|
+
/** What the frame is OF, or null when it is all background. */
|
|
32
|
+
subject: Subject | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The thing in the shot: everything that is not the background.
|
|
36
|
+
*
|
|
37
|
+
* `black` and `uniform` answer "did anything render at all". They do not answer
|
|
38
|
+
* the question that actually goes wrong — "is the thing IN the shot, and can
|
|
39
|
+
* you see it" — and every visual defect reported against the editor's model
|
|
40
|
+
* previews walked straight past them. A white square with a faint sky gradient
|
|
41
|
+
* is neither black nor uniform; a character drawn as a speck in the corner has
|
|
42
|
+
* a perfectly ordinary mean luminance.
|
|
43
|
+
*/
|
|
44
|
+
interface Subject {
|
|
45
|
+
/** Bounding box in pixels, origin top-left. */
|
|
46
|
+
box: {
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
w: number;
|
|
50
|
+
h: number;
|
|
51
|
+
};
|
|
52
|
+
/** Share of the frame's pixels that are subject, 0..1. */
|
|
53
|
+
coverage: number;
|
|
54
|
+
/** Touching an edge — the shot is a crop, and something is outside it. */
|
|
55
|
+
clipped: boolean;
|
|
31
56
|
}
|
|
32
57
|
interface FrameStatsOptions {
|
|
33
58
|
/** Cells across and down. Default 16x9 — 144 cells, printable. */
|
|
@@ -44,6 +44,98 @@ function cellMeans(pixels, width, height, cols, rows) {
|
|
|
44
44
|
}
|
|
45
45
|
return out;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* How far a pixel must sit from the background colour to count as subject.
|
|
49
|
+
* Chosen above the banding in a sky gradient and well below any real material.
|
|
50
|
+
*/
|
|
51
|
+
const SUBJECT_DELTA = 24;
|
|
52
|
+
/** Subject smaller than this is present but not visible — say so differently. */
|
|
53
|
+
const TINY_COVERAGE = .01;
|
|
54
|
+
/**
|
|
55
|
+
* The background colour, read off the BORDER rather than voted for.
|
|
56
|
+
*
|
|
57
|
+
* A subject filling most of the shot would win a popularity contest and the
|
|
58
|
+
* report would invert — the sky would become "the thing", and a well-framed
|
|
59
|
+
* character would read as an empty frame. The edge of a frame is background in
|
|
60
|
+
* every shot this is asked about; where it is not, the subject is clipped, and
|
|
61
|
+
* that gets reported too.
|
|
62
|
+
*/
|
|
63
|
+
function borderColour(pixels, width, height) {
|
|
64
|
+
let r = 0;
|
|
65
|
+
let g = 0;
|
|
66
|
+
let b = 0;
|
|
67
|
+
let n = 0;
|
|
68
|
+
const take = (x, y) => {
|
|
69
|
+
const o = (y * width + x) * 4;
|
|
70
|
+
r += pixels[o];
|
|
71
|
+
g += pixels[o + 1];
|
|
72
|
+
b += pixels[o + 2];
|
|
73
|
+
n += 1;
|
|
74
|
+
};
|
|
75
|
+
for (let x = 0; x < width; x++) {
|
|
76
|
+
take(x, 0);
|
|
77
|
+
take(x, height - 1);
|
|
78
|
+
}
|
|
79
|
+
for (let y = 1; y < height - 1; y++) {
|
|
80
|
+
take(0, y);
|
|
81
|
+
take(width - 1, y);
|
|
82
|
+
}
|
|
83
|
+
return n === 0 ? [
|
|
84
|
+
0,
|
|
85
|
+
0,
|
|
86
|
+
0
|
|
87
|
+
] : [
|
|
88
|
+
r / n,
|
|
89
|
+
g / n,
|
|
90
|
+
b / n
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
/** Where the non-background pixels are, or null when there are none worth it. */
|
|
94
|
+
function findSubject(pixels, width, height) {
|
|
95
|
+
const global = borderColour(pixels, width, height);
|
|
96
|
+
const at = (x, y) => {
|
|
97
|
+
const o = (y * width + x) * 4;
|
|
98
|
+
return [
|
|
99
|
+
pixels[o],
|
|
100
|
+
pixels[o + 1],
|
|
101
|
+
pixels[o + 2]
|
|
102
|
+
];
|
|
103
|
+
};
|
|
104
|
+
const dist = (a, b) => Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]);
|
|
105
|
+
let minX = width;
|
|
106
|
+
let minY = height;
|
|
107
|
+
let maxX = -1;
|
|
108
|
+
let maxY = -1;
|
|
109
|
+
let count = 0;
|
|
110
|
+
for (let y = 0; y < height; y++) {
|
|
111
|
+
const left = at(0, y);
|
|
112
|
+
const right = at(width - 1, y);
|
|
113
|
+
const row = dist(left, right) > SUBJECT_DELTA ? dist(left, global) <= dist(right, global) ? left : right : [
|
|
114
|
+
(left[0] + right[0]) / 2,
|
|
115
|
+
(left[1] + right[1]) / 2,
|
|
116
|
+
(left[2] + right[2]) / 2
|
|
117
|
+
];
|
|
118
|
+
for (let x = 0; x < width; x++) {
|
|
119
|
+
if (dist(at(x, y), row) < SUBJECT_DELTA) continue;
|
|
120
|
+
count += 1;
|
|
121
|
+
if (x < minX) minX = x;
|
|
122
|
+
if (x > maxX) maxX = x;
|
|
123
|
+
if (y < minY) minY = y;
|
|
124
|
+
if (y > maxY) maxY = y;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (maxX < 0) return null;
|
|
128
|
+
return {
|
|
129
|
+
box: {
|
|
130
|
+
x: minX,
|
|
131
|
+
y: minY,
|
|
132
|
+
w: maxX - minX + 1,
|
|
133
|
+
h: maxY - minY + 1
|
|
134
|
+
},
|
|
135
|
+
coverage: count / (width * height),
|
|
136
|
+
clipped: minX === 0 || minY === 0 || maxX === width - 1 || maxY === height - 1
|
|
137
|
+
};
|
|
138
|
+
}
|
|
47
139
|
function frameStats(pixels, width, height, opts = {}) {
|
|
48
140
|
const [cols, rows] = opts.grid ?? [16, 9];
|
|
49
141
|
const means = cellMeans(pixels, width, height, cols, rows);
|
|
@@ -70,21 +162,30 @@ function frameStats(pixels, width, height, opts = {}) {
|
|
|
70
162
|
}
|
|
71
163
|
const cells = cols * rows;
|
|
72
164
|
const meanLuma = total / cells;
|
|
165
|
+
const black = maxLuma <= BLACK_LEVEL;
|
|
73
166
|
return {
|
|
74
167
|
width,
|
|
75
168
|
height,
|
|
76
|
-
black
|
|
169
|
+
black,
|
|
77
170
|
uniform: maxLuma - minLuma < UNIFORM_EPSILON,
|
|
78
171
|
meanLuma: Math.round(meanLuma * 1e3) / 1e3,
|
|
79
|
-
grid
|
|
172
|
+
grid,
|
|
173
|
+
subject: black ? null : findSubject(pixels, width, height)
|
|
80
174
|
};
|
|
81
175
|
}
|
|
176
|
+
const pct = (v) => `${(v * 100).toFixed(1)}%`;
|
|
82
177
|
/** The report a person or an agent reads. Verdict first, numbers after. */
|
|
83
178
|
function frameText(stats) {
|
|
84
179
|
const lines = [];
|
|
85
180
|
if (stats.black) lines.push("BLACK SCREEN — nothing was drawn (no light, no camera, or nothing in view)");
|
|
86
181
|
else if (stats.uniform) lines.push("one flat colour — the camera may be inside geometry, or only the sky is drawn");
|
|
182
|
+
else if (!stats.subject) lines.push("nothing but background — the camera is pointed away from everything in the scene");
|
|
183
|
+
else if (stats.subject.coverage < TINY_COVERAGE) lines.push(`the subject fills almost nothing (${pct(stats.subject.coverage)}) — the camera is too far back, or what you meant to see is not what got drawn`);
|
|
87
184
|
lines.push(`frame ${stats.width}×${stats.height} · luminance ${stats.meanLuma.toFixed(2)}`);
|
|
185
|
+
if (stats.subject) {
|
|
186
|
+
const { box, coverage, clipped } = stats.subject;
|
|
187
|
+
lines.push(`subject ${box.w}×${box.h} at ${box.x},${box.y} · fills ${pct(coverage)} of frame` + (clipped ? " · CLIPPED by the frame edge" : ""));
|
|
188
|
+
}
|
|
88
189
|
return lines.join("\n");
|
|
89
190
|
}
|
|
90
191
|
/**
|
|
@@ -193,5 +294,31 @@ function fromBase64(text) {
|
|
|
193
294
|
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
194
295
|
return out;
|
|
195
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* A `data:` PNG of the frame, scaled to fit `maxSide`.
|
|
299
|
+
*
|
|
300
|
+
* Rows are taken as they come, TOP-DOWN. `gl.readPixels` is bottom-up, which
|
|
301
|
+
* is why this wants flipping — but `Renderer3D.captureFrame` already flipped
|
|
302
|
+
* once, "rather than in each of" its consumers, and this is one of them.
|
|
303
|
+
* Flipping again shipped an upside-down picture that the unit test happily
|
|
304
|
+
* confirmed, because the test asserted the belief instead of the boundary.
|
|
305
|
+
*/
|
|
306
|
+
function frameImage(pixels, width, height, makeCanvas, maxSide = 512) {
|
|
307
|
+
const full = makeCanvas(width, height);
|
|
308
|
+
const ctx = full.getContext("2d");
|
|
309
|
+
if (!ctx) return null;
|
|
310
|
+
const image = ctx.createImageData(width, height);
|
|
311
|
+
image.data.set(pixels.subarray(0, width * height * 4));
|
|
312
|
+
ctx.putImageData(image, 0, 0);
|
|
313
|
+
const scale = Math.min(1, maxSide / Math.max(width, height));
|
|
314
|
+
if (scale >= 1) return full.toDataURL("image/png");
|
|
315
|
+
const w = Math.max(1, Math.round(width * scale));
|
|
316
|
+
const h = Math.max(1, Math.round(height * scale));
|
|
317
|
+
const small = makeCanvas(w, h);
|
|
318
|
+
const sctx = small.getContext("2d");
|
|
319
|
+
if (!sctx) return full.toDataURL("image/png");
|
|
320
|
+
sctx.drawImage(full, 0, 0, w, h);
|
|
321
|
+
return small.toDataURL("image/png");
|
|
322
|
+
}
|
|
196
323
|
//#endregion
|
|
197
|
-
export {
|
|
324
|
+
export { frameSignature as a, frameImage as i, diffSignatures as n, frameStats as o, diffText as r, frameText as s, 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-Ca3oV1fe.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-D16MVIPO.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CniOiWzN.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.47.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 };
|
|
@@ -345,12 +345,14 @@ function makeOracle(engine, player) {
|
|
|
345
345
|
let outcome = null;
|
|
346
346
|
let damage = 0;
|
|
347
347
|
const offs = [];
|
|
348
|
+
let declaresEnd = false;
|
|
348
349
|
/** First verdict wins — a run ends at its first conclusion, not its last. */
|
|
349
350
|
const settle = (verdict) => {
|
|
350
351
|
if (outcome === null) outcome = verdict;
|
|
351
352
|
};
|
|
352
353
|
const walk = (node) => {
|
|
353
354
|
if (node.behavior) for (const signal of node.declaredSignalNames()) {
|
|
355
|
+
if (signal === "won" || signal === "lost" || signal === "flowChanged") declaresEnd = true;
|
|
354
356
|
if (signal === "won") offs.push(node.on(signal, () => settle("won")));
|
|
355
357
|
if (signal === "lost") offs.push(node.on(signal, () => settle("lost")));
|
|
356
358
|
if (signal === "died" && node === player) offs.push(node.on(signal, () => settle("lost")));
|
|
@@ -368,6 +370,7 @@ function makeOracle(engine, player) {
|
|
|
368
370
|
if (root) walk(root);
|
|
369
371
|
return {
|
|
370
372
|
outcome: () => outcome,
|
|
373
|
+
declaresEnd: () => declaresEnd,
|
|
371
374
|
damage: () => damage,
|
|
372
375
|
dispose: () => {
|
|
373
376
|
for (const off of offs) off();
|
|
@@ -549,7 +552,8 @@ async function runOnce(json, seed, opts) {
|
|
|
549
552
|
return {
|
|
550
553
|
run,
|
|
551
554
|
targets,
|
|
552
|
-
signals
|
|
555
|
+
signals,
|
|
556
|
+
declaresEnd: oracle.declaresEnd()
|
|
553
557
|
};
|
|
554
558
|
}
|
|
555
559
|
/** Play the scene `runs` times and report what happened. */
|
|
@@ -559,11 +563,13 @@ async function playtest(json, opts = {}) {
|
|
|
559
563
|
const runs = [];
|
|
560
564
|
let targets = [];
|
|
561
565
|
let signals = [];
|
|
566
|
+
let declaresWin = false;
|
|
562
567
|
for (let i = 0; i < count; i++) {
|
|
563
568
|
const one = await runOnce(json, base + i, opts);
|
|
564
569
|
runs.push(one.run);
|
|
565
570
|
targets = one.targets;
|
|
566
571
|
signals = one.signals;
|
|
572
|
+
declaresWin = declaresWin || one.declaresEnd;
|
|
567
573
|
}
|
|
568
574
|
const probe = await createPlaySession(json, {
|
|
569
575
|
seed: base,
|
|
@@ -577,7 +583,8 @@ async function playtest(json, opts = {}) {
|
|
|
577
583
|
targets,
|
|
578
584
|
signals: [...new Set(signals)],
|
|
579
585
|
actions,
|
|
580
|
-
inertActions: []
|
|
586
|
+
inertActions: [],
|
|
587
|
+
declaresWin
|
|
581
588
|
};
|
|
582
589
|
}
|
|
583
590
|
function pct(n, total) {
|
|
@@ -603,7 +610,7 @@ function playtestText(report) {
|
|
|
603
610
|
lines.push("");
|
|
604
611
|
const won = runs.filter((r) => r.outcome === "won");
|
|
605
612
|
if (won.length > 0) lines.push(` ✓ reached "won" in ${pct(won.length, total)} median ${Math.round(median(won.map((r) => r.timeMs)) / 1e3)}s`);
|
|
606
|
-
else lines.push(` ✗ never reached "won" in ${total} runs —
|
|
613
|
+
else lines.push(report.declaresWin ? ` ✗ never reached "won" in ${total} runs — a win IS declared, and no run got to it` : ` · no win declared in this scene — nothing emits won/lost, so there is nothing to reach`);
|
|
607
614
|
for (const bad of [
|
|
608
615
|
"error",
|
|
609
616
|
"fell",
|
|
@@ -1139,6 +1146,46 @@ function feelText(report) {
|
|
|
1139
1146
|
return lines.join("\n");
|
|
1140
1147
|
}
|
|
1141
1148
|
//#endregion
|
|
1149
|
+
//#region src/test/verify-ladder.ts
|
|
1150
|
+
const MARK = {
|
|
1151
|
+
pass: "✓",
|
|
1152
|
+
fail: "✗",
|
|
1153
|
+
unmeasured: "?",
|
|
1154
|
+
skipped: "·"
|
|
1155
|
+
};
|
|
1156
|
+
function ladderVerdict(rungs, ctx = {}) {
|
|
1157
|
+
const failed = rungs.find((r) => r.status === "fail");
|
|
1158
|
+
const unmeasured = rungs.filter((r) => r.status === "unmeasured");
|
|
1159
|
+
const next = failed ? failed.fix ?? failed.summary : unmeasured[0]?.fix ?? unmeasured[0]?.summary ?? null;
|
|
1160
|
+
const ambiguous = rungs.length === 0 && (ctx.candidates?.length ?? 0) > 1;
|
|
1161
|
+
return {
|
|
1162
|
+
ok: !failed,
|
|
1163
|
+
rungs,
|
|
1164
|
+
unmeasured: unmeasured.map((r) => r.name),
|
|
1165
|
+
next: ambiguous ? "name the one you mean: `incanto-verify <scene.json>`" : next ?? null,
|
|
1166
|
+
candidates: ctx.candidates ?? []
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
/** The ladder as a person or an agent reads it: every rung, then the one action. */
|
|
1170
|
+
function ladderText(v) {
|
|
1171
|
+
if (v.rungs.length === 0) {
|
|
1172
|
+
if (v.candidates.length > 1) return [
|
|
1173
|
+
`${v.candidates.length} scenes here, and guessing between them would verify the wrong one:`,
|
|
1174
|
+
...v.candidates.map((c) => ` ${c}`),
|
|
1175
|
+
"",
|
|
1176
|
+
`next: ${v.next}`
|
|
1177
|
+
].join("\n");
|
|
1178
|
+
return "nothing to verify — no scene was given and none was found";
|
|
1179
|
+
}
|
|
1180
|
+
const lines = v.rungs.map((r) => `${MARK[r.status]} ${r.name} — ${r.summary}`);
|
|
1181
|
+
lines.push("");
|
|
1182
|
+
if (!v.ok) lines.push("NOT verified.");
|
|
1183
|
+
else if (v.unmeasured.length > 0) lines.push(`passes what was measured — ${v.unmeasured.join(", ")} not measured.`);
|
|
1184
|
+
else lines.push("verified: it loads, it plays, and it draws.");
|
|
1185
|
+
if (v.next) lines.push(`next: ${v.next}`);
|
|
1186
|
+
return lines.join("\n");
|
|
1187
|
+
}
|
|
1188
|
+
//#endregion
|
|
1142
1189
|
//#region src/test/index.ts
|
|
1143
1190
|
/**
|
|
1144
1191
|
* incanto/test — the browserless verification harness.
|
|
@@ -1524,4 +1571,4 @@ async function createPlaySession(json, opts = {}) {
|
|
|
1524
1571
|
};
|
|
1525
1572
|
}
|
|
1526
1573
|
//#endregion
|
|
1527
|
-
export {
|
|
1574
|
+
export { playtestText as _, registerAllNodes as a, ladderText as c, feelText as d, facingReport as f, playtest as g, findPlayer as h, findFloatingProps as i, ladderVerdict as l, failingReplays as m, createPlaySession as n, runScript as o, facingText as p, describeCapture as r, validateScene as s, captureScene as t, feelReport as u, describeFraming as v, framingText as y };
|
package/dist/test.d.ts
CHANGED
|
@@ -165,6 +165,15 @@ interface PlaytestReport {
|
|
|
165
165
|
actions: string[];
|
|
166
166
|
/** Actions that never changed the world in any run. */
|
|
167
167
|
inertActions: string[];
|
|
168
|
+
/**
|
|
169
|
+
* Does anything in this scene declare an END?
|
|
170
|
+
*
|
|
171
|
+
* "No run reached a win" has two causes that need opposite fixes: a goal
|
|
172
|
+
* that cannot be reached, and a scene with no goal in it. A walkabout
|
|
173
|
+
* template is not a broken game, and reporting one as a failure sends its
|
|
174
|
+
* author looking for a bug that was never there.
|
|
175
|
+
*/
|
|
176
|
+
declaresWin: boolean;
|
|
168
177
|
}
|
|
169
178
|
/**
|
|
170
179
|
* Who the driver is playing as.
|
|
@@ -190,6 +199,51 @@ declare function failingReplays(report: PlaytestReport): Array<{
|
|
|
190
199
|
replay: JsonValue;
|
|
191
200
|
}>;
|
|
192
201
|
//#endregion
|
|
202
|
+
//#region src/test/verify-ladder.d.ts
|
|
203
|
+
/**
|
|
204
|
+
* The verification ladder, as one answer.
|
|
205
|
+
*
|
|
206
|
+
* The rungs already exist — `incanto-check` says the scene loads,
|
|
207
|
+
* `incanto-playtest` says it can be finished, `incanto-frame` says something
|
|
208
|
+
* was drawn. They are documented together and used apart, because using them
|
|
209
|
+
* together means knowing three things nobody writes down:
|
|
210
|
+
*
|
|
211
|
+
* the ORDER (a scene that does not load cannot be played or drawn, so the
|
|
212
|
+
* first red rung is the only one worth reading)
|
|
213
|
+
* that an unmeasured rung is not a failing one — "no dev server" means the
|
|
214
|
+
* question was never asked, and answering it "fail" sends an agent editing
|
|
215
|
+
* a scene that is fine
|
|
216
|
+
* what to do NEXT, which is one sentence and never the whole report
|
|
217
|
+
*
|
|
218
|
+
* Pure: the rungs are run by the CLI, this decides what they add up to.
|
|
219
|
+
*/
|
|
220
|
+
type RungStatus = "pass" | "fail" | "unmeasured" | "skipped";
|
|
221
|
+
interface RungResult {
|
|
222
|
+
name: string;
|
|
223
|
+
status: RungStatus;
|
|
224
|
+
/** One line: what this rung found. */
|
|
225
|
+
summary: string;
|
|
226
|
+
/** What to do about it, when there is something to do. */
|
|
227
|
+
fix?: string;
|
|
228
|
+
}
|
|
229
|
+
interface LadderVerdict {
|
|
230
|
+
ok: boolean;
|
|
231
|
+
rungs: RungResult[];
|
|
232
|
+
/** Rungs that could not be measured — questions unasked, not answers. */
|
|
233
|
+
unmeasured: string[];
|
|
234
|
+
/** The single next action, or null when there is nothing to do. */
|
|
235
|
+
next: string | null;
|
|
236
|
+
/** Scenes found when none was named (empty once one is being verified). */
|
|
237
|
+
candidates: string[];
|
|
238
|
+
}
|
|
239
|
+
interface LadderContext {
|
|
240
|
+
/** Scenes found when none was named — none, or more than one to choose from. */
|
|
241
|
+
candidates?: string[];
|
|
242
|
+
}
|
|
243
|
+
declare function ladderVerdict(rungs: RungResult[], ctx?: LadderContext): LadderVerdict;
|
|
244
|
+
/** The ladder as a person or an agent reads it: every rung, then the one action. */
|
|
245
|
+
declare function ladderText(v: LadderVerdict): string;
|
|
246
|
+
//#endregion
|
|
193
247
|
//#region src/test/index.d.ts
|
|
194
248
|
/** One thing that is not standing where it should be. */
|
|
195
249
|
interface GroundingIssue {
|
|
@@ -371,4 +425,4 @@ interface PlaySession {
|
|
|
371
425
|
*/
|
|
372
426
|
declare function createPlaySession(json: unknown, opts?: PlaySessionOptions): Promise<PlaySession>;
|
|
373
427
|
//#endregion
|
|
374
|
-
export { type FacingOptions, type FacingReport, type FeelOptions, type FeelReport, type FramingEntry, type FramingOptions, type FramingReport, GroundingIssue, GroundingOptions, NodeCapture, type Outcome, PlaySession, PlaySessionOptions, type PlaytestOptions, type PlaytestReport, type PlaytestRun, RunContext, RunFailure, RunResult, RunScriptOptions, SceneCapture, ScriptStep, ValidateSceneOptions, ValidationResult, type Where, auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, playtest, playtestText, registerAllNodes, runScript, validateScene };
|
|
428
|
+
export { type FacingOptions, type FacingReport, type FeelOptions, type FeelReport, type FramingEntry, type FramingOptions, type FramingReport, GroundingIssue, GroundingOptions, type LadderVerdict, NodeCapture, type Outcome, PlaySession, PlaySessionOptions, type PlaytestOptions, type PlaytestReport, type PlaytestRun, RunContext, RunFailure, RunResult, RunScriptOptions, type RungResult, type RungStatus, SceneCapture, ScriptStep, ValidateSceneOptions, ValidationResult, type Where, auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, ladderText, ladderVerdict, playtest, playtestText, registerAllNodes, runScript, validateScene };
|
package/dist/test.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { r as auditScene } from "./replay-DilbZgQI.js";
|
|
2
|
-
import { _ as
|
|
3
|
-
export { auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, playtest, playtestText, registerAllNodes, runScript, validateScene };
|
|
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-E4-otKqK.js";
|
|
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.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as FrameSignature } from "./frame-report-
|
|
1
|
+
import { n as FrameSignature } from "./frame-report-DZ70IY26.js";
|
|
2
2
|
|
|
3
3
|
//#region src/vite/discover.d.ts
|
|
4
4
|
/**
|
|
@@ -29,9 +29,20 @@ interface FrameHost {
|
|
|
29
29
|
middlewares: {
|
|
30
30
|
use(path: string, handler: (req: FrameReq, res: FrameRes) => void): void;
|
|
31
31
|
};
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Vite's HMR channel, which has moved twice. `ws` on v5, `hot` on v6+, and
|
|
34
|
+
* since the Environment API the one that actually reaches the BROWSER is
|
|
35
|
+
* `environments.client.hot` — on v8 the top-level `hot` still exists and
|
|
36
|
+
* still accepts `send()` without error, it simply arrives nowhere. Every
|
|
37
|
+
* candidate is used, because a silent no-op is the worst of the three.
|
|
38
|
+
*/
|
|
33
39
|
ws?: FrameChannel;
|
|
34
40
|
hot?: FrameChannel;
|
|
41
|
+
environments?: {
|
|
42
|
+
client?: {
|
|
43
|
+
hot?: FrameChannel;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
35
46
|
}
|
|
36
47
|
interface FrameChannel {
|
|
37
48
|
send(payload: {
|