incanto 0.70.0 → 0.72.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.
Files changed (74) hide show
  1. package/bin/incanto-editor.mjs +15 -9
  2. package/bin/incanto-feel.mjs +11 -1
  3. package/bin/incanto-play.mjs +11 -1
  4. package/bin/incanto-playtest.mjs +11 -1
  5. package/bin/incanto-verify.mjs +36 -98
  6. package/dist/2d.d.ts +18 -3
  7. package/dist/2d.js +3 -3
  8. package/dist/3d.d.ts +3 -3
  9. package/dist/3d.js +6 -6
  10. package/dist/{agent8-CmNF01gA.js → agent8-BqZ0_O1u.js} +1 -1
  11. package/dist/{audio-player-DaMxqfNE.d.ts → audio-player-ioZ7VKC6.d.ts} +1 -1
  12. package/dist/{behavior-DZExDn9o.d.ts → behavior-Bf9P9oB2.d.ts} +12 -1
  13. package/dist/{create-game-Bwvh6q8A.js → create-game-C9rWqqAC.js} +37 -13
  14. package/dist/{create-game-C7ffQWW7.js → create-game-CmFAtLZp.js} +8 -7
  15. package/dist/debug.d.ts +1 -1
  16. package/dist/editor.js +1083 -1045
  17. package/dist/{environment-presets-D6Q5BxeE.js → environment-presets-BeHoaGhy.js} +3 -3
  18. package/dist/{frame-report-Dlq13Gyj.js → frame-report-D-_7YF2G.js} +1 -1
  19. package/dist/{gameplay-BfHkuzVb.js → gameplay-BlQe-M07.js} +108 -6
  20. package/dist/gameplay.d.ts +68 -4
  21. package/dist/gameplay.js +2 -2
  22. package/dist/index.d.ts +27 -4
  23. package/dist/index.js +4 -4
  24. package/dist/{loader-Cff09LMm.d.ts → loader-CBfQzB6S.d.ts} +10 -3
  25. package/dist/net.d.ts +2 -2
  26. package/dist/net.js +2 -2
  27. package/dist/{physics-2d-CE0Qvy3V.js → physics-2d-DrIgTlLk.js} +2 -2
  28. package/dist/{physics-3d-CpH-2gn5.js → physics-3d-BzOCw1VS.js} +3 -3
  29. package/dist/{picking-CQJ_PJKh.js → picking-C9McVJrO.js} +3 -3
  30. package/dist/react.d.ts +1 -1
  31. package/dist/react.js +1 -1
  32. package/dist/{register-6DYnKZcy.js → register-5IYfooAN.js} +49 -15
  33. package/dist/{register-Bkk0wSDB.js → register-C8HuRkOf.js} +100 -8
  34. package/dist/{replay-Dmw-PKQu.d.ts → replay-CBEChqXq.d.ts} +2 -2
  35. package/dist/{replay-DjAkAzMq.js → replay-datiAE-b.js} +15 -8
  36. package/dist/{save-slots-BXVg148r.js → save-slots-CEuJPUle.js} +102 -4
  37. package/dist/{split-screen-PL78oVXP.js → split-screen-Bm5DTElG.js} +3 -3
  38. package/dist/{split-screen--k-XpBjr.d.ts → split-screen-DkqLng6q.d.ts} +2 -2
  39. package/dist/{src-Cxfiv1Hg.js → src-DRSsRleD.js} +1 -1
  40. package/dist/{test-iHYVUcDK.js → test-Djuq3Vhq.js} +252 -24
  41. package/dist/test.d.ts +113 -5
  42. package/dist/test.js +3 -3
  43. package/dist/vite.d.ts +8 -1
  44. package/dist/vite.js +23 -6
  45. package/editor/assets/{agent8-D0MS174y.js → agent8-CMKz2cax.js} +1 -1
  46. package/editor/assets/{debug-BnXkKuYu.js → debug-DLrQm-az.js} +1 -1
  47. package/editor/assets/{index-CIu3uc3l.js → index-BEKuxYcE.js} +94 -94
  48. package/editor/index.html +1 -1
  49. package/package.json +1 -1
  50. package/schemas/scene.schema.json +4 -0
  51. package/skills/incanto-behaviors-and-scripts.md +30 -9
  52. package/skills/incanto-building-2d-games.md +26 -2
  53. package/skills/incanto-editor.md +4 -0
  54. package/skills/incanto-gameplay-behaviors.md +63 -1
  55. package/skills/incanto-hud.md +14 -0
  56. package/skills/incanto-node-reference.md +18 -2
  57. package/skills/incanto-playtesting.md +34 -0
  58. package/skills/incanto-save-slots.md +56 -1
  59. package/skills/incanto-scene-json-authoring.md +30 -0
  60. package/skills/incanto-verifying-your-game.md +11 -0
  61. package/templates-app/beacon-isle-3d/package.json +1 -1
  62. package/templates-app/beacon-isle-3d/src/game.scene.json +396 -386
  63. package/templates-app/molehill-2d/package.json +1 -1
  64. package/templates-app/molehill-2d/src/game.scene.json +10 -60
  65. package/templates-app/platformer-2d/package.json +1 -1
  66. package/templates-app/platformer-2d/src/game.scene.json +268 -504
  67. package/templates-app/star-survivor/package.json +1 -1
  68. package/templates-app/star-survivor/src/game.scene.json +23 -33
  69. package/templates-app/tps-3d/package.json +1 -1
  70. package/templates-app/tps-3d/src/game.scene.json +90 -105
  71. package/templates-app/village-quest-3d/package.json +1 -1
  72. package/templates-app/village-quest-3d/src/behaviors.ts +21 -4
  73. package/templates-app/village-quest-3d/src/grove.scene.json +99 -118
  74. package/templates-app/village-quest-3d/src/village.scene.json +449 -245
@@ -1,11 +1,11 @@
1
- import { q as diagnose } from "./save-slots-BXVg148r.js";
2
- import { M as translateOn, t as registerCoreNodes } from "./register-6DYnKZcy.js";
1
+ import { Y as diagnose } from "./save-slots-CEuJPUle.js";
2
+ import { M as translateOn, t as registerCoreNodes } from "./register-5IYfooAN.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-CDOMybym.js";
5
5
  import { c as getNodeSchema, h as didYouMean, p as registerNode } from "./touch-BnCyPA0G.js";
6
6
  import { t as createNoise2D } from "./noise-D3nPpmFg.js";
7
7
  import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimations, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CqR2o3SA.js";
8
- import { B as Area3D, E as effectRng, G as Node3D, H as PhysicsBody3D, I as Water3D, R as WaterCutout3D, U as RigidBody3D, V as CharacterBody3D, W as StaticBody3D, Y as colliderFootDrop, Z as worldPosition, w as tolerateUnknownAction, z as rejectScale } from "./gameplay-BfHkuzVb.js";
8
+ import { B as rejectScale, D as effectRng, G as StaticBody3D, H as CharacterBody3D, K as Node3D, L as Water3D, Q as worldPosition, T as tolerateUnknownAction, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, X as colliderFootDrop, z as WaterCutout3D } from "./gameplay-BlQe-M07.js";
9
9
  import { t as checkSheetGrid } from "./sheet-grid-Cea343VO.js";
10
10
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-BYgD5Edk.js";
11
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";
@@ -177,7 +177,7 @@ const pct = (v) => `${(v * 100).toFixed(1)}%`;
177
177
  /** The report a person or an agent reads. Verdict first, numbers after. */
178
178
  function frameText(stats) {
179
179
  const lines = [];
180
- if (stats.hidden) lines.push("THE PAGE IS HIDDEN a browser stops animating a window covered by another, so these pixels are whatever it last drew. Bring it to the front and capture again.");
180
+ if (stats.hidden) lines.push("the page is hidden, so the game is PAUSED these pixels were rendered for this capture (current), but nothing is moving. A `--do` drive needs the window in front.");
181
181
  if (stats.black) lines.push("BLACK SCREEN — nothing was drawn (no light, no camera, or nothing in view)");
182
182
  else if (stats.uniform) lines.push("one flat colour — the camera may be inside geometry, or only the sky is drawn");
183
183
  else if (!stats.subject) lines.push("nothing but background — the camera is pointed away from everything in the scene");
@@ -1,5 +1,5 @@
1
1
  import { t as jsonClone } from "./json-CfTjpvW8.js";
2
- import { K as Node, M as effectiveOrder, Q as Behavior, R as HudLayer, W as requireNumber, it as registerBehavior, n as SaveSlots, p as loadScene, q as diagnose, s as duplicateNode, u as rebuildOptions } from "./save-slots-BXVg148r.js";
2
+ import { B as HudLayer, J as Node, K as requireNumber, P as effectiveOrder, Y as diagnose, d as rebuildOptions, et as Behavior, f as rememberAuthoredPath, h as loadScene, n as SaveSlots, ot as registerBehavior, s as duplicateNode } from "./save-slots-CEuJPUle.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-CDOMybym.js";
5
5
  import { h as didYouMean } from "./touch-BnCyPA0G.js";
@@ -6984,6 +6984,103 @@ function findCollector(node, group) {
6984
6984
  return null;
6985
6985
  }
6986
6986
  //#endregion
6987
+ //#region src/gameplay/prefab-shelf.ts
6988
+ /**
6989
+ * A shelf of templates the GAME clones itself — towers, bolts, damage numbers.
6990
+ *
6991
+ * `Spawner` detaches its one prefab at enter, so a spawned enemy's whole
6992
+ * subtree is live and nobody has to think about it. A game that clones on its
6993
+ * own terms — a tower the player places, a bolt that tower fires — has no
6994
+ * spawner, so its templates sat in the tree as LIVE nodes: their behaviours
6995
+ * ticked, their turrets shot, and `visible: false` hid a node without switching
6996
+ * it off. The documented answer was to author every template `"enabled": false`
6997
+ * and wake the clone with `clone.behavior?.enable()`.
6998
+ *
6999
+ * That wakes ONE node, and the same docs teach that a node carries one
7000
+ * behaviour — so a prefab's parts live on children, and every one of those
7001
+ * stays asleep, silently and for good, because `enabled` is a pause switch that
7002
+ * never complains. Measured on a shipped tower defense: the bolt's
7003
+ * `Projectile` was woken and its sibling `Lifetime` was not, so every bolt that
7004
+ * MISSED flew forever — five still in the tree at 69 seconds, in a game that
7005
+ * passed its harness, its audit and every rung of the ladder.
7006
+ *
7007
+ * ```json
7008
+ * { "name": "Prefabs", "type": "Node2D", "script": { "name": "PrefabShelf" },
7009
+ * "children": [ { "name": "Tower", "type": "Node2D", "children": [] } ] }
7010
+ * ```
7011
+ * ```ts
7012
+ * const shelf = this.node.getNode('/Game/Prefabs').behavior as PrefabShelf;
7013
+ * const tower = shelf.make('Tower'); // detached, like `duplicateNode`
7014
+ * tower.position = at; // set it up BEFORE it readies
7015
+ * this.node.getNode('/Game/Towers').addChild(tower);
7016
+ * ```
7017
+ *
7018
+ * The children never ENTER the tree, so nothing has to be asleep, nothing has
7019
+ * to be hidden, and `make()` hands back a clone that is awake all the way down.
7020
+ */
7021
+ var PrefabShelf = class extends Behavior {
7022
+ static props = {
7023
+ /**
7024
+ * Which children to take, comma-separated. Empty (the default) takes them
7025
+ * ALL, which is what a shelf is; name them when the node doubles as
7026
+ * something else and only some of its children are templates.
7027
+ */
7028
+ prefabs: { default: "" } };
7029
+ static signals = [];
7030
+ prefabs = "";
7031
+ /** Templates, in the order they were authored. */
7032
+ held = /* @__PURE__ */ new Map();
7033
+ onEnterTree() {
7034
+ this.claim();
7035
+ }
7036
+ /** A shelf attached at runtime never saw `onEnterTree`; idempotent. */
7037
+ onReady() {
7038
+ this.claim();
7039
+ }
7040
+ claim() {
7041
+ const wanted = this.prefabs.split(",").map((name) => name.trim()).filter((name) => name !== "");
7042
+ const from = wanted.length > 0 ? wanted : [...this.node.children].map((child) => child.name);
7043
+ for (const name of from) {
7044
+ if (this.held.has(name)) continue;
7045
+ const template = this.node.children.find((child) => child.name === name);
7046
+ if (!template) throw new IncantoError("NODE_NOT_FOUND", `PrefabShelf on '${this.node.getPath()}': "prefabs" names '${name}', which is not a child of this node. It holds: ${this.childNames() || "(nothing)"}.`, {
7047
+ prop: "prefabs",
7048
+ path: this.node.getPath()
7049
+ });
7050
+ rememberAuthoredPath(template);
7051
+ this.node.removeChild(template);
7052
+ this.held.set(name, template);
7053
+ }
7054
+ }
7055
+ /** What this shelf holds, in authored order. */
7056
+ names() {
7057
+ return [...this.held.keys()];
7058
+ }
7059
+ /**
7060
+ * A live clone of one template, DETACHED — `addChild` it yourself.
7061
+ *
7062
+ * Detached for the same reason `duplicateNode` is, and it is not a
7063
+ * formality: `onReady` fires on attach, and a behaviour that reads its node
7064
+ * there (`FloatAway` banks its starting position) would bank the wrong one.
7065
+ * Set the clone up, then attach it.
7066
+ *
7067
+ * The clone is VISIBLE even if the template was authored hidden (a scene
7068
+ * migrating off the old `visible: false` shelf keeps working), and every
7069
+ * behaviour in it is however it was authored: awake unless the template
7070
+ * itself asks to sleep, which is now only ever a state machine's own doing.
7071
+ */
7072
+ make(name) {
7073
+ const template = this.held.get(name);
7074
+ if (!template) throw new IncantoError("NODE_NOT_FOUND", `PrefabShelf on '${this.node.getPath()}': no template named '${name}'. This shelf holds: ${this.childNames() || "(nothing)"}.`, { path: this.node.getPath() });
7075
+ const clone = duplicateNode(template, rebuildOptions(this.node));
7076
+ if (typeof clone.visible === "boolean") clone.visible = true;
7077
+ return clone;
7078
+ }
7079
+ childNames() {
7080
+ return this.names().join(", ");
7081
+ }
7082
+ };
7083
+ //#endregion
6987
7084
  //#region src/gameplay/projectile.ts
6988
7085
  /**
6989
7086
  * Constant-velocity motion in a straight line — bullets, arrows, thrown rocks.
@@ -7282,7 +7379,7 @@ var SavePoint = class extends Behavior {
7282
7379
  return;
7283
7380
  }
7284
7381
  const report = this.engine.restoreState(slot.state);
7285
- const here = this.engine?.scene?.name;
7382
+ const here = this.scene || this.engine?.scene?.source.name;
7286
7383
  if (here && slot.scene && slot.scene !== here) diagnose(this.engine, "error", `[incanto] SavePoint '${this.slot}': this slot was saved in scene '${slot.scene}' and you are in '${here}'. Load that scene first — a save is restored INTO the scene it came from (incanto-save-slots.md).`);
7287
7384
  else if (report.expected > 0 && report.restored === 0) diagnose(this.engine, "error", `[incanto] SavePoint '${this.slot}': restored 0 of ${report.expected} saveable behaviour(s)` + (report.missing.length > 0 ? ` — ${report.missing.length} uid(s) in the save are not in this scene, starting with '${report.missing[0]}'. That save is from an older build, or from another scene.` : ". Nothing in the save matched a node here."));
7288
7385
  this.emit("restored", report.restored);
@@ -7437,6 +7534,7 @@ var SpawnSource = class {
7437
7534
  });
7438
7535
  }
7439
7536
  if (claim) node.tree?._templates.set(claim, template);
7537
+ rememberAuthoredPath(template);
7440
7538
  if (template.parent) template.parent.removeChild(template);
7441
7539
  this.detached.set(prefab, template);
7442
7540
  return template;
@@ -7669,8 +7767,9 @@ var Wander = class extends Behavior {
7669
7767
  * every `interval` seconds, then waits until they're ALL cleared (freed) before
7670
7768
  * starting the next wave.
7671
7769
  *
7672
- * Signals: `waveStarted(i)` when a wave begins spawning, `waveCleared(i)` when
7673
- * its last instance frees, `allCleared` after the final wave clears.
7770
+ * Signals: `spawned(node)` per clone, `waveStarted(i)` when a wave begins
7771
+ * spawning, `waveCleared(i)` when its last instance frees, `allCleared` after
7772
+ * the final wave clears.
7674
7773
  */
7675
7774
  var WaveSpawner = class extends Behavior {
7676
7775
  static props = {
@@ -7683,6 +7782,7 @@ var WaveSpawner = class extends Behavior {
7683
7782
  autoStart: { default: true }
7684
7783
  };
7685
7784
  static signals = [
7785
+ "spawned",
7686
7786
  "waveStarted",
7687
7787
  "waveCleared",
7688
7788
  "allCleared"
@@ -7768,6 +7868,7 @@ var WaveSpawner = class extends Behavior {
7768
7868
  const clone = this.source.spawn(this.source.resolveTemplate(wave.prefab));
7769
7869
  this.live.add(clone);
7770
7870
  this.spawnedThisWave += 1;
7871
+ this.emit("spawned", clone);
7771
7872
  this.timer += wave.interval;
7772
7873
  }
7773
7874
  if (this.spawnedThisWave >= wave.count) this.phase = "clearing";
@@ -8024,7 +8125,8 @@ const GAMEPLAY_BEHAVIORS = {
8024
8125
  WaveSpawner,
8025
8126
  Projectile,
8026
8127
  Buoyancy,
8027
- FloatAway
8128
+ FloatAway,
8129
+ PrefabShelf
8028
8130
  };
8029
8131
  /**
8030
8132
  * Register all built-in gameplay behaviors. Idempotent and hot-reload tolerant
@@ -8036,4 +8138,4 @@ function registerGameplayBehaviors(opts) {
8036
8138
  for (const [name, ctor] of Object.entries(GAMEPLAY_BEHAVIORS)) registerBehavior(name, ctor, { replace });
8037
8139
  }
8038
8140
  //#endregion
8039
- export { phaseOf as A, Area3D as B, restartScene as C, FloatAway as D, effectRng as E, Chase as F, Node3D as G, PhysicsBody3D as H, Water3D as I, WATER_MAX_RIPPLES as J, validateCollider3D as K, WATER_CUTOUT_MAX as L, Health as M, Currency as N, FaceTarget as O, Collector as P, WaterCutout3D as R, goToScene as S, FollowCamera as T, RigidBody3D as U, CharacterBody3D as V, StaticBody3D as W, localFromWorld as X, colliderFootDrop as Y, worldPosition as Z, Cooldown as _, Wander as a, Interactable as b, SavePoint as c, Patrol as d, PathFollow as f, CameraShake as g, Lifetime as h, WaveSpawner as i, DamageOnContact as j, DayNight as k, Projectile as l, MoveTo as m, registerGameplayBehaviors as n, Spawner as o, Oscillate as p, createCausticsQuad as q, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, Pickup as u, hitStop as v, tolerateUnknownAction as w, GameFlow as x, screenFlash as y, rejectScale as z };
8141
+ export { DayNight as A, rejectScale as B, goToScene as C, effectRng as D, FollowCamera as E, Collector as F, StaticBody3D as G, CharacterBody3D as H, Chase as I, createCausticsQuad as J, Node3D as K, Water3D as L, DamageOnContact as M, Health as N, FloatAway as O, Currency as P, worldPosition as Q, WATER_CUTOUT_MAX as R, GameFlow as S, tolerateUnknownAction as T, PhysicsBody3D as U, Area3D as V, RigidBody3D as W, colliderFootDrop as X, WATER_MAX_RIPPLES as Y, localFromWorld as Z, CameraShake as _, Wander as a, screenFlash as b, SavePoint as c, Pickup as d, Patrol as f, Lifetime as g, MoveTo as h, WaveSpawner as i, phaseOf as j, FaceTarget as k, Projectile as l, Oscillate as m, registerGameplayBehaviors as n, Spawner as o, PathFollow as p, validateCollider3D as q, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, PrefabShelf as u, Cooldown as v, restartScene as w, Interactable as x, hitStop as y, WaterCutout3D as z };
@@ -1,4 +1,4 @@
1
- import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-DZExDn9o.js";
1
+ import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-Bf9P9oB2.js";
2
2
  import { c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
@@ -826,8 +826,9 @@ declare class Wander extends Behavior {
826
826
  * every `interval` seconds, then waits until they're ALL cleared (freed) before
827
827
  * starting the next wave.
828
828
  *
829
- * Signals: `waveStarted(i)` when a wave begins spawning, `waveCleared(i)` when
830
- * its last instance frees, `allCleared` after the final wave clears.
829
+ * Signals: `spawned(node)` per clone, `waveStarted(i)` when a wave begins
830
+ * spawning, `waveCleared(i)` when its last instance frees, `allCleared` after
831
+ * the final wave clears.
831
832
  */
832
833
  declare class WaveSpawner extends Behavior {
833
834
  static readonly props: PropSchema;
@@ -1265,6 +1266,69 @@ declare class PathFollow extends Behavior {
1265
1266
  override update(dt: number): void;
1266
1267
  }
1267
1268
  //#endregion
1269
+ //#region src/gameplay/prefab-shelf.d.ts
1270
+ /**
1271
+ * A shelf of templates the GAME clones itself — towers, bolts, damage numbers.
1272
+ *
1273
+ * `Spawner` detaches its one prefab at enter, so a spawned enemy's whole
1274
+ * subtree is live and nobody has to think about it. A game that clones on its
1275
+ * own terms — a tower the player places, a bolt that tower fires — has no
1276
+ * spawner, so its templates sat in the tree as LIVE nodes: their behaviours
1277
+ * ticked, their turrets shot, and `visible: false` hid a node without switching
1278
+ * it off. The documented answer was to author every template `"enabled": false`
1279
+ * and wake the clone with `clone.behavior?.enable()`.
1280
+ *
1281
+ * That wakes ONE node, and the same docs teach that a node carries one
1282
+ * behaviour — so a prefab's parts live on children, and every one of those
1283
+ * stays asleep, silently and for good, because `enabled` is a pause switch that
1284
+ * never complains. Measured on a shipped tower defense: the bolt's
1285
+ * `Projectile` was woken and its sibling `Lifetime` was not, so every bolt that
1286
+ * MISSED flew forever — five still in the tree at 69 seconds, in a game that
1287
+ * passed its harness, its audit and every rung of the ladder.
1288
+ *
1289
+ * ```json
1290
+ * { "name": "Prefabs", "type": "Node2D", "script": { "name": "PrefabShelf" },
1291
+ * "children": [ { "name": "Tower", "type": "Node2D", "children": [] } ] }
1292
+ * ```
1293
+ * ```ts
1294
+ * const shelf = this.node.getNode('/Game/Prefabs').behavior as PrefabShelf;
1295
+ * const tower = shelf.make('Tower'); // detached, like `duplicateNode`
1296
+ * tower.position = at; // set it up BEFORE it readies
1297
+ * this.node.getNode('/Game/Towers').addChild(tower);
1298
+ * ```
1299
+ *
1300
+ * The children never ENTER the tree, so nothing has to be asleep, nothing has
1301
+ * to be hidden, and `make()` hands back a clone that is awake all the way down.
1302
+ */
1303
+ declare class PrefabShelf extends Behavior {
1304
+ static readonly props: PropSchema;
1305
+ static readonly signals: readonly string[];
1306
+ prefabs: string;
1307
+ /** Templates, in the order they were authored. */
1308
+ private readonly held;
1309
+ override onEnterTree(): void;
1310
+ /** A shelf attached at runtime never saw `onEnterTree`; idempotent. */
1311
+ override onReady(): void;
1312
+ private claim;
1313
+ /** What this shelf holds, in authored order. */
1314
+ names(): string[];
1315
+ /**
1316
+ * A live clone of one template, DETACHED — `addChild` it yourself.
1317
+ *
1318
+ * Detached for the same reason `duplicateNode` is, and it is not a
1319
+ * formality: `onReady` fires on attach, and a behaviour that reads its node
1320
+ * there (`FloatAway` banks its starting position) would bank the wrong one.
1321
+ * Set the clone up, then attach it.
1322
+ *
1323
+ * The clone is VISIBLE even if the template was authored hidden (a scene
1324
+ * migrating off the old `visible: false` shelf keeps working), and every
1325
+ * behaviour in it is however it was authored: awake unless the template
1326
+ * itself asks to sleep, which is now only ever a state machine's own doing.
1327
+ */
1328
+ make(name: string): Node;
1329
+ private childNames;
1330
+ }
1331
+ //#endregion
1268
1332
  //#region src/gameplay/spatial.d.ts
1269
1333
  /**
1270
1334
  * A node's position with every spatial ancestor's added in.
@@ -1311,4 +1375,4 @@ declare function registerGameplayBehaviors(opts?: {
1311
1375
  replace?: boolean;
1312
1376
  }): void;
1313
1377
  //#endregion
1314
- export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, type DayPhase, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
1378
+ export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, type DayPhase, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, PrefabShelf, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as phaseOf, C as restartScene, D as FloatAway, F as Chase, M as Health, N as Currency, O as FaceTarget, P as Collector, S as goToScene, T as FollowCamera, X as localFromWorld, Z as worldPosition, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as DamageOnContact, k as DayNight, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-BfHkuzVb.js";
2
- export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
1
+ import { A as DayNight, C as goToScene, E as FollowCamera, F as Collector, I as Chase, M as DamageOnContact, N as Health, O as FloatAway, P as Currency, Q as worldPosition, S as GameFlow, Z as localFromWorld, _ as CameraShake, a as Wander, b as screenFlash, c as SavePoint, d as Pickup, f as Patrol, g as Lifetime, h as MoveTo, i as WaveSpawner, j as phaseOf, k as FaceTarget, l as Projectile, m as Oscillate, n as registerGameplayBehaviors, o as Spawner, p as PathFollow, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as PrefabShelf, v as Cooldown, w as restartScene, x as Interactable, y as hitStop } from "./gameplay-BlQe-M07.js";
2
+ export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, PrefabShelf, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as ParticleSimConfig, c as showBootFailure, i as ParticleSim, n as AudioPlayer, o as ParticleView, s as isWebGLAvailable, t as AudioElementLike } from "./audio-player-DaMxqfNE.js";
2
- import { $ as T_PREFIX, A as SettingsValues, At as BusName, B as captureBehaviors, C as EngineStats, Ct as sfxDuration, Dt as MusicTrack, E as DeviceHints, Et as MusicManager, F as BehaviorState, Ft as LogLevel, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as LogManager, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as Signal, M as readDeviceHints, Mt as NodeLifecycle, N as suggestQuality, Nt as SceneTree, O as QualityTier, Ot as PlayMusicOptions, P as Scene, Pt as LogEntry, Q as Localization, R as SaveSlots, Rt as SignalListener, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicBackend, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as spatialPan, a as clearBehaviors, at as EffectLog, b as Engine, bt as SfxParams, c as registeredBehaviors, ct as isAudioContextAvailable, d as PropSchema, dt as Listener, et as suggestLocale, f as clearRegistry, ft as ROLLOFF_MODELS, g as getNodeType, gt as spatialGain, h as getNodeSignals, ht as Vec3, i as behaviorSignals, it as EffectKind, j as qualityEnvironment, jt as Node, k as Settings, kt as AudioBuses, l as NodeCtor, lt as Voice, m as getNodeSchema, mt as SpatialParams, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as SfxEngine, p as createNode, pt as RolloffModel, q as OrderGroupTable, r as behaviorSchema, rt as EffectEvent, s as registerBehavior, st as SfxPlayOptions, t as Behavior, tt as translationKey, u as PropDef, ut as VoicePreset, v as registerNode, vt as SFX_PRESETS, w as GameStats, wt as synthSfx, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-DZExDn9o.js";
1
+ import { a as ParticleSimConfig, c as showBootFailure, i as ParticleSim, n as AudioPlayer, o as ParticleView, s as isWebGLAvailable, t as AudioElementLike } from "./audio-player-ioZ7VKC6.js";
2
+ import { $ as T_PREFIX, A as SettingsValues, At as BusName, B as captureBehaviors, C as EngineStats, Ct as sfxDuration, Dt as MusicTrack, E as DeviceHints, Et as MusicManager, F as BehaviorState, Ft as LogLevel, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as LogManager, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as Signal, M as readDeviceHints, Mt as NodeLifecycle, N as suggestQuality, Nt as SceneTree, O as QualityTier, Ot as PlayMusicOptions, P as Scene, Pt as LogEntry, Q as Localization, R as SaveSlots, Rt as SignalListener, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicBackend, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as spatialPan, a as clearBehaviors, at as EffectLog, b as Engine, bt as SfxParams, c as registeredBehaviors, ct as isAudioContextAvailable, d as PropSchema, dt as Listener, et as suggestLocale, f as clearRegistry, ft as ROLLOFF_MODELS, g as getNodeType, gt as spatialGain, h as getNodeSignals, ht as Vec3, i as behaviorSignals, it as EffectKind, j as qualityEnvironment, jt as Node, k as Settings, kt as AudioBuses, l as NodeCtor, lt as Voice, m as getNodeSchema, mt as SpatialParams, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as SfxEngine, p as createNode, pt as RolloffModel, q as OrderGroupTable, r as behaviorSchema, rt as EffectEvent, s as registerBehavior, st as SfxPlayOptions, t as Behavior, tt as translationKey, u as PropDef, ut as VoicePreset, v as registerNode, vt as SFX_PRESETS, w as GameStats, wt as synthSfx, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-Bf9P9oB2.js";
3
3
  import { a as SceneJson, c as JsonValue, d as jsonKind, i as SCENE_FORMAT, l as jsonClone, n as ConnectionJson, o as JsonKind, r as NodeJson, s as JsonObject, t as Rng, u as jsonEquals } from "./rng-Bb-IutXB.js";
4
- import { n as loadScene, t as LoadSceneOptions } from "./loader-Cff09LMm.js";
4
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-CBfQzB6S.js";
5
5
  import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js";
6
6
  import { i as gridFromRows, n as PathGrid, r as findPath, t as FindPathOptions } from "./pathfinding-_fGrCFmH.js";
7
- import { a as startRecording, c as IncantoErrorDetails, i as replay, l as auditScene, n as ReplayEvent, o as IncantoError, r as ReplayJson, s as IncantoErrorCode, t as Recorder } from "./replay-Dmw-PKQu.js";
7
+ import { a as startRecording, c as IncantoErrorDetails, i as replay, l as auditScene, n as ReplayEvent, o as IncantoError, r as ReplayJson, s as IncantoErrorCode, t as Recorder } from "./replay-CBEChqXq.js";
8
8
 
9
9
  //#region src/core/audio/crossfade.d.ts
10
10
  /**
@@ -973,6 +973,17 @@ declare class UiSlider extends HudWidgetBase {
973
973
  override focusable: boolean;
974
974
  /** Left/right nudge by one step — the only sane thing a d-pad can mean here. */
975
975
  override _activate(dir: number): void;
976
+ /**
977
+ * Move it as a PERSON would: set the value and say so.
978
+ *
979
+ * `UiButton` has `press()` and the value widgets had nothing, because
980
+ * assigning `.value` deliberately does not emit (restoring a saved setting
981
+ * must not fire the handler that saved it). So a harness — or a game driving
982
+ * its own menu: a preset button, "reset to defaults", a tutorial that moves a
983
+ * slider for you — had one route to "the player changed this", and it was
984
+ * `_activate`, which is internal and takes a direction rather than a value.
985
+ */
986
+ choose(value: number): void;
976
987
  private input;
977
988
  private text;
978
989
  private last;
@@ -989,6 +1000,8 @@ declare class UiToggle extends HudWidgetBase {
989
1000
  override focusable: boolean;
990
1001
  /** Confirm flips it; left/right set it explicitly, which reads better on a pad. */
991
1002
  override _activate(dir: number): void;
1003
+ /** Flip it as a PERSON would: set the value and say so. See `UiSlider.choose`. */
1004
+ choose(value: boolean): void;
992
1005
  private input;
993
1006
  private last;
994
1007
  protected _build(): HTMLElement;
@@ -1023,6 +1036,16 @@ declare class UiSelect extends HudWidgetBase {
1023
1036
  static validateJson(node: Node): void;
1024
1037
  /** Left/right (and confirm) walk the list — a select has no other gesture. */
1025
1038
  override _activate(dir: number): void;
1039
+ /**
1040
+ * Pick an option as a PERSON would: set the value and say so. See
1041
+ * `UiSlider.choose`.
1042
+ *
1043
+ * An option the list does not offer is refused, for the reason
1044
+ * `validateJson` refuses it in the file: the browser would show the first one
1045
+ * while the node kept answering with what it was given, and nothing would
1046
+ * ever reconcile them.
1047
+ */
1048
+ choose(value: string): void;
1026
1049
  private select;
1027
1050
  private labelEl;
1028
1051
  private lastLabel;
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-CfTjpvW8.js";
2
- import { C as isConstRef, D as newUid, E as resolveViewport, H as UiBar, J as describeRefProblem, K as Node, M as effectiveOrder, N as resolveOrderGroups, O as SceneTree, P as InputMap, Q as Behavior, R as HudLayer, S as CONST_REF_KEY, T as computeViewport, U as UiText, V as UiBanner, X as resolveRefInJson, Y as nodeRefWarnings, Z as parseNodePath, _ as SCENE_FORMAT, a as restoreBehaviors, at as registeredBehaviors, et as behaviorSchema, g as serializeNode, h as Scene, i as captureBehaviors, it as registerBehavior, j as ORDER_GROUP_BASE, k as createSaveStore, n as SaveSlots, nt as clearBehaviors, o as savesWithoutUid, ot as Signal, p as loadScene, r as behaviorsWithoutSave, rt as getBehavior, s as duplicateNode, tt as behaviorSignals, w as resolveConstants } from "./save-slots-BXVg148r.js";
3
- import { A as T_PREFIX, B as SFX_PRESET_NAMES, D as EffectLog, E as LogManager, F as isAudioContextAvailable, G as crossfadeGains, H as synthSfx, I as ROLLOFF_MODELS, K as fadeGain, L as spatialGain, N as translationKey, O as BASE_LOCALE, P as SfxEngine, R as spatialPan, S as qualityEnvironment, T as suggestQuality, U as MusicManager, V as sfxDuration, W as WebAudioMusicBackend, _ as AudioPlayer, a as UiMuteToggle, b as Settings, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as UiDialogue, h as UiButton, i as UiLanguageSelect, j as suggestLocale, k as Localization, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as AudioBuses, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SFX_PRESETS } from "./register-6DYnKZcy.js";
2
+ import { $ as parseNodePath, A as SceneTree, B as HudLayer, D as computeViewport, E as resolveConstants, F as resolveOrderGroups, G as UiText, I as InputMap, J as Node, N as ORDER_GROUP_BASE, O as resolveViewport, P as effectiveOrder, Q as resolveRefInJson, T as isConstRef, U as UiBanner, W as UiBar, X as describeRefProblem, Z as nodeRefWarnings, _ as Scene, a as restoreBehaviors, at as getBehavior, ct as Signal, et as Behavior, h as loadScene, i as captureBehaviors, it as clearBehaviors, j as createSaveStore, k as newUid, n as SaveSlots, nt as behaviorSchema, o as savesWithoutUid, ot as registerBehavior, r as behaviorsWithoutSave, rt as behaviorSignals, s as duplicateNode, st as registeredBehaviors, v as serializeNode, w as CONST_REF_KEY, y as SCENE_FORMAT } from "./save-slots-CEuJPUle.js";
3
+ import { A as T_PREFIX, B as SFX_PRESET_NAMES, D as EffectLog, E as LogManager, F as isAudioContextAvailable, G as crossfadeGains, H as synthSfx, I as ROLLOFF_MODELS, K as fadeGain, L as spatialGain, N as translationKey, O as BASE_LOCALE, P as SfxEngine, R as spatialPan, S as qualityEnvironment, T as suggestQuality, U as MusicManager, V as sfxDuration, W as WebAudioMusicBackend, _ as AudioPlayer, a as UiMuteToggle, b as Settings, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as UiDialogue, h as UiButton, i as UiLanguageSelect, j as suggestLocale, k as Localization, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as AudioBuses, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SFX_PRESETS } from "./register-5IYfooAN.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { t as Rng } from "./rng-CDOMybym.js";
6
6
  import { c as getNodeSchema, f as mergeStaticSignals, i as joystickVector, l as getNodeSignals, m as registeredTypes, n as attachTouchControls, o as clearRegistry, p as registerNode, s as createNode, t as TouchControls, u as getNodeType } from "./touch-BnCyPA0G.js";
7
- import { n as startRecording, r as auditScene, t as replay } from "./replay-DjAkAzMq.js";
7
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-datiAE-b.js";
8
8
  import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
9
9
  import { a as logText, i as logReport, o as parseDrive, r as resolveRendering, t as isWebGLAvailable } from "./webgl-unavailable-C8aDbGmR.js";
10
10
  import { t as createNoise2D } from "./noise-D3nPpmFg.js";
11
11
  import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CqR2o3SA.js";
12
- import { a as findPath, i as preloadUrls, n as assetUrls, o as gridFromRows, r as preloadSceneAssets, t as VERSION } from "./src-Cxfiv1Hg.js";
12
+ import { a as findPath, i as preloadUrls, n as assetUrls, o as gridFromRows, r as preloadSceneAssets, t as VERSION } from "./src-DRSsRleD.js";
13
13
  export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Respawn, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, isWebGLAvailable, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, showBootFailure, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -1,10 +1,17 @@
1
- import { P as Scene, b as Engine } from "./behavior-DZExDn9o.js";
1
+ import { P as Scene, b as Engine } from "./behavior-Bf9P9oB2.js";
2
2
  import { a as SceneJson } from "./rng-Bb-IutXB.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
5
5
  interface LoadSceneOptions {
6
- /** Resolve a sub-scene path referenced by `{instance: '<path>'}`. */
7
- resolveScene?: (path: string) => SceneJson;
6
+ /**
7
+ * Resolve a sub-scene path referenced by `{instance: '<path>'}`.
8
+ *
9
+ * Returning nothing is a real answer — "I do not have that one" — and the
10
+ * loader turns it into `UNRESOLVED_INSTANCE` naming the node and the path.
11
+ * The type said `=> SceneJson`, so the one recipe a game actually writes
12
+ * (`prefabs[path] ?? null` over a bundler's eager glob) did not typecheck.
13
+ */
14
+ resolveScene?: (path: string) => SceneJson | null | undefined;
8
15
  /**
9
16
  * Assets declared ELSEWHERE in the project, for `$ref` validation.
10
17
  *
package/dist/net.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { d as PropSchema, jt as Node } from "./behavior-DZExDn9o.js";
1
+ import { d as PropSchema, jt as Node } from "./behavior-Bf9P9oB2.js";
2
2
  import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
3
- import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen--k-XpBjr.js";
3
+ import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen-DkqLng6q.js";
4
4
 
5
5
  //#region src/net/agent8.d.ts
6
6
  /**
package/dist/net.js CHANGED
@@ -1,3 +1,3 @@
1
- import { n as createAgent8Server } from "./agent8-CmNF01gA.js";
2
- import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-PL78oVXP.js";
1
+ import { n as createAgent8Server } from "./agent8-BqZ0_O1u.js";
2
+ import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-Bm5DTElG.js";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -1,7 +1,7 @@
1
1
  import { o as __exportAll } from "./json-CfTjpvW8.js";
2
- import { q as diagnose } from "./save-slots-BXVg148r.js";
2
+ import { Y as diagnose } from "./save-slots-CEuJPUle.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-Bkk0wSDB.js";
4
+ import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-C8HuRkOf.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  import { n as jointContacts, t as withoutRapierInitNoise } from "./quiet-rapier-C6fW4zcW.js";
7
7
  //#region src/2d/physics/physics-2d.ts
@@ -1,10 +1,10 @@
1
1
  import { o as __exportAll } from "./json-CfTjpvW8.js";
2
- import { q as diagnose } from "./save-slots-BXVg148r.js";
2
+ import { Y as diagnose } from "./save-slots-CEuJPUle.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { B as Area3D, G as Node3D, H as PhysicsBody3D, K as validateCollider3D, U as RigidBody3D, V as CharacterBody3D } from "./gameplay-BfHkuzVb.js";
4
+ import { H as CharacterBody3D, K as Node3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, q as validateCollider3D } from "./gameplay-BlQe-M07.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  import { n as jointContacts, t as withoutRapierInitNoise } from "./quiet-rapier-C6fW4zcW.js";
7
- import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-D6Q5BxeE.js";
7
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-BeHoaGhy.js";
8
8
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
9
9
  //#region src/3d/physics/collider-lines.ts
10
10
  /**
@@ -1,7 +1,7 @@
1
- import { _ as AudioPlayer } from "./register-6DYnKZcy.js";
1
+ import { _ as AudioPlayer } from "./register-5IYfooAN.js";
2
2
  import { i as logReport, o as parseDrive } from "./webgl-unavailable-C8aDbGmR.js";
3
- import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-Dlq13Gyj.js";
4
- import { Z as worldPosition } from "./gameplay-BfHkuzVb.js";
3
+ import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-D-_7YF2G.js";
4
+ import { Q as worldPosition } from "./gameplay-BlQe-M07.js";
5
5
  //#region src/core/audio-errors.ts
6
6
  /** Every AudioPlayer under `root` whose source could not be played. */
7
7
  function audioErrors(root) {
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-DZExDn9o.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-Bf9P9oB2.js";
2
2
  import { c as JsonValue } from "./rng-Bb-IutXB.js";
3
3
  import { CSSProperties, ReactNode } from "react";
4
4
 
package/dist/react.js CHANGED
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
156
156
  pointer: latest.pointer,
157
157
  ...keyboard !== void 0 ? { keyboard } : {}
158
158
  };
159
- const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-C7ffQWW7.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-Bwvh6q8A.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-CmFAtLZp.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-C9rWqqAC.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1,5 +1,5 @@
1
1
  import { o as __exportAll } from "./json-CfTjpvW8.js";
2
- import { B as TAPPABLE, G as requireText, H as UiBar, K as Node, L as FITS, N as resolveOrderGroups, P as InputMap, R as HudLayer, U as UiText, V as UiBanner, a as restoreBehaviors, i as captureBehaviors, j as ORDER_GROUP_BASE, k as createSaveStore, ot as Signal, q as diagnose, t as ENGINE_STATE_KEY, z as HudWidgetBase } from "./save-slots-BXVg148r.js";
2
+ import { B as HudLayer, F as resolveOrderGroups, G as UiText, H as TAPPABLE, I as InputMap, J as Node, N as ORDER_GROUP_BASE, U as UiBanner, V as HudWidgetBase, W as UiBar, Y as diagnose, a as restoreBehaviors, ct as Signal, i as captureBehaviors, j as createSaveStore, q as requireText, t as ENGINE_STATE_KEY, z as FITS } from "./save-slots-CEuJPUle.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-CDOMybym.js";
5
5
  import { p as registerNode } from "./touch-BnCyPA0G.js";
@@ -2470,7 +2470,11 @@ var Engine = class {
2470
2470
  this._scene.root.free();
2471
2471
  this._scene.tree._setEngine(null);
2472
2472
  this._scene = null;
2473
- this.sceneChanged.emit(null);
2473
+ try {
2474
+ this.sceneChanged.emit(null);
2475
+ } catch (error) {
2476
+ diagnose(this, "warn", "[incanto] teardown: a `sceneChanged` listener threw (continuing) — it fires with NULL here, so a handler that reads the scene has to check for it", error);
2477
+ }
2474
2478
  }
2475
2479
  this.input.dispose();
2476
2480
  this.input.clear();
@@ -3612,7 +3616,20 @@ var UiSlider = class extends HudWidgetBase {
3612
3616
  /** Left/right nudge by one step — the only sane thing a d-pad can mean here. */
3613
3617
  _activate(dir) {
3614
3618
  if (dir === 0) return;
3615
- const next = Math.min(this.max, Math.max(this.min, this.value + dir * this.step));
3619
+ this.choose(this.value + dir * this.step);
3620
+ }
3621
+ /**
3622
+ * Move it as a PERSON would: set the value and say so.
3623
+ *
3624
+ * `UiButton` has `press()` and the value widgets had nothing, because
3625
+ * assigning `.value` deliberately does not emit (restoring a saved setting
3626
+ * must not fire the handler that saved it). So a harness — or a game driving
3627
+ * its own menu: a preset button, "reset to defaults", a tutorial that moves a
3628
+ * slider for you — had one route to "the player changed this", and it was
3629
+ * `_activate`, which is internal and takes a direction rather than a value.
3630
+ */
3631
+ choose(value) {
3632
+ const next = Math.min(this.max, Math.max(this.min, value));
3616
3633
  if (next === this.value) return;
3617
3634
  this.value = next;
3618
3635
  this.last = next;
@@ -3670,12 +3687,15 @@ var UiToggle = class extends HudWidgetBase {
3670
3687
  focusable = true;
3671
3688
  /** Confirm flips it; left/right set it explicitly, which reads better on a pad. */
3672
3689
  _activate(dir) {
3673
- const next = dir === 0 ? !this.value : dir > 0;
3674
- if (next === this.value) return;
3675
- this.value = next;
3676
- this.last = next;
3677
- if (this.input) this.input.checked = next;
3678
- this.emit("changed", next);
3690
+ this.choose(dir === 0 ? !this.value : dir > 0);
3691
+ }
3692
+ /** Flip it as a PERSON would: set the value and say so. See `UiSlider.choose`. */
3693
+ choose(value) {
3694
+ if (value === this.value) return;
3695
+ this.value = value;
3696
+ this.last = value;
3697
+ if (this.input) this.input.checked = value;
3698
+ this.emit("changed", value);
3679
3699
  }
3680
3700
  input = null;
3681
3701
  last = null;
@@ -3754,14 +3774,28 @@ var UiSelect = class extends HudWidgetBase {
3754
3774
  }
3755
3775
  /** Left/right (and confirm) walk the list — a select has no other gesture. */
3756
3776
  _activate(dir) {
3757
- const choices = this.options.split(",").map((o) => o.trim()).filter(Boolean);
3777
+ const choices = this.choices();
3758
3778
  if (choices.length === 0) return;
3759
3779
  const next = choices[(Math.max(0, choices.indexOf(this.value)) + (dir === 0 ? 1 : dir) + choices.length) % choices.length];
3760
- if (!next || next === this.value) return;
3761
- this.value = next;
3762
- this.last = next;
3763
- if (this.select) this.select.value = next;
3764
- this.emit("changed", next);
3780
+ if (next) this.choose(next);
3781
+ }
3782
+ /**
3783
+ * Pick an option as a PERSON would: set the value and say so. See
3784
+ * `UiSlider.choose`.
3785
+ *
3786
+ * An option the list does not offer is refused, for the reason
3787
+ * `validateJson` refuses it in the file: the browser would show the first one
3788
+ * while the node kept answering with what it was given, and nothing would
3789
+ * ever reconcile them.
3790
+ */
3791
+ choose(value) {
3792
+ const choices = this.choices();
3793
+ if (choices.length > 0 && !choices.includes(value)) throw new IncantoError("PROP_TYPE_MISMATCH", `UiSelect '${this.name}': choose('${value}') is not one of its options [${choices.join(", ")}].`, { prop: "value" });
3794
+ if (value === this.value) return;
3795
+ this.value = value;
3796
+ this.last = value;
3797
+ if (this.select) this.select.value = value;
3798
+ this.emit("changed", value);
3765
3799
  }
3766
3800
  select = null;
3767
3801
  labelEl = null;