incanto 0.40.1 → 0.42.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 +144 -0
- package/dist/2d.d.ts +2 -2
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +76 -4
- package/dist/3d.js +5 -5
- package/dist/{behavior-TA8nySqb.d.ts → behavior-62q0HWBO.d.ts} +3 -1
- package/dist/{create-game-B7c5icho.js → create-game-9KPppR0L.js} +4 -4
- package/dist/{create-game-p-O66Y_U.js → create-game-DItIRyXH.js} +158 -5
- package/dist/debug.d.ts +36 -2
- package/dist/debug.js +183 -2
- package/dist/{environment-presets-CIZXGKnS.js → environment-presets-Vl5xBsXp.js} +2 -2
- package/dist/{gameplay-By8mslMc.js → gameplay-DRi9524r.js} +13 -0
- package/dist/gameplay.d.ts +13 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -167
- package/dist/{loader-CvSDKC3v.d.ts → loader-CeyU_bm1.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +2 -2
- package/dist/{pathfinding-_UlKUoox.d.ts → pathfinding-C49JSNNq.d.ts} +1 -1
- package/dist/{physics-2d-CxIqR1cW.js → physics-2d-BmgXBNDB.js} +1 -1
- package/dist/{physics-3d-CgIHivyL.js → physics-3d-C2G604O1.js} +2 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-DMFJI-c_.js → register-BSXV8T9F.js} +5 -1
- package/dist/{register-D5v8iqjZ.js → register-D651it1J.js} +1 -1
- package/dist/{register-CgMO5JBY.js → register-R2JTnIMw.js} +1 -1
- package/dist/{replay-DN-kG4va.d.ts → replay-CAphXMyM.d.ts} +1 -1
- package/dist/{replay-D_QY5Xm3.js → replay-DilbZgQI.js} +1 -1
- package/dist/src-cU57Uwdw.js +166 -0
- package/dist/{test-DUrGse_6.js → test-DuOD1DO8.js} +22 -11
- package/dist/test.d.ts +3 -3
- package/dist/test.js +2 -2
- package/dist/vite.d.ts +61 -1
- package/dist/vite.js +149 -2
- package/editor/assets/{agent8-CFq3LMrx.js → agent8-Csw0T7jh.js} +1 -1
- package/editor/assets/debug-C3qeBD4X.js +3 -0
- package/editor/assets/{index-CRwy6mq9.js → index-B1rUkWxB.js} +50 -50
- package/editor/index.html +1 -1
- package/package.json +3 -2
- package/skills/incanto-audio.md +6 -2
- package/skills/incanto-verifying-your-game.md +60 -2
- 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-C2aomumK.js +0 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
/**
|
|
4
|
+
* incanto-frame — what is actually on screen, as numbers.
|
|
5
|
+
*
|
|
6
|
+
* bunx incanto-frame
|
|
7
|
+
*
|
|
8
|
+
* The rung above `incanto-check`. That one says the scene loads; `framing` says
|
|
9
|
+
* what the camera SHOULD see; this says what the GPU actually drew — the only
|
|
10
|
+
* one that can catch a black screen, a material that failed, or a shader
|
|
11
|
+
* drawing where it should not.
|
|
12
|
+
*
|
|
13
|
+
* It writes no files. An image is only worth producing when a human is going to
|
|
14
|
+
* look at one, and a report is what an agent can act on.
|
|
15
|
+
*
|
|
16
|
+
* Finding the dev server is this program's job, never the caller's: it reads
|
|
17
|
+
* the ports that are actually listening and asks each one whether it is an
|
|
18
|
+
* incanto dev server. You run one command; it works out the rest.
|
|
19
|
+
*/
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
import { dirname, join } from 'node:path';
|
|
22
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
|
+
|
|
24
|
+
const PKG = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
+
const { listeningPorts, parseProcNetTcp } = await import(
|
|
26
|
+
pathToFileURL(join(PKG, 'dist', 'vite.js')).href
|
|
27
|
+
);
|
|
28
|
+
const { frameText } = await import(pathToFileURL(join(PKG, 'dist', '3d.js')).href);
|
|
29
|
+
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const asJson = args.includes('--json');
|
|
32
|
+
const gridArg = args[args.indexOf('--grid') + 1];
|
|
33
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
34
|
+
console.error(`Usage: incanto-frame [options]
|
|
35
|
+
|
|
36
|
+
--json the full report as JSON instead of prose
|
|
37
|
+
--grid WxH cells across and down (default 16x9)
|
|
38
|
+
--port N skip discovery and use this port
|
|
39
|
+
|
|
40
|
+
Run your game's dev server and open the preview page first: the pixels live in
|
|
41
|
+
the browser, not in the dev server.`);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const read = (path) => {
|
|
46
|
+
try {
|
|
47
|
+
return readFileSync(path, 'utf-8');
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Ports that are listening, on a machine with no `/proc` (macOS, BSD).
|
|
55
|
+
* The container this is built for is Linux, but a developer's laptop is not,
|
|
56
|
+
* and a tool that only works in CI teaches nobody anything.
|
|
57
|
+
*/
|
|
58
|
+
function bsdListeningPorts() {
|
|
59
|
+
try {
|
|
60
|
+
const { execFileSync } = createRequire(import.meta.url)('node:child_process');
|
|
61
|
+
const out = execFileSync('lsof', ['-nP', '-iTCP', '-sTCP:LISTEN'], {
|
|
62
|
+
encoding: 'utf-8',
|
|
63
|
+
timeout: 1500,
|
|
64
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
65
|
+
});
|
|
66
|
+
const ports = new Set();
|
|
67
|
+
for (const line of out.split('\n')) {
|
|
68
|
+
const m = line.match(/:(\d+)\s*\(LISTEN\)/);
|
|
69
|
+
if (m) ports.add(Number(m[1]));
|
|
70
|
+
}
|
|
71
|
+
return [...ports];
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const explicit = args.includes('--port') ? Number(args[args.indexOf('--port') + 1]) : null;
|
|
78
|
+
const discovered = explicit ? [explicit] : await listeningPorts({ read });
|
|
79
|
+
const ports = explicit ? discovered : [...new Set([...bsdListeningPorts(), ...discovered])];
|
|
80
|
+
|
|
81
|
+
let found = null;
|
|
82
|
+
// BOTH hosts, deliberately: vite binds to ::1 on some machines and 127.0.0.1 on
|
|
83
|
+
// others, and a probe of the wrong one reports "no dev server" while the page
|
|
84
|
+
// is plainly on screen.
|
|
85
|
+
const HOSTS = ['localhost', '127.0.0.1'];
|
|
86
|
+
outer: for (const port of ports) {
|
|
87
|
+
for (const host of HOSTS) {
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(`http://${host}:${port}/__incanto/ping`, {
|
|
90
|
+
signal: AbortSignal.timeout(300),
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok) continue;
|
|
93
|
+
const body = await res.json();
|
|
94
|
+
if (body?.incanto) {
|
|
95
|
+
found = { host, port, ...body };
|
|
96
|
+
break outer;
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// not listening, not ours, or not answering — either way, next
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!found) {
|
|
105
|
+
console.error(
|
|
106
|
+
'no incanto dev server found.\n' +
|
|
107
|
+
' Start your game (the dev server) and try again — this reads the frame\n' +
|
|
108
|
+
' from the running preview, so there has to be one.',
|
|
109
|
+
);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const res = await fetch(`http://${found.host}:${found.port}/__incanto/frame`, {
|
|
114
|
+
signal: AbortSignal.timeout(10_000),
|
|
115
|
+
});
|
|
116
|
+
const body = await res.json().catch(() => null);
|
|
117
|
+
|
|
118
|
+
if (res.status === 504 || body?.error === 'no-page-connected') {
|
|
119
|
+
console.error(
|
|
120
|
+
`the dev server is running on :${found.port}, but no page is connected.\n` +
|
|
121
|
+
' Open the preview in a browser — the pixels are there, not in the server.',
|
|
122
|
+
);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
if (!res.ok || !body?.report) {
|
|
126
|
+
console.error(`could not capture a frame: ${body?.error ?? res.status}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const report = body.report;
|
|
131
|
+
if (report.error) {
|
|
132
|
+
console.error(`the page could not capture a frame: ${report.error}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (asJson) {
|
|
137
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
138
|
+
} else {
|
|
139
|
+
process.stdout.write(`${frameText(report)}\n`);
|
|
140
|
+
}
|
|
141
|
+
process.exit(report.black ? 1 : 0);
|
|
142
|
+
|
|
143
|
+
void parseProcNetTcp; // re-exported for tests; referenced so bundlers keep it
|
|
144
|
+
void gridArg;
|
package/dist/2d.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-
|
|
1
|
+
import { Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-62q0HWBO.js";
|
|
2
2
|
import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DVwIGZdK.js";
|
|
3
3
|
import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
4
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
4
|
+
import { t as LoadSceneOptions } from "./loader-CeyU_bm1.js";
|
|
5
5
|
import { t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
|
|
6
6
|
import { Group, Mesh, Object3D, Scene, Texture } from "three";
|
|
7
7
|
import * as RapierNs from "@dimforge/rapier2d-compat";
|
package/dist/2d.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
|
-
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-
|
|
3
|
-
import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-
|
|
4
|
-
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-
|
|
2
|
+
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-9KPppR0L.js";
|
|
3
|
+
import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-R2JTnIMw.js";
|
|
4
|
+
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BmgXBNDB.js";
|
|
5
5
|
//#region src/2d/library-sprite.ts
|
|
6
6
|
/**
|
|
7
7
|
* Turn a library sprite-animation JSON into a scene-ready spritesheet asset
|
package/dist/3d.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { D as QualityCaps, Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-
|
|
1
|
+
import { D as QualityCaps, Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-62q0HWBO.js";
|
|
2
2
|
import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DVwIGZdK.js";
|
|
3
3
|
import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
4
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
4
|
+
import { t as LoadSceneOptions } from "./loader-CeyU_bm1.js";
|
|
5
5
|
import { t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
|
|
6
|
-
import { n as PathGrid, s as SpatialPose } from "./pathfinding-
|
|
6
|
+
import { n as PathGrid, s as SpatialPose } from "./pathfinding-C49JSNNq.js";
|
|
7
7
|
import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
|
|
8
8
|
import { VRM } from "@pixiv/three-vrm";
|
|
9
9
|
import { Sky } from "three/examples/jsm/objects/Sky.js";
|
|
@@ -190,6 +190,45 @@ declare function keyboardIntensity(moving: boolean, sprinting: boolean): number;
|
|
|
190
190
|
/** intensity → movement state (the animation driver). */
|
|
191
191
|
declare function movementState(grounded: boolean, intensity: number): "idle" | "walk" | "run" | "fastRun" | "airborne";
|
|
192
192
|
//#endregion
|
|
193
|
+
//#region src/3d/frame-report.d.ts
|
|
194
|
+
/**
|
|
195
|
+
* What the frame looks like, as something worth saying.
|
|
196
|
+
*
|
|
197
|
+
* The pixels are not the deliverable. A 1280x720 frame is 2.8 million numbers:
|
|
198
|
+
* unreadable by a person, unaffordable for a model (roughly a thousand tokens
|
|
199
|
+
* as an image, roughly a million as text). The deliverable is the JUDGEMENT —
|
|
200
|
+
* black screen, one flat colour, washed out — and the numbers behind it, small
|
|
201
|
+
* enough to print and compare.
|
|
202
|
+
*
|
|
203
|
+
* Same principle as `framing`'s "lit by: NOTHING": a report is a sentence
|
|
204
|
+
* before it is a number, because a number needs a reader who already knows what
|
|
205
|
+
* to compare it against.
|
|
206
|
+
*
|
|
207
|
+
* Pure arithmetic over an RGBA buffer — no three, no DOM, no GPU. Whoever has
|
|
208
|
+
* the pixels calls this; in practice that is the browser holding the canvas.
|
|
209
|
+
*/
|
|
210
|
+
/** Per-cell mean colour, `[r, g, b]` 0..255. */
|
|
211
|
+
type GridCell = [number, number, number];
|
|
212
|
+
interface FrameStats {
|
|
213
|
+
width: number;
|
|
214
|
+
height: number;
|
|
215
|
+
/** Every pixel below the black threshold — the render produced nothing. */
|
|
216
|
+
black: boolean;
|
|
217
|
+
/** One colour everywhere: a clear-colour fill, or a camera inside geometry. */
|
|
218
|
+
uniform: boolean;
|
|
219
|
+
/** 0..1 mean relative luminance. */
|
|
220
|
+
meanLuma: number;
|
|
221
|
+
/** Coarse mean colour per cell, rows top to bottom. */
|
|
222
|
+
grid: GridCell[][];
|
|
223
|
+
}
|
|
224
|
+
interface FrameStatsOptions {
|
|
225
|
+
/** Cells across and down. Default 16x9 — 144 cells, printable. */
|
|
226
|
+
grid?: [number, number];
|
|
227
|
+
}
|
|
228
|
+
declare function frameStats(pixels: Uint8Array | Uint8ClampedArray, width: number, height: number, opts?: FrameStatsOptions): FrameStats;
|
|
229
|
+
/** The report a person or an agent reads. Verdict first, numbers after. */
|
|
230
|
+
declare function frameText(stats: FrameStats): string;
|
|
231
|
+
//#endregion
|
|
193
232
|
//#region src/3d/nodes/node-3d.d.ts
|
|
194
233
|
/**
|
|
195
234
|
* Per-frame hand-off from `Renderer3D` to nodes that need the LIVE WebGL
|
|
@@ -643,6 +682,20 @@ interface Game3D {
|
|
|
643
682
|
/** Engine + renderer perf counters in one read (fps/nodes/triangles/…). */
|
|
644
683
|
stats(): GameStats;
|
|
645
684
|
/**
|
|
685
|
+
* What is actually on screen, as numbers.
|
|
686
|
+
*
|
|
687
|
+
* The rung above `framing()`: that one PREDICTS what the camera should see
|
|
688
|
+
* from the scene graph, this one measures what the GPU drew. The difference
|
|
689
|
+
* between the two is the whole class of bug a scene graph cannot express — a
|
|
690
|
+
* black screen, a material that failed, a shader drawing where it should not.
|
|
691
|
+
*
|
|
692
|
+
* Returns the report, never a file. An image is only worth writing when a
|
|
693
|
+
* human is going to look at it.
|
|
694
|
+
*/
|
|
695
|
+
frame(opts?: {
|
|
696
|
+
grid?: [number, number];
|
|
697
|
+
}): Promise<FrameStats>;
|
|
698
|
+
/**
|
|
646
699
|
* Assets that failed to load, with the reason.
|
|
647
700
|
*
|
|
648
701
|
* "The model just never appeared" used to be unanswerable from outside: the
|
|
@@ -3351,6 +3404,25 @@ declare class Renderer3D {
|
|
|
3351
3404
|
warmUp(onProgress?: (fraction: number) => void): Promise<void>;
|
|
3352
3405
|
private render;
|
|
3353
3406
|
/**
|
|
3407
|
+
* A frame's pixels, read on the NEXT frame that draws.
|
|
3408
|
+
*
|
|
3409
|
+
* `preserveDrawingBuffer` is false by default — with good reason, it costs
|
|
3410
|
+
* bandwidth on every frame — so `canvas.toDataURL()` comes back blank, which
|
|
3411
|
+
* is the trap this repo has fallen into before. Reading with `gl.readPixels`
|
|
3412
|
+
* at the END of a render, in the same frame, needs no such flag and no
|
|
3413
|
+
* per-frame cost: nothing happens at all until somebody asks.
|
|
3414
|
+
*
|
|
3415
|
+
* That is why this lives in the engine rather than in a generic screenshot
|
|
3416
|
+
* tool. An outside tool cannot get inside the frame.
|
|
3417
|
+
*/
|
|
3418
|
+
captureFrame(): Promise<{
|
|
3419
|
+
pixels: Uint8Array;
|
|
3420
|
+
width: number;
|
|
3421
|
+
height: number;
|
|
3422
|
+
}>;
|
|
3423
|
+
private pendingCaptures;
|
|
3424
|
+
private drainFrameCapture;
|
|
3425
|
+
/**
|
|
3354
3426
|
* Bloom post pass (only when `environment.bloom` is declared). LDR and safe:
|
|
3355
3427
|
* 1. scene → full-res offscreen (tone-mapped exactly like direct output)
|
|
3356
3428
|
* 2. bright-pass at HALF res (keep pixels above threshold)
|
|
@@ -3575,4 +3647,4 @@ declare function createNavDebugNode(nav: TerrainNav, opts?: {
|
|
|
3575
3647
|
name?: string;
|
|
3576
3648
|
}): InstancedMesh3D;
|
|
3577
3649
|
//#endregion
|
|
3578
|
-
export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, findRiverCoverageGaps, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
3650
|
+
export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type FrameStats, type FrameStatsOptions, type Game3D, type GridCell, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, findRiverCoverageGaps, frameStats, frameText, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
package/dist/3d.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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-
|
|
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-
|
|
3
|
-
import { a as Environment3D, c as
|
|
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-DRi9524r.js";
|
|
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-Vl5xBsXp.js";
|
|
3
|
+
import { a as Environment3D, c as setEnvironment3D, d as sunDirectionFromElevationAzimuth, f as sunDirectionFromSky, i as syncTree, l as horizonColorFromSky, o as frameStats, p as AssetStore3D, r as Renderer3D, s as frameText, t as createGame3D, u as parseEnvironment3D } from "./create-game-DItIRyXH.js";
|
|
4
4
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
5
|
-
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-
|
|
5
|
+
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-C2G604O1.js";
|
|
6
6
|
//#region src/3d/terrain-nav.ts
|
|
7
7
|
function buildTerrainNav(terrain, opts) {
|
|
8
8
|
const cellSize = opts?.cellSize ?? 2;
|
|
@@ -114,4 +114,4 @@ function createNavDebugNode(nav, opts) {
|
|
|
114
114
|
return node;
|
|
115
115
|
}
|
|
116
116
|
//#endregion
|
|
117
|
-
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, 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, enablePhysics3D, findRiverCoverageGaps, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
117
|
+
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, 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, enablePhysics3D, findRiverCoverageGaps, frameStats, frameText, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
@@ -390,7 +390,9 @@ interface PlayMusicOptions {
|
|
|
390
390
|
* frame to advance fades (the engine wires this to `updated`).
|
|
391
391
|
*
|
|
392
392
|
* ```ts
|
|
393
|
-
*
|
|
393
|
+
* // NOTE: the bundle ships SFX only — no music track. A background track is
|
|
394
|
+
* // your game's own file under public/, or a URL.
|
|
395
|
+
* engine.music.play('/audio/theme.mp3'); // loop, full
|
|
394
396
|
* engine.music.crossfadeTo('boss.mp3', 3); // 3s equal-power swap
|
|
395
397
|
* engine.music.stop(2); // fade out over 2s
|
|
396
398
|
* engine.music.setVolume(0.5); // per-manager volume
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { n as loadScene, o as computeViewport, s as resolveViewport, v as diagnose, w as registerBehavior } from "./loader-r49nDwB4.js";
|
|
3
|
-
import { h as Engine, m as AudioPlayer } from "./register-
|
|
3
|
+
import { h as Engine, m as AudioPlayer } from "./register-BSXV8T9F.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-BoNg_MnF.js";
|
|
6
6
|
import { i as openBundledEditor, n as crossFade, r as devServerLibrary, t as teardown } from "./teardown-CCtAMDLB.js";
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
-
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-DRi9524r.js";
|
|
8
|
+
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-R2JTnIMw.js";
|
|
9
9
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
10
|
-
import { n as enablePhysics2D } from "./physics-2d-
|
|
10
|
+
import { n as enablePhysics2D } from "./physics-2d-BmgXBNDB.js";
|
|
11
11
|
import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
|
|
12
12
|
//#region src/2d/assets.ts
|
|
13
13
|
/**
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { n as loadScene, v as diagnose, w as registerBehavior } from "./loader-r49nDwB4.js";
|
|
3
|
-
import { _ as qualityCaps, h as Engine, m as AudioPlayer, y as qualityRendering } from "./register-
|
|
3
|
+
import { _ as qualityCaps, h as Engine, m as AudioPlayer, y as qualityRendering } from "./register-BSXV8T9F.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-BoNg_MnF.js";
|
|
6
6
|
import { a as poseFromRenderer, i as openBundledEditor, n as crossFade, r as devServerLibrary, t as teardown } from "./teardown-CCtAMDLB.js";
|
|
7
|
-
import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-
|
|
7
|
+
import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-DRi9524r.js";
|
|
8
8
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
9
|
-
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-
|
|
10
|
-
import { n as enablePhysics3D } from "./physics-3d-
|
|
9
|
+
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-Vl5xBsXp.js";
|
|
10
|
+
import { n as enablePhysics3D } from "./physics-3d-C2G604O1.js";
|
|
11
11
|
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";
|
|
12
12
|
import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
|
|
13
13
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
@@ -486,6 +486,76 @@ function setEnvironment3D(engine, patch) {
|
|
|
486
486
|
scene.environment = merged;
|
|
487
487
|
}
|
|
488
488
|
//#endregion
|
|
489
|
+
//#region src/3d/frame-report.ts
|
|
490
|
+
/** Rec. 709 relative luminance of an 8-bit triple, 0..1. */
|
|
491
|
+
function luma(r, g, b) {
|
|
492
|
+
return (.2126 * r + .7152 * g + .0722 * b) / 255;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* A pixel this dark is indistinguishable from "nothing was drawn". Not zero:
|
|
496
|
+
* a real render of a night scene still lands a few levels above black, and
|
|
497
|
+
* calling that a failure would make the check useless where it matters most.
|
|
498
|
+
*/
|
|
499
|
+
const BLACK_LEVEL = .012;
|
|
500
|
+
/** Cell-to-cell luminance range below this: the frame is one flat colour. */
|
|
501
|
+
const UNIFORM_EPSILON = .004;
|
|
502
|
+
function frameStats(pixels, width, height, opts = {}) {
|
|
503
|
+
const [cols, rows] = opts.grid ?? [16, 9];
|
|
504
|
+
const grid = [];
|
|
505
|
+
let total = 0;
|
|
506
|
+
let maxLuma = 0;
|
|
507
|
+
let minLuma = 1;
|
|
508
|
+
for (let row = 0; row < rows; row++) {
|
|
509
|
+
const line = [];
|
|
510
|
+
for (let col = 0; col < cols; col++) {
|
|
511
|
+
const x0 = Math.floor(col * width / cols);
|
|
512
|
+
const x1 = Math.max(x0 + 1, Math.floor((col + 1) * width / cols));
|
|
513
|
+
const y0 = Math.floor(row * height / rows);
|
|
514
|
+
const y1 = Math.max(y0 + 1, Math.floor((row + 1) * height / rows));
|
|
515
|
+
let r = 0;
|
|
516
|
+
let g = 0;
|
|
517
|
+
let b = 0;
|
|
518
|
+
let n = 0;
|
|
519
|
+
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) {
|
|
520
|
+
const i = (y * width + x) * 4;
|
|
521
|
+
r += pixels[i] ?? 0;
|
|
522
|
+
g += pixels[i + 1] ?? 0;
|
|
523
|
+
b += pixels[i + 2] ?? 0;
|
|
524
|
+
n += 1;
|
|
525
|
+
}
|
|
526
|
+
const cell = [
|
|
527
|
+
Math.round(r / n),
|
|
528
|
+
Math.round(g / n),
|
|
529
|
+
Math.round(b / n)
|
|
530
|
+
];
|
|
531
|
+
const cellLuma = luma(cell[0], cell[1], cell[2]);
|
|
532
|
+
total += cellLuma;
|
|
533
|
+
maxLuma = Math.max(maxLuma, cellLuma);
|
|
534
|
+
minLuma = Math.min(minLuma, cellLuma);
|
|
535
|
+
line.push(cell);
|
|
536
|
+
}
|
|
537
|
+
grid.push(line);
|
|
538
|
+
}
|
|
539
|
+
const cells = cols * rows;
|
|
540
|
+
const meanLuma = total / cells;
|
|
541
|
+
return {
|
|
542
|
+
width,
|
|
543
|
+
height,
|
|
544
|
+
black: maxLuma <= BLACK_LEVEL,
|
|
545
|
+
uniform: maxLuma - minLuma < UNIFORM_EPSILON,
|
|
546
|
+
meanLuma: Math.round(meanLuma * 1e3) / 1e3,
|
|
547
|
+
grid
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/** The report a person or an agent reads. Verdict first, numbers after. */
|
|
551
|
+
function frameText(stats) {
|
|
552
|
+
const lines = [];
|
|
553
|
+
if (stats.black) lines.push("BLACK SCREEN — nothing was drawn (no light, no camera, or nothing in view)");
|
|
554
|
+
else if (stats.uniform) lines.push("one flat colour — the camera may be inside geometry, or only the sky is drawn");
|
|
555
|
+
lines.push(`frame ${stats.width}×${stats.height} · luminance ${stats.meanLuma.toFixed(2)}`);
|
|
556
|
+
return lines.join("\n");
|
|
557
|
+
}
|
|
558
|
+
//#endregion
|
|
489
559
|
//#region src/3d/adaptive-resolution.ts
|
|
490
560
|
const DEFAULTS = {
|
|
491
561
|
slowMs: 20,
|
|
@@ -1869,6 +1939,50 @@ var Renderer3D = class {
|
|
|
1869
1939
|
else if (clouds && !underwater) this.renderWithClouds(activeCamera, clouds, threeLight);
|
|
1870
1940
|
else if ((bloom || post) && !underwater) this.renderWithBloom(activeCamera, bloom, post);
|
|
1871
1941
|
else this.webgl.render(this.threeScene, activeCamera);
|
|
1942
|
+
this.drainFrameCapture();
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* A frame's pixels, read on the NEXT frame that draws.
|
|
1946
|
+
*
|
|
1947
|
+
* `preserveDrawingBuffer` is false by default — with good reason, it costs
|
|
1948
|
+
* bandwidth on every frame — so `canvas.toDataURL()` comes back blank, which
|
|
1949
|
+
* is the trap this repo has fallen into before. Reading with `gl.readPixels`
|
|
1950
|
+
* at the END of a render, in the same frame, needs no such flag and no
|
|
1951
|
+
* per-frame cost: nothing happens at all until somebody asks.
|
|
1952
|
+
*
|
|
1953
|
+
* That is why this lives in the engine rather than in a generic screenshot
|
|
1954
|
+
* tool. An outside tool cannot get inside the frame.
|
|
1955
|
+
*/
|
|
1956
|
+
captureFrame() {
|
|
1957
|
+
return new Promise((resolve, reject) => {
|
|
1958
|
+
this.pendingCaptures.push({
|
|
1959
|
+
resolve,
|
|
1960
|
+
reject
|
|
1961
|
+
});
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
pendingCaptures = [];
|
|
1965
|
+
drainFrameCapture() {
|
|
1966
|
+
if (this.pendingCaptures.length === 0) return;
|
|
1967
|
+
const waiting = this.pendingCaptures;
|
|
1968
|
+
this.pendingCaptures = [];
|
|
1969
|
+
try {
|
|
1970
|
+
const gl = this.webgl.getContext();
|
|
1971
|
+
const width = this.webgl.domElement.width;
|
|
1972
|
+
const height = this.webgl.domElement.height;
|
|
1973
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
1974
|
+
this.webgl.setRenderTarget(null);
|
|
1975
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
1976
|
+
flipRows(pixels, width, height);
|
|
1977
|
+
for (const w of waiting) w.resolve({
|
|
1978
|
+
pixels,
|
|
1979
|
+
width,
|
|
1980
|
+
height
|
|
1981
|
+
});
|
|
1982
|
+
} catch (error) {
|
|
1983
|
+
const e = error instanceof Error ? error : new Error(String(error));
|
|
1984
|
+
for (const w of waiting) w.reject(e);
|
|
1985
|
+
}
|
|
1872
1986
|
}
|
|
1873
1987
|
/**
|
|
1874
1988
|
* Bloom post pass (only when `environment.bloom` is declared). LDR and safe:
|
|
@@ -2235,6 +2349,18 @@ const camPosScratch = new Vector3();
|
|
|
2235
2349
|
/** Reused click-pick scratches (no per-pick allocation). */
|
|
2236
2350
|
const pickRaycaster = new Raycaster();
|
|
2237
2351
|
const pickNdc = new Vector2();
|
|
2352
|
+
/** Turn a bottom-left GL buffer into top-left screen order, in place. */
|
|
2353
|
+
function flipRows(pixels, width, height) {
|
|
2354
|
+
const stride = width * 4;
|
|
2355
|
+
const row = new Uint8Array(stride);
|
|
2356
|
+
for (let y = 0; y < Math.floor(height / 2); y++) {
|
|
2357
|
+
const top = y * stride;
|
|
2358
|
+
const bottom = (height - 1 - y) * stride;
|
|
2359
|
+
row.set(pixels.subarray(top, top + stride));
|
|
2360
|
+
pixels.copyWithin(top, bottom, bottom + stride);
|
|
2361
|
+
pixels.set(row, bottom);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2238
2364
|
//#endregion
|
|
2239
2365
|
//#region src/3d/create-game.ts
|
|
2240
2366
|
var create_game_exports = /* @__PURE__ */ __exportAll({ createGame3D: () => createGame3D });
|
|
@@ -2378,6 +2504,27 @@ async function createGame3D(opts) {
|
|
|
2378
2504
|
cleanups.push(engine.settings.bindRenderScale((scale) => {
|
|
2379
2505
|
renderer.setRenderScale?.(scale);
|
|
2380
2506
|
}));
|
|
2507
|
+
const captureReport = async (frameOpts) => {
|
|
2508
|
+
const capture = renderer.captureFrame;
|
|
2509
|
+
if (!capture) throw new IncantoError("TREE_VIOLATION", "this renderer cannot capture frames");
|
|
2510
|
+
const shot = await capture.call(renderer);
|
|
2511
|
+
return frameStats(shot.pixels, shot.width, shot.height, frameOpts);
|
|
2512
|
+
};
|
|
2513
|
+
const hot = import.meta.hot;
|
|
2514
|
+
if (hot) {
|
|
2515
|
+
const onFrameRequest = (data) => {
|
|
2516
|
+
const id = data?.id;
|
|
2517
|
+
captureReport().then((frameReport) => hot.send("incanto:frame-report", {
|
|
2518
|
+
id,
|
|
2519
|
+
report: frameReport
|
|
2520
|
+
})).catch((error) => hot.send("incanto:frame-report", {
|
|
2521
|
+
id,
|
|
2522
|
+
report: { error: error instanceof Error ? error.message : String(error) }
|
|
2523
|
+
}));
|
|
2524
|
+
};
|
|
2525
|
+
hot.on("incanto:frame-request", onFrameRequest);
|
|
2526
|
+
cleanups.push(() => hot.off?.("incanto:frame-request", onFrameRequest));
|
|
2527
|
+
}
|
|
2381
2528
|
await report(1, "ready");
|
|
2382
2529
|
function disposeGame() {
|
|
2383
2530
|
teardown([
|
|
@@ -2398,6 +2545,12 @@ async function createGame3D(opts) {
|
|
|
2398
2545
|
assetErrors() {
|
|
2399
2546
|
return renderer.assets?.errors() ?? [];
|
|
2400
2547
|
},
|
|
2548
|
+
async frame(frameOpts) {
|
|
2549
|
+
const capture = renderer.captureFrame;
|
|
2550
|
+
if (!capture) throw new IncantoError("TREE_VIOLATION", "this renderer cannot capture frames");
|
|
2551
|
+
const shot = await capture.call(renderer);
|
|
2552
|
+
return frameStats(shot.pixels, shot.width, shot.height, frameOpts);
|
|
2553
|
+
},
|
|
2401
2554
|
pick(x, y) {
|
|
2402
2555
|
return engine.picker?.(x, y) ?? null;
|
|
2403
2556
|
},
|
|
@@ -2464,4 +2617,4 @@ function wireAudioUnlock(engine, root, canvas) {
|
|
|
2464
2617
|
return detachAll;
|
|
2465
2618
|
}
|
|
2466
2619
|
//#endregion
|
|
2467
|
-
export { Environment3D as a,
|
|
2620
|
+
export { Environment3D as a, setEnvironment3D as c, sunDirectionFromElevationAzimuth as d, sunDirectionFromSky as f, syncTree as i, horizonColorFromSky as l, create_game_exports as n, frameStats as o, AssetStore3D as p, Renderer3D as r, frameText as s, createGame3D as t, parseEnvironment3D as u };
|
package/dist/debug.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { At as LogLevel, T as RendererStats, b as Engine } from "./behavior-
|
|
1
|
+
import { At as LogLevel, T as RendererStats, b as Engine } from "./behavior-62q0HWBO.js";
|
|
2
2
|
|
|
3
3
|
//#region src/debug/panel.d.ts
|
|
4
4
|
/** Minimal document surface the overlay needs (injectable for tests). */
|
|
@@ -7,7 +7,7 @@ interface DocumentLike {
|
|
|
7
7
|
}
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/debug/index.d.ts
|
|
10
|
-
type DebugPanelId = "explorer" | "inspector" | "logs" | "stats";
|
|
10
|
+
type DebugPanelId = "explorer" | "inspector" | "logs" | "stats" | "time";
|
|
11
11
|
/** Menu entries that are switches rather than panels. */
|
|
12
12
|
type DebugSwitchId = "colliders";
|
|
13
13
|
/** `all` is every collider in the world; `selected` follows the Explorer. */
|
|
@@ -64,6 +64,15 @@ declare class DebugOverlay {
|
|
|
64
64
|
private editing;
|
|
65
65
|
/** Which bulky values are unfolded, per node ('transforms', 'collider.vertices'). */
|
|
66
66
|
private readonly detailOpen;
|
|
67
|
+
/**
|
|
68
|
+
* The scale to come back to when a pause is lifted.
|
|
69
|
+
*
|
|
70
|
+
* Captured only from a RUNNING scale: pausing an already-frozen game (a
|
|
71
|
+
* hit-stop, a previous pause) must not record 0 as the thing to restore, or
|
|
72
|
+
* Resume does nothing and the game looks hung.
|
|
73
|
+
*/
|
|
74
|
+
private resumeScale;
|
|
75
|
+
private timeEls;
|
|
67
76
|
/** Pointer inside the inspector — it stops refreshing under your hand. */
|
|
68
77
|
private hovering;
|
|
69
78
|
constructor(engine: Engine, container: HTMLElement, doc: DocumentLike, statsSource?: (() => Partial<RendererStats>) | undefined, actions?: DebugAction[]);
|
|
@@ -107,6 +116,31 @@ declare class DebugOverlay {
|
|
|
107
116
|
private renderValueRow;
|
|
108
117
|
private trackEditing;
|
|
109
118
|
private pushLog;
|
|
119
|
+
/**
|
|
120
|
+
* Set game time, defensively. `timeScale` multiplies every dt in the engine,
|
|
121
|
+
* so a NaN from a text field would poison physics, timers and animation in
|
|
122
|
+
* one frame, and a negative would run the simulation backwards through code
|
|
123
|
+
* that has never been asked to.
|
|
124
|
+
*/
|
|
125
|
+
setTimeScale(next: number): void;
|
|
126
|
+
/** Freeze or unfreeze game time. Rendering keeps running — this is a pause,
|
|
127
|
+
* not a stop: the camera still moves and the overlay stays interactive. */
|
|
128
|
+
setPaused(paused: boolean): void;
|
|
129
|
+
get paused(): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Advance exactly one frame, pausing first.
|
|
132
|
+
*
|
|
133
|
+
* Stepping a RUNNING game is meaningless — the next real frame lands a
|
|
134
|
+
* millisecond later and swamps the step — so this pauses and then steps,
|
|
135
|
+
* which is also the only way the button is useful twice in a row.
|
|
136
|
+
*/
|
|
137
|
+
nextFrame(): void;
|
|
138
|
+
/** Push the engine's current time state into the panel, without rebuilding it. */
|
|
139
|
+
private syncTime;
|
|
140
|
+
private renderTime;
|
|
141
|
+
/** After a committed edit the field must show what the ENGINE took — a
|
|
142
|
+
* rejected 'abc' or a clamped -2 has to be visible, not left on screen. */
|
|
143
|
+
private syncTimeAfterEdit;
|
|
110
144
|
private renderLogs;
|
|
111
145
|
}
|
|
112
146
|
//#endregion
|