incanto 0.64.1 → 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 (59) 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 +5 -5
  5. package/dist/{audio-player-D5GJgb_x.d.ts → audio-player-_UAcHxnC.d.ts} +1 -1
  6. package/dist/{behavior-DsgayMsH.d.ts → behavior-BXNLfIJk.d.ts} +128 -0
  7. package/dist/{create-game-B746rYbr.js → create-game-DYJCIzO0.js} +67 -6
  8. package/dist/{create-game-B1KM6dA6.js → create-game-Dd7H4bJV.js} +6 -6
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-BOOKmkQ7.js → duplicate-CqSAtdrh.js} +1 -1
  11. package/dist/{environment-presets-D2vzw583.js → environment-presets-BlPsEmq6.js} +70 -6
  12. package/dist/{gameplay-CLgFdkh5.js → gameplay-D1RADWu3.js} +374 -18
  13. package/dist/gameplay.d.ts +94 -3
  14. package/dist/gameplay.js +2 -2
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +5 -5
  17. package/dist/{loader-DolLJWJn.d.ts → loader-Cga7FVP4.d.ts} +1 -1
  18. package/dist/{loader-zDynoew_.js → loader-lQDCwNag.js} +59 -3
  19. package/dist/net.d.ts +2 -2
  20. package/dist/net.js +1 -1
  21. package/dist/{physics-2d-BPJcJRRP.js → physics-2d-Cji5A6sX.js} +32 -4
  22. package/dist/{physics-3d-BOO58xzw.js → physics-3d-BkHjwJgI.js} +49 -5
  23. package/dist/{quiet-rapier-BAJ4K94N.js → quiet-rapier-C6fW4zcW.js} +14 -1
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-Btm7_Emq.js → register-BmuqYTiY.js} +44 -1
  27. package/dist/{register-dGsbnJ87.js → register-D0CxCveZ.js} +38 -4
  28. package/dist/{replay-C5x2vPF5.d.ts → replay-BHoB6fCU.d.ts} +1 -1
  29. package/dist/{replay-DKRmiVjk.js → replay-IsZbNu6d.js} +2 -2
  30. package/dist/{split-screen-Dx0LvzqS.js → split-screen-D_i7GRcY.js} +2 -2
  31. package/dist/{split-screen-D7OopelJ.d.ts → split-screen-eJUFjhi1.d.ts} +2 -2
  32. package/dist/{src-CNS2xQB1.js → src-BTLbXFPZ.js} +1 -1
  33. package/dist/{teardown-BwhkcNt8.js → teardown-byR9USax.js} +1 -1
  34. package/dist/{test-Ccob3X5i.js → test-DgrD0jHD.js} +13 -13
  35. package/dist/test.d.ts +4 -4
  36. package/dist/test.js +2 -2
  37. package/dist/vite.js +2 -2
  38. package/editor/assets/{agent8-Di-UEn5O.js → agent8-9N-Pd_YS.js} +1 -1
  39. package/editor/assets/{debug-DWztJ5_y.js → debug-CkbJICYp.js} +1 -1
  40. package/editor/assets/{index-fct4H89G.js → index-CeDhIPTC.js} +53 -53
  41. package/editor/index.html +1 -1
  42. package/package.json +1 -1
  43. package/schemas/scene.schema.json +19 -0
  44. package/skills/incanto-3d-character.md +11 -1
  45. package/skills/incanto-behaviors-and-scripts.md +21 -0
  46. package/skills/incanto-gameplay-behaviors.md +177 -9
  47. package/skills/incanto-node-reference.md +59 -1
  48. package/skills/incanto-physics-and-input.md +84 -1
  49. package/skills/incanto-web-integration.md +24 -3
  50. package/templates-app/beacon-isle-3d/package.json +1 -1
  51. package/templates-app/platformer-2d/package.json +1 -1
  52. package/templates-app/star-survivor/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
  55. package/templates-app/beacon-isle-3d/coverage.json +0 -9
  56. package/templates-app/platformer-2d/coverage.json +0 -5
  57. package/templates-app/star-survivor/coverage.json +0 -5
  58. package/templates-app/tps-3d/coverage.json +0 -5
  59. package/templates-app/village-quest-3d/coverage.json +0 -9
@@ -1,4 +1,4 @@
1
- import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-DsgayMsH.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
@@ -11,6 +11,8 @@ import { c as JsonValue, s as JsonObject } from "./schema-CFeioQRE.js";
11
11
  * it leaves), so wire it to an attack.
12
12
  * - `loseRange` (>0) gives up the chase when the target gets that far away,
13
13
  * emitting `lostTarget` once.
14
+ * - `facePath`/`turnSpeed` turn a skin toward the direction of travel — the job
15
+ * `CharacterController3D` does for the player and nobody did for an NPC.
14
16
  * - `moveParent` (default false) moves the parent node instead of this one.
15
17
  * A node carries ONE behavior, so an enemy whose ROOT must hold `Health`
16
18
  * (e.g. for `Health.freeOnDeath` clone-safe cleanup) puts `Chase` on a CHILD
@@ -25,6 +27,8 @@ declare class Chase extends Behavior {
25
27
  stopRange: number;
26
28
  loseRange: number;
27
29
  moveParent: boolean;
30
+ facePath: string;
31
+ turnSpeed: number;
28
32
  private inRange;
29
33
  private lost;
30
34
  override onReady(): void;
@@ -56,6 +60,37 @@ declare class Collector extends Behavior {
56
60
  override deserialize(data: JsonValue): void;
57
61
  }
58
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
59
94
  //#region src/gameplay/health.d.ts
60
95
  /**
61
96
  * Hit points with regeneration and post-hit invulnerability (i-frames) —
@@ -158,7 +193,9 @@ declare class Health extends Behavior {
158
193
  * - `repeatEvery` (seconds, 0 = off) re-damages targets that STAY overlapped —
159
194
  * lava pools, poison clouds, an enemy standing on you. Contact events fire
160
195
  * only on entry/exit; this is the "and it keeps hurting" knob.
161
- * - `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.
162
199
  *
163
200
  * SCORING PATTERN (clone-safe): wire the KILLER's `dealtDamage` →
164
201
  * `ScoreKeeper.addScore`. The weapon is usually a non-cloned node (it lives on
@@ -173,6 +210,7 @@ declare class DamageOnContact extends Behavior {
173
210
  oncePerTarget: boolean;
174
211
  repeatEvery: number;
175
212
  destroySelf: boolean;
213
+ freeParent: boolean;
176
214
  private readonly hit;
177
215
  /** Targets currently overlapping → seconds until their next repeat tick. */
178
216
  private readonly inside;
@@ -181,6 +219,42 @@ declare class DamageOnContact extends Behavior {
181
219
  private tryDamage;
182
220
  }
183
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
184
258
  //#region src/gameplay/follow-camera.d.ts
185
259
  /**
186
260
  * Make the node it sits on chase a target's position — THE camera-follow
@@ -235,6 +309,10 @@ declare class Interactable extends Behavior {
235
309
  * pickups that vanish. Accumulates `dt`; on elapse emits `expired` then
236
310
  * `queueFree()`s its node.
237
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
+ *
238
316
  * With `startOnSignal: true` the countdown is armed manually via `startTimer()`
239
317
  * (wire a signal → `startTimer`), so the lifetime begins on an event rather
240
318
  * than at spawn.
@@ -244,6 +322,7 @@ declare class Lifetime extends Behavior {
244
322
  static readonly signals: readonly string[];
245
323
  seconds: number;
246
324
  startOnSignal: boolean;
325
+ freeParent: boolean;
247
326
  private elapsed;
248
327
  private running;
249
328
  private fired;
@@ -327,6 +406,13 @@ declare class Oscillate extends Behavior {
327
406
  *
328
407
  * - `loop` (default) — after the last point, head back to the first.
329
408
  * - `mode: 'pingpong'` — reverse direction at each end instead of wrapping.
409
+ * - `moveParent` (default false) moves the parent node instead of this one,
410
+ * exactly as `Chase` does. A node carries ONE behavior, so a guard that must
411
+ * patrol AND hold `Health` on its body puts `Patrol` on a CHILD. Without
412
+ * this the patrolling body had to carry `Patrol` itself and could hold
413
+ * nothing else — so a patrolling enemy could not be hurt.
414
+ * - `facePath`/`turnSpeed` turn a skin toward the direction of travel, the job
415
+ * `CharacterController3D` does for the player and nobody did for an NPC.
330
416
  */
331
417
  declare class Patrol extends Behavior {
332
418
  static readonly props: PropSchema;
@@ -336,11 +422,16 @@ declare class Patrol extends Behavior {
336
422
  loop: boolean;
337
423
  mode: string;
338
424
  pauseAt: number;
425
+ moveParent: boolean;
426
+ facePath: string;
427
+ turnSpeed: number;
339
428
  private index;
340
429
  private direction;
341
430
  private pauseTimer;
342
431
  override onReady(): void;
343
432
  override update(dt: number): void;
433
+ /** Turn the skin toward the step just taken. */
434
+ private face;
344
435
  /** Waypoints already reported dead, so a per-frame stall says it once. */
345
436
  private readonly reportedDead;
346
437
  /**
@@ -971,4 +1062,4 @@ declare function registerGameplayBehaviors(opts?: {
971
1062
  replace?: boolean;
972
1063
  }): void;
973
1064
  //#endregion
974
- 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-CLgFdkh5.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-DsgayMsH.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-DolLJWJn.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-D5GJgb_x.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-C5x2vPF5.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
- 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-zDynoew_.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-Btm7_Emq.js";
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-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-DKRmiVjk.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-CNS2xQB1.js";
12
- import { t as duplicateNode } from "./duplicate-BOOKmkQ7.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
+ 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-DsgayMsH.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
@@ -42,7 +42,47 @@ var Signal = class {
42
42
  * schema/defaults/delta model as node props.
43
43
  */
44
44
  var Behavior = class {
45
- static props;
45
+ /**
46
+ * Every behavior accepts `enabled`, inherited down the static chain — see
47
+ * `enabled` below for why it is a universal prop rather than one each AI
48
+ * behavior declares for itself.
49
+ */
50
+ static props = { enabled: { default: true } };
51
+ /**
52
+ * Whether this behavior's `update`/`fixedUpdate` run. `false` stops it
53
+ * without removing it — and this is what makes ENEMY AI expressible in JSON.
54
+ *
55
+ * A node holds ONE behavior, so `Patrol` and `Chase` cannot share one. Both
56
+ * can ride child nodes with `moveParent`, but then both run at once, and
57
+ * before this nothing could stop either — so "patrols until it spots you,
58
+ * then chases", the most ordinary AI there is, had to be hand-written in
59
+ * TypeScript, which is exactly what the gameplay library exists to prevent.
60
+ * With this the whole state machine is two wires:
61
+ *
62
+ * ```jsonc
63
+ * { "signal": "spotted", "from": "Eyes", "to": "Walk", "handler": "disable" },
64
+ * { "signal": "spotted", "from": "Eyes", "to": "Hunt", "handler": "enable" }
65
+ * ```
66
+ *
67
+ * `Clickable` had already hand-rolled this exact prop for itself. One
68
+ * behavior needed it and solved it locally; nothing generalized it.
69
+ *
70
+ * Asleep is NOT detached: the behavior keeps its props and its accumulated
71
+ * state, stays reachable as `node.behavior`, keeps its signal connections,
72
+ * and still saves through `serialize()`. Only the per-frame hooks pause. The
73
+ * lifecycle hooks (`onEnterTree`/`onReady`/`onExitTree`) are not gated — they
74
+ * are structural, they fire once, and a behavior that skipped its own
75
+ * `onReady` would wake up uninitialized.
76
+ */
77
+ enabled = true;
78
+ /** Start running again — a connection handler, so a wire can do it. */
79
+ enable() {
80
+ this.enabled = true;
81
+ }
82
+ /** Stop running without being removed — a connection handler. */
83
+ disable() {
84
+ this.enabled = false;
85
+ }
46
86
  /**
47
87
  * Custom signals this behavior emits via `this.emit(...)` — declared onto
48
88
  * its node at load. Undeclared emits are hard errors (typo safety).
@@ -68,6 +108,22 @@ var Behavior = class {
68
108
  get log() {
69
109
  return this.engine.log;
70
110
  }
111
+ /**
112
+ * The physics world, for the queries AI asks — `castRay` for line of sight,
113
+ * `castSphere` for a probe that must not skim past a wall.
114
+ *
115
+ * ```ts
116
+ * const hit = this.physics?.castRay(eye, dir, range, this.node);
117
+ * const blocked = hit !== null && hit.node !== player;
118
+ * ```
119
+ *
120
+ * `null` in a game with no physics, so a behavior that only sometimes needs
121
+ * a ray degrades instead of throwing. Pass the caster as `exclude` — a ray
122
+ * that starts inside its own collider hits itself at distance 0.
123
+ */
124
+ get physics() {
125
+ return this.node?.tree?.engine?.physics ?? null;
126
+ }
71
127
  getNode(path) {
72
128
  return this.node.getNode(path);
73
129
  }
@@ -650,7 +706,7 @@ var Node = class {
650
706
  } catch (error) {
651
707
  this._quarantine("update", error, "node");
652
708
  }
653
- if (this.behavior && !this.behaviorErrored) try {
709
+ if (this.behavior && this.behavior.enabled !== false && !this.behaviorErrored) try {
654
710
  this.behavior.update?.(dt);
655
711
  } catch (error) {
656
712
  this._quarantine("update", error, "behavior");
@@ -665,7 +721,7 @@ var Node = class {
665
721
  } catch (error) {
666
722
  this._quarantine("fixedUpdate", error, "node");
667
723
  }
668
- if (this.behavior && !this.behaviorErrored) try {
724
+ if (this.behavior && this.behavior.enabled !== false && !this.behaviorErrored) try {
669
725
  this.behavior.fixedUpdate?.(dt);
670
726
  } catch (error) {
671
727
  this._quarantine("fixedUpdate", error, "behavior");
package/dist/net.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as PropSchema, jt as Node } from "./behavior-DsgayMsH.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-D7OopelJ.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-Dx0LvzqS.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
- import { k as diagnose } from "./loader-zDynoew_.js";
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-dGsbnJ87.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,
@@ -68,6 +68,7 @@ var Physics2D = class {
68
68
  this.disconnectScene = engine.sceneChanged.connect(() => {
69
69
  if (engine.scene) this.syncBodies();
70
70
  });
71
+ engine.physics = this;
71
72
  this.syncBodies();
72
73
  }
73
74
  /** @internal Driven by engine.fixedUpdated. */
@@ -118,6 +119,8 @@ var Physics2D = class {
118
119
  const b = this.byColliderHandle.get(h2);
119
120
  if (!a || !b) return;
120
121
  const sig = started ? "triggerEnter" : "triggerExit";
122
+ this.trackOverlap(a, b, started);
123
+ this.trackOverlap(b, a, started);
121
124
  try {
122
125
  a.emit(sig, b);
123
126
  b.emit(sig, a);
@@ -283,9 +286,33 @@ var Physics2D = class {
283
286
  else if (joint.type === "rope") data = R.JointData.rope(length, a1, a2);
284
287
  else if (joint.type === "spring") data = R.JointData.spring(length, joint.stiffness, joint.damping, a1, a2);
285
288
  else data = R.JointData.revolute(a1, a2);
286
- 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);
287
292
  }
288
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
+ }
289
316
  /**
290
317
  * World-space raycast in PIXELS (y-down). `exclude` skips that body — a
291
318
  * shooter probing from inside its own collider needs it. Sensors (Area2D)
@@ -315,6 +342,7 @@ var Physics2D = class {
315
342
  this.disconnect();
316
343
  this.disconnectScene();
317
344
  this.unregisterDebug();
345
+ if (this.engine.physics === this) this.engine.physics = null;
318
346
  this.world.free();
319
347
  }
320
348
  lastStructure = -1;
@@ -1,10 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { k as diagnose } from "./loader-zDynoew_.js";
2
+ import { k as diagnose } from "./loader-lQDCwNag.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { B as CharacterBody3D, G as validateCollider3D, H as RigidBody3D, V as PhysicsBody3D, W as Node3D, z as Area3D } from "./gameplay-CLgFdkh5.js";
4
+ import { H as CharacterBody3D, K as Node3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, q as validateCollider3D } from "./gameplay-D1RADWu3.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
- import { t as withoutRapierInitNoise } from "./quiet-rapier-BAJ4K94N.js";
7
- import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-D2vzw583.js";
6
+ import { n as jointContacts, t as withoutRapierInitNoise } from "./quiet-rapier-C6fW4zcW.js";
7
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-BlPsEmq6.js";
8
8
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
9
9
  //#region src/3d/physics/collider-lines.ts
10
10
  /**
@@ -264,6 +264,7 @@ var Physics3D = class {
264
264
  this.disconnectScene = engine.sceneChanged.connect(() => {
265
265
  if (engine.scene) this.syncBodies();
266
266
  });
267
+ engine.physics = this;
267
268
  this.syncBodies();
268
269
  }
269
270
  /** Whether the solver runs at all. False = collider outlines only. */
@@ -327,6 +328,11 @@ var Physics3D = class {
327
328
  nv[0] = lv.x;
328
329
  nv[1] = lv.y;
329
330
  nv[2] = lv.z;
331
+ const av = e.body.angvel();
332
+ const na = node.angularVelocity;
333
+ na[0] = av.x;
334
+ na[1] = av.y;
335
+ na[2] = av.z;
330
336
  e.lastV[0] = lv.x;
331
337
  e.lastV[1] = lv.y;
332
338
  e.lastV[2] = lv.z;
@@ -341,6 +347,8 @@ var Physics3D = class {
341
347
  const b = this.byColliderHandle.get(h2);
342
348
  if (!a || !b) return;
343
349
  const sig = started ? "triggerEnter" : "triggerExit";
350
+ this.trackOverlap(a, b, started);
351
+ this.trackOverlap(b, a, started);
344
352
  try {
345
353
  a.emit(sig, b);
346
354
  b.emit(sig, a);
@@ -472,6 +480,7 @@ var Physics3D = class {
472
480
  this.disconnect();
473
481
  this.disconnectScene();
474
482
  this.unregisterDebug();
483
+ if (this.engine.physics === this) this.engine.physics = null;
475
484
  this.world.free();
476
485
  }
477
486
  lastStructure = -1;
@@ -633,6 +642,11 @@ var Physics3D = class {
633
642
  if (node instanceof RigidBody3D) {
634
643
  desc.setGravityScale(node.gravityScale);
635
644
  desc.setLinvel(node.linearVelocity[0] ?? 0, node.linearVelocity[1] ?? 0, node.linearVelocity[2] ?? 0);
645
+ desc.setAngvel({
646
+ x: node.angularVelocity[0] ?? 0,
647
+ y: node.angularVelocity[1] ?? 0,
648
+ z: node.angularVelocity[2] ?? 0
649
+ });
636
650
  if (node.fixedRotation) desc.lockRotations();
637
651
  }
638
652
  const body = this.world.createRigidBody(desc);
@@ -789,10 +803,40 @@ var Physics3D = class {
789
803
  else if (joint.type === "rope") data = R.JointData.rope(length, a1, a2);
790
804
  else if (joint.type === "spring") data = R.JointData.spring(length, joint.stiffness, joint.damping, a1, a2);
791
805
  else data = R.JointData.spherical(a1, a2);
792
- this.joints.set(joint, this.world.createImpulseJoint(data, ea.body, eb.body, true));
806
+ const made = this.world.createImpulseJoint(data, ea.body, eb.body, true);
807
+ made.setContactsEnabled(jointContacts(joint.type, joint.collide));
808
+ this.joints.set(joint, made);
809
+ }
810
+ }
811
+ /** Who is currently inside each sensor/body, maintained from the event drain. */
812
+ overlaps = /* @__PURE__ */ new Map();
813
+ trackOverlap(self, other, started) {
814
+ let set = this.overlaps.get(self);
815
+ if (started) {
816
+ if (!set) {
817
+ set = /* @__PURE__ */ new Set();
818
+ this.overlaps.set(self, set);
819
+ }
820
+ set.add(other);
821
+ } else if (set) {
822
+ set.delete(other);
823
+ if (set.size === 0) this.overlaps.delete(self);
793
824
  }
794
825
  }
795
826
  /**
827
+ * The bodies currently overlapping `node` — the standing answer behind
828
+ * `triggerEnter`/`triggerExit`.
829
+ *
830
+ * Freed nodes are dropped on read rather than tracked, so a hazard that
831
+ * despawns inside a plate cannot leave a ghost holding a door open.
832
+ */
833
+ overlapping(node) {
834
+ const set = this.overlaps.get(node);
835
+ if (!set) return [];
836
+ for (const other of set) if (other.tree === null) set.delete(other);
837
+ return [...set];
838
+ }
839
+ /**
796
840
  * World-space raycast (meters). `exclude` skips that body's collider —
797
841
  * ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
798
842
  * fixed/static world (excludes both dynamic AND kinematic bodies): the camera
@@ -1,3 +1,16 @@
1
+ //#region src/core/joint-contacts.ts
2
+ /**
3
+ * Should these two jointed bodies collide with each other?
4
+ *
5
+ * `null` means "decide from the type": a PIVOT wants its bodies overlapping and
6
+ * must not let the contact solver fight the joint; a TETHER anchors something to
7
+ * the world and that something still has to rest on it.
8
+ */
9
+ function jointContacts(type, collide) {
10
+ if (collide !== null) return collide;
11
+ return type === "rope" || type === "spring";
12
+ }
13
+ //#endregion
1
14
  //#region src/core/quiet-rapier.ts
2
15
  /**
3
16
  * Swallow one wrong warning that rapier prints about itself.
@@ -43,4 +56,4 @@ async function withoutRapierInitNoise(boot, out = console) {
43
56
  }
44
57
  }
45
58
  //#endregion
46
- export { withoutRapierInitNoise as t };
59
+ export { jointContacts as n, withoutRapierInitNoise as t };
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-DsgayMsH.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-BXNLfIJk.js";
2
2
  import { c as JsonValue } from "./schema-CFeioQRE.js";
3
3
  import { CSSProperties, ReactNode } from "react";
4
4
 
package/dist/react.js CHANGED
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
156
156
  pointer: latest.pointer,
157
157
  ...keyboard !== void 0 ? { keyboard } : {}
158
158
  };
159
- const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-B746rYbr.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-B1KM6dA6.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-DYJCIzO0.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-Dd7H4bJV.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-zDynoew_.js";
2
+ import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-lQDCwNag.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 { l as registerNode } from "./registry-CF70EArN.js";
@@ -2365,6 +2365,20 @@ var Engine = class {
2365
2365
  sceneChanged = new Signal();
2366
2366
  /** Declarative input — scene `input{}` declarations load on setScene. */
2367
2367
  input = new InputMap();
2368
+ /**
2369
+ * The physics world, once something enabled it — `null` in a game that has
2370
+ * none, which is most 2D puzzle games and every menu scene.
2371
+ *
2372
+ * `enablePhysics2D/3D` set this, and `dispose()` clears it. It is the handle
2373
+ * `incanto-physics-and-input.md` has always assumed you had: the skill teaches
2374
+ * `physics.castRay(...)` but the object lived only where the game BOOTED, so
2375
+ * a `Behavior` — where AI lives, and where line of sight is decided — had no
2376
+ * way to reach it short of casting the private `node._physics`.
2377
+ *
2378
+ * Typed as the query surface rather than the concrete class so core never
2379
+ * learns about Rapier or three. From a behavior, prefer `this.physics`.
2380
+ */
2381
+ physics = null;
2368
2382
  /** Seeded randomness for game logic (deterministic when `seed` is set). */
2369
2383
  rng;
2370
2384
  /** The engine log channel (debug overlay + headless harness tail this). */
@@ -2379,6 +2393,33 @@ var Engine = class {
2379
2393
  * and cleared on dispose; null when nothing renders (headless).
2380
2394
  */
2381
2395
  picker = null;
2396
+ /** Frames the engine has stepped — monotonic, and the pick cache's key. */
2397
+ frameNumber = 0;
2398
+ /** @internal frame number + answer, so N Clickables cost ONE raycast. */
2399
+ pickCacheFrame = -1;
2400
+ pickCacheKey = "";
2401
+ pickCacheHit = null;
2402
+ /**
2403
+ * The node under (x, y), answered at most ONCE per frame per cursor position.
2404
+ *
2405
+ * `Clickable` asks every frame, and a board game has one per cell: 42 pads
2406
+ * measured **420 picker calls over ten frames** — one GPU raycast per pad per
2407
+ * frame, all answering the same question about the same pixel. At 60 fps that
2408
+ * is ~2500 raycasts a second to find out what the cursor is on, and a 200-tile
2409
+ * board was unusable.
2410
+ *
2411
+ * Cached on (frame, x, y), so a game that picks at two different points in one
2412
+ * frame still gets two real answers.
2413
+ */
2414
+ pickAt(x, y) {
2415
+ if (!this.picker) return null;
2416
+ const key = `${x},${y}`;
2417
+ if (this.pickCacheFrame === this.frameNumber && this.pickCacheKey === key) return this.pickCacheHit;
2418
+ this.pickCacheFrame = this.frameNumber;
2419
+ this.pickCacheKey = key;
2420
+ this.pickCacheHit = this.picker(x, y);
2421
+ return this.pickCacheHit;
2422
+ }
2382
2423
  /** Global volume buses — `master` × `sfx`/`music` gain + `muted`. AudioPlayer
2383
2424
  * routes its volume through this; games set it for global volume/mute. */
2384
2425
  audio = new AudioBuses();
@@ -2714,6 +2755,7 @@ var Engine = class {
2714
2755
  scene.tree.fixedUpdate(dt);
2715
2756
  this.fixedUpdated.emit(dt);
2716
2757
  const startedUpdate = performance.now();
2758
+ this.frameNumber += 1;
2717
2759
  scene.tree.update(dt);
2718
2760
  this.music.tick(dt);
2719
2761
  this.updated.emit(dt);
@@ -2748,6 +2790,7 @@ var Engine = class {
2748
2790
  }
2749
2791
  if (this.accumulator >= this.fixedStep) this.accumulator %= this.fixedStep;
2750
2792
  const startedUpdate = performance.now();
2793
+ this.frameNumber += 1;
2751
2794
  scene.tree.update(dt);
2752
2795
  this.music.tick(dt);
2753
2796
  this.updated.emit(dt);