incanto 0.65.0 → 0.66.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 (55) hide show
  1. package/dist/2d.d.ts +38 -3
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +130 -3
  4. package/dist/3d.js +4 -4
  5. package/dist/{audio-player-BrEHwbK1.d.ts → audio-player-_UAcHxnC.d.ts} +1 -1
  6. package/dist/{behavior-CmMuQ6CL.d.ts → behavior-BXNLfIJk.d.ts} +19 -0
  7. package/dist/{create-game-W2xzTJhs.js → create-game-DYJCIzO0.js} +66 -5
  8. package/dist/{create-game-DIyh5Zy9.js → create-game-Dd7H4bJV.js} +5 -5
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{environment-presets-BvHs6HEk.js → environment-presets-BlPsEmq6.js} +69 -5
  11. package/dist/{gameplay-D-H986A4.js → gameplay-D1RADWu3.js} +267 -7
  12. package/dist/gameplay.d.ts +78 -3
  13. package/dist/gameplay.js +2 -2
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +3 -3
  16. package/dist/{loader-CTB441z4.d.ts → loader-Cga7FVP4.d.ts} +1 -1
  17. package/dist/net.d.ts +2 -2
  18. package/dist/net.js +1 -1
  19. package/dist/{physics-2d-CmnunaR2.js → physics-2d-Cji5A6sX.js} +29 -3
  20. package/dist/{physics-3d-DULB1lD1.js → physics-3d-BkHjwJgI.js} +46 -4
  21. package/dist/{quiet-rapier-BAJ4K94N.js → quiet-rapier-C6fW4zcW.js} +14 -1
  22. package/dist/react.d.ts +1 -1
  23. package/dist/react.js +1 -1
  24. package/dist/{register-CgeR4ckF.js → register-BmuqYTiY.js} +29 -0
  25. package/dist/{register-DO5_Qp0Q.js → register-D0CxCveZ.js} +37 -3
  26. package/dist/{replay-CThvwkcQ.d.ts → replay-BHoB6fCU.d.ts} +1 -1
  27. package/dist/{replay-CEw2xAhG.js → replay-IsZbNu6d.js} +1 -1
  28. package/dist/{split-screen-gb0c3xdb.js → split-screen-D_i7GRcY.js} +1 -1
  29. package/dist/{split-screen-DxZzC1nm.d.ts → split-screen-eJUFjhi1.d.ts} +2 -2
  30. package/dist/{src-DYffzHbd.js → src-BTLbXFPZ.js} +1 -1
  31. package/dist/{teardown-BordHL9o.js → teardown-byR9USax.js} +1 -1
  32. package/dist/{test-CXZuXs8k.js → test-DgrD0jHD.js} +11 -11
  33. package/dist/test.d.ts +4 -4
  34. package/dist/test.js +2 -2
  35. package/dist/vite.js +2 -2
  36. package/editor/assets/{agent8-DO5qzIOD.js → agent8-9N-Pd_YS.js} +1 -1
  37. package/editor/assets/{debug-DntG9CVD.js → debug-CkbJICYp.js} +1 -1
  38. package/editor/assets/{index-CqEUVaPN.js → index-CeDhIPTC.js} +72 -72
  39. package/editor/index.html +1 -1
  40. package/package.json +1 -1
  41. package/schemas/scene.schema.json +19 -0
  42. package/skills/incanto-gameplay-behaviors.md +85 -6
  43. package/skills/incanto-node-reference.md +59 -1
  44. package/skills/incanto-physics-and-input.md +69 -0
  45. package/skills/incanto-web-integration.md +24 -3
  46. package/templates-app/beacon-isle-3d/package.json +1 -1
  47. package/templates-app/platformer-2d/package.json +1 -1
  48. package/templates-app/star-survivor/package.json +1 -1
  49. package/templates-app/tps-3d/package.json +1 -1
  50. package/templates-app/village-quest-3d/package.json +1 -1
  51. package/templates-app/beacon-isle-3d/coverage.json +0 -9
  52. package/templates-app/platformer-2d/coverage.json +0 -5
  53. package/templates-app/star-survivor/coverage.json +0 -5
  54. package/templates-app/tps-3d/coverage.json +0 -5
  55. package/templates-app/village-quest-3d/coverage.json +0 -9
@@ -2534,6 +2534,36 @@ var StaticBody3D = class extends PhysicsBody3D {
2534
2534
  /** Sensor volume emitting `triggerEnter(other)` / `triggerExit(other)`. */
2535
2535
  var Area3D = class extends PhysicsBody3D {
2536
2536
  static typeName = "Area3D";
2537
+ /**
2538
+ * Who is inside RIGHT NOW — the standing answer behind the enter/exit pair.
2539
+ *
2540
+ * Enter and exit tell you about crossings; a pressure plate, a capture point,
2541
+ * a "how many enemies are in the blast" count and a shop trigger all want
2542
+ * OCCUPANCY. Without this every one of them kept a private `Set` fed by the
2543
+ * two signals, and had to guard it, because a body that is teleported or
2544
+ * freed inside a sensor does not reliably announce its exit. Physics already
2545
+ * knows; now it says.
2546
+ *
2547
+ * ```ts
2548
+ * const load = this.area.overlapping()
2549
+ * .reduce((kg, b) => kg + ((b as RigidBody3D).mass ?? 0), 0);
2550
+ * door.open = load >= 60;
2551
+ * ```
2552
+ *
2553
+ * It reports everything the solver says is inside, STATIC WORLD INCLUDED — a
2554
+ * plate laid into the floor genuinely contains the floor. Pass a group to ask
2555
+ * the question you usually mean:
2556
+ *
2557
+ * ```ts
2558
+ * area.overlapping('crate') // only things tagged `crate`
2559
+ * ```
2560
+ *
2561
+ * Empty in a scene with no physics world.
2562
+ */
2563
+ overlapping(group) {
2564
+ const all = this._physics?.overlapping(this) ?? [];
2565
+ return group === void 0 ? all : all.filter((n) => n.isInGroup(group));
2566
+ }
2537
2567
  };
2538
2568
  /** Dynamic simulated body. */
2539
2569
  var RigidBody3D = class extends PhysicsBody3D {
@@ -2548,6 +2578,11 @@ var RigidBody3D = class extends PhysicsBody3D {
2548
2578
  0,
2549
2579
  0,
2550
2580
  0
2581
+ ] },
2582
+ angularVelocity: { default: [
2583
+ 0,
2584
+ 0,
2585
+ 0
2551
2586
  ] }
2552
2587
  };
2553
2588
  mass = 1;
@@ -2561,6 +2596,18 @@ var RigidBody3D = class extends PhysicsBody3D {
2561
2596
  0,
2562
2597
  0
2563
2598
  ];
2599
+ /**
2600
+ * rad/s about each world axis. Read back every step; write to spin.
2601
+ *
2602
+ * A puzzle builder could neither launch a spinning body nor read how fast a
2603
+ * lever was swinging — only its angle, differenced by hand — because this had
2604
+ * no counterpart to `linearVelocity`. `fixedRotation` pins it at zero.
2605
+ */
2606
+ angularVelocity = [
2607
+ 0,
2608
+ 0,
2609
+ 0
2610
+ ];
2564
2611
  /** @internal set by Physics3D */
2565
2612
  _physics3d = null;
2566
2613
  /** World-space impulse (kg·m/s) — the floating-capsule controller's verb. */
@@ -4711,7 +4758,7 @@ var Clickable = class extends Behavior {
4711
4758
  if (this.hovering) this.setHover(false);
4712
4759
  return;
4713
4760
  }
4714
- const hit = picker(pointer.x, pointer.y);
4761
+ const hit = engine.pickAt(pointer.x, pointer.y);
4715
4762
  const over = hit !== null && this.isSelfOrDescendant(hit) && this.withinRange(hit);
4716
4763
  if (over !== this.hovering) this.setHover(over);
4717
4764
  if (engine.input.mouseJustPressed(this.button)) this.pressedHere = over;
@@ -4781,6 +4828,104 @@ group: { default: "player" } };
4781
4828
  }
4782
4829
  };
4783
4830
  //#endregion
4831
+ //#region src/gameplay/currency.ts
4832
+ /**
4833
+ * Money. Earn it, spend it, and refuse what you cannot afford.
4834
+ *
4835
+ * `ScoreKeeper` is the closest thing the library had and it is a SCORE: it
4836
+ * counts up, it has no spend, no affordability question, and its counter is
4837
+ * already wired to the win condition. So a tower defense built on 0.65.0 wrote
4838
+ * its own wallet — and so does every shop, every build mode, every upgrade tree
4839
+ * and every economy game.
4840
+ *
4841
+ * `spend` is the whole point: it returns whether it went through, and emits
4842
+ * `refused` when it did not, so "you cannot afford that" is a wire rather than
4843
+ * a comparison every caller repeats and one caller forgets.
4844
+ */
4845
+ var Currency = class extends Behavior {
4846
+ static props = {
4847
+ /** What you start with. */
4848
+ amount: { default: 0 },
4849
+ /** Ceiling (0 = none). */
4850
+ max: { default: 0 }
4851
+ };
4852
+ static signals = [
4853
+ "changed",
4854
+ "earned",
4855
+ "spent",
4856
+ "refused"
4857
+ ];
4858
+ amount = 0;
4859
+ max = 0;
4860
+ /** Can this be paid right now? */
4861
+ canAfford(cost) {
4862
+ return cost <= this.amount;
4863
+ }
4864
+ /** Pay `cost` if it is there. Returns whether it went through. */
4865
+ spend(cost) {
4866
+ if (!this.canAfford(cost)) {
4867
+ this.emit("refused", cost);
4868
+ return false;
4869
+ }
4870
+ this.amount -= cost;
4871
+ this.emit("spent", cost);
4872
+ this.emit("changed", this.amount);
4873
+ return true;
4874
+ }
4875
+ /** Take payment in. */
4876
+ earn(gain) {
4877
+ this.amount = this.max > 0 ? Math.min(this.max, this.amount + gain) : this.amount + gain;
4878
+ this.emit("earned", gain);
4879
+ this.emit("changed", this.amount);
4880
+ }
4881
+ /** Set it outright (a shop that grants, a cheat, a restore). */
4882
+ setAmount(next) {
4883
+ this.amount = this.max > 0 ? Math.min(this.max, next) : next;
4884
+ this.emit("changed", this.amount);
4885
+ }
4886
+ serialize() {
4887
+ return { amount: this.amount };
4888
+ }
4889
+ deserialize(data) {
4890
+ const d = data;
4891
+ if (typeof d.amount === "number") this.amount = d.amount;
4892
+ }
4893
+ };
4894
+ //#endregion
4895
+ //#region src/gameplay/free-target.ts
4896
+ /**
4897
+ * Free the ENTITY a despawning behavior belongs to, not just the node it sits on.
4898
+ *
4899
+ * `Lifetime` and `DamageOnContact` both remove their own node, which is right
4900
+ * when the behavior IS the thing. It is wrong for the composition the docs
4901
+ * teach: a node carries ONE script, so a bullet that both hurts and expires must
4902
+ * put those on CHILD nodes — and then each child quietly removed itself while
4903
+ * the bullet flew on forever, leaking a node per shot.
4904
+ *
4905
+ * `freeParent: true` is the other half. It is the same escape hatch the movement
4906
+ * behaviors call `moveParent`, named the same way for the same reason.
4907
+ */
4908
+ function freeOwner(behavior, freeParent, who) {
4909
+ if (!freeParent) {
4910
+ behavior.node.queueFree();
4911
+ return;
4912
+ }
4913
+ const parent = behavior.node.parent;
4914
+ if (!parent) {
4915
+ diagnose(behavior.node.tree?.engine ?? null, "error", `[incanto] ${who} on '${behavior.node.getPath()}': "freeParent" is set and this node has no parent to free — it is the scene root. Nothing was removed. Drop the prop, or move this behavior onto a child of the thing you meant to despawn.`);
4916
+ return;
4917
+ }
4918
+ parent.queueFree();
4919
+ }
4920
+ /** The shared prop, so both behaviors declare it identically. */
4921
+ const FREE_PARENT_PROP = {
4922
+ /**
4923
+ * Free this node's PARENT instead of this node — the composition where a
4924
+ * bullet carries `Projectile` and its children carry the damage and the
4925
+ * timer. Without it each child removes only itself and the bullet leaks.
4926
+ */
4927
+ freeParent: { default: false } };
4928
+ //#endregion
4784
4929
  //#region src/gameplay/health.ts
4785
4930
  /**
4786
4931
  * Hit points with regeneration and post-hit invulnerability (i-frames) —
@@ -4971,7 +5116,9 @@ function onTriggerEnter(behavior, fn) {
4971
5116
  * - `repeatEvery` (seconds, 0 = off) re-damages targets that STAY overlapped —
4972
5117
  * lava pools, poison clouds, an enemy standing on you. Contact events fire
4973
5118
  * only on entry/exit; this is the "and it keeps hurting" knob.
4974
- * - `destroySelf` frees the hazard after a hit (single-use projectiles).
5119
+ * - `destroySelf` frees the hazard after a hit (single-use projectiles), and
5120
+ * `freeParent` makes that free the PARENT — a bullet whose hitbox is a child,
5121
+ * which is the only shape a node-carries-one-script engine allows.
4975
5122
  *
4976
5123
  * SCORING PATTERN (clone-safe): wire the KILLER's `dealtDamage` →
4977
5124
  * `ScoreKeeper.addScore`. The weapon is usually a non-cloned node (it lives on
@@ -4989,7 +5136,8 @@ var DamageOnContact = class extends Behavior {
4989
5136
  /** Re-damage targets still overlapping every N seconds (0 = entry only). */
4990
5137
  repeatEvery: { default: 0 },
4991
5138
  /** queueFree() this node after the first successful hit. */
4992
- destroySelf: { default: false }
5139
+ destroySelf: { default: false },
5140
+ ...FREE_PARENT_PROP
4993
5141
  };
4994
5142
  static signals = ["dealtDamage"];
4995
5143
  amount = 10;
@@ -4997,6 +5145,7 @@ var DamageOnContact = class extends Behavior {
4997
5145
  oncePerTarget = true;
4998
5146
  repeatEvery = 0;
4999
5147
  destroySelf = false;
5148
+ freeParent = false;
5000
5149
  hit = /* @__PURE__ */ new WeakSet();
5001
5150
  /** Targets currently overlapping → seconds until their next repeat tick. */
5002
5151
  inside = /* @__PURE__ */ new Map();
@@ -5032,7 +5181,7 @@ var DamageOnContact = class extends Behavior {
5032
5181
  if (this.oncePerTarget) this.hit.add(other);
5033
5182
  found.health.damage(this.amount);
5034
5183
  this.emit("dealtDamage", this.amount, found.owner);
5035
- if (this.destroySelf) this.node.queueFree();
5184
+ if (this.destroySelf) freeOwner(this, this.freeParent, "DamageOnContact");
5036
5185
  }
5037
5186
  };
5038
5187
  /**
@@ -5146,6 +5295,109 @@ function phaseOf(hour) {
5146
5295
  return "dusk";
5147
5296
  }
5148
5297
  //#endregion
5298
+ //#region src/gameplay/face-target.ts
5299
+ /**
5300
+ * Turn toward the nearest thing in a group, and say when you are on it.
5301
+ *
5302
+ * A turret is the shape every tower defense, sentry gun, security camera and
5303
+ * idle-NPC-that-watches-you needs, and it could not be composed. `Chase`
5304
+ * already finds the nearest target and turns toward it — and then MOVES, which
5305
+ * is the one thing a turret must not do; `Patrol`/`Chase`'s `facePath` only
5306
+ * turns as a side effect of locomotion, so a stationary node cannot use it.
5307
+ * A tower defense built on 0.65.0 hand-wrote acquisition (~20 lines) and
5308
+ * turn-and-gate-the-shot (~15) because of that.
5309
+ *
5310
+ * `aimed` fires when the facing settles onto the target and `lostAim` when it
5311
+ * comes off, so "only fire while actually pointed at it" is a wire rather than
5312
+ * an angle comparison you write yourself.
5313
+ */
5314
+ var FaceTarget = class extends Behavior {
5315
+ static props = {
5316
+ /** Group to acquire from — the nearest live member wins. */
5317
+ targetGroup: {
5318
+ default: "",
5319
+ required: true
5320
+ },
5321
+ /** Only acquire within this distance (0 = anywhere). */
5322
+ range: { default: 0 },
5323
+ /** Node to TURN (usually a skin); empty turns this behavior's own node. */
5324
+ facePath: {
5325
+ default: "",
5326
+ nodePath: true
5327
+ },
5328
+ /** Turn rate rad/s; 100 is an instant snap, as on the controller. */
5329
+ turnSpeed: { default: 6 },
5330
+ /** Degrees of slop that still counts as pointed at it. */
5331
+ aimTolerance: { default: 6 }
5332
+ };
5333
+ static signals = [
5334
+ "acquired",
5335
+ "lostTarget",
5336
+ "aimed",
5337
+ "lostAim"
5338
+ ];
5339
+ targetGroup = "";
5340
+ range = 0;
5341
+ facePath = "";
5342
+ turnSpeed = 6;
5343
+ aimTolerance = 6;
5344
+ /** The node currently being tracked, or null. */
5345
+ target = null;
5346
+ /** True while the facing is within `aimTolerance` of the target. */
5347
+ onTarget = false;
5348
+ onReady() {
5349
+ if (this.targetGroup === "") throw new IncantoError("PROP_TYPE_MISMATCH", `FaceTarget on '${this.node.getPath()}': "targetGroup" is required — it is the group this turret acquires from.`, { prop: "targetGroup" });
5350
+ }
5351
+ update(dt) {
5352
+ const self = this.node;
5353
+ if (!hasPosition$1(self)) return;
5354
+ const found = this.nearest();
5355
+ if (found !== this.target) {
5356
+ this.target = found;
5357
+ this.emit(found ? "acquired" : "lostTarget", found);
5358
+ if (!found) this.setAim(false);
5359
+ }
5360
+ if (!found) return;
5361
+ const turret = this.facePath === "" ? self : this.node.getNodeOrNull(this.facePath);
5362
+ if (!turret) return;
5363
+ const dir = found.position.map((v, i) => v - (self.position[i] ?? 0));
5364
+ faceTravel(turret, dir, dt, this.turnSpeed);
5365
+ this.setAim(this.pointedAt(turret, dir));
5366
+ }
5367
+ nearest() {
5368
+ const self = this.node;
5369
+ let best = null;
5370
+ let bestD = Number.POSITIVE_INFINITY;
5371
+ for (const candidate of this.node.tree?.getNodesInGroup(this.targetGroup) ?? []) {
5372
+ if (!hasPosition$1(candidate)) continue;
5373
+ const d = Math.hypot(...candidate.position.map((v, i) => v - (self.position[i] ?? 0)));
5374
+ if (this.range > 0 && d > this.range) continue;
5375
+ if (d < bestD) {
5376
+ bestD = d;
5377
+ best = candidate;
5378
+ }
5379
+ }
5380
+ return best;
5381
+ }
5382
+ /** Is the turret's forward within tolerance of the direction to the target? */
5383
+ pointedAt(turret, dir) {
5384
+ const rot = turret.rotation;
5385
+ const is3d = Array.isArray(rot);
5386
+ const [a, b] = is3d ? [dir[0] ?? 0, dir[2] ?? 0] : [dir[0] ?? 0, dir[1] ?? 0];
5387
+ const len = Math.hypot(a, b);
5388
+ if (len < 1e-6) return true;
5389
+ const yaw = (is3d ? rot[1] ?? 0 : rot ?? 0) * Math.PI / 180;
5390
+ const fx = is3d ? Math.sin(yaw) : Math.cos(yaw);
5391
+ const fy = is3d ? Math.cos(yaw) : Math.sin(yaw);
5392
+ return (fx * a + fy * b) / len >= Math.cos(this.aimTolerance * Math.PI / 180);
5393
+ }
5394
+ setAim(on) {
5395
+ if (on === this.onTarget) return;
5396
+ this.onTarget = on;
5397
+ this.emit(on ? "aimed" : "lostAim", this.target);
5398
+ }
5399
+ };
5400
+ //#endregion
5149
5401
  //#region src/gameplay/float-away.ts
5150
5402
  /**
5151
5403
  * Rise, fade, and be gone — the second half of a damage number.
@@ -5780,6 +6032,10 @@ const hitStops = /* @__PURE__ */ new Map();
5780
6032
  * pickups that vanish. Accumulates `dt`; on elapse emits `expired` then
5781
6033
  * `queueFree()`s its node.
5782
6034
  *
6035
+ * `freeParent: true` frees the node's PARENT instead — the shape a bullet needs,
6036
+ * since a node carries ONE script so `Projectile`, the damage and the timer have
6037
+ * to live on separate nodes.
6038
+ *
5783
6039
  * With `startOnSignal: true` the countdown is armed manually via `startTimer()`
5784
6040
  * (wire a signal → `startTimer`), so the lifetime begins on an event rather
5785
6041
  * than at spawn.
@@ -5789,11 +6045,13 @@ var Lifetime = class extends Behavior {
5789
6045
  /** Seconds before the node frees itself. */
5790
6046
  seconds: { default: 1 },
5791
6047
  /** Defer the countdown until `startTimer()` is called (default: start at ready). */
5792
- startOnSignal: { default: false }
6048
+ startOnSignal: { default: false },
6049
+ ...FREE_PARENT_PROP
5793
6050
  };
5794
6051
  static signals = ["expired"];
5795
6052
  seconds = 1;
5796
6053
  startOnSignal = false;
6054
+ freeParent = false;
5797
6055
  elapsed = 0;
5798
6056
  running = false;
5799
6057
  fired = false;
@@ -5814,7 +6072,7 @@ var Lifetime = class extends Behavior {
5814
6072
  this.fired = true;
5815
6073
  this.running = false;
5816
6074
  this.emit("expired");
5817
- this.node.queueFree();
6075
+ freeOwner(this, this.freeParent, "Lifetime");
5818
6076
  }
5819
6077
  }
5820
6078
  };
@@ -7102,6 +7360,7 @@ const GAMEPLAY_BEHAVIORS = {
7102
7360
  Health,
7103
7361
  Lifetime,
7104
7362
  ScoreKeeper,
7363
+ Currency,
7105
7364
  Pickup,
7106
7365
  Collector,
7107
7366
  DamageOnContact,
@@ -7111,6 +7370,7 @@ const GAMEPLAY_BEHAVIORS = {
7111
7370
  FollowCamera,
7112
7371
  Patrol,
7113
7372
  Chase,
7373
+ FaceTarget,
7114
7374
  Wander,
7115
7375
  ZombieAI,
7116
7376
  MoveTo,
@@ -7131,4 +7391,4 @@ function registerGameplayBehaviors(opts) {
7131
7391
  for (const [name, ctor] of Object.entries(GAMEPLAY_BEHAVIORS)) registerBehavior(name, ctor, { replace });
7132
7392
  }
7133
7393
  //#endregion
7134
- export { Health as A, CharacterBody3D as B, restartScene as C, DayNight as D, FloatAway as E, Water3D as F, validateCollider3D as G, RigidBody3D as H, WATER_CUTOUT_MAX as I, colliderFootDrop as J, createCausticsQuad as K, WaterCutout3D as L, Chase as M, localFromWorld as N, phaseOf as O, worldPosition as P, rejectScale as R, goToScene as S, FollowCamera as T, StaticBody3D as U, PhysicsBody3D as V, Node3D as W, 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, Collector as j, DamageOnContact as k, Projectile as l, MoveTo as m, registerGameplayBehaviors as n, Spawner as o, Oscillate as p, WATER_MAX_RIPPLES 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, Area3D as z };
7394
+ export { DamageOnContact as A, rejectScale as B, restartScene as C, FaceTarget as D, FloatAway as E, localFromWorld as F, StaticBody3D as G, CharacterBody3D as H, worldPosition as I, createCausticsQuad as J, Node3D as K, Water3D as L, Currency as M, Collector as N, DayNight as O, Chase as P, WATER_CUTOUT_MAX as R, goToScene as S, FollowCamera as T, PhysicsBody3D as U, Area3D as V, RigidBody3D as W, colliderFootDrop as X, WATER_MAX_RIPPLES as Y, 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, Health as j, phaseOf as k, Projectile as l, MoveTo as m, registerGameplayBehaviors as n, Spawner as o, Oscillate as p, validateCollider3D 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, 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-CmMuQ6CL.js";
1
+ import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-BXNLfIJk.js";
2
2
  import { c as JsonValue, s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
@@ -60,6 +60,37 @@ declare class Collector extends Behavior {
60
60
  override deserialize(data: JsonValue): void;
61
61
  }
62
62
  //#endregion
63
+ //#region src/gameplay/currency.d.ts
64
+ /**
65
+ * Money. Earn it, spend it, and refuse what you cannot afford.
66
+ *
67
+ * `ScoreKeeper` is the closest thing the library had and it is a SCORE: it
68
+ * counts up, it has no spend, no affordability question, and its counter is
69
+ * already wired to the win condition. So a tower defense built on 0.65.0 wrote
70
+ * its own wallet — and so does every shop, every build mode, every upgrade tree
71
+ * and every economy game.
72
+ *
73
+ * `spend` is the whole point: it returns whether it went through, and emits
74
+ * `refused` when it did not, so "you cannot afford that" is a wire rather than
75
+ * a comparison every caller repeats and one caller forgets.
76
+ */
77
+ declare class Currency extends Behavior {
78
+ static readonly props: PropSchema;
79
+ static readonly signals: readonly string[];
80
+ amount: number;
81
+ max: number;
82
+ /** Can this be paid right now? */
83
+ canAfford(cost: number): boolean;
84
+ /** Pay `cost` if it is there. Returns whether it went through. */
85
+ spend(cost: number): boolean;
86
+ /** Take payment in. */
87
+ earn(gain: number): void;
88
+ /** Set it outright (a shop that grants, a cheat, a restore). */
89
+ setAmount(next: number): void;
90
+ override serialize(): JsonValue;
91
+ override deserialize(data: JsonValue): void;
92
+ }
93
+ //#endregion
63
94
  //#region src/gameplay/health.d.ts
64
95
  /**
65
96
  * Hit points with regeneration and post-hit invulnerability (i-frames) —
@@ -162,7 +193,9 @@ declare class Health extends Behavior {
162
193
  * - `repeatEvery` (seconds, 0 = off) re-damages targets that STAY overlapped —
163
194
  * lava pools, poison clouds, an enemy standing on you. Contact events fire
164
195
  * only on entry/exit; this is the "and it keeps hurting" knob.
165
- * - `destroySelf` frees the hazard after a hit (single-use projectiles).
196
+ * - `destroySelf` frees the hazard after a hit (single-use projectiles), and
197
+ * `freeParent` makes that free the PARENT — a bullet whose hitbox is a child,
198
+ * which is the only shape a node-carries-one-script engine allows.
166
199
  *
167
200
  * SCORING PATTERN (clone-safe): wire the KILLER's `dealtDamage` →
168
201
  * `ScoreKeeper.addScore`. The weapon is usually a non-cloned node (it lives on
@@ -177,6 +210,7 @@ declare class DamageOnContact extends Behavior {
177
210
  oncePerTarget: boolean;
178
211
  repeatEvery: number;
179
212
  destroySelf: boolean;
213
+ freeParent: boolean;
180
214
  private readonly hit;
181
215
  /** Targets currently overlapping → seconds until their next repeat tick. */
182
216
  private readonly inside;
@@ -185,6 +219,42 @@ declare class DamageOnContact extends Behavior {
185
219
  private tryDamage;
186
220
  }
187
221
  //#endregion
222
+ //#region src/gameplay/face-target.d.ts
223
+ /**
224
+ * Turn toward the nearest thing in a group, and say when you are on it.
225
+ *
226
+ * A turret is the shape every tower defense, sentry gun, security camera and
227
+ * idle-NPC-that-watches-you needs, and it could not be composed. `Chase`
228
+ * already finds the nearest target and turns toward it — and then MOVES, which
229
+ * is the one thing a turret must not do; `Patrol`/`Chase`'s `facePath` only
230
+ * turns as a side effect of locomotion, so a stationary node cannot use it.
231
+ * A tower defense built on 0.65.0 hand-wrote acquisition (~20 lines) and
232
+ * turn-and-gate-the-shot (~15) because of that.
233
+ *
234
+ * `aimed` fires when the facing settles onto the target and `lostAim` when it
235
+ * comes off, so "only fire while actually pointed at it" is a wire rather than
236
+ * an angle comparison you write yourself.
237
+ */
238
+ declare class FaceTarget extends Behavior {
239
+ static readonly props: PropSchema;
240
+ static readonly signals: readonly string[];
241
+ targetGroup: string;
242
+ range: number;
243
+ facePath: string;
244
+ turnSpeed: number;
245
+ aimTolerance: number;
246
+ /** The node currently being tracked, or null. */
247
+ target: Node | null;
248
+ /** True while the facing is within `aimTolerance` of the target. */
249
+ onTarget: boolean;
250
+ override onReady(): void;
251
+ override update(dt: number): void;
252
+ private nearest;
253
+ /** Is the turret's forward within tolerance of the direction to the target? */
254
+ private pointedAt;
255
+ private setAim;
256
+ }
257
+ //#endregion
188
258
  //#region src/gameplay/follow-camera.d.ts
189
259
  /**
190
260
  * Make the node it sits on chase a target's position — THE camera-follow
@@ -239,6 +309,10 @@ declare class Interactable extends Behavior {
239
309
  * pickups that vanish. Accumulates `dt`; on elapse emits `expired` then
240
310
  * `queueFree()`s its node.
241
311
  *
312
+ * `freeParent: true` frees the node's PARENT instead — the shape a bullet needs,
313
+ * since a node carries ONE script so `Projectile`, the damage and the timer have
314
+ * to live on separate nodes.
315
+ *
242
316
  * With `startOnSignal: true` the countdown is armed manually via `startTimer()`
243
317
  * (wire a signal → `startTimer`), so the lifetime begins on an event rather
244
318
  * than at spawn.
@@ -248,6 +322,7 @@ declare class Lifetime extends Behavior {
248
322
  static readonly signals: readonly string[];
249
323
  seconds: number;
250
324
  startOnSignal: boolean;
325
+ freeParent: boolean;
251
326
  private elapsed;
252
327
  private running;
253
328
  private fired;
@@ -987,4 +1062,4 @@ declare function registerGameplayBehaviors(opts?: {
987
1062
  replace?: boolean;
988
1063
  }): void;
989
1064
  //#endregion
990
- export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, type DayPhase, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
1065
+ 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, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as Health, C as restartScene, D as DayNight, E as FloatAway, M as Chase, O as phaseOf, S as goToScene, T as FollowCamera, _ 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 Collector, k as DamageOnContact, 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-D-H986A4.js";
2
- export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
1
+ import { A as DamageOnContact, C as restartScene, D as FaceTarget, E as FloatAway, M as Currency, N as Collector, O as DayNight, P as Chase, S as goToScene, T as FollowCamera, _ 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 Health, k as phaseOf, 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-D1RADWu3.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, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- 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-CmMuQ6CL.js";
1
+ 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-BXNLfIJk.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
- import { n as loadScene, t as LoadSceneOptions } from "./loader-CTB441z4.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-Cga7FVP4.js";
4
4
  import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js";
5
- import { a as ParticleSimConfig, i as ParticleSim, n as AudioPlayer, o as ParticleView, t as AudioElementLike } from "./audio-player-BrEHwbK1.js";
5
+ import { a as ParticleSimConfig, i as ParticleSim, n as AudioPlayer, o as ParticleView, t as AudioElementLike } from "./audio-player-_UAcHxnC.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-CThvwkcQ.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-BHoB6fCU.js";
8
8
 
9
9
  //#region src/core/audio/crossfade.d.ts
10
10
  /**
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { A as describeRefProblem, B as registeredBehaviors, C as ORDER_GROUP_BASE, E as InputMap, F as behaviorSchema, I as behaviorSignals, L as clearBehaviors, M as resolveRefInJson, N as parseNodePath, O as Node, P as Behavior, R as getBehavior, S as createSaveStore, T as resolveOrderGroups, V as Signal, _ as SaveSlots, a as serializeNode, b as restoreBehaviors, d as computeViewport, f as resolveViewport, g as resolveConstants, h as isConstRef, i as Scene, j as nodeRefWarnings, m as CONST_REF_KEY, n as loadScene, o as SCENE_FORMAT, p as SceneTree, v as behaviorsWithoutSave, w as effectiveOrder, x as savesWithoutUid, y as captureBehaviors, z as registerBehavior } from "./loader-lQDCwNag.js";
2
- import { A as UiBanner, B as isAudioContextAvailable, D as EffectLog, E as LogManager, F as T_PREFIX, G as SFX_PRESET_NAMES, H as spatialGain, I as suggestLocale, J as MusicManager, K as sfxDuration, M as UiText, N as BASE_LOCALE, O as HudLayer, P as Localization, Q as AudioBuses, R as translationKey, S as qualityEnvironment, T as suggestQuality, U as spatialPan, V as ROLLOFF_MODELS, W as SFX_PRESETS, X as crossfadeGains, Y as WebAudioMusicBackend, Z as fadeGain, _ 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 UiBar, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as synthSfx, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SfxEngine } from "./register-CgeR4ckF.js";
2
+ import { A as UiBanner, B as isAudioContextAvailable, D as EffectLog, E as LogManager, F as T_PREFIX, G as SFX_PRESET_NAMES, H as spatialGain, I as suggestLocale, J as MusicManager, K as sfxDuration, M as UiText, N as BASE_LOCALE, O as HudLayer, P as Localization, Q as AudioBuses, R as translationKey, S as qualityEnvironment, T as suggestQuality, U as spatialPan, V as ROLLOFF_MODELS, W as SFX_PRESETS, X as crossfadeGains, Y as WebAudioMusicBackend, Z as fadeGain, _ 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 UiBar, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as synthSfx, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SfxEngine } from "./register-BmuqYTiY.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 { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-CwwhxQgb.js";
6
6
  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-CF70EArN.js";
7
- import { n as startRecording, r as auditScene, t as replay } from "./replay-CEw2xAhG.js";
7
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-IsZbNu6d.js";
8
8
  import { a as logReport, i as resolveRendering, n as attachTouchControls, o as logText, r as joystickVector, s as parseDrive, t as TouchControls } from "./touch-DESwnpOc.js";
9
9
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
10
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-CY-mrr1L.js";
11
- import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-DYffzHbd.js";
11
+ import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-BTLbXFPZ.js";
12
12
  import { t as duplicateNode } from "./duplicate-CqSAtdrh.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, 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, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine } from "./behavior-CmMuQ6CL.js";
1
+ import { P as Scene, b as Engine } from "./behavior-BXNLfIJk.js";
2
2
  import { i as SceneJson } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
package/dist/net.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as PropSchema, jt as Node } from "./behavior-CmMuQ6CL.js";
2
- 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-DxZzC1nm.js";
1
+ import { d as PropSchema, jt as Node } from "./behavior-BXNLfIJk.js";
2
+ 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-eJUFjhi1.js";
3
3
 
4
4
  //#region src/net/agent8.d.ts
5
5
  /**
package/dist/net.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as createAgent8Server } from "./agent8-CvsfVskX.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-gb0c3xdb.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-D_i7GRcY.js";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { k as diagnose } from "./loader-lQDCwNag.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-DO5_Qp0Q.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-D0CxCveZ.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
- import { t as withoutRapierInitNoise } from "./quiet-rapier-BAJ4K94N.js";
6
+ import { n as jointContacts, t as withoutRapierInitNoise } from "./quiet-rapier-C6fW4zcW.js";
7
7
  //#region src/2d/physics/physics-2d.ts
8
8
  var physics_2d_exports = /* @__PURE__ */ __exportAll({
9
9
  Physics2D: () => Physics2D,
@@ -119,6 +119,8 @@ var Physics2D = class {
119
119
  const b = this.byColliderHandle.get(h2);
120
120
  if (!a || !b) return;
121
121
  const sig = started ? "triggerEnter" : "triggerExit";
122
+ this.trackOverlap(a, b, started);
123
+ this.trackOverlap(b, a, started);
122
124
  try {
123
125
  a.emit(sig, b);
124
126
  b.emit(sig, a);
@@ -284,9 +286,33 @@ var Physics2D = class {
284
286
  else if (joint.type === "rope") data = R.JointData.rope(length, a1, a2);
285
287
  else if (joint.type === "spring") data = R.JointData.spring(length, joint.stiffness, joint.damping, a1, a2);
286
288
  else data = R.JointData.revolute(a1, a2);
287
- this.joints.set(joint, this.world.createImpulseJoint(data, ea.body, eb.body, true));
289
+ const made = this.world.createImpulseJoint(data, ea.body, eb.body, true);
290
+ made.setContactsEnabled(jointContacts(joint.type, joint.collide));
291
+ this.joints.set(joint, made);
288
292
  }
289
293
  }
294
+ /** Who is currently inside each sensor/body, maintained from the event drain. */
295
+ overlaps = /* @__PURE__ */ new Map();
296
+ trackOverlap(self, other, started) {
297
+ let set = this.overlaps.get(self);
298
+ if (started) {
299
+ if (!set) {
300
+ set = /* @__PURE__ */ new Set();
301
+ this.overlaps.set(self, set);
302
+ }
303
+ set.add(other);
304
+ } else if (set) {
305
+ set.delete(other);
306
+ if (set.size === 0) this.overlaps.delete(self);
307
+ }
308
+ }
309
+ /** The bodies currently overlapping `node`. Freed nodes are dropped on read. */
310
+ overlapping(node) {
311
+ const set = this.overlaps.get(node);
312
+ if (!set) return [];
313
+ for (const other of set) if (other.tree === null) set.delete(other);
314
+ return [...set];
315
+ }
290
316
  /**
291
317
  * World-space raycast in PIXELS (y-down). `exclude` skips that body — a
292
318
  * shooter probing from inside its own collider needs it. Sensors (Area2D)