incanto 0.70.0 → 0.71.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 (67) hide show
  1. package/bin/incanto-verify.mjs +36 -98
  2. package/dist/2d.d.ts +18 -3
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +3 -3
  5. package/dist/3d.js +6 -6
  6. package/dist/{agent8-CmNF01gA.js → agent8-Cfmd3ar_.js} +1 -1
  7. package/dist/{audio-player-DaMxqfNE.d.ts → audio-player-L8yccdMP.d.ts} +1 -1
  8. package/dist/{behavior-DZExDn9o.d.ts → behavior-Dcz0fr1S.d.ts} +11 -0
  9. package/dist/{create-game-Bwvh6q8A.js → create-game-CH4kt78v.js} +7 -7
  10. package/dist/{create-game-C7ffQWW7.js → create-game-LCHBLfA4.js} +7 -7
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/editor.js +29 -25
  13. package/dist/{environment-presets-D6Q5BxeE.js → environment-presets-99BzP_L-.js} +3 -3
  14. package/dist/{frame-report-Dlq13Gyj.js → frame-report-D-_7YF2G.js} +1 -1
  15. package/dist/{gameplay-BfHkuzVb.js → gameplay-CNULJvwh.js} +108 -6
  16. package/dist/gameplay.d.ts +68 -4
  17. package/dist/gameplay.js +2 -2
  18. package/dist/index.d.ts +27 -4
  19. package/dist/index.js +4 -4
  20. package/dist/{loader-Cff09LMm.d.ts → loader-8-IHvbKD.d.ts} +1 -1
  21. package/dist/net.d.ts +2 -2
  22. package/dist/net.js +2 -2
  23. package/dist/{physics-2d-CE0Qvy3V.js → physics-2d-FhrXUc6B.js} +2 -2
  24. package/dist/{physics-3d-CpH-2gn5.js → physics-3d-CFEGxBXZ.js} +3 -3
  25. package/dist/{picking-CQJ_PJKh.js → picking-DVo7fI13.js} +3 -3
  26. package/dist/react.d.ts +1 -1
  27. package/dist/react.js +1 -1
  28. package/dist/{register-Bkk0wSDB.js → register-BYQCBySi.js} +100 -8
  29. package/dist/{register-6DYnKZcy.js → register-DuVdzxbq.js} +49 -15
  30. package/dist/{replay-DjAkAzMq.js → replay-DEvp3kyV.js} +2 -2
  31. package/dist/{replay-Dmw-PKQu.d.ts → replay-F7IZHdFR.d.ts} +1 -1
  32. package/dist/{save-slots-BXVg148r.js → save-slots-Bvuh2p_r.js} +56 -3
  33. package/dist/{split-screen--k-XpBjr.d.ts → split-screen-CIYf1zSf.d.ts} +2 -2
  34. package/dist/{split-screen-PL78oVXP.js → split-screen-eULetcg-.js} +3 -3
  35. package/dist/{src-Cxfiv1Hg.js → src-_jk0qLsS.js} +1 -1
  36. package/dist/{test-iHYVUcDK.js → test-CmN54kWv.js} +252 -24
  37. package/dist/test.d.ts +113 -5
  38. package/dist/test.js +3 -3
  39. package/dist/vite.js +3 -3
  40. package/editor/assets/{agent8-D0MS174y.js → agent8-yLIEYHbd.js} +1 -1
  41. package/editor/assets/{debug-BnXkKuYu.js → debug-u31w_yhq.js} +1 -1
  42. package/editor/assets/{index-CIu3uc3l.js → index-DK9xMGpW.js} +94 -94
  43. package/editor/index.html +1 -1
  44. package/package.json +1 -1
  45. package/schemas/scene.schema.json +4 -0
  46. package/skills/incanto-behaviors-and-scripts.md +30 -9
  47. package/skills/incanto-building-2d-games.md +20 -2
  48. package/skills/incanto-gameplay-behaviors.md +63 -1
  49. package/skills/incanto-hud.md +14 -0
  50. package/skills/incanto-node-reference.md +18 -2
  51. package/skills/incanto-playtesting.md +34 -0
  52. package/skills/incanto-save-slots.md +56 -1
  53. package/skills/incanto-verifying-your-game.md +11 -0
  54. package/templates-app/beacon-isle-3d/package.json +1 -1
  55. package/templates-app/beacon-isle-3d/src/game.scene.json +44 -226
  56. package/templates-app/molehill-2d/package.json +1 -1
  57. package/templates-app/molehill-2d/src/game.scene.json +10 -60
  58. package/templates-app/platformer-2d/package.json +1 -1
  59. package/templates-app/platformer-2d/src/game.scene.json +80 -416
  60. package/templates-app/star-survivor/package.json +1 -1
  61. package/templates-app/star-survivor/src/game.scene.json +5 -24
  62. package/templates-app/tps-3d/package.json +1 -1
  63. package/templates-app/tps-3d/src/game.scene.json +13 -71
  64. package/templates-app/village-quest-3d/package.json +1 -1
  65. package/templates-app/village-quest-3d/src/behaviors.ts +21 -4
  66. package/templates-app/village-quest-3d/src/grove.scene.json +16 -87
  67. package/templates-app/village-quest-3d/src/village.scene.json +5 -27
@@ -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-Bvuh2p_r.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-Dcz0fr1S.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-CNULJvwh.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-L8yccdMP.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-Dcz0fr1S.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-8-IHvbKD.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-F7IZHdFR.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-Bvuh2p_r.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-DuVdzxbq.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-DEvp3kyV.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-_jk0qLsS.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,4 +1,4 @@
1
- import { P as Scene, b as Engine } from "./behavior-DZExDn9o.js";
1
+ import { P as Scene, b as Engine } from "./behavior-Dcz0fr1S.js";
2
2
  import { a as SceneJson } from "./rng-Bb-IutXB.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
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-Dcz0fr1S.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-CIYf1zSf.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-Cfmd3ar_.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-eULetcg-.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-Bvuh2p_r.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-BYQCBySi.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-Bvuh2p_r.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-CNULJvwh.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-99BzP_L-.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-DuVdzxbq.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-CNULJvwh.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-Dcz0fr1S.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-LCHBLfA4.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CH4kt78v.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1,9 +1,9 @@
1
- import { K as Node, M as effectiveOrder, q as diagnose } from "./save-slots-BXVg148r.js";
2
- import { M as translateOn, t as registerCoreNodes } from "./register-6DYnKZcy.js";
1
+ import { J as Node, P as effectiveOrder, Y as diagnose } from "./save-slots-Bvuh2p_r.js";
2
+ import { M as translateOn, t as registerCoreNodes } from "./register-DuVdzxbq.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { c as getNodeSchema, h as didYouMean, p as registerNode } from "./touch-BnCyPA0G.js";
5
5
  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";
6
- import { E as effectRng, X as localFromWorld, Z as worldPosition, w as tolerateUnknownAction } from "./gameplay-BfHkuzVb.js";
6
+ import { D as effectRng, Q as worldPosition, T as tolerateUnknownAction, Z as localFromWorld } from "./gameplay-CNULJvwh.js";
7
7
  import { t as checkSheetGrid } from "./sheet-grid-Cea343VO.js";
8
8
  import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
9
9
  //#region src/2d/physics/collider-validate.ts
@@ -1617,6 +1617,15 @@ function mergeSolidRects(grid, solid) {
1617
1617
  //#endregion
1618
1618
  //#region src/2d/nodes/tile-map-2d.ts
1619
1619
  /**
1620
+ * Where colour cells live in the index space, above any atlas anyone will draw.
1621
+ *
1622
+ * A million tiles is 1000×1000 atlas cells; a texture that size does not exist,
1623
+ * and the loader refuses `colors` and `texture` together anyway.
1624
+ */
1625
+ const COLOR_BASE = 1e6;
1626
+ /** `#rgb` or `#rrggbb`, which is what every other colour prop in the engine takes. */
1627
+ const HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
1628
+ /**
1620
1629
  * A whole tile level as ONE node — grid render in a single draw call plus
1621
1630
  * greedy-merged static colliders. Author the level as rows of characters:
1622
1631
  *
@@ -1653,6 +1662,23 @@ var TileMap2D = class extends Node2D {
1653
1662
  cells: { default: [] },
1654
1663
  /** char → atlas tile index for non-digit chars. */
1655
1664
  legend: { default: {} },
1665
+ /**
1666
+ * Char → COLOUR, for a level you author before you have any art.
1667
+ *
1668
+ * `incanto-assets.md` is titled "No art yet? Ship gameplay anyway", and it
1669
+ * was true of everything except the node a grid game is made of: this one
1670
+ * draws from an atlas, so with no texture it rendered nothing at all. A
1671
+ * sokoban built from the published package came out as 24 hand-placed
1672
+ * `ColorRect2D` walls and a `wallAt()` comparing positions — because the
1673
+ * one node that already answers `cellAt`/`tileAt`/`setTile`/`worldAt`, and
1674
+ * merges the colliders, could not be seen.
1675
+ *
1676
+ * A cell painted with one of these chars is a flat quad of that colour, in
1677
+ * the same single draw call. Exclusive with `texture`: one material, one
1678
+ * draw call, and "the art arrived" is a two-line edit rather than a
1679
+ * silently half-textured level.
1680
+ */
1681
+ colors: { default: {} },
1656
1682
  /** Tile indices that get merged static colliders. */
1657
1683
  solid: { default: [] },
1658
1684
  opacity: {
@@ -1693,6 +1719,42 @@ var TileMap2D = class extends Node2D {
1693
1719
  this.geometryDirty = true;
1694
1720
  this.collidersDirty = true;
1695
1721
  }
1722
+ _colors = {};
1723
+ get colors() {
1724
+ return this._colors;
1725
+ }
1726
+ set colors(value) {
1727
+ this._colors = value;
1728
+ this.geometryDirty = true;
1729
+ this.collidersDirty = true;
1730
+ }
1731
+ /**
1732
+ * The colours, in declaration order — a cell's tile index is `COLOR_BASE + i`.
1733
+ *
1734
+ * A synthetic index rather than a second grid: everything downstream (the
1735
+ * collider merge, `tileAt`, `setTile`, the editor's paint tool) already
1736
+ * speaks in indices, and one number per cell keeps it that way.
1737
+ */
1738
+ palette() {
1739
+ return Object.values(this._colors);
1740
+ }
1741
+ /** `legend` plus the colour chars, which is what every cell lookup wants. */
1742
+ allChars() {
1743
+ const merged = { ...this._legend };
1744
+ Object.keys(this._colors).forEach((ch, i) => {
1745
+ merged[ch] = COLOR_BASE + i;
1746
+ });
1747
+ return merged;
1748
+ }
1749
+ /** `solid` as INDICES: an entry may name the char that painted the cell. */
1750
+ solidIndices() {
1751
+ const chars = this.allChars();
1752
+ const out = [];
1753
+ for (const entry of this._solid) if (typeof entry === "number") out.push(entry);
1754
+ else if (chars[entry] !== void 0) out.push(chars[entry]);
1755
+ else if (entry.length === 1 && entry >= "0" && entry <= "9") out.push(entry.charCodeAt(0) - 48);
1756
+ return out;
1757
+ }
1696
1758
  get solid() {
1697
1759
  return this._solid;
1698
1760
  }
@@ -1707,11 +1769,16 @@ var TileMap2D = class extends Node2D {
1707
1769
  /** Loader hook: bad cells fail at LOAD (unknown chars), not at render. */
1708
1770
  static validateJson(node) {
1709
1771
  const map = node;
1710
- parseCells(map._cells, map._legend);
1772
+ for (const [ch, value] of Object.entries(map._colors)) {
1773
+ if (typeof value === "string" && HEX.test(value)) continue;
1774
+ throw new IncantoError("PROP_TYPE_MISMATCH", `TileMap2D '${node.name}': "colors" ['${ch}'] is ${JSON.stringify(value)} — a colour here is \`#rgb\` or \`#rrggbb\`, the same as every other colour prop.`, { prop: "colors" });
1775
+ }
1776
+ if (map.texture !== "" && Object.keys(map._colors).length > 0) throw new IncantoError("PROP_TYPE_MISMATCH", `TileMap2D '${node.name}': "colors" and "texture" are exclusive — colours are for a level authored before the art arrives. Drop one of them.`, { prop: "colors" });
1777
+ parseCells(map._cells, map.allChars());
1711
1778
  }
1712
1779
  /** The parsed index grid (test/tooling hook). */
1713
1780
  grid() {
1714
- return parseCells(this._cells, this._legend);
1781
+ return parseCells(this._cells, this.allChars());
1715
1782
  }
1716
1783
  /**
1717
1784
  * Which CELL is under this world point — `null` outside the grid.
@@ -1809,7 +1876,7 @@ var TileMap2D = class extends Node2D {
1809
1876
  if (this._solid.length === 0) return;
1810
1877
  const ts = this.tileSize;
1811
1878
  const [sx, sy] = this.builtScale;
1812
- mergeSolidRects(this.grid(), new Set(this._solid)).forEach((r, i) => {
1879
+ mergeSolidRects(this.grid(), new Set(this.solidIndices())).forEach((r, i) => {
1813
1880
  const body = new StaticBody2D(`__tileSolid${i}`);
1814
1881
  body.position = [(r.x + r.w / 2) * ts * sx, (r.y + r.h / 2) * ts * sy];
1815
1882
  body.collider = {
@@ -1839,6 +1906,23 @@ var TileMap2D = class extends Node2D {
1839
1906
  super._syncObject2D(assets);
1840
1907
  const mesh = this._mesh();
1841
1908
  const mat = mesh.material;
1909
+ if (Object.keys(this._colors).length > 0) {
1910
+ mesh.visible = this.opacity > 0;
1911
+ mesh.renderOrder = this.effectiveRenderOrder;
1912
+ mat.opacity = this.opacity;
1913
+ if (mat.map !== null || !mat.vertexColors) {
1914
+ mat.map = null;
1915
+ mat.vertexColors = true;
1916
+ mat.needsUpdate = true;
1917
+ this.geometryDirty = true;
1918
+ }
1919
+ if (!this.geometryDirty) return;
1920
+ this.geometryDirty = false;
1921
+ this.builtColumns = 1;
1922
+ this.builtRows = 1;
1923
+ this.buildGeometry(mesh.geometry, 1, 1);
1924
+ return;
1925
+ }
1842
1926
  if (!this.texture || !assets) {
1843
1927
  mesh.visible = false;
1844
1928
  return;
@@ -1882,6 +1966,8 @@ var TileMap2D = class extends Node2D {
1882
1966
  const positions = [];
1883
1967
  const uvs = [];
1884
1968
  const indices = [];
1969
+ const palette = this.palette().map((hex) => new Color(hex));
1970
+ const colors = [];
1885
1971
  for (let y = 0; y < grid.length; y++) {
1886
1972
  const row = grid[y];
1887
1973
  for (let x = 0; x < row.length; x++) {
@@ -1893,18 +1979,24 @@ var TileMap2D = class extends Node2D {
1893
1979
  const y1 = -((y + 1) * ts);
1894
1980
  const base = positions.length / 3;
1895
1981
  positions.push(x0, y0, 0, x1, y0, 0, x1, y1, 0, x0, y1, 0);
1896
- const col = tile % columns;
1897
- const rowIdx = Math.floor(tile / columns);
1982
+ const col = tile >= COLOR_BASE ? 0 : tile % columns;
1983
+ const rowIdx = tile >= COLOR_BASE ? 0 : Math.floor(tile / columns);
1898
1984
  const u0 = col / columns;
1899
1985
  const u1 = (col + 1) / columns;
1900
1986
  const v0 = 1 - rowIdx / atlasRows;
1901
1987
  const v1 = 1 - (rowIdx + 1) / atlasRows;
1902
1988
  uvs.push(u0, v0, u1, v0, u1, v1, u0, v1);
1989
+ const paint = tile >= COLOR_BASE ? palette[tile - COLOR_BASE] : void 0;
1990
+ const r = paint?.r ?? 1;
1991
+ const g = paint?.g ?? 1;
1992
+ const b = paint?.b ?? 1;
1993
+ for (let v = 0; v < 4; v++) colors.push(r, g, b);
1903
1994
  indices.push(base, base + 2, base + 1, base, base + 3, base + 2);
1904
1995
  }
1905
1996
  }
1906
1997
  geometry.setAttribute("position", new BufferAttribute(new Float32Array(positions), 3));
1907
1998
  geometry.setAttribute("uv", new BufferAttribute(new Float32Array(uvs), 2));
1999
+ geometry.setAttribute("color", new BufferAttribute(new Float32Array(colors), 3));
1908
2000
  geometry.setIndex(indices);
1909
2001
  geometry.computeBoundingSphere();
1910
2002
  }