incanto 0.48.0 → 0.50.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 (54) hide show
  1. package/bin/incanto-model.mjs +13 -1
  2. package/bin/incanto-play.mjs +42 -17
  3. package/bin/incanto-playtest.mjs +7 -0
  4. package/bin/incanto-verify.mjs +10 -2
  5. package/dist/2d.d.ts +21 -12
  6. package/dist/2d.js +57 -3
  7. package/dist/3d.d.ts +38 -1
  8. package/dist/3d.js +114 -4
  9. package/dist/{create-game-CniOiWzN.js → create-game-BLDjy_PW.js} +5 -5
  10. package/dist/{create-game-akjPkFv5.js → create-game-DqqxEax1.js} +5 -5
  11. package/dist/{duplicate-CRtihGmC.js → duplicate-CI9WF_bg.js} +1 -1
  12. package/dist/{environment-presets-D1b0ydTS.js → environment-presets-XFuqu5jv.js} +15 -20
  13. package/dist/{gameplay-BQOeAid6.js → gameplay-DbaI313d.js} +96 -11
  14. package/dist/gameplay.d.ts +17 -0
  15. package/dist/gameplay.js +1 -1
  16. package/dist/index.d.ts +40 -2
  17. package/dist/index.js +7 -7
  18. package/dist/{loader-r49nDwB4.js → loader-DwazzlQb.js} +36 -6
  19. package/dist/net.js +3 -3
  20. package/dist/{physics-2d-BmgXBNDB.js → physics-2d-_9VBOHn6.js} +4 -4
  21. package/dist/{physics-3d-CSoGjM8P.js → physics-3d-DrpF5hcG.js} +9 -9
  22. package/dist/react.js +1 -1
  23. package/dist/{register-R2JTnIMw.js → register-BNPZYJmd.js} +23 -22
  24. package/dist/{register-D651it1J.js → register-CB11yp21.js} +2 -2
  25. package/dist/{register-BSXV8T9F.js → register-uvaZj1KX.js} +62 -2
  26. package/dist/{replay-CAphXMyM.d.ts → replay-DYdy1wb0.d.ts} +5 -1
  27. package/dist/{replay-DilbZgQI.js → replay-j-m6lJ4W.js} +23 -3
  28. package/dist/sprite-animation-CMr6f1K2.d.ts +44 -0
  29. package/dist/{particle-sim-Bw7hB93B.js → sprite-animation-D_p28jwU.js} +63 -1
  30. package/dist/{src-Bl1Kire1.js → src-B3HrKuAi.js} +1 -1
  31. package/dist/{test-E4-otKqK.js → test-D16igj4C.js} +245 -38
  32. package/dist/test.d.ts +39 -7
  33. package/dist/test.js +2 -2
  34. package/dist/vite.js +2 -2
  35. package/editor/assets/{agent8-_007gPF8.js → agent8-t3kl5q9K.js} +1 -1
  36. package/editor/assets/{debug-0DI_MJaq.js → debug-Bu3eeAlO.js} +1 -1
  37. package/editor/assets/{index-B-6eYZEi.js → index-Df5g8ofT.js} +92 -92
  38. package/editor/index.html +1 -1
  39. package/package.json +1 -1
  40. package/schemas/scene.schema.json +10 -3
  41. package/skills/incanto-3d-character.md +13 -2
  42. package/skills/incanto-3d-models.md +40 -0
  43. package/skills/incanto-assets.md +15 -0
  44. package/skills/incanto-building-2d-games.md +57 -5
  45. package/skills/incanto-building-3d-games.md +7 -0
  46. package/skills/incanto-gameplay-behaviors.md +29 -1
  47. package/skills/incanto-hud.md +39 -0
  48. package/skills/incanto-node-reference.md +5 -3
  49. package/skills/incanto-physics-and-input.md +1 -1
  50. package/skills/incanto-playtesting.md +22 -4
  51. package/skills/incanto-verifying-your-game.md +14 -0
  52. package/templates-app/beacon-isle-3d/package.json +1 -1
  53. package/templates-app/tps-3d/package.json +1 -1
  54. package/templates-app/village-quest-3d/package.json +1 -1
@@ -1,7 +1,7 @@
1
- import { f as Node, h as effectiveOrder, n as loadScene, w as registerBehavior, y as Behavior } from "./loader-r49nDwB4.js";
1
+ import { f as Node, h as effectiveOrder, n as loadScene, w as registerBehavior, y as Behavior } from "./loader-DwazzlQb.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
4
- import { t as duplicateNode } from "./duplicate-CRtihGmC.js";
4
+ import { t as duplicateNode } from "./duplicate-CI9WF_bg.js";
5
5
  import { Color, CubeCamera, DepthTexture, DoubleSide, Euler, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
6
6
  //#region src/3d/frustum.ts
7
7
  const scratchFrustum = new Frustum();
@@ -2553,7 +2553,7 @@ var CharacterBody3D = class extends PhysicsBody3D {
2553
2553
  0,
2554
2554
  0
2555
2555
  ] },
2556
- snapToGround: { default: true },
2556
+ stickToGround: { default: true },
2557
2557
  slopeLimitDeg: { default: 45 }
2558
2558
  };
2559
2559
  /** m/s, y-up (up = +y). */
@@ -2562,9 +2562,30 @@ var CharacterBody3D = class extends PhysicsBody3D {
2562
2562
  0,
2563
2563
  0
2564
2564
  ];
2565
- snapToGround = true;
2565
+ /**
2566
+ * Hold the capsule against the surface while it moves, so walking down a
2567
+ * slope does not become a series of small falls.
2568
+ *
2569
+ * Named apart from `Node3D.snapToGround` (which PLACES a node on the ground
2570
+ * at load) because sharing the name silently broke both: the boolean shadowed
2571
+ * the placement prop, its `true` default made the placement pass teleport
2572
+ * every character body in a terrain scene, and a lift value was rejected
2573
+ * outright — so the only escape from the teleport also turned this off.
2574
+ */
2575
+ stickToGround = true;
2566
2576
  /** Max climbable slope angle. */
2567
2577
  slopeLimitDeg = 45;
2578
+ /**
2579
+ * `snapToGround` on a character body is ambiguous, so it is refused.
2580
+ *
2581
+ * A boolean here used to mean the ground-stick, and as placement it means
2582
+ * "leave y alone" — which is already the default. Rather than pick one and
2583
+ * silently do the other thing to an existing scene, say which prop was meant.
2584
+ */
2585
+ onReady() {
2586
+ if (typeof this.snapToGround === "boolean") throw new IncantoError("PROP_TYPE_MISMATCH", `'${this.getPath()}' sets snapToGround to a boolean. On a character body that is ambiguous: 'stickToGround' holds the capsule against the surface while it moves (default true), while 'snapToGround' PLACES the node on the ground at load and takes a lift in metres (e.g. 0.8) — omit it to leave y exactly as authored.`, { prop: "snapToGround" });
2587
+ super.onReady();
2588
+ }
2568
2589
  /** @internal Updated by Physics3D.moveAndSlide. */
2569
2590
  _grounded = false;
2570
2591
  moveAndSlide() {
@@ -4231,6 +4252,20 @@ function firstWater(node) {
4231
4252
  return null;
4232
4253
  }
4233
4254
  //#endregion
4255
+ //#region src/gameplay/move-body.ts
4256
+ function moveBody(node, to, dt) {
4257
+ const body = node;
4258
+ if (typeof body.moveAndSlide !== "function" || body._physics == null || dt <= 0) {
4259
+ node.position = [...to];
4260
+ return;
4261
+ }
4262
+ const from = body.position;
4263
+ const v = [];
4264
+ for (let i = 0; i < from.length; i++) v.push(((to[i] ?? from[i] ?? 0) - (from[i] ?? 0)) / dt);
4265
+ body.velocity = v;
4266
+ body.moveAndSlide();
4267
+ }
4268
+ //#endregion
4234
4269
  //#region src/gameplay/spatial.ts
4235
4270
  /** Duck-type test: every spatial node (Node2D/Node3D) exposes `position: number[]`. */
4236
4271
  function hasPosition$1(node) {
@@ -4393,7 +4428,7 @@ var Chase = class extends Behavior {
4393
4428
  this.inRange = false;
4394
4429
  const step = Math.min(this.speed * dt, this.stopRange > 0 ? d - this.stopRange : d);
4395
4430
  const { position } = moveToward(mover.position, target.position, Math.max(0, step));
4396
- mover.position = position;
4431
+ moveBody(mover, position, dt);
4397
4432
  }
4398
4433
  };
4399
4434
  //#endregion
@@ -4553,7 +4588,8 @@ var Health = class extends Behavior {
4553
4588
  static signals = [
4554
4589
  "damaged",
4555
4590
  "healed",
4556
- "died"
4591
+ "died",
4592
+ "healthChanged"
4557
4593
  ];
4558
4594
  max = 100;
4559
4595
  regenPerSec = 0;
@@ -4602,13 +4638,29 @@ var Health = class extends Behavior {
4602
4638
  this.setCurrent(0);
4603
4639
  this.die();
4604
4640
  }
4641
+ /**
4642
+ * The one funnel every change goes through — and therefore the one place a
4643
+ * HUD can be told about.
4644
+ *
4645
+ * `damaged`/`healed` both lead with the DELTA, which is the number a bar must
4646
+ * not show; wire either to `UiBar.setValue` and the bar paints the damage as
4647
+ * the health and looks like it works. Regeneration is worse: it moves
4648
+ * `current` through here and emits NEITHER, so a regen bar sat still while
4649
+ * the character healed. `healthChanged(current, max)` carries the max too, so
4650
+ * a raised ceiling needs no second wire.
4651
+ *
4652
+ * Silent when nothing moved: a bar that repaints on nothing is noise.
4653
+ */
4605
4654
  setCurrent(value) {
4606
- this.current = Math.max(0, Math.min(this.max, value));
4655
+ const next = Math.max(0, Math.min(this.max, value));
4656
+ if (next === this.current) return;
4657
+ this.current = next;
4658
+ this.emit("healthChanged", this.current, this.max);
4607
4659
  }
4608
4660
  die() {
4609
4661
  if (this.dead) return;
4610
4662
  this.dead = true;
4611
- this.current = 0;
4663
+ this.setCurrent(0);
4612
4664
  this.emit("died");
4613
4665
  if (this.freeOnDeath) this.node.queueFree();
4614
4666
  }
@@ -5637,7 +5689,7 @@ var Patrol = class extends Behavior {
5637
5689
  const target = this.pointAt(this.index);
5638
5690
  if (!target) return;
5639
5691
  const { position, reached } = moveToward(node.position, target, this.speed * dt);
5640
- node.position = position;
5692
+ moveBody(node, position, dt);
5641
5693
  if (reached) {
5642
5694
  this.emit("reachedPoint", this.index);
5643
5695
  this.advance();
@@ -5675,6 +5727,10 @@ var Patrol = class extends Behavior {
5675
5727
  * On overlap with a node in `collectorGroup`, emits `collected(value, other)`
5676
5728
  * then `queueFree()`s itself. Wire `collected → ScoreKeeper.addScore` (the
5677
5729
  * value is the first arg) or `collected → Collector.collect`.
5730
+ *
5731
+ * `other` is the ENTITY, not whichever of its parts touched — see
5732
+ * {@link findCollector}. A player is a body with children (a reach sensor, a
5733
+ * hitbox), and the part that enters first is rarely the part carrying the group.
5678
5734
  */
5679
5735
  var Pickup = class extends Behavior {
5680
5736
  static props = {
@@ -5695,12 +5751,41 @@ var Pickup = class extends Behavior {
5695
5751
  }
5696
5752
  tryCollect(other) {
5697
5753
  if (this.collectedAlready) return;
5698
- if (!other.isInGroup(this.collectorGroup)) return;
5754
+ const collector = findCollector(other, this.collectorGroup);
5755
+ if (!collector) return;
5699
5756
  this.collectedAlready = true;
5700
- this.emit("collected", this.value, other);
5757
+ this.emit("collected", this.value, collector);
5701
5758
  this.node.queueFree();
5702
5759
  }
5703
5760
  };
5761
+ /**
5762
+ * The entity in `group` that this contact belongs to, or null.
5763
+ *
5764
+ * The same search `DamageOnContact` has always used for `Health` — the
5765
+ * contacted node, its descendants, then the ancestor CHAIN — and for the same
5766
+ * reason. An entity is a body with children (a reach sensor, a hitbox, a skin),
5767
+ * the group sits on one node of it, and whichever part happens to touch first
5768
+ * is rarely that node.
5769
+ *
5770
+ * Matching the literal entering node was a silent failure in exactly the shape
5771
+ * the gameplay skill recommends: a player with a `Grab` area collected almost
5772
+ * nothing, because the sensor is wider than the body and entered first. Five
5773
+ * pickups fired `triggerEnter`; one fired `collected`.
5774
+ *
5775
+ * The ancestor climb deliberately does NOT descend into siblings: past a shared
5776
+ * parent every other entity hangs off it too, and a coin would be collected by
5777
+ * a crate standing next to the player.
5778
+ */
5779
+ function findCollector(node, group) {
5780
+ const queue = [node];
5781
+ while (queue.length > 0) {
5782
+ const n = queue.shift();
5783
+ if (n.isInGroup(group)) return n;
5784
+ queue.push(...n.children);
5785
+ }
5786
+ for (let n = node.parent; n; n = n.parent) if (n.isInGroup(group)) return n;
5787
+ return null;
5788
+ }
5704
5789
  //#endregion
5705
5790
  //#region src/gameplay/projectile.ts
5706
5791
  /**
@@ -97,6 +97,19 @@ declare class Health extends Behavior {
97
97
  heal(n: number): void;
98
98
  /** Drop to 0 and die immediately (ignores i-frames). */
99
99
  kill(): void;
100
+ /**
101
+ * The one funnel every change goes through — and therefore the one place a
102
+ * HUD can be told about.
103
+ *
104
+ * `damaged`/`healed` both lead with the DELTA, which is the number a bar must
105
+ * not show; wire either to `UiBar.setValue` and the bar paints the damage as
106
+ * the health and looks like it works. Regeneration is worse: it moves
107
+ * `current` through here and emits NEITHER, so a regen bar sat still while
108
+ * the character healed. `healthChanged(current, max)` carries the max too, so
109
+ * a raised ceiling needs no second wire.
110
+ *
111
+ * Silent when nothing moved: a bar that repaints on nothing is noise.
112
+ */
100
113
  private setCurrent;
101
114
  private die;
102
115
  /** Current HP and whether death already fired — `max` comes back from JSON. */
@@ -318,6 +331,10 @@ declare class Patrol extends Behavior {
318
331
  * On overlap with a node in `collectorGroup`, emits `collected(value, other)`
319
332
  * then `queueFree()`s itself. Wire `collected → ScoreKeeper.addScore` (the
320
333
  * value is the first arg) or `collected → Collector.collect`.
334
+ *
335
+ * `other` is the ENTITY, not whichever of its parts touched — see
336
+ * {@link findCollector}. A player is a body with children (a reach sensor, a
337
+ * hitbox), and the part that enters first is rarely the part carrying the group.
321
338
  */
322
339
  declare class Pickup extends Behavior {
323
340
  static readonly props: PropSchema;
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-BQOeAid6.js";
1
+ import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-DbaI313d.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { $ as suggestLocale, A as SettingsValues, At as LogLevel, B as captureBehaviors, C as EngineStats, Ct as PlayMusicOptions, Dt as NodeLifecycle, E as DeviceHints, Et as Node, F as BehaviorState, G as OrderGroup, H as SaveStore, I as RestoreReport, J as resolveOrderGroups, K as OrderGroupTable, L as SaveSlot, M as readDeviceHints, Mt as Signal, N as suggestQuality, Nt as SignalListener, O as QualityTier, Ot as SceneTree, P as Scene, Q as T_PREFIX, R as SaveSlots, S as Scheduler, St as MusicTrack, T as RendererStats, Tt as BusName, U as createSaveStore, V as restoreBehaviors, W as ORDER_GROUP_BASE, X as LocaleTables, Y as BASE_LOCALE, Z as Localization, _ as mergeStaticSignals, _t as SfxWave, a as clearBehaviors, at as Voice, b as Engine, bt as MusicBackend, c as registeredBehaviors, ct as ROLLOFF_MODELS, d as PropSchema, dt as Vec3, et as translationKey, f as clearRegistry, ft as spatialGain, g as getNodeType, gt as SfxParams, h as getNodeSignals, ht as SFX_PRESET_NAMES, i as behaviorSignals, it as isAudioContextAvailable, j as qualityEnvironment, jt as LogManager, k as Settings, kt as LogEntry, l as NodeCtor, lt as RolloffModel, m as getNodeSchema, mt as SFX_PRESETS, n as BehaviorCtor, nt as SfxEngine, o as getBehavior, ot as VoicePreset, p as createNode, pt as spatialPan, q as effectiveOrder, r as behaviorSchema, rt as SfxPlayOptions, s as registerBehavior, st as Listener, t as Behavior, tt as InputMap, u as PropDef, ut as SpatialParams, v as registerNode, vt as SynthOptions, w as GameStats, wt as AudioBuses, x as EngineOptions, xt as MusicManager, y as registeredTypes, yt as synthSfx, z as behaviorsWithoutSave } from "./behavior-62q0HWBO.js";
2
2
  import { a as Rng, c as JsonValue, d as jsonKind, i as SceneJson, l as jsonClone, n as NodeJson, o as JsonKind, r as SCENE_FORMAT, s as JsonObject, t as ConnectionJson, u as jsonEquals } from "./schema-CFeioQRE.js";
3
3
  import { n as loadScene, t as LoadSceneOptions } from "./loader-CeyU_bm1.js";
4
+ import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js";
4
5
  import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
5
6
  import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-C49JSNNq.js";
6
- 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-CAphXMyM.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-DYdy1wb0.js";
7
8
 
8
9
  //#region src/core/audio/crossfade.d.ts
9
10
  /**
@@ -319,10 +320,37 @@ declare class UiText extends HudWidgetBase {
319
320
  static override readonly typeName: string;
320
321
  static override readonly props: PropSchema;
321
322
  text: string;
323
+ /**
324
+ * A template with a `{}` slot that `setText` fills — `"Gems {} / 8"`.
325
+ *
326
+ * Empty (the default) means `setText` replaces the whole line. Resolved at
327
+ * PAINT like `text`, so `"@t:hud.gems"` works and changing locale re-reads
328
+ * it: the value lives here, the words live in the strings table.
329
+ */
330
+ format: string;
322
331
  size: number;
323
332
  color: string;
324
333
  shadow: boolean;
325
334
  private last;
335
+ /** null until something has actually set a value — see `shown`. */
336
+ private slot;
337
+ /**
338
+ * Put a signal's value on screen — `scoreChanged → setText`.
339
+ *
340
+ * A `connections[]` handler must be a METHOD and `text` is a property, so the
341
+ * one wire every game needs (score to screen) could not be declared: every
342
+ * HUD began with a behavior whose whole job was one assignment.
343
+ */
344
+ setText(value: unknown): void;
345
+ /**
346
+ * What it actually paints, after the template and the locale.
347
+ *
348
+ * Before the first value arrives, a formatted widget shows `text` — its
349
+ * authored opening line. Filling the slot with nothing instead would put
350
+ * `"Gems / 8"` on screen for every game's first frame, and a score line
351
+ * that starts blank looks broken rather than empty.
352
+ */
353
+ get shown(): string;
326
354
  protected _build(): HTMLElement;
327
355
  protected override _sync(): void;
328
356
  }
@@ -345,6 +373,16 @@ declare class UiBar extends HudWidgetBase {
345
373
  private lastRatio;
346
374
  /** Current fill ratio 0..1 (what the bar shows). */
347
375
  get ratio(): number;
376
+ /**
377
+ * Drive the bar from a signal — `healthChanged → setValue`.
378
+ *
379
+ * Takes the max as a second argument because that is the shape the signal
380
+ * already has, so a bar whose ceiling moves (a max-HP upgrade) needs no
381
+ * second wire.
382
+ */
383
+ setValue(value: number, max?: number): void;
384
+ /** Move the ceiling on its own, leaving the fill where it is. */
385
+ setMax(max: number): void;
348
386
  protected _build(): HTMLElement;
349
387
  protected override _sync(): void;
350
388
  }
@@ -810,4 +848,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
810
848
  /** Engine version. Kept in sync with package.json by the release pipeline. */
811
849
  declare const VERSION: string;
812
850
  //#endregion
813
- export { type AssetFailure, AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, type DriveScript, type DriveStep, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, type OrderGroupTable, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, parseDrive, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveOrderGroups, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
851
+ export { type AnimationDef, type AnimationEntry, type AssetFailure, AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, type DriveScript, type DriveStep, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, type OrderGroupTable, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, parseDrive, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
- import { C as getBehavior, E as Signal, S as clearBehaviors, T as registeredBehaviors, _ as InputMap, a as SCENE_FORMAT, b as behaviorSchema, c as SceneTree, d as resolveConstants, f as Node, g as resolveOrderGroups, h as effectiveOrder, i as serializeNode, l as CONST_REF_KEY, m as ORDER_GROUP_BASE, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, w as registerBehavior, x as behaviorSignals, y as Behavior } from "./loader-r49nDwB4.js";
2
- import { A as T_PREFIX, B as synthSfx, C as behaviorsWithoutSave, D as LogManager, E as createSaveStore, F as ROLLOFF_MODELS, G as AudioBuses, H as WebAudioMusicBackend, I as spatialGain, L as spatialPan, M as translationKey, N as SfxEngine, O as BASE_LOCALE, P as isAudioContextAvailable, R as SFX_PRESETS, S as SaveSlots, T as restoreBehaviors, U as crossfadeGains, V as MusicManager, W as fadeGain, a as UiSlider, b as readDeviceHints, c as UiButton, d as UiBanner, f as UiBar, g as Settings, h as Engine, i as UiSelect, j as suggestLocale, k as Localization, l as UiDialogue, m as AudioPlayer, n as UiImage, o as UiToggle, p as UiText, r as UiPanel, s as Timer, t as registerCoreNodes, u as HudLayer, v as qualityEnvironment, w as captureBehaviors, x as suggestQuality, z as SFX_PRESET_NAMES } from "./register-BSXV8T9F.js";
1
+ import { C as getBehavior, E as Signal, S as clearBehaviors, T as registeredBehaviors, _ as InputMap, a as SCENE_FORMAT, b as behaviorSchema, c as SceneTree, d as resolveConstants, f as Node, g as resolveOrderGroups, h as effectiveOrder, i as serializeNode, l as CONST_REF_KEY, m as ORDER_GROUP_BASE, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, w as registerBehavior, x as behaviorSignals, y as Behavior } from "./loader-DwazzlQb.js";
2
+ import { A as T_PREFIX, B as synthSfx, C as behaviorsWithoutSave, D as LogManager, E as createSaveStore, F as ROLLOFF_MODELS, G as AudioBuses, H as WebAudioMusicBackend, I as spatialGain, L as spatialPan, M as translationKey, N as SfxEngine, O as BASE_LOCALE, P as isAudioContextAvailable, R as SFX_PRESETS, S as SaveSlots, T as restoreBehaviors, U as crossfadeGains, V as MusicManager, W as fadeGain, a as UiSlider, b as readDeviceHints, c as UiButton, d as UiBanner, f as UiBar, g as Settings, h as Engine, i as UiSelect, j as suggestLocale, k as Localization, l as UiDialogue, m as AudioPlayer, n as UiImage, o as UiToggle, p as UiText, r as UiPanel, s as Timer, t as registerCoreNodes, u as HudLayer, v as qualityEnvironment, w as captureBehaviors, x as suggestQuality, z as SFX_PRESET_NAMES } from "./register-uvaZj1KX.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
- import { n as startRecording, r as auditScene, t as replay } from "./replay-DilbZgQI.js";
5
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-j-m6lJ4W.js";
6
6
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
7
7
  import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-IyWCGe4q.js";
8
8
  import { n as logText, r as parseDrive, t as logReport } from "./log-report-lxrQY9cH.js";
9
9
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
10
- import { i as applyParticlePreset, n as PARTICLE_PRESETS, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
11
- import { a as findPath, i as preloadUrls, n as newUid, o as gridFromRows, r as assetUrls, t as VERSION } from "./src-Bl1Kire1.js";
10
+ 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-D_p28jwU.js";
11
+ import { a as findPath, i as preloadUrls, n as newUid, o as gridFromRows, r as assetUrls, t as VERSION } from "./src-B3HrKuAi.js";
12
12
  import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-BoNg_MnF.js";
13
- import { t as duplicateNode } from "./duplicate-CRtihGmC.js";
14
- export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, parseDrive, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveOrderGroups, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
13
+ import { t as duplicateNode } from "./duplicate-CI9WF_bg.js";
14
+ export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, parseDrive, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -1606,13 +1606,17 @@ function wireConnections(root, connections, declareSignals = false) {
1606
1606
  const listener = (...args) => {
1607
1607
  if (conn.filter && !matchesFilter(args[0], conn.filter)) return;
1608
1608
  if (conn.once) fromNode.off(conn.signal, listener);
1609
- const fn = toNode[conn.handler];
1610
- if (typeof fn === "function") {
1611
- fn.apply(toNode, args);
1612
- return;
1609
+ try {
1610
+ const fn = toNode[conn.handler];
1611
+ if (typeof fn === "function") {
1612
+ fn.apply(toNode, args);
1613
+ return;
1614
+ }
1615
+ const bfn = toNode.behavior ? toNode.behavior[conn.handler] : void 0;
1616
+ if (typeof bfn === "function") bfn.apply(toNode.behavior, args);
1617
+ } catch (error) {
1618
+ quarantineWire(fromNode, toNode, conn, listener, error);
1613
1619
  }
1614
- const bfn = toNode.behavior ? toNode.behavior[conn.handler] : void 0;
1615
- if (typeof bfn === "function") bfn.apply(toNode.behavior, args);
1616
1620
  };
1617
1621
  try {
1618
1622
  fromNode.on(conn.signal, listener);
@@ -1622,6 +1626,32 @@ function wireConnections(root, connections, declareSignals = false) {
1622
1626
  }
1623
1627
  }
1624
1628
  }
1629
+ /**
1630
+ * A handler threw. Turn THAT WIRE off, and leave the game running.
1631
+ *
1632
+ * The engine already draws this line for scripts: "a behavior is your code; the
1633
+ * node is the engine's. A typo in the first must not switch off the second, or
1634
+ * breaking a sword script stops the character from walking." A connection is
1635
+ * the same category — authored wiring, not the emitting node's own logic — and
1636
+ * it behaved the opposite way, because the handler runs inside `Signal.emit`
1637
+ * and its exception surfaces in whoever EMITTED. The node quarantine then blames
1638
+ * and skips that node, so a `movementStateChanged → play` wire naming a clip the
1639
+ * sheet does not have switched off the character controller: no animation, and
1640
+ * no movement either.
1641
+ *
1642
+ * Reported ONCE and disconnected, on the same reasoning as the node quarantine:
1643
+ * a handler that throws throws every frame, and sixty identical traces a second
1644
+ * bury the one line that matters.
1645
+ */
1646
+ function quarantineWire(fromNode, toNode, conn, listener, error) {
1647
+ fromNode.off(conn.signal, listener);
1648
+ const target = toNode.behavior ? `'${conn.handler}' on behavior '${toNode.script?.name}' (${toNode.getPath()})` : `'${conn.handler}' on ${toNode.getPath()}`;
1649
+ const message = `[incanto] connection '${conn.signal}' from ${fromNode.getPath()} → handler ${target} threw — THIS WIRE is now off; the node that emitted the signal, and the rest of the scene, keep running.`;
1650
+ const engine = toNode.tree?.engine ?? fromNode.tree?.engine;
1651
+ engine?.log.error(message, error);
1652
+ engine?._nodeErrored(toNode);
1653
+ console.error(message, error);
1654
+ }
1625
1655
  function resolveEndpoint(root, path, conn, side) {
1626
1656
  try {
1627
1657
  return root.getNode(path);
package/dist/net.js CHANGED
@@ -1,9 +1,9 @@
1
- import { n as loadScene } from "./loader-r49nDwB4.js";
2
- import { h as Engine } from "./register-BSXV8T9F.js";
1
+ import { n as loadScene } from "./loader-DwazzlQb.js";
2
+ import { h as Engine } from "./register-uvaZj1KX.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
6
- import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-D651it1J.js";
6
+ import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-CB11yp21.js";
7
7
  //#region src/net/loopback.ts
8
8
  /**
9
9
  * In-memory multiplayer hub implementing the SAME kernel contract as
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { v as diagnose } from "./loader-r49nDwB4.js";
2
+ import { v as diagnose } from "./loader-DwazzlQb.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-R2JTnIMw.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-BNPZYJmd.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  //#region src/2d/physics/physics-2d.ts
7
7
  var physics_2d_exports = /* @__PURE__ */ __exportAll({
@@ -90,7 +90,7 @@ var Physics2D = class {
90
90
  this.world.step(this.events);
91
91
  for (const [node, e] of this.entries) {
92
92
  const t = e.body.translation();
93
- if (!e.body.isFixed()) {
93
+ if (!e.body.isFixed() && !(node instanceof Area2D)) {
94
94
  const [ox, oy] = parentWorldOffset2D(node);
95
95
  node.position = [t.x * SCALE - ox, t.y * SCALE - oy];
96
96
  }
@@ -122,7 +122,7 @@ var Physics2D = class {
122
122
  moveAndSlide(node) {
123
123
  const e = this.ensureEntry(node);
124
124
  const vy = node.velocity[1] ?? 0;
125
- if (node.snapToGround && vy >= 0) this.kcc.enableSnapToGround(.05);
125
+ if (node.stickToGround && vy >= 0) this.kcc.enableSnapToGround(.05);
126
126
  else this.kcc.disableSnapToGround();
127
127
  this.kcc.setMaxSlopeClimbAngle(node.slopeLimitDeg * DEG);
128
128
  let carryX = 0;
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { v as diagnose } from "./loader-r49nDwB4.js";
2
+ import { v as diagnose } from "./loader-DwazzlQb.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-BQOeAid6.js";
4
+ import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-DbaI313d.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
- import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-D1b0ydTS.js";
6
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-XFuqu5jv.js";
7
7
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
8
8
  //#region src/3d/physics/collider-lines.ts
9
9
  /**
@@ -291,7 +291,7 @@ var Physics3D = class {
291
291
  this.world.step(this.events);
292
292
  for (const [node, e] of this.entries) {
293
293
  const t = e.body.translation();
294
- if (!e.body.isFixed()) {
294
+ if (!e.body.isFixed() && !(node instanceof Area3D)) {
295
295
  const [ox, oy, oz] = parentWorldOffset3D(node);
296
296
  const next = [
297
297
  t.x - ox,
@@ -323,10 +323,10 @@ var Physics3D = class {
323
323
  e.lastV[1] = lv.y;
324
324
  e.lastV[2] = lv.z;
325
325
  }
326
- const wp = worldPosition3D(node);
327
- e.last[0] = wp[0];
328
- e.last[1] = wp[1];
329
- e.last[2] = wp[2];
326
+ const t2 = e.body.translation();
327
+ e.last[0] = t2.x;
328
+ e.last[1] = t2.y;
329
+ e.last[2] = t2.z;
330
330
  }
331
331
  this.events.drainCollisionEvents((h1, h2, started) => {
332
332
  const a = this.byColliderHandle.get(h1);
@@ -373,7 +373,7 @@ var Physics3D = class {
373
373
  const e = this.ensureEntry(node);
374
374
  if (!e) return;
375
375
  const vy = node.velocity[1] ?? 0;
376
- if (node.snapToGround && vy <= 0) this.kcc.enableSnapToGround(.1);
376
+ if (node.stickToGround && vy <= 0) this.kcc.enableSnapToGround(.1);
377
377
  else this.kcc.disableSnapToGround();
378
378
  this.kcc.setMaxSlopeClimbAngle(node.slopeLimitDeg * DEG);
379
379
  let carryX = 0;
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-akjPkFv5.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CniOiWzN.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-DqqxEax1.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-BLDjy_PW.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 { f as Node, h as effectiveOrder } from "./loader-r49nDwB4.js";
2
- import { t as registerCoreNodes } from "./register-BSXV8T9F.js";
1
+ import { f as Node, h as effectiveOrder } from "./loader-DwazzlQb.js";
2
+ import { t as registerCoreNodes } from "./register-uvaZj1KX.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { i as getNodeSchema, l as registerNode } from "./registry-IyWCGe4q.js";
6
- import { i as applyParticlePreset, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
6
+ import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-D_p28jwU.js";
7
7
  import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry } from "three";
8
8
  //#region src/2d/physics/collider-validate.ts
9
9
  /**
@@ -198,13 +198,19 @@ var CharacterBody2D = class extends PhysicsBody2D {
198
198
  static typeName = "CharacterBody2D";
199
199
  static props = {
200
200
  velocity: { default: [0, 0] },
201
- snapToGround: { default: true },
201
+ stickToGround: { default: true },
202
202
  slopeLimitDeg: { default: 45 }
203
203
  };
204
+ /**
205
+ * Renamed to match the 3D body, where `snapToGround` had to give the name
206
+ * back to `Node3D`'s PLACEMENT prop. 2D has no placement prop, so nothing is
207
+ * ambiguous here and old scenes keep loading unchanged.
208
+ */
209
+ static propAliases = { snapToGround: "stickToGround" };
204
210
  /** px/s, y-down (up = -y). */
205
211
  velocity = [0, 0];
206
212
  /** Keep contact on slopes/steps while not moving upward. */
207
- snapToGround = true;
213
+ stickToGround = true;
208
214
  /** Max climbable slope angle. */
209
215
  slopeLimitDeg = 45;
210
216
  /** @internal Updated by Physics2D.moveAndSlide. */
@@ -402,13 +408,19 @@ var AnimatedSprite2D = class extends Sprite2D {
402
408
  frameList = [];
403
409
  frameIndex = 0;
404
410
  frameTime = 0;
411
+ currentDef = null;
405
412
  ownTexture = null;
406
413
  loadedSheetRef = "";
414
+ /**
415
+ * `currentAnimation` reports the clip that is RUNNING, not the name asked
416
+ * for: an alias is a rename, and a state machine that reads back `fall` while
417
+ * the idle frames play is describing something that is not happening.
418
+ */
407
419
  play(name) {
408
- const def = this.animations[name];
409
- if (!def) throw new IncantoError("UNKNOWN_ANIMATION", `No animation '${name}' on '${this.name}'. Available: [${Object.keys(this.animations).join(", ")}].`);
410
- this.currentAnimation = name;
411
- this.frameList = resolveFrames(def.frames, name);
420
+ const { name: resolved, def } = resolveAnimation(this.animations, name, this.name);
421
+ this.currentAnimation = resolved;
422
+ this.currentDef = def;
423
+ this.frameList = resolveFrames(def.frames, resolved);
412
424
  this.frameIndex = 0;
413
425
  this.currentFrame = this.frameList[0] ?? 0;
414
426
  this.frameTime = 0;
@@ -418,11 +430,12 @@ var AnimatedSprite2D = class extends Sprite2D {
418
430
  this.playing = false;
419
431
  }
420
432
  onReady() {
433
+ validateAnimationAliases(this.animations, this.name);
421
434
  if (this.autoplay) this.play(this.autoplay);
422
435
  }
423
436
  update(dt) {
424
437
  if (!this.playing || this.currentAnimation === "") return;
425
- const def = this.animations[this.currentAnimation];
438
+ const def = this.currentDef;
426
439
  if (!def || !Number.isFinite(def.fps) || def.fps <= 0) return;
427
440
  const perFrame = 1 / def.fps;
428
441
  this.frameTime += dt;
@@ -462,18 +475,6 @@ var AnimatedSprite2D = class extends Sprite2D {
462
475
  };
463
476
  }
464
477
  };
465
- function resolveFrames(frames, name) {
466
- if (!Array.isArray(frames) || frames.length === 0) throw new IncantoError("BAD_FORMAT", `Animation '${name}': "frames" must be a non-empty array.`);
467
- if (frames.length === 2) {
468
- const [start, end] = frames;
469
- if (end >= start) {
470
- const out = [];
471
- for (let f = start; f <= end; f++) out.push(f);
472
- return out;
473
- }
474
- }
475
- return [...frames];
476
- }
477
478
  //#endregion
478
479
  //#region src/2d/nodes/camera-2d.ts
479
480
  /**
@@ -1,5 +1,5 @@
1
- import { E as Signal, f as Node, t as buildNodeJson } from "./loader-r49nDwB4.js";
2
- import { t as registerCoreNodes } from "./register-BSXV8T9F.js";
1
+ import { E as Signal, f as Node, t as buildNodeJson } from "./loader-DwazzlQb.js";
2
+ import { t as registerCoreNodes } from "./register-uvaZj1KX.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { l as registerNode } from "./registry-IyWCGe4q.js";