incanto 0.60.0 → 0.62.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 +13 -0
- package/bin/incanto-verify.mjs +134 -55
- package/dist/2d.d.ts +64 -3
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +5 -5
- package/dist/3d.js +5 -4
- package/dist/{pathfinding-BqWBb0kh.d.ts → audio-player-D5GJgb_x.d.ts} +103 -38
- package/dist/{behavior-DWKTUzKI.d.ts → behavior-DsgayMsH.d.ts} +79 -1
- package/dist/{create-game-ClnIb_M5.js → create-game-BpunnGPX.js} +78 -13
- package/dist/{create-game-BCm38FJV.js → create-game-Caut3bqN.js} +21 -294
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/{duplicate-DJQd44CD.js → duplicate-E4FUs5Bn.js} +1 -1
- package/dist/editor.js +41 -19
- package/dist/{environment-presets-8cjF3t6w.js → environment-presets-BAWeOeqf.js} +50 -13
- package/dist/{gameplay-BBEjPFsR.js → gameplay-CaHqDiQD.js} +177 -24
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +97 -6
- package/dist/index.js +7 -7
- package/dist/{loader-D8n7TU8W.js → loader-DEe272nY.js} +900 -2
- package/dist/{loader-TvkRFbyL.d.ts → loader-DolLJWJn.d.ts} +13 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/pathfinding-_fGrCFmH.d.ts +28 -0
- package/dist/{physics-2d-DqdVp1bt.js → physics-2d-BXmu2i7W.js} +11 -3
- package/dist/{physics-3d-BP0DZb_1.js → physics-3d-ClxP6Uv7.js} +121 -17
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-BSu2dWGC.js → register-CNh4FlbD.js} +104 -16
- package/dist/{register-Da3hXh2H.js → register-D3yx8D4r.js} +228 -16
- package/dist/{registry-WWcQcfMr.js → registry-CF70EArN.js} +55 -3
- package/dist/{replay-BCMK_VRP.d.ts → replay-C5x2vPF5.d.ts} +2 -2
- package/dist/{replay-BlNuIDdg.js → replay-DlgHItNv.js} +57 -197
- package/dist/{split-screen-CL5Yvxse.js → split-screen-CBM9wcMX.js} +3 -3
- package/dist/{split-screen-BQ3tAsf-.d.ts → split-screen-D7OopelJ.d.ts} +2 -2
- package/dist/{src-DFpXBMJN.js → src-CH00_JsR.js} +1 -1
- package/dist/{teardown-CTTwhWSe.js → teardown-C7qP-dcC.js} +1 -1
- package/dist/{test-BeZ95pqw.js → test-BMkg8zMV.js} +29 -16
- package/dist/test.d.ts +45 -4
- package/dist/test.js +2 -2
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-DSJries_.js → agent8-m5mtAO_A.js} +1 -1
- package/editor/assets/{debug-D15Wi5TO.js → debug-CPhzCT8f.js} +1 -1
- package/editor/assets/{index-BjC88k97.js → index-D422P4kW.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +91 -0
- package/skills/incanto-3d-models.md +1 -1
- package/skills/incanto-assets.md +10 -1
- package/skills/incanto-audio.md +21 -13
- package/skills/incanto-behaviors-and-scripts.md +44 -3
- package/skills/incanto-building-2d-games.md +32 -4
- package/skills/incanto-building-3d-games.md +1 -1
- package/skills/incanto-editor.md +1 -1
- package/skills/incanto-gameplay-behaviors.md +39 -2
- package/skills/incanto-hud.md +5 -3
- package/skills/incanto-localization.md +7 -0
- package/skills/incanto-node-reference.md +12 -0
- package/skills/incanto-physics-and-input.md +23 -2
- package/skills/incanto-scene-json-authoring.md +6 -1
- package/skills/incanto-verifying-your-game.md +31 -1
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/star-survivor/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/particle-sim-C5OfBbmU.d.ts +0 -77
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-DEe272nY.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
|
-
import { l as registerNode } from "./registry-
|
|
5
|
+
import { l as registerNode } from "./registry-CF70EArN.js";
|
|
6
6
|
//#region src/core/audio/buses.ts
|
|
7
7
|
const AUDIO_LOG_LIMIT = 200;
|
|
8
8
|
var AudioBuses = class {
|
|
@@ -612,6 +612,16 @@ const SFX_PRESETS = {
|
|
|
612
612
|
}
|
|
613
613
|
};
|
|
614
614
|
const SFX_PRESET_NAMES = Object.keys(SFX_PRESETS);
|
|
615
|
+
/**
|
|
616
|
+
* How long a preset lasts, in seconds — the same `attack + sustain + decay`
|
|
617
|
+
* `synthSfx` uses to size its buffer.
|
|
618
|
+
*
|
|
619
|
+
* `pitch` deliberately does not enter into it: it is a frequency multiplier,
|
|
620
|
+
* not a playback rate, so a preset played an octave up is the same length.
|
|
621
|
+
*/
|
|
622
|
+
function sfxDuration(params) {
|
|
623
|
+
return params.attack + params.sustain + params.decay;
|
|
624
|
+
}
|
|
615
625
|
const TWO_PI = Math.PI * 2;
|
|
616
626
|
/**
|
|
617
627
|
* Render a preset to mono PCM in [-1, 1]. PURE: identical inputs → identical
|
|
@@ -1369,6 +1379,14 @@ var HudLayer = class extends Node {
|
|
|
1369
1379
|
* widget (an icon inside a slot), so the walk upward is the point.
|
|
1370
1380
|
*/
|
|
1371
1381
|
const widgetOf = /* @__PURE__ */ new WeakMap();
|
|
1382
|
+
/** The nearest ancestor widget that actually accepts a drop. */
|
|
1383
|
+
function dropTargetFromElement(el) {
|
|
1384
|
+
for (let n = el; n; n = n.parentElement) {
|
|
1385
|
+
const found = widgetOf.get(n);
|
|
1386
|
+
if (found?.dropTarget) return found;
|
|
1387
|
+
}
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1372
1390
|
function widgetFromElement(el) {
|
|
1373
1391
|
for (let n = el; n; n = n.parentElement) {
|
|
1374
1392
|
const found = widgetOf.get(n);
|
|
@@ -1378,6 +1396,8 @@ function widgetFromElement(el) {
|
|
|
1378
1396
|
}
|
|
1379
1397
|
/** The drag in flight, if any. One pointer, one drag. */
|
|
1380
1398
|
let dragging = null;
|
|
1399
|
+
/** The drop target the dragged widget came OUT of — putting it back is a no-op. */
|
|
1400
|
+
let draggingFrom = null;
|
|
1381
1401
|
/** Shared plumbing: mount into the parent HudLayer's anchor slot. */
|
|
1382
1402
|
var HudWidgetBase = class extends Node {
|
|
1383
1403
|
/**
|
|
@@ -1495,6 +1515,7 @@ var HudWidgetBase = class extends Node {
|
|
|
1495
1515
|
event.preventDefault();
|
|
1496
1516
|
el.setPointerCapture?.(event.pointerId);
|
|
1497
1517
|
dragging = this;
|
|
1518
|
+
draggingFrom = dropTargetFromElement(el.parentElement);
|
|
1498
1519
|
el.style.opacity = "0.6";
|
|
1499
1520
|
this.emit("dragStarted", this);
|
|
1500
1521
|
});
|
|
@@ -1502,9 +1523,12 @@ var HudWidgetBase = class extends Node {
|
|
|
1502
1523
|
if (dragging !== this) return;
|
|
1503
1524
|
el.style.opacity = "";
|
|
1504
1525
|
el.releasePointerCapture?.(event.pointerId);
|
|
1505
|
-
const
|
|
1526
|
+
const under = document.elementFromPoint?.(event.clientX, event.clientY) ?? null;
|
|
1527
|
+
const target = dropTargetFromElement(under) ?? widgetFromElement(under);
|
|
1528
|
+
const origin = draggingFrom;
|
|
1506
1529
|
dragging = null;
|
|
1507
|
-
|
|
1530
|
+
draggingFrom = null;
|
|
1531
|
+
if (target && target !== this && target !== origin && target.dropTarget) {
|
|
1508
1532
|
this.emit("droppedOn", target);
|
|
1509
1533
|
target.emit("dropped", this);
|
|
1510
1534
|
} else this.emit("dragCancelled", this);
|
|
@@ -2463,6 +2487,7 @@ var Engine = class {
|
|
|
2463
2487
|
this.input.clear();
|
|
2464
2488
|
this._time = 0;
|
|
2465
2489
|
this._unscaledTime = 0;
|
|
2490
|
+
this.timeScale = 1;
|
|
2466
2491
|
if (scene.input) this.input.declare(scene.input);
|
|
2467
2492
|
if (scene.strings) this.locale.load(scene.strings);
|
|
2468
2493
|
this.orderGroups = resolveOrderGroups(scene.orderGroups);
|
|
@@ -2744,7 +2769,7 @@ function createElement() {
|
|
|
2744
2769
|
}
|
|
2745
2770
|
/**
|
|
2746
2771
|
* One sound. `play()` (or `autoplay: true`) starts it; `finished` fires when
|
|
2747
|
-
* it ends. Browsers block audio before the first user gesture — a blocked
|
|
2772
|
+
* it ends — for a procedural preset as well as a `src` clip. Browsers block audio before the first user gesture — a blocked
|
|
2748
2773
|
* play marks `pendingGesture`; `createGame` retries pending players on the
|
|
2749
2774
|
* first pointer/key gesture automatically.
|
|
2750
2775
|
*
|
|
@@ -2801,10 +2826,12 @@ var AudioPlayer = class extends Node {
|
|
|
2801
2826
|
/** Start on the first frame in the tree (subject to the gesture policy). */
|
|
2802
2827
|
autoplay = false;
|
|
2803
2828
|
/**
|
|
2804
|
-
*
|
|
2805
|
-
*
|
|
2806
|
-
*
|
|
2807
|
-
*
|
|
2829
|
+
* Positional audio: the sound pans + attenuates by the emitter's world
|
|
2830
|
+
* position relative to the active camera (the listener). Default false →
|
|
2831
|
+
* identical non-spatial behavior.
|
|
2832
|
+
*
|
|
2833
|
+
* Both dimensions. A 2D scene measures in PIXELS, so the distances below —
|
|
2834
|
+
* metre-shaped defaults — need setting there; `auditScene` says so.
|
|
2808
2835
|
*/
|
|
2809
2836
|
spatial = false;
|
|
2810
2837
|
/** Distance at which spatial gain is full; closer never gets louder. */
|
|
@@ -2838,6 +2865,21 @@ var AudioPlayer = class extends Node {
|
|
|
2838
2865
|
loadError = null;
|
|
2839
2866
|
element = null;
|
|
2840
2867
|
_playing = false;
|
|
2868
|
+
/**
|
|
2869
|
+
* `engine.unscaledTime` at which the preset currently sounding ends.
|
|
2870
|
+
*
|
|
2871
|
+
* A preset used to be fire-and-forget: `playing` stayed false through a sound
|
|
2872
|
+
* that was audibly playing, and `finished` — a signal this node DECLARES, so
|
|
2873
|
+
* a connection to it loads clean — never fired at all. Wiring "when the coin
|
|
2874
|
+
* chime ends, free the pickup" to a preset player produced a wire that is
|
|
2875
|
+
* dead for the life of the game and says nothing.
|
|
2876
|
+
*
|
|
2877
|
+
* The end is known exactly: `attack + sustain + decay` is the length
|
|
2878
|
+
* `synthSfx` sizes its buffer to. Measured on the UNSCALED clock because a
|
|
2879
|
+
* sound does not slow down when the game does, and a paused game still hears
|
|
2880
|
+
* the tail of the hit that paused it.
|
|
2881
|
+
*/
|
|
2882
|
+
presetEndsAt = null;
|
|
2841
2883
|
autoplayed = false;
|
|
2842
2884
|
/** Last spatial pose pushed by the 3D adapter (null until/unless spatial). */
|
|
2843
2885
|
_spatialPose = null;
|
|
@@ -2940,15 +2982,18 @@ var AudioPlayer = class extends Node {
|
|
|
2940
2982
|
}
|
|
2941
2983
|
/**
|
|
2942
2984
|
* Procedural-SFX path: synthesize the preset and fire it through WebAudio
|
|
2943
|
-
* (low-latency, overlap-friendly).
|
|
2944
|
-
*
|
|
2945
|
-
*
|
|
2946
|
-
*
|
|
2985
|
+
* (low-latency, overlap-friendly). Headless (no AudioContext) plays nothing —
|
|
2986
|
+
* but still reports `playing` and still emits `finished` on schedule, because
|
|
2987
|
+
* the length is arithmetic, not a device. WebAudio needs a gesture; a
|
|
2988
|
+
* suspended context is resumed by the same unlock listener as `src` players.
|
|
2947
2989
|
*/
|
|
2948
2990
|
playPreset() {
|
|
2949
2991
|
const params = SFX_PRESETS[this.preset];
|
|
2950
2992
|
if (!params) return;
|
|
2951
|
-
const
|
|
2993
|
+
const engine = this.tree?.engine;
|
|
2994
|
+
this._playing = true;
|
|
2995
|
+
this.presetEndsAt = (engine?.unscaledTime ?? 0) + sfxDuration(params);
|
|
2996
|
+
const sfx = engine?.sfx;
|
|
2952
2997
|
if (!sfx) return;
|
|
2953
2998
|
sfx.play(params, this.gain(), {
|
|
2954
2999
|
pitch: this.pitch,
|
|
@@ -2974,6 +3019,7 @@ var AudioPlayer = class extends Node {
|
|
|
2974
3019
|
this.element.pause();
|
|
2975
3020
|
this.element.currentTime = 0;
|
|
2976
3021
|
}
|
|
3022
|
+
this.presetEndsAt = null;
|
|
2977
3023
|
this._playing = false;
|
|
2978
3024
|
this.pendingGesture = false;
|
|
2979
3025
|
}
|
|
@@ -2986,6 +3032,13 @@ var AudioPlayer = class extends Node {
|
|
|
2986
3032
|
this.autoplayed = true;
|
|
2987
3033
|
this.play();
|
|
2988
3034
|
}
|
|
3035
|
+
if (this.presetEndsAt !== null) {
|
|
3036
|
+
if ((this.tree?.engine?.unscaledTime ?? 0) >= this.presetEndsAt) {
|
|
3037
|
+
this.presetEndsAt = null;
|
|
3038
|
+
this._playing = false;
|
|
3039
|
+
this.emit("finished");
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
2989
3042
|
if (this.element && this._playing) {
|
|
2990
3043
|
this.element.volume = this.gain() * this.spatialElementGain();
|
|
2991
3044
|
this.element.loop = this.loop;
|
|
@@ -3241,6 +3294,161 @@ var UiDialogue = class extends HudWidgetBase {
|
|
|
3241
3294
|
}
|
|
3242
3295
|
};
|
|
3243
3296
|
//#endregion
|
|
3297
|
+
//#region src/core/nodes/respawn.ts
|
|
3298
|
+
/** Out of the world, from the spawn: metres in 3D, pixels in 2D. */
|
|
3299
|
+
const AUTO_DROP_3D_M = 50;
|
|
3300
|
+
const AUTO_DROP_2D_PX = 1e3;
|
|
3301
|
+
/**
|
|
3302
|
+
* Catch whatever this node hangs off when it leaves the world, and put it back.
|
|
3303
|
+
*
|
|
3304
|
+
* Nothing in the engine did this. Not a node, not a behavior, not a scene
|
|
3305
|
+
* header — and the shipped examples prove what that costs: with the `plays`
|
|
3306
|
+
* rung finally counting defects, `basic-3d-sideview` fails 8 seeded runs out of
|
|
3307
|
+
* 8, `water-lake-3d` 7, `water-ocean-3d` 5, `water-pool-3d` 4,
|
|
3308
|
+
* `water-river-3d` 1. Every one of them the same way — the player walks off the
|
|
3309
|
+
* edge of the terrain and falls forever, and the only fix available was a
|
|
3310
|
+
* per-frame `y` check hand-written in TypeScript, which is exactly the kind of
|
|
3311
|
+
* thing this engine exists to keep out of TypeScript.
|
|
3312
|
+
*
|
|
3313
|
+
* A CHILD NODE rather than a Behavior, deliberately: a node holds one behavior
|
|
3314
|
+
* and the player's is already spoken for (`PlayerControl`, `Health`, the game's
|
|
3315
|
+
* own script). Children stack, the way `CharacterController3D` already hangs
|
|
3316
|
+
* off its body.
|
|
3317
|
+
*
|
|
3318
|
+
* ```jsonc
|
|
3319
|
+
* { "name": "Player", "type": "RigidBody3D", "props": { … },
|
|
3320
|
+
* "children": [
|
|
3321
|
+
* { "name": "Ctl", "type": "CharacterController3D" },
|
|
3322
|
+
* { "name": "Catch", "type": "Respawn" } // that is the whole thing
|
|
3323
|
+
* ] }
|
|
3324
|
+
* ```
|
|
3325
|
+
*
|
|
3326
|
+
* With no props it guards its parent, uses the same line the playtest's fall
|
|
3327
|
+
* detector uses (50 m under the spawn in 3D, 1000 px in 2D), and puts the node
|
|
3328
|
+
* back where it started. `respawned` fires each time, so a life can be taken
|
|
3329
|
+
* off a `ScoreKeeper` or a `Health` with an ordinary connection — this node
|
|
3330
|
+
* does not decide what falling COSTS, which is a game-design question and not
|
|
3331
|
+
* a physics one.
|
|
3332
|
+
*/
|
|
3333
|
+
var Respawn = class extends Node {
|
|
3334
|
+
static typeName = "Respawn";
|
|
3335
|
+
static signals = ["respawned"];
|
|
3336
|
+
static props = {
|
|
3337
|
+
target: {
|
|
3338
|
+
default: "..",
|
|
3339
|
+
nodePath: true
|
|
3340
|
+
},
|
|
3341
|
+
below: { default: null },
|
|
3342
|
+
to: { default: [] },
|
|
3343
|
+
resetVelocity: { default: true }
|
|
3344
|
+
};
|
|
3345
|
+
/**
|
|
3346
|
+
* Who is being caught. Defaults to the parent, which is the whole point of
|
|
3347
|
+
* hanging this off the thing it guards.
|
|
3348
|
+
*/
|
|
3349
|
+
target = "..";
|
|
3350
|
+
/**
|
|
3351
|
+
* The line past which the target has left the world, in the scene's own
|
|
3352
|
+
* down: 3D counts down (−y), 2D counts down the screen (+y).
|
|
3353
|
+
*
|
|
3354
|
+
* `null` is AUTO — the spawn position offset by the same distance the
|
|
3355
|
+
* playtest's own fall detector uses, so "the level says it fell" and "the
|
|
3356
|
+
* ladder says it fell" cannot disagree. Author a number when the level has a
|
|
3357
|
+
* real floor to name; leave it alone otherwise.
|
|
3358
|
+
*/
|
|
3359
|
+
below = null;
|
|
3360
|
+
/** Where to put it back. Empty = wherever it started. */
|
|
3361
|
+
to = [];
|
|
3362
|
+
/**
|
|
3363
|
+
* Zero the body's velocity on the way back.
|
|
3364
|
+
*
|
|
3365
|
+
* On by default because the alternative is invisible: a body respawned with
|
|
3366
|
+
* 40 m/s of accumulated fall still in it drops straight back through the
|
|
3367
|
+
* line on the next frame, and the level flickers instead of resuming.
|
|
3368
|
+
*/
|
|
3369
|
+
resetVelocity = true;
|
|
3370
|
+
/** How many times this has caught the target — for HUDs and tests. */
|
|
3371
|
+
falls = 0;
|
|
3372
|
+
home = [];
|
|
3373
|
+
line = null;
|
|
3374
|
+
/** +1 when "out" means y greater (2D screen-down), −1 when smaller (3D). */
|
|
3375
|
+
down = -1;
|
|
3376
|
+
onReady() {
|
|
3377
|
+
const target = this.guarded();
|
|
3378
|
+
this.home = this.to.length > 0 ? [...this.to] : [...positionOf(target)];
|
|
3379
|
+
this.spawnY = worldY(target);
|
|
3380
|
+
}
|
|
3381
|
+
spawnY = 0;
|
|
3382
|
+
/**
|
|
3383
|
+
* Which way is out, and where the line is.
|
|
3384
|
+
*
|
|
3385
|
+
* Resolved on the first `update` rather than in `onReady`, because
|
|
3386
|
+
* `onReady` runs inside `loadScene` — BEFORE `setScene` attaches the engine —
|
|
3387
|
+
* so the scene's `dimension` is not readable there yet. Reading it there
|
|
3388
|
+
* silently gives every 2D scene the 3D answer, and a line above a falling
|
|
3389
|
+
* player fires on frame one, forever.
|
|
3390
|
+
*
|
|
3391
|
+
* Not called `resolve`: `Node` has a private method by that name and
|
|
3392
|
+
* `getNodeOrNull` calls it, so a subclass method with the same name
|
|
3393
|
+
* overrides it on the prototype — `private` is a TypeScript fiction that
|
|
3394
|
+
* costs nothing at runtime — and every path lookup this node made returned a
|
|
3395
|
+
* number. `node-method-shadowing.test.ts` refuses the whole family now.
|
|
3396
|
+
*/
|
|
3397
|
+
fallLine() {
|
|
3398
|
+
if (this.line !== null) return this.line;
|
|
3399
|
+
this.down = this.tree?.engine?.scene?.dimension === "2d" ? 1 : -1;
|
|
3400
|
+
const drop = this.down > 0 ? AUTO_DROP_2D_PX : AUTO_DROP_3D_M;
|
|
3401
|
+
this.line = this.below ?? this.spawnY + this.down * drop;
|
|
3402
|
+
return this.line;
|
|
3403
|
+
}
|
|
3404
|
+
/** The node this guards, or itself if the path no longer resolves. */
|
|
3405
|
+
guarded() {
|
|
3406
|
+
return this.getNodeOrNull(this.target) ?? this;
|
|
3407
|
+
}
|
|
3408
|
+
update(_dt) {
|
|
3409
|
+
const line = this.fallLine();
|
|
3410
|
+
const target = this.guarded();
|
|
3411
|
+
if (target === this) return;
|
|
3412
|
+
const y = worldY(target);
|
|
3413
|
+
if ((y - line) * this.down <= 0) return;
|
|
3414
|
+
this.falls += 1;
|
|
3415
|
+
setPosition(target, this.home);
|
|
3416
|
+
if (this.resetVelocity) zeroVelocity(target);
|
|
3417
|
+
this.emit("respawned", target, y);
|
|
3418
|
+
}
|
|
3419
|
+
};
|
|
3420
|
+
function positionOf(node) {
|
|
3421
|
+
const p = node.position;
|
|
3422
|
+
return Array.isArray(p) ? p : [];
|
|
3423
|
+
}
|
|
3424
|
+
/** Sum of the `position[1]` of the node and every spatial ancestor. */
|
|
3425
|
+
function worldY(node) {
|
|
3426
|
+
let y = 0;
|
|
3427
|
+
let at = node;
|
|
3428
|
+
while (at) {
|
|
3429
|
+
y += positionOf(at)[1] ?? 0;
|
|
3430
|
+
at = at.parent;
|
|
3431
|
+
}
|
|
3432
|
+
return y;
|
|
3433
|
+
}
|
|
3434
|
+
function setPosition(node, to) {
|
|
3435
|
+
const slot = node;
|
|
3436
|
+
if (!Array.isArray(slot.position)) return;
|
|
3437
|
+
slot.position = slot.position.map((v, i) => to[i] ?? v);
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Stop whatever the fall built up.
|
|
3441
|
+
*
|
|
3442
|
+
* Both names are real: `RigidBody` calls it `linearVelocity` and
|
|
3443
|
+
* `CharacterBody` calls it `velocity`, and a respawn has to reach whichever
|
|
3444
|
+
* one this body has.
|
|
3445
|
+
*/
|
|
3446
|
+
function zeroVelocity(node) {
|
|
3447
|
+
const body = node;
|
|
3448
|
+
if (Array.isArray(body.linearVelocity)) body.linearVelocity = body.linearVelocity.map(() => 0);
|
|
3449
|
+
if (Array.isArray(body.velocity)) body.velocity = body.velocity.map(() => 0);
|
|
3450
|
+
}
|
|
3451
|
+
//#endregion
|
|
3244
3452
|
//#region src/core/nodes/timer.ts
|
|
3245
3453
|
/**
|
|
3246
3454
|
* The canonical serializable game clock — never `setTimeout` in game logic.
|
|
@@ -3372,7 +3580,10 @@ var UiImage = class extends HudWidgetBase {
|
|
|
3372
3580
|
static typeName = "UiImage";
|
|
3373
3581
|
static props = {
|
|
3374
3582
|
...HudWidgetBase.props,
|
|
3375
|
-
src: {
|
|
3583
|
+
src: {
|
|
3584
|
+
default: "",
|
|
3585
|
+
asset: { kind: "texture" }
|
|
3586
|
+
},
|
|
3376
3587
|
width: { default: 48 },
|
|
3377
3588
|
height: { default: 48 },
|
|
3378
3589
|
/** How the picture fills its box — `contain` never crops. */
|
|
@@ -3965,6 +4176,7 @@ var UiMuteToggle = class extends UiToggle {
|
|
|
3965
4176
|
function registerCoreNodes() {
|
|
3966
4177
|
registerNode(Node);
|
|
3967
4178
|
registerNode(Timer);
|
|
4179
|
+
registerNode(Respawn);
|
|
3968
4180
|
registerNode(AudioPlayer);
|
|
3969
4181
|
registerNode(HudLayer);
|
|
3970
4182
|
registerNode(UiText);
|
|
@@ -3985,4 +4197,4 @@ function registerCoreNodes() {
|
|
|
3985
4197
|
registerNode(UiMuteToggle);
|
|
3986
4198
|
}
|
|
3987
4199
|
//#endregion
|
|
3988
|
-
export {
|
|
4200
|
+
export { UiBanner as A, isAudioContextAvailable as B, qualityRendering as C, EffectLog as D, LogManager as E, T_PREFIX as F, SFX_PRESET_NAMES as G, spatialGain as H, suggestLocale as I, MusicManager as J, sfxDuration as K, translateOn as L, UiText as M, BASE_LOCALE as N, HudLayer as O, Localization as P, AudioBuses as Q, translationKey as R, qualityEnvironment as S, suggestQuality as T, spatialPan as U, ROLLOFF_MODELS as V, SFX_PRESETS as W, crossfadeGains as X, WebAudioMusicBackend as Y, fadeGain as Z, AudioPlayer as _, UiMuteToggle as a, Settings as b, UiRenderScaleSelect as c, UiToggle as d, UiVolumeSlider as f, UiDialogue as g, UiButton as h, UiLanguageSelect as i, UiBar as j, HudWidgetBase as k, UiSelect as l, Respawn as m, UiFrameCapSelect as n, UiPanel as o, Timer as p, synthSfx as q, UiImage as r, UiQualitySelect as s, registerCoreNodes as t, UiSlider as u, Engine as v, readDeviceHints as w, qualityCaps as x, engine_exports as y, SfxEngine as z };
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
2
|
import { r as jsonKind, t as jsonClone } from "./json-CwwhxQgb.js";
|
|
3
|
+
//#region src/core/prop-range.ts
|
|
4
|
+
function describe(range) {
|
|
5
|
+
const { min, max, exclusiveMin, allow } = range;
|
|
6
|
+
let body;
|
|
7
|
+
if (min !== void 0 && max !== void 0) body = `${min}..${max}`;
|
|
8
|
+
else if (min !== void 0) body = exclusiveMin ? `greater than ${min}` : `at least ${min}`;
|
|
9
|
+
else if (max !== void 0) body = `at most ${max}`;
|
|
10
|
+
else body = "a finite number";
|
|
11
|
+
return allow && allow.length > 0 ? `${body} (or ${allow.join(", ")})` : body;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Throws `PROP_TYPE_MISMATCH` when a number falls outside its documented range.
|
|
15
|
+
*
|
|
16
|
+
* A non-number is ignored: the kind check has already run and reporting the
|
|
17
|
+
* same value twice, under two codes, tells its reader nothing new.
|
|
18
|
+
*
|
|
19
|
+
* @param value the authored value
|
|
20
|
+
* @param label `Type.prop`, for the message
|
|
21
|
+
* @param range what the prop documents
|
|
22
|
+
*/
|
|
23
|
+
function validatePropRange(value, label, range) {
|
|
24
|
+
if (typeof value !== "number") return;
|
|
25
|
+
if (range.allow?.includes(value)) return;
|
|
26
|
+
if (!Number.isFinite(value)) throw new IncantoError("PROP_TYPE_MISMATCH", `Prop '${label}' must be a finite number, got ${value}.`, { prop: label });
|
|
27
|
+
const belowMin = range.min !== void 0 && (range.exclusiveMin ? value <= range.min : value < range.min);
|
|
28
|
+
const aboveMax = range.max !== void 0 && value > range.max;
|
|
29
|
+
if (!belowMin && !aboveMax) return;
|
|
30
|
+
throw new IncantoError("PROP_TYPE_MISMATCH", `Prop '${label}' must be ${describe(range)}, got ${value}.`, { prop: label });
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
3
33
|
//#region src/core/registry.ts
|
|
4
34
|
/**
|
|
5
35
|
* Validate a prop value against its schema default:
|
|
@@ -120,7 +150,10 @@ function mergeStaticAliases(ctor) {
|
|
|
120
150
|
*/
|
|
121
151
|
function applySchemaProps(target, schema, props, label, aliases = {}, extraOptions = {}) {
|
|
122
152
|
for (const [key, def] of Object.entries(schema)) target[key] = jsonClone(def.default);
|
|
123
|
-
if (
|
|
153
|
+
if (props) applyGivenProps(target, schema, props, label, aliases, extraOptions);
|
|
154
|
+
requireNonEmpty(target, schema, label);
|
|
155
|
+
}
|
|
156
|
+
function applyGivenProps(target, schema, props, label, aliases, extraOptions) {
|
|
124
157
|
for (const [rawKey, value] of Object.entries(props)) {
|
|
125
158
|
const key = schema[rawKey] ? rawKey : aliases[rawKey] ?? rawKey;
|
|
126
159
|
const def = schema[key];
|
|
@@ -135,18 +168,37 @@ function applySchemaProps(target, schema, props, label, aliases = {}, extraOptio
|
|
|
135
168
|
const extra = extraOptions[key];
|
|
136
169
|
const options = def.options && extra && extra.length > 0 ? [...def.options, ...extra] : def.options;
|
|
137
170
|
validatePropValue(value, def.default, `${label}.${key}`, options);
|
|
171
|
+
if (def.range) validatePropRange(value, `${label}.${key}`, def.range);
|
|
138
172
|
target[key] = jsonClone(value);
|
|
139
173
|
}
|
|
174
|
+
}
|
|
175
|
+
function requireNonEmpty(target, schema, label) {
|
|
140
176
|
for (const [key, def] of Object.entries(schema)) {
|
|
141
177
|
if (!def.required) continue;
|
|
142
|
-
|
|
143
|
-
|
|
178
|
+
if (!isEmptyValue(target[key])) continue;
|
|
179
|
+
throw new IncantoError("PROP_TYPE_MISMATCH", `'${label}' needs a "${key}" — it is empty.`, {
|
|
144
180
|
prop: key,
|
|
145
181
|
nodeType: label
|
|
146
182
|
});
|
|
147
183
|
}
|
|
148
184
|
}
|
|
149
185
|
/**
|
|
186
|
+
* "Not a configuration" — what `required` refuses.
|
|
187
|
+
*
|
|
188
|
+
* The first cut only knew about `''`, which is what the two props that
|
|
189
|
+
* introduced the flag hold. Three of the ones declared since default to an
|
|
190
|
+
* ARRAY — `MoveTo.to`, `Patrol.points`, `WaveSpawner.waves` — and for those the
|
|
191
|
+
* flag was inert: a `MoveTo` with no `to` loaded clean and moved nowhere, which
|
|
192
|
+
* is exactly the failure the flag was added to stop. Found by running the
|
|
193
|
+
* shipped package as a consumer instead of trusting the declaration.
|
|
194
|
+
*/
|
|
195
|
+
function isEmptyValue(value) {
|
|
196
|
+
if (value === "" || value === void 0 || value === null) return true;
|
|
197
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
198
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
150
202
|
* Instantiate a registered type, assign schema defaults (cloned), then the
|
|
151
203
|
* given props — validating every key and its JSON kind against the default.
|
|
152
204
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as Engine } from "./behavior-
|
|
1
|
+
import { b as Engine } from "./behavior-DsgayMsH.js";
|
|
2
2
|
import { s as JsonObject } from "./schema-CFeioQRE.js";
|
|
3
3
|
|
|
4
4
|
//#region src/core/audit.d.ts
|
|
@@ -25,7 +25,7 @@ declare function auditScene(scene: JsonObject, opts?: AuditOptions): string[];
|
|
|
25
25
|
* Incanto fails loudly with machine-readable codes and messages that list valid
|
|
26
26
|
* alternatives — AI agents self-correct on hard failures, not warnings.
|
|
27
27
|
*/
|
|
28
|
-
type IncantoErrorCode = "BAD_FORMAT" | "BAD_NODE_PATH" | "DANGLING_CONNECTION" | "DUPLICATE_BEHAVIOR" | "DUPLICATE_NODE_TYPE" | "DUPLICATE_UNIQUE_NAME" | "DUPLICATE_UID" | "NODE_NOT_FOUND" | "PROP_TYPE_MISMATCH" | "TREE_VIOLATION" | "UNKNOWN_ANIMATION" | "UNKNOWN_ASSET" | "UNKNOWN_CONSTANT" | "UNKNOWN_BEHAVIOR" | "UNKNOWN_HANDLER" | "UNKNOWN_NODE_TYPE" | "UNKNOWN_PROP" | "UNKNOWN_SIGNAL" | "UNRESOLVED_INSTANCE";
|
|
28
|
+
type IncantoErrorCode = "AMBIGUOUS_HANDLER" | "BAD_FORMAT" | "BAD_NODE_PATH" | "DANGLING_CONNECTION" | "DUPLICATE_BEHAVIOR" | "DUPLICATE_NODE_TYPE" | "DUPLICATE_UNIQUE_NAME" | "DUPLICATE_UID" | "NODE_NOT_FOUND" | "PROP_TYPE_MISMATCH" | "TREE_VIOLATION" | "UNKNOWN_ANIMATION" | "UNKNOWN_ASSET" | "UNKNOWN_CONSTANT" | "UNKNOWN_BEHAVIOR" | "UNKNOWN_HANDLER" | "UNKNOWN_NODE_TYPE" | "UNKNOWN_PROP" | "UNKNOWN_SIGNAL" | "UNRESOLVED_INSTANCE";
|
|
29
29
|
/**
|
|
30
30
|
* Structured context mirrored from the prose message — tools and agents parse
|
|
31
31
|
* THESE instead of regexing the message (which is for humans and may reword).
|