incanto 0.56.0 → 0.58.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/README.md +6 -4
- package/bin/incanto-check.mjs +27 -0
- package/bin/incanto-new.mjs +29 -7
- package/bin/incanto-playtest.mjs +28 -2
- package/bin/incanto-verify.mjs +96 -17
- package/bin/incanto.mjs +106 -0
- package/dist/2d.d.ts +59 -7
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +34 -4
- package/dist/3d.js +5 -5
- package/dist/{behavior-CyQoSu4n.d.ts → behavior-l08AEbq9.d.ts} +5 -0
- package/dist/{create-game-YvaNWhe6.js → create-game-BZwWJIns.js} +146 -7
- package/dist/{create-game-CqJkfMzm.js → create-game-D10bU5_J.js} +83 -78
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-MNLMAcbz.js → duplicate-BPLZDZpd.js} +1 -1
- package/dist/{environment-presets-DLHqgNAF.js → environment-presets-CvvQr_bJ.js} +106 -24
- package/dist/{frame-report-njybhZon.js → frame-report-BSMny7oe.js} +1 -1
- package/dist/{frame-report-DZ70IY26.d.ts → frame-report-DCnHFmto.d.ts} +1 -1
- package/dist/{gameplay-C_SPzmUe.js → gameplay-BVphcxmE.js} +89 -13
- package/dist/gameplay.d.ts +26 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +45 -5
- package/dist/index.js +8 -9
- package/dist/{loader-DhI1jFW_.d.ts → loader-BbEMTuWg.d.ts} +1 -1
- package/dist/{loader-BTkHYrQn.js → loader-BcrRSjxB.js} +682 -682
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{pathfinding-CXGCpRQe.d.ts → pathfinding-mEN4V1CU.d.ts} +1 -1
- package/dist/{physics-2d-CfWAggJ1.js → physics-2d-EqA-jddf.js} +4 -3
- package/dist/{physics-3d-NzDwWPrF.js → physics-3d-Dnz4fsXX.js} +5 -4
- package/dist/quiet-rapier-BAJ4K94N.js +46 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-en63AEZO.js → register-C6ZBFRjd.js} +63 -60
- package/dist/{register-p48lHE2o.js → register-Ch70uByv.js} +1615 -1502
- package/dist/{replay-ePMz26jw.d.ts → replay-Dw6gMlYA.d.ts} +1 -1
- package/dist/{replay-t1pP0gQg.js → replay-s7I2GstT.js} +31 -11
- package/dist/sheet-grid-BT6N_Bjs.js +59 -0
- package/dist/{split-screen-BsdOHbzP.d.ts → split-screen-B0baBwxI.d.ts} +5 -1
- package/dist/{split-screen-CSb_uZ6W.js → split-screen-DLsUrleX.js} +14 -3
- package/dist/{sprite-animation-7qvUxF6Z.js → sprite-animation-C0wXLBZJ.js} +8 -4
- package/dist/{src-51GA-9m-.js → src-C1J09Op6.js} +55 -2
- package/dist/{teardown-C7uVSJvx.js → teardown-Cs113S9F.js} +74 -2
- package/dist/{test-Bu6SPNsV.js → test-ZBga8kQ9.js} +60 -25
- package/dist/test.d.ts +5 -5
- package/dist/test.js +2 -2
- package/dist/{touch-BnMyy9tr.js → touch-DESwnpOc.js} +173 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-X0Nesj-k.js → agent8-BQQjE9UQ.js} +1 -1
- package/editor/assets/{debug-TXqu-ftX.js → debug-C9UCsXBS.js} +1 -1
- package/editor/assets/{index-DTdwKKIv.js → index-CBgfM3WD.js} +62 -62
- package/editor/index.html +1 -1
- package/package.json +2 -1
- package/skills/incanto-assets.md +5 -1
- package/skills/incanto-audio.md +7 -1
- package/skills/incanto-building-2d-games.md +13 -0
- package/skills/incanto-building-3d-games.md +3 -3
- package/skills/incanto-gameplay-behaviors.md +2 -1
- package/skills/incanto-hud.md +6 -0
- package/skills/incanto-performance.md +5 -0
- package/skills/incanto-physics-and-input.md +14 -0
- package/skills/incanto-playtesting.md +19 -5
- package/skills/incanto-save-slots.md +2 -1
- package/skills/incanto-verifying-your-game.md +15 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +7 -6
- package/templates-app/platformer-2d/PROJECT/Context.md +70 -0
- package/templates-app/platformer-2d/PROJECT/Requirements.md +63 -0
- package/templates-app/platformer-2d/PROJECT/Status.md +60 -0
- package/templates-app/platformer-2d/PROJECT/Structure.md +77 -0
- package/templates-app/platformer-2d/docs/project-2d-rules.md +61 -0
- package/templates-app/platformer-2d/index.html +99 -0
- package/templates-app/platformer-2d/package.json +23 -0
- package/templates-app/platformer-2d/src/behaviors.ts +541 -0
- package/templates-app/platformer-2d/src/game.scene.json +2061 -0
- package/templates-app/platformer-2d/src/main.ts +68 -0
- package/templates-app/platformer-2d/tsconfig.json +13 -0
- package/templates-app/platformer-2d/verify.ts +275 -0
- package/templates-app/platformer-2d/vite.config.ts +12 -0
- package/templates-app/star-survivor/PROJECT/Context.md +55 -0
- package/templates-app/star-survivor/PROJECT/Requirements.md +47 -0
- package/templates-app/star-survivor/PROJECT/Status.md +44 -0
- package/templates-app/star-survivor/PROJECT/Structure.md +63 -0
- package/templates-app/star-survivor/docs/project-2d-rules.md +53 -0
- package/templates-app/star-survivor/index.html +232 -0
- package/templates-app/star-survivor/package.json +23 -0
- package/templates-app/star-survivor/src/behaviors.ts +624 -0
- package/templates-app/star-survivor/src/game.scene.json +464 -0
- package/templates-app/star-survivor/src/main.ts +49 -0
- package/templates-app/star-survivor/tsconfig.json +13 -0
- package/templates-app/star-survivor/verify.ts +193 -0
- package/templates-app/star-survivor/vite.config.ts +12 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +6 -3
- package/templates-app/tps-3d/verify.ts +17 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +14 -13
- package/templates-app/village-quest-3d/src/village.scene.json +5 -5
- package/dist/log-report-CPFm4OXf.js +0 -173
|
@@ -465,6 +465,11 @@ declare class MusicManager {
|
|
|
465
465
|
* With an inert backend the state machine runs identically and nothing plays.
|
|
466
466
|
*/
|
|
467
467
|
private ensure;
|
|
468
|
+
/**
|
|
469
|
+
* Where a track that will never play gets reported. The engine sets this; a
|
|
470
|
+
* bare MusicManager (tests) stays quiet.
|
|
471
|
+
*/
|
|
472
|
+
onTrackError: ((src: string, reason: string) => void) | null;
|
|
468
473
|
/** Resume a gesture-suspended context + (re)start any pending track. */
|
|
469
474
|
unlock(): void;
|
|
470
475
|
/** Play a single track (replacing any current one immediately, no fade). */
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { A as registerBehavior, C as diagnose, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-
|
|
3
|
-
import {
|
|
2
|
+
import { A as registerBehavior, C as diagnose, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-BcrRSjxB.js";
|
|
3
|
+
import { _ as Engine, g as AudioPlayer } from "./register-Ch70uByv.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
|
-
import { i as resolveRendering, n as attachTouchControls } from "./touch-
|
|
6
|
-
import { a as openBundledEditor, c as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
|
|
6
|
+
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-Cs113S9F.js";
|
|
7
|
+
import { o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
8
|
+
import { n as registerGameplayBehaviors } from "./gameplay-BVphcxmE.js";
|
|
9
|
+
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-C6ZBFRjd.js";
|
|
9
10
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
10
|
-
import { n as enablePhysics2D } from "./physics-2d-
|
|
11
|
+
import { n as enablePhysics2D } from "./physics-2d-EqA-jddf.js";
|
|
11
12
|
import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
|
|
12
13
|
//#region src/2d/assets.ts
|
|
13
14
|
/**
|
|
@@ -276,6 +277,8 @@ function prune(obj, visited) {
|
|
|
276
277
|
* (center/zoom/limits), then a screen-space UI pass for UILayer subtrees.
|
|
277
278
|
* 1 unit = 1 px, (0,0) top-left, +y down.
|
|
278
279
|
*/
|
|
280
|
+
/** How long to wait for the game's own loop before drawing a frame ourselves. */
|
|
281
|
+
const CAPTURE_NUDGE_MS = 120;
|
|
279
282
|
var Renderer2D = class {
|
|
280
283
|
/**
|
|
281
284
|
* When set, the world pass frames THIS view instead of the scene's active
|
|
@@ -284,6 +287,76 @@ var Renderer2D = class {
|
|
|
284
287
|
*/
|
|
285
288
|
viewOverride = null;
|
|
286
289
|
webgl;
|
|
290
|
+
/**
|
|
291
|
+
* The pixels of the next drawn frame — how `incanto-frame` sees a 2D game.
|
|
292
|
+
*
|
|
293
|
+
* Mirrors Renderer3D exactly: read inside the render, so
|
|
294
|
+
* `preserveDrawingBuffer` can stay off, and nudge a frame ourselves after a
|
|
295
|
+
* moment because a browser stops handing rAF to a tab it thinks is hidden —
|
|
296
|
+
* including a window merely covered by another one. Without the nudge the
|
|
297
|
+
* promise never settles and the CLI reports "no page connected" about a page
|
|
298
|
+
* that is right there.
|
|
299
|
+
*/
|
|
300
|
+
captureFrame() {
|
|
301
|
+
return new Promise((resolve, reject) => {
|
|
302
|
+
this.pendingCaptures.push({
|
|
303
|
+
resolve,
|
|
304
|
+
reject
|
|
305
|
+
});
|
|
306
|
+
setTimeout(() => {
|
|
307
|
+
if (this.pendingCaptures.length > 0) try {
|
|
308
|
+
this.render();
|
|
309
|
+
} catch (error) {
|
|
310
|
+
const e = error instanceof Error ? error : new Error(String(error));
|
|
311
|
+
for (const w of this.pendingCaptures.splice(0)) w.reject(e);
|
|
312
|
+
}
|
|
313
|
+
}, CAPTURE_NUDGE_MS);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
pendingCaptures = [];
|
|
317
|
+
/** Hand the waiting captures this frame's pixels. Called at the end of render. */
|
|
318
|
+
drainFrameCapture() {
|
|
319
|
+
if (this.pendingCaptures.length === 0) return;
|
|
320
|
+
const waiting = this.pendingCaptures;
|
|
321
|
+
this.pendingCaptures = [];
|
|
322
|
+
try {
|
|
323
|
+
const gl = this.webgl.getContext();
|
|
324
|
+
const width = this.webgl.domElement.width;
|
|
325
|
+
const height = this.webgl.domElement.height;
|
|
326
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
327
|
+
this.webgl.setRenderTarget(null);
|
|
328
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
329
|
+
for (let y = 0; y < Math.floor(height / 2); y++) {
|
|
330
|
+
const top = y * width * 4;
|
|
331
|
+
const bottom = (height - 1 - y) * width * 4;
|
|
332
|
+
for (let i = 0; i < width * 4; i++) {
|
|
333
|
+
const t = pixels[top + i];
|
|
334
|
+
pixels[top + i] = pixels[bottom + i];
|
|
335
|
+
pixels[bottom + i] = t;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (const w of waiting) w.resolve({
|
|
339
|
+
pixels,
|
|
340
|
+
width,
|
|
341
|
+
height
|
|
342
|
+
});
|
|
343
|
+
} catch (error) {
|
|
344
|
+
const e = error instanceof Error ? error : new Error(String(error));
|
|
345
|
+
for (const w of waiting) w.reject(e);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/** The pixel ratio the scene asked for, before any render-scale setting. */
|
|
349
|
+
basePixelRatio = 1;
|
|
350
|
+
/**
|
|
351
|
+
* Fewer pixels for the same world — the cheapest frames on a weak device.
|
|
352
|
+
*
|
|
353
|
+
* `settings.renderScale` persisted and was applied by the 3D boot only, so a
|
|
354
|
+
* 2D game's resolution slider saved a number and changed nothing, forever.
|
|
355
|
+
*/
|
|
356
|
+
setRenderScale(scale) {
|
|
357
|
+
const clamped = Number.isFinite(scale) && scale > 0 ? Math.min(2, Math.max(.25, scale)) : 1;
|
|
358
|
+
this.webgl.setPixelRatio(this.basePixelRatio * clamped);
|
|
359
|
+
}
|
|
287
360
|
worldScene = new Scene();
|
|
288
361
|
syncScratch = createSync2DScratch();
|
|
289
362
|
/** Editors set this: keep syncing `static: true` subtrees every frame. */
|
|
@@ -313,6 +386,7 @@ var Renderer2D = class {
|
|
|
313
386
|
});
|
|
314
387
|
this.webgl.info.autoReset = false;
|
|
315
388
|
this.engine.picker = (x, y) => this.pick(x, y);
|
|
389
|
+
this.basePixelRatio = rendering.pixelRatio;
|
|
316
390
|
this.webgl.setPixelRatio(rendering.pixelRatio);
|
|
317
391
|
this.webgl.info.autoReset = false;
|
|
318
392
|
this.debugLines = new LineSegments(new BufferGeometry(), new LineBasicMaterial({
|
|
@@ -447,6 +521,7 @@ var Renderer2D = class {
|
|
|
447
521
|
this.webgl.autoClear = false;
|
|
448
522
|
this.webgl.render(this.uiScene, this.uiCam);
|
|
449
523
|
this.webgl.autoClear = true;
|
|
524
|
+
this.drainFrameCapture();
|
|
450
525
|
}
|
|
451
526
|
/** UI-pass extent from the last render — the space HUD nodes are posed in. */
|
|
452
527
|
lastUi = {
|
|
@@ -663,6 +738,64 @@ async function createGame2D(opts) {
|
|
|
663
738
|
...opts.pixelRatio !== void 0 ? { pixelRatio: opts.pixelRatio } : {},
|
|
664
739
|
...opts.antialias !== void 0 ? { antialias: opts.antialias } : {}
|
|
665
740
|
});
|
|
741
|
+
cleanups.push(engine.settings.bindFrameCap((fps) => {
|
|
742
|
+
engine.maxFps = fps;
|
|
743
|
+
}));
|
|
744
|
+
cleanups.push(engine.settings.bindRenderScale((scale) => {
|
|
745
|
+
renderer.setRenderScale?.(scale);
|
|
746
|
+
}));
|
|
747
|
+
/**
|
|
748
|
+
* Feed the running game a drive script and count the frames it drew.
|
|
749
|
+
*
|
|
750
|
+
* Same as the 3D boot: the count travels with the report because a browser
|
|
751
|
+
* stops handing rAF to a hidden tab, and inputs landing on a game that never
|
|
752
|
+
* advances produce an UNCHANGED capture that says nothing about why.
|
|
753
|
+
*/
|
|
754
|
+
const applyDrive = async (steps) => {
|
|
755
|
+
let frames = 0;
|
|
756
|
+
let watching = true;
|
|
757
|
+
const tick = () => {
|
|
758
|
+
if (!watching) return;
|
|
759
|
+
frames += 1;
|
|
760
|
+
requestAnimationFrame(tick);
|
|
761
|
+
};
|
|
762
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(tick);
|
|
763
|
+
for (const step of steps) switch (step.kind) {
|
|
764
|
+
case "press":
|
|
765
|
+
engine.input.pressAction(step.action);
|
|
766
|
+
break;
|
|
767
|
+
case "release":
|
|
768
|
+
engine.input.releaseAction(step.action);
|
|
769
|
+
break;
|
|
770
|
+
case "vector":
|
|
771
|
+
engine.input.setActionVector(step.action, step.x, step.y);
|
|
772
|
+
break;
|
|
773
|
+
case "key":
|
|
774
|
+
engine.input.handleKey(step.code, step.down);
|
|
775
|
+
break;
|
|
776
|
+
case "pointer":
|
|
777
|
+
engine.input.handlePointerMove(step.dx, step.dy);
|
|
778
|
+
break;
|
|
779
|
+
case "step":
|
|
780
|
+
await new Promise((r) => setTimeout(r, step.ms));
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
watching = false;
|
|
784
|
+
return frames;
|
|
785
|
+
};
|
|
786
|
+
cleanups.push(...wireDevChannel({
|
|
787
|
+
engine,
|
|
788
|
+
applyDrive,
|
|
789
|
+
captureFramePixels: () => {
|
|
790
|
+
const capture = renderer.captureFrame;
|
|
791
|
+
if (!capture) return Promise.reject(new IncantoError("TREE_VIOLATION", "this renderer cannot capture frames"));
|
|
792
|
+
return capture.call(renderer);
|
|
793
|
+
},
|
|
794
|
+
rendererStats: () => renderer.stats?.(),
|
|
795
|
+
assetErrors: () => {
|
|
796
|
+
return [...renderer.assets?.errors() ?? [], ...audioErrors(engine.scene?.tree.root)];
|
|
797
|
+
}
|
|
798
|
+
}));
|
|
666
799
|
const wantDebug = opts.debug ?? false;
|
|
667
800
|
const wantEditor = opts.editor ?? wantDebug;
|
|
668
801
|
const editorSwitch = wantEditor === true ? {} : wantEditor ? wantEditor : null;
|
|
@@ -727,6 +860,12 @@ async function createGame2D(opts) {
|
|
|
727
860
|
pick(x, y) {
|
|
728
861
|
return engine.picker?.(x, y) ?? null;
|
|
729
862
|
},
|
|
863
|
+
async frame(frameOpts) {
|
|
864
|
+
const capture = renderer.captureFrame;
|
|
865
|
+
if (!capture) throw new IncantoError("TREE_VIOLATION", "this renderer cannot capture frames");
|
|
866
|
+
const shot = await capture.call(renderer);
|
|
867
|
+
return frameStats(shot.pixels, shot.width, shot.height, frameOpts);
|
|
868
|
+
},
|
|
730
869
|
assetErrors() {
|
|
731
870
|
return [...renderer.assets?.errors() ?? [], ...audioErrors(engine.scene?.tree.root)];
|
|
732
871
|
},
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { A as registerBehavior, C as diagnose, n as loadScene } from "./loader-
|
|
3
|
-
import {
|
|
2
|
+
import { A as registerBehavior, C as diagnose, n as loadScene } from "./loader-BcrRSjxB.js";
|
|
3
|
+
import { _ as Engine, g as AudioPlayer, x as qualityRendering, y as qualityCaps } from "./register-Ch70uByv.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
|
-
import {
|
|
6
|
-
import { i as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
|
|
6
|
+
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-Cs113S9F.js";
|
|
7
|
+
import { o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
8
|
+
import { R as PhysicsBody3D, U as createCausticsQuad, V as Node3D, n as registerGameplayBehaviors } from "./gameplay-BVphcxmE.js";
|
|
9
9
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
10
|
-
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-
|
|
11
|
-
import {
|
|
12
|
-
import { n as enablePhysics3D } from "./physics-3d-NzDwWPrF.js";
|
|
10
|
+
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-CvvQr_bJ.js";
|
|
11
|
+
import { n as enablePhysics3D } from "./physics-3d-Dnz4fsXX.js";
|
|
13
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";
|
|
14
13
|
import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
|
|
15
14
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
@@ -174,6 +173,59 @@ var AssetStore3D = class {
|
|
|
174
173
|
}
|
|
175
174
|
};
|
|
176
175
|
//#endregion
|
|
176
|
+
//#region src/core/yield-frame.ts
|
|
177
|
+
/**
|
|
178
|
+
* Give the browser a chance to paint, and come back either way.
|
|
179
|
+
*
|
|
180
|
+
* Boot work that runs in slices — compiling shaders a few materials at a time,
|
|
181
|
+
* reporting progress — yields between slices so the page stays responsive and
|
|
182
|
+
* the loading bar can move. The obvious way to write that yield is
|
|
183
|
+
* `requestAnimationFrame`, and it is a trap: **a browser does not run rAF for
|
|
184
|
+
* a tab it considers hidden**, and "hidden" includes a background tab, a
|
|
185
|
+
* minimized window, and a window fully covered by another one. Not slowly —
|
|
186
|
+
* not at all. An `await` on rAF alone in that tab never returns.
|
|
187
|
+
*
|
|
188
|
+
* That is what happened to `createGame3D`: the shader warm-up hung mid-boot,
|
|
189
|
+
* so the promise never resolved, the game's own `#loading` overlay was never
|
|
190
|
+
* removed, and the dev channel — wired on the line after the one that hung —
|
|
191
|
+
* never answered `incanto-frame` or `incanto-logs`. `incanto-verify` reported
|
|
192
|
+
* `draws` and `says` as unmeasurable and told the reader to bring the window
|
|
193
|
+
* to the front, which was true but sounded like a browser limitation. It was
|
|
194
|
+
* ours, and it made two of the five verification rungs unreachable to anything
|
|
195
|
+
* that cannot focus a window.
|
|
196
|
+
*
|
|
197
|
+
* So: race the frame against a timer. When frames are coming the frame wins
|
|
198
|
+
* (~16 ms) and the behaviour is what it always was. When none is coming the
|
|
199
|
+
* timer wins and the boot finishes — slower, because a hidden tab also clamps
|
|
200
|
+
* timers to about a second, but finished beats hung.
|
|
201
|
+
*/
|
|
202
|
+
/** How long to wait for a paint that may never come. */
|
|
203
|
+
const DEFAULT_MAX_WAIT_MS = 50;
|
|
204
|
+
/**
|
|
205
|
+
* Resolve on the next painted frame, or after `maxWaitMs`, whichever is first.
|
|
206
|
+
*
|
|
207
|
+
* @param maxWaitMs how long to wait for a frame before giving up on one
|
|
208
|
+
* @param raf injected for tests; defaults to the global
|
|
209
|
+
*/
|
|
210
|
+
function yieldToFrame(maxWaitMs = DEFAULT_MAX_WAIT_MS, raf = typeof requestAnimationFrame === "function" ? requestAnimationFrame : void 0) {
|
|
211
|
+
return new Promise((resolve) => {
|
|
212
|
+
let done = false;
|
|
213
|
+
const finish = () => {
|
|
214
|
+
if (done) return;
|
|
215
|
+
done = true;
|
|
216
|
+
resolve();
|
|
217
|
+
};
|
|
218
|
+
const timer = setTimeout(finish, Math.max(0, maxWaitMs));
|
|
219
|
+
if (!raf) return;
|
|
220
|
+
raf(() => {
|
|
221
|
+
setTimeout(() => {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
finish();
|
|
224
|
+
}, 0);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
177
229
|
//#region src/3d/environment.ts
|
|
178
230
|
const SKY_KEYS = [
|
|
179
231
|
"type",
|
|
@@ -1014,6 +1066,9 @@ var Environment3D = class {
|
|
|
1014
1066
|
/** Underwater fog/tint, created lazily the first time the camera submerges. */
|
|
1015
1067
|
underwaterFog = null;
|
|
1016
1068
|
underwaterBg = new Color();
|
|
1069
|
+
/** Told when the scene's HDRI/preset lighting fails to arrive. Set by the
|
|
1070
|
+
* renderer, which is the piece that knows the engine and the asset store. */
|
|
1071
|
+
onHdriError = null;
|
|
1017
1072
|
constructor(scene) {
|
|
1018
1073
|
this.scene = scene;
|
|
1019
1074
|
this.scene.add(this.ambient);
|
|
@@ -1161,6 +1216,8 @@ var Environment3D = class {
|
|
|
1161
1216
|
}
|
|
1162
1217
|
texture.mapping = EquirectangularReflectionMapping;
|
|
1163
1218
|
this.hdriTexture = texture;
|
|
1219
|
+
}, void 0, (error) => {
|
|
1220
|
+
this.onHdriError?.(url, error);
|
|
1164
1221
|
});
|
|
1165
1222
|
}
|
|
1166
1223
|
applySky(sky, gl) {
|
|
@@ -1575,7 +1632,14 @@ var Renderer3D = class {
|
|
|
1575
1632
|
};
|
|
1576
1633
|
webgl;
|
|
1577
1634
|
threeScene = new Scene();
|
|
1578
|
-
environment =
|
|
1635
|
+
environment = (() => {
|
|
1636
|
+
const env = new Environment3D(this.threeScene);
|
|
1637
|
+
env.onHdriError = (url, error) => {
|
|
1638
|
+
this.assets.textures.noteError(url, error);
|
|
1639
|
+
diagnose(this.engine, "error", `[incanto] the scene's lighting (${url}) could not be loaded — nothing else lights it.`);
|
|
1640
|
+
};
|
|
1641
|
+
return env;
|
|
1642
|
+
})();
|
|
1579
1643
|
engine;
|
|
1580
1644
|
disconnect;
|
|
1581
1645
|
canvas;
|
|
@@ -1793,9 +1857,7 @@ var Renderer3D = class {
|
|
|
1793
1857
|
for (let k = i * per; k < Math.min((i + 1) * per, hidden.length); k++) hidden[k].visible = true;
|
|
1794
1858
|
this.webgl.compile(this.threeScene, camera);
|
|
1795
1859
|
onProgress?.((i + 1) / slices);
|
|
1796
|
-
|
|
1797
|
-
requestAnimationFrame(() => setTimeout(resolve, 0));
|
|
1798
|
-
});
|
|
1860
|
+
await yieldToFrame();
|
|
1799
1861
|
}
|
|
1800
1862
|
} finally {
|
|
1801
1863
|
for (const obj of hidden) obj.visible = true;
|
|
@@ -2322,10 +2384,7 @@ async function createGame3D(opts) {
|
|
|
2322
2384
|
const report = async (fraction, label) => {
|
|
2323
2385
|
if (!opts.onProgress) return;
|
|
2324
2386
|
opts.onProgress(fraction, label);
|
|
2325
|
-
|
|
2326
|
-
await new Promise((resolve) => {
|
|
2327
|
-
requestAnimationFrame(() => setTimeout(resolve, 0));
|
|
2328
|
-
});
|
|
2387
|
+
await yieldToFrame();
|
|
2329
2388
|
};
|
|
2330
2389
|
registerNodes3D();
|
|
2331
2390
|
if (opts.gameplay ?? true) registerGameplayBehaviors();
|
|
@@ -2500,67 +2559,13 @@ async function createGame3D(opts) {
|
|
|
2500
2559
|
watching = false;
|
|
2501
2560
|
return frames;
|
|
2502
2561
|
};
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
if (drive?.error) {
|
|
2511
|
-
hot.send("incanto:frame-report", {
|
|
2512
|
-
id,
|
|
2513
|
-
report: { error: drive.error }
|
|
2514
|
-
});
|
|
2515
|
-
return;
|
|
2516
|
-
}
|
|
2517
|
-
let droveFrames = null;
|
|
2518
|
-
(drive ? applyDrive(drive.steps) : Promise.resolve(null)).then((frames) => {
|
|
2519
|
-
droveFrames = frames;
|
|
2520
|
-
return captureFramePixels();
|
|
2521
|
-
}).then((shot) => hot.send("incanto:frame-report", {
|
|
2522
|
-
id,
|
|
2523
|
-
report: {
|
|
2524
|
-
...frameStats(shot.pixels, shot.width, shot.height, { grid: request?.grid }),
|
|
2525
|
-
signature: frameSignature(shot.pixels, shot.width, shot.height),
|
|
2526
|
-
...droveFrames === null ? {} : { droveFrames },
|
|
2527
|
-
...request?.image ? { image: frameImage(shot.pixels, shot.width, shot.height, (w, h) => Object.assign(document.createElement("canvas"), {
|
|
2528
|
-
width: w,
|
|
2529
|
-
height: h
|
|
2530
|
-
}), request.image) } : {}
|
|
2531
|
-
}
|
|
2532
|
-
})).catch((error) => hot.send("incanto:frame-report", {
|
|
2533
|
-
id,
|
|
2534
|
-
report: { error: error instanceof Error ? error.message : String(error) }
|
|
2535
|
-
}));
|
|
2536
|
-
};
|
|
2537
|
-
hot.on("incanto:frame-request", onFrameRequest);
|
|
2538
|
-
cleanups.push(() => hot.off?.("incanto:frame-request", onFrameRequest));
|
|
2539
|
-
const onLogsRequest = (data) => {
|
|
2540
|
-
const id = data?.id;
|
|
2541
|
-
try {
|
|
2542
|
-
hot.send("incanto:logs-report", {
|
|
2543
|
-
id,
|
|
2544
|
-
report: logReport({
|
|
2545
|
-
entries: engine.log.entries(),
|
|
2546
|
-
stats: {
|
|
2547
|
-
...engine.stats(),
|
|
2548
|
-
...renderer.stats?.() ?? {}
|
|
2549
|
-
},
|
|
2550
|
-
assetErrors: renderer.assets?.errors() ?? [],
|
|
2551
|
-
hidden: typeof document !== "undefined" && document.hidden
|
|
2552
|
-
})
|
|
2553
|
-
});
|
|
2554
|
-
} catch (error) {
|
|
2555
|
-
hot.send("incanto:logs-report", {
|
|
2556
|
-
id,
|
|
2557
|
-
report: { error: error instanceof Error ? error.message : String(error) }
|
|
2558
|
-
});
|
|
2559
|
-
}
|
|
2560
|
-
};
|
|
2561
|
-
hot.on("incanto:logs-request", onLogsRequest);
|
|
2562
|
-
cleanups.push(() => hot.off?.("incanto:logs-request", onLogsRequest));
|
|
2563
|
-
}
|
|
2562
|
+
cleanups.push(...wireDevChannel({
|
|
2563
|
+
engine,
|
|
2564
|
+
applyDrive,
|
|
2565
|
+
captureFramePixels,
|
|
2566
|
+
rendererStats: () => renderer.stats?.(),
|
|
2567
|
+
assetErrors: () => renderer.assets?.errors() ?? []
|
|
2568
|
+
}));
|
|
2564
2569
|
cleanups.push(pauseWhenHidden(engine));
|
|
2565
2570
|
await report(1, "ready");
|
|
2566
2571
|
function disposeGame() {
|
package/dist/debug.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Pt as LogLevel, T as RendererStats, b as Engine } from "./behavior-
|
|
1
|
+
import { Pt as LogLevel, T as RendererStats, b as Engine } from "./behavior-l08AEbq9.js";
|
|
2
2
|
|
|
3
3
|
//#region src/debug/panel.d.ts
|
|
4
4
|
/** Minimal document surface the overlay needs (injectable for tests). */
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as diagnose } from "./loader-BcrRSjxB.js";
|
|
2
|
+
import { t as registerCoreNodes } from "./register-Ch70uByv.js";
|
|
2
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
4
5
|
import { i as getNodeSchema, l as registerNode } from "./registry-C7u42TID.js";
|
|
5
6
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
6
|
-
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-
|
|
7
|
-
import { B as
|
|
7
|
+
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-C0wXLBZJ.js";
|
|
8
|
+
import { B as StaticBody3D, F as WaterCutout3D, G as colliderFootDrop, I as Area3D, L as CharacterBody3D, N as Water3D, R as PhysicsBody3D, V as Node3D, w as tolerateUnknownAction, z as RigidBody3D } from "./gameplay-BVphcxmE.js";
|
|
9
|
+
import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
|
|
8
10
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
9
11
|
import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, ClampToEdgeWrapping, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NoColorSpace, NormalBlending, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector3, Vector4, VectorKeyframeTrack } from "three";
|
|
10
12
|
import { clone } from "three/addons/utils/SkeletonUtils.js";
|
|
@@ -2175,8 +2177,41 @@ var AnimatedSprite3D = class extends Sprite3D {
|
|
|
2175
2177
|
}
|
|
2176
2178
|
this.currentFrame = this.frameList[this.frameIndex] ?? 0;
|
|
2177
2179
|
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Does the grid fit the image, and does any animation run off the end?
|
|
2182
|
+
*
|
|
2183
|
+
* The same floor-division as the 2D twin, and for a long time the same
|
|
2184
|
+
* division with none of its checking — the checker sat one directory away in
|
|
2185
|
+
* 2d/ where this node could not see it.
|
|
2186
|
+
*/
|
|
2187
|
+
checkGrid(width, height) {
|
|
2188
|
+
if (this.gridChecked === this.sheet) return;
|
|
2189
|
+
this.gridChecked = this.sheet;
|
|
2190
|
+
let highest = 0;
|
|
2191
|
+
for (const [name, entry] of Object.entries(this.animations)) {
|
|
2192
|
+
if (typeof entry === "string") continue;
|
|
2193
|
+
try {
|
|
2194
|
+
for (const frame of resolveFrames(entry.frames, name)) if (frame > highest) highest = frame;
|
|
2195
|
+
} catch {
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
const problem = checkSheetGrid(this.sheet, this.frameWidth, this.frameHeight, width, height, highest);
|
|
2200
|
+
if (problem) diagnose(this.tree?.engine ?? null, "error", problem);
|
|
2201
|
+
}
|
|
2202
|
+
/** Reported once per sheet, so a bad grid says so on the first draw only. */
|
|
2203
|
+
gridChecked = "";
|
|
2204
|
+
/** Reported once, so a sheet with no frame size does not shout every frame. */
|
|
2205
|
+
sizeReported = false;
|
|
2178
2206
|
resolveTexture(assets) {
|
|
2179
|
-
if (!this.sheet
|
|
2207
|
+
if (!this.sheet) return null;
|
|
2208
|
+
if (this.frameWidth <= 0 || this.frameHeight <= 0) {
|
|
2209
|
+
if (!this.sizeReported) {
|
|
2210
|
+
this.sizeReported = true;
|
|
2211
|
+
diagnose(this.tree?.engine ?? null, "error", `[incanto] AnimatedSprite3D '${this.name}' has a sheet ('${this.sheet}') but frameWidth/frameHeight are 0 — it draws nothing. Set both to one frame's pixel size (bunx incanto-assets info <name> prints it for a built-in).`);
|
|
2212
|
+
}
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2180
2215
|
if (this.loadedSheetRef !== this.sheet) {
|
|
2181
2216
|
this.sheetTexture?.dispose();
|
|
2182
2217
|
this.loadedSheetRef = this.sheet;
|
|
@@ -2186,6 +2221,7 @@ var AnimatedSprite3D = class extends Sprite3D {
|
|
|
2186
2221
|
if (!tex) return null;
|
|
2187
2222
|
const image = tex.image;
|
|
2188
2223
|
if (!image?.width || !image.height) return tex;
|
|
2224
|
+
this.checkGrid(image.width, image.height);
|
|
2189
2225
|
const cols = Math.max(1, Math.floor(image.width / this.frameWidth));
|
|
2190
2226
|
const col = this.currentFrame % cols;
|
|
2191
2227
|
const row = Math.floor(this.currentFrame / cols);
|
|
@@ -2319,10 +2355,14 @@ var BoneAttachment3D = class extends Node3D {
|
|
|
2319
2355
|
this.lookedUpFor = key;
|
|
2320
2356
|
this.boneObj = null;
|
|
2321
2357
|
const source = this.getNodeOrNull(this.target);
|
|
2358
|
+
this.source = source;
|
|
2322
2359
|
if (source && typeof source.findBone === "function") this.boneObj = source.findBone(this.bone);
|
|
2323
2360
|
}
|
|
2324
2361
|
const boneObj = this.boneObj;
|
|
2325
|
-
if (!boneObj)
|
|
2362
|
+
if (!boneObj) {
|
|
2363
|
+
this.reportMissingBone();
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2326
2366
|
const obj = this._ensureObject3D();
|
|
2327
2367
|
const parent = obj.parent;
|
|
2328
2368
|
if (!parent) return;
|
|
@@ -2339,6 +2379,28 @@ var BoneAttachment3D = class extends Node3D {
|
|
|
2339
2379
|
this.boneObj = null;
|
|
2340
2380
|
super.free();
|
|
2341
2381
|
}
|
|
2382
|
+
/** The model this attachment last looked in. */
|
|
2383
|
+
source = null;
|
|
2384
|
+
reportedBone = "";
|
|
2385
|
+
/**
|
|
2386
|
+
* Say when the bone NAME is wrong, without accusing a model that is still
|
|
2387
|
+
* loading.
|
|
2388
|
+
*
|
|
2389
|
+
* The lookup retries every frame on purpose — a GLB mounts async — so the
|
|
2390
|
+
* node can never conclude on its own that the name is bad. But once the model
|
|
2391
|
+
* HAS bones and this one is not among them, it is a typo, and the list that
|
|
2392
|
+
* would fix it is sitting in `boneNames()` right next to `findBone`. Until
|
|
2393
|
+
* now a misspelled bone left the sword at the mount node's authored position
|
|
2394
|
+
* while the character animated away from it, forever, in silence.
|
|
2395
|
+
*/
|
|
2396
|
+
reportMissingBone() {
|
|
2397
|
+
if (this.reportedBone === this.bone) return;
|
|
2398
|
+
const names = this.source?.boneNames?.() ?? [];
|
|
2399
|
+
if (names.length === 0) return;
|
|
2400
|
+
this.reportedBone = this.bone;
|
|
2401
|
+
const shown = names.slice(0, 8).join(", ");
|
|
2402
|
+
diagnose(this.tree?.engine ?? null, "error", `[incanto] ${this.getPath()}: '${this.target}' has no bone '${this.bone}'. It has ${names.length}: [${shown}${names.length > 8 ? ", …" : ""}].`);
|
|
2403
|
+
}
|
|
2342
2404
|
};
|
|
2343
2405
|
const offsetScratch = new Matrix4();
|
|
2344
2406
|
const boneScratch = new Matrix4();
|
|
@@ -2506,6 +2568,11 @@ const DRAG_DAMPING = .15;
|
|
|
2506
2568
|
const SPRING_K = 1.2;
|
|
2507
2569
|
const SPRING_DAMPING = .08;
|
|
2508
2570
|
const FALLING_MAX_VEL = -20;
|
|
2571
|
+
/** The schema's own default for an action prop — never a second copy of it. */
|
|
2572
|
+
function defaultActionOf(prop) {
|
|
2573
|
+
const schema = CharacterController3D.props[prop];
|
|
2574
|
+
return typeof schema?.default === "string" ? schema.default : "";
|
|
2575
|
+
}
|
|
2509
2576
|
const LOOK_SPEED = .002;
|
|
2510
2577
|
const RAD2DEG = 180 / Math.PI;
|
|
2511
2578
|
/** Spring-arm camera collision: keep the camera this far in front of an obstacle. */
|
|
@@ -2706,8 +2773,10 @@ var CharacterController3D = class extends Node3D {
|
|
|
2706
2773
|
const len = Math.hypot(dir[0], dir[2]) || 1;
|
|
2707
2774
|
mx = dir[0] / len;
|
|
2708
2775
|
mz = dir[2] / len;
|
|
2709
|
-
} catch {
|
|
2710
|
-
|
|
2776
|
+
} catch (error) {
|
|
2777
|
+
this.reportMissingAction("moveAction", error, "The character will not move.");
|
|
2778
|
+
}
|
|
2779
|
+
const sprinting = this.readAction("sprintAction", () => input.isPressed(this.sprintAction), false, "Sprint will never engage.");
|
|
2711
2780
|
const intensity = keyboardIntensity(moving, sprinting);
|
|
2712
2781
|
const speedMultiplier = 1 + (this.sprintMultiplier - 1) * intensity;
|
|
2713
2782
|
const targetSpeed = moving ? this.maxSpeed * speedMultiplier : 0;
|
|
@@ -2741,7 +2810,7 @@ var CharacterController3D = class extends Node3D {
|
|
|
2741
2810
|
this.coyoteLeft = Math.max(0, this.coyoteLeft - dt);
|
|
2742
2811
|
if (!this.grounded && this.coyoteLeft <= 0 && this.jumpsUsed === 0) this.jumpsUsed = 1;
|
|
2743
2812
|
}
|
|
2744
|
-
const pressedJump =
|
|
2813
|
+
const pressedJump = this.readAction("jumpAction", () => input.justPressed(this.jumpAction), false, "The character will never jump.");
|
|
2745
2814
|
if (pressedJump) this.bufferLeft = Math.max(this.jumpBufferSeconds, 0);
|
|
2746
2815
|
else this.bufferLeft = Math.max(0, this.bufferLeft - dt);
|
|
2747
2816
|
if ((this.bufferLeft > 0 || pressedJump) && (footed || this.coyoteLeft > 0 || this.jumpsUsed < this.maxJumps)) {
|
|
@@ -2761,7 +2830,7 @@ var CharacterController3D = class extends Node3D {
|
|
|
2761
2830
|
}
|
|
2762
2831
|
if (this.rising) {
|
|
2763
2832
|
if ((vel[1] ?? 0) <= 0) this.rising = false;
|
|
2764
|
-
else if (this.jumpCutMultiplier < 1 && !
|
|
2833
|
+
else if (this.jumpCutMultiplier < 1 && !this.readAction("jumpAction", () => input.isPressed(this.jumpAction), false, "The character will never jump.")) {
|
|
2765
2834
|
const cut = (vel[1] ?? 0) * this.jumpCutMultiplier;
|
|
2766
2835
|
body.linearVelocity = [
|
|
2767
2836
|
vel[0],
|
|
@@ -2806,6 +2875,31 @@ var CharacterController3D = class extends Node3D {
|
|
|
2806
2875
|
}
|
|
2807
2876
|
}
|
|
2808
2877
|
}
|
|
2878
|
+
/** Actions already reported missing, so a per-frame read says it once. */
|
|
2879
|
+
reportedActions = /* @__PURE__ */ new Set();
|
|
2880
|
+
/**
|
|
2881
|
+
* Read an input action this controller is allowed to be missing.
|
|
2882
|
+
*
|
|
2883
|
+
* Tolerated because a game may declare no sprint key and no jump; reported
|
|
2884
|
+
* because otherwise a TYPO looks exactly like a deliberate omission, and the
|
|
2885
|
+
* engine has already thrown the sentence that fixes it.
|
|
2886
|
+
*/
|
|
2887
|
+
readAction(prop, read, fallback, consequence) {
|
|
2888
|
+
const action = this[prop];
|
|
2889
|
+
return tolerateUnknownAction({
|
|
2890
|
+
engine: this.tree?.engine,
|
|
2891
|
+
who: this.getPath(),
|
|
2892
|
+
reported: this.reportedActions,
|
|
2893
|
+
consequence,
|
|
2894
|
+
authored: action !== defaultActionOf(prop)
|
|
2895
|
+
}, action, read, fallback);
|
|
2896
|
+
}
|
|
2897
|
+
/** Report a throw the caller already caught (the move read has its own try). */
|
|
2898
|
+
reportMissingAction(prop, error, consequence) {
|
|
2899
|
+
this.readAction(prop, () => {
|
|
2900
|
+
throw error;
|
|
2901
|
+
}, void 0, consequence);
|
|
2902
|
+
}
|
|
2809
2903
|
update(dt) {
|
|
2810
2904
|
const input = this.tree?.engine?.input;
|
|
2811
2905
|
if (!input) return;
|
|
@@ -2921,28 +3015,14 @@ function worldOf(node) {
|
|
|
2921
3015
|
z
|
|
2922
3016
|
];
|
|
2923
3017
|
}
|
|
2924
|
-
function safePressed(input, action) {
|
|
2925
|
-
try {
|
|
2926
|
-
return input.isPressed(action);
|
|
2927
|
-
} catch {
|
|
2928
|
-
return false;
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
3018
|
/**
|
|
2932
3019
|
* The EDGE, not the level.
|
|
2933
3020
|
*
|
|
2934
3021
|
* Jump used to read `isPressed`, so holding the button re-jumped on every frame
|
|
2935
3022
|
* the raycast found ground — an auto-hop nobody asked for, and one that makes
|
|
2936
3023
|
* coyote time incoherent (a held button would re-fire through the whole
|
|
2937
|
-
* window).
|
|
3024
|
+
* window). Undeclared actions are tolerated, and reported once.
|
|
2938
3025
|
*/
|
|
2939
|
-
function safeJustPressed(input, action) {
|
|
2940
|
-
try {
|
|
2941
|
-
return input.justPressed(action);
|
|
2942
|
-
} catch {
|
|
2943
|
-
return false;
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
3026
|
//#endregion
|
|
2947
3027
|
//#region src/3d/vegetation/grass-placement.ts
|
|
2948
3028
|
const NOISE_SCALE = .15;
|
|
@@ -3668,6 +3748,7 @@ var Flowers3D = class extends Node3D {
|
|
|
3668
3748
|
this.height,
|
|
3669
3749
|
this.clustering,
|
|
3670
3750
|
this.drape,
|
|
3751
|
+
this.avoidWater,
|
|
3671
3752
|
this.terrain
|
|
3672
3753
|
]);
|
|
3673
3754
|
if (key === this.fieldKey) return;
|
|
@@ -5222,6 +5303,7 @@ var Foliage3D = class Foliage3D extends Node3D {
|
|
|
5222
5303
|
this.colorA,
|
|
5223
5304
|
this.colorB,
|
|
5224
5305
|
this.coverage,
|
|
5306
|
+
this.avoidWater,
|
|
5225
5307
|
this.flowers,
|
|
5226
5308
|
this.seed,
|
|
5227
5309
|
this.drape,
|