incanto 0.65.0 → 0.67.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 (88) hide show
  1. package/bin/_behaviors-loader.mjs +87 -0
  2. package/bin/incanto-editor.mjs +39 -2
  3. package/bin/incanto-feel.mjs +4 -1
  4. package/bin/incanto-frame.mjs +52 -3
  5. package/bin/incanto-logs.mjs +33 -3
  6. package/bin/incanto-multiplay.mjs +50 -1
  7. package/bin/incanto-play.mjs +3 -1
  8. package/bin/incanto-playtest.mjs +3 -25
  9. package/bin/incanto-verify.mjs +15 -2
  10. package/dist/2d.d.ts +76 -4
  11. package/dist/2d.js +3 -3
  12. package/dist/3d.d.ts +213 -14
  13. package/dist/3d.js +6 -6
  14. package/dist/{audio-player-BrEHwbK1.d.ts → audio-player-BNUHo0JF.d.ts} +1 -1
  15. package/dist/{behavior-CmMuQ6CL.d.ts → behavior-ZnfzKDo3.d.ts} +58 -1
  16. package/dist/{create-game-DIyh5Zy9.js → create-game-DMetd3vR.js} +14 -8
  17. package/dist/{create-game-W2xzTJhs.js → create-game-FAXdHta3.js} +138 -11
  18. package/dist/debug.d.ts +1 -1
  19. package/dist/debug.js +3 -2
  20. package/dist/{duplicate-CqSAtdrh.js → duplicate-B8QnX2-r.js} +1 -1
  21. package/dist/editor.js +7 -2
  22. package/dist/{environment-presets-BvHs6HEk.js → environment-presets-C-5g2pMh.js} +113 -9
  23. package/dist/{gameplay-D-H986A4.js → gameplay-CGSv-9XR.js} +295 -12
  24. package/dist/gameplay.d.ts +102 -6
  25. package/dist/gameplay.js +2 -2
  26. package/dist/index.d.ts +50 -6
  27. package/dist/index.js +7 -6
  28. package/dist/{loader-CTB441z4.d.ts → loader-DyT30ArB.d.ts} +1 -1
  29. package/dist/{loader-lQDCwNag.js → loader-KZESOZmE.js} +148 -1
  30. package/dist/net.d.ts +2 -2
  31. package/dist/net.js +1 -1
  32. package/dist/{physics-2d-CmnunaR2.js → physics-2d-_B6zj3zU.js} +57 -4
  33. package/dist/{physics-3d-DULB1lD1.js → physics-3d-i1IeAY0F.js} +74 -5
  34. package/dist/{quiet-rapier-BAJ4K94N.js → quiet-rapier-C6fW4zcW.js} +14 -1
  35. package/dist/react.d.ts +1 -1
  36. package/dist/react.js +1 -1
  37. package/dist/{register-CgeR4ckF.js → register-Cs53CY7s.js} +110 -16
  38. package/dist/{register-DO5_Qp0Q.js → register-vIxU3Xbv.js} +38 -4
  39. package/dist/rendering-options-ByAdi8PF.js +184 -0
  40. package/dist/{replay-CEw2xAhG.js → replay-BkpDV51O.js} +4 -3
  41. package/dist/{replay-CThvwkcQ.d.ts → replay-D0GcEHYU.d.ts} +1 -1
  42. package/dist/{split-screen-DxZzC1nm.d.ts → split-screen-DD5eYIgW.d.ts} +2 -2
  43. package/dist/{split-screen-gb0c3xdb.js → split-screen-DS8RaUV_.js} +2 -2
  44. package/dist/{src-DYffzHbd.js → src-BLlsGJW9.js} +1 -1
  45. package/dist/{teardown-BordHL9o.js → teardown-LB9cTnXJ.js} +2 -2
  46. package/dist/{test-CXZuXs8k.js → test-81uZ967a.js} +127 -24
  47. package/dist/test.d.ts +7 -5
  48. package/dist/test.js +2 -2
  49. package/dist/{touch-DESwnpOc.js → touch-DEAmqGdf.js} +30 -186
  50. package/dist/vite.d.ts +0 -7
  51. package/dist/vite.js +13 -3
  52. package/editor/assets/{agent8-DO5qzIOD.js → agent8-C2jtNKbx.js} +1 -1
  53. package/editor/assets/debug-DoU3KeqS.js +3 -0
  54. package/editor/assets/{index-CqEUVaPN.js → index-CNqHojcC.js} +92 -92
  55. package/editor/index.html +1 -1
  56. package/package.json +1 -1
  57. package/schemas/scene.schema.json +20 -1
  58. package/skills/incanto-building-3d-games.md +19 -0
  59. package/skills/incanto-editor.md +5 -1
  60. package/skills/incanto-environment.md +2 -2
  61. package/skills/incanto-gameplay-behaviors.md +86 -7
  62. package/skills/incanto-hud.md +14 -1
  63. package/skills/incanto-multiplayer.md +14 -0
  64. package/skills/incanto-node-reference.md +60 -2
  65. package/skills/incanto-performance.md +35 -0
  66. package/skills/incanto-physics-and-input.md +69 -0
  67. package/skills/incanto-playtesting.md +15 -1
  68. package/skills/incanto-save-slots.md +21 -1
  69. package/skills/incanto-verifying-your-game.md +19 -0
  70. package/skills/incanto-web-integration.md +24 -3
  71. package/skills/incanto-your-first-game.md +4 -4
  72. package/templates-app/beacon-isle-3d/package.json +1 -1
  73. package/templates-app/beacon-isle-3d/src/game.scene.json +38 -19
  74. package/templates-app/platformer-2d/package.json +1 -1
  75. package/templates-app/platformer-2d/src/game.scene.json +99 -53
  76. package/templates-app/star-survivor/package.json +1 -1
  77. package/templates-app/star-survivor/src/game.scene.json +31 -17
  78. package/templates-app/tps-3d/package.json +1 -1
  79. package/templates-app/tps-3d/src/game.scene.json +14 -8
  80. package/templates-app/village-quest-3d/package.json +1 -1
  81. package/templates-app/village-quest-3d/src/grove.scene.json +20 -11
  82. package/templates-app/village-quest-3d/src/village.scene.json +10 -7
  83. package/editor/assets/debug-DntG9CVD.js +0 -3
  84. package/templates-app/beacon-isle-3d/coverage.json +0 -9
  85. package/templates-app/platformer-2d/coverage.json +0 -5
  86. package/templates-app/star-survivor/coverage.json +0 -5
  87. package/templates-app/tps-3d/coverage.json +0 -5
  88. package/templates-app/village-quest-3d/coverage.json +0 -9
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-ZnfzKDo3.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-DyT30ArB.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-BNUHo0JF.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-D0GcEHYU.js";
8
8
 
9
9
  //#region src/core/audio/crossfade.d.ts
10
10
  /**
@@ -313,8 +313,24 @@ declare class HudLayer extends Node {
313
313
  * play resumes.
314
314
  */
315
315
  private stepFocus;
316
- /** Focusable widgets under this layer, in tree order. */
317
- private focusables;
316
+ /**
317
+ * Focusable widgets under this layer, in tree order — **on screen only**.
318
+ *
319
+ * This filtered on each widget's OWN `visible` and recursed into hidden
320
+ * containers anyway, so arrow-keys walked into closed panels and Enter really
321
+ * pressed what it found. Measured on a title screen: 23 focus stops, 21 of
322
+ * them under an ancestor with `visible = false`, and
323
+ *
324
+ * gold before = 150 -> Enter on the hidden Shop/Row0/Buy -> gold 120,
325
+ * screen still 'title'
326
+ *
327
+ * The layer's own visibility was not a gate either. It is now: a HUD nobody
328
+ * can see has no ring, and a subtree nobody can see is not walked.
329
+ *
330
+ * PUBLIC, because a game that wants to know what its ring contains could not
331
+ * ask — and "why is focus on that" is exactly the question this answers.
332
+ */
333
+ focusables(): HudWidgetBase[];
318
334
  /** Move focus to a widget by hand — opening a menu should start somewhere. */
319
335
  focus(widget: HudWidgetBase | null): void;
320
336
  /** @internal Widgets mount into per-anchor flex columns. */
@@ -377,6 +393,11 @@ declare abstract class HudWidgetBase extends Node {
377
393
  private host;
378
394
  override onExitTree(): void;
379
395
  override update(_dt: number): void;
396
+ /**
397
+ * The display this widget's own css asks for while shown — `grid` for a grid
398
+ * panel, `flex` for a row, `''` for a widget that never says.
399
+ */
400
+ private _shownDisplay;
380
401
  /** Which anchor slot this widget was mounted into. */
381
402
  private mountedAnchor;
382
403
  /**
@@ -607,6 +628,29 @@ declare class UiDialogue extends HudWidgetBase {
607
628
  /** Queue a line; optional `choices` renders buttons after the line types out. */
608
629
  say(speaker: string, text: string, choices?: string[]): void;
609
630
  /**
631
+ * A dialogue takes focus while it is up.
632
+ *
633
+ * It paints a focus ring and the generated reference advertises the prop, and
634
+ * it was inert: choices are raw DOM buttons inside the widget, so
635
+ * `HudLayer.focusables()` can never see them, and this class — unlike
636
+ * `UiButton` a hundred lines above it — never overrode `_activate`. Eleven
637
+ * keys and eight pad codes did nothing to a waiting choice while the same
638
+ * Enter pressed a sibling button. Native Tab worked, and is closed by any
639
+ * game that binds Tab; a pad has no Tab at all.
640
+ */
641
+ override focusable: boolean;
642
+ /** Which choice the keyboard/pad is on. Reset by every new line. */
643
+ private cursor;
644
+ /**
645
+ * Enter / A, and left / right between the options.
646
+ *
647
+ * `dir` is 0 for "activate" and ±1 for a nudge, the same shape `UiSelect`
648
+ * uses — so one key layout drives a menu, a slider and a conversation.
649
+ */
650
+ override _activate(dir: number): void;
651
+ /** Show which option the keyboard is on — a ring nobody can see is not one. */
652
+ private paintCursor;
653
+ /**
610
654
  * Player pressed "next": mid-typing reveals the whole line; a fully-typed
611
655
  * line without choices advances (choice lines wait for `choose`).
612
656
  */
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
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";
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-KZESOZmE.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-Cs53CY7s.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";
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";
7
+ import { i as joystickVector, n as attachTouchControls, t as TouchControls } from "./touch-DEAmqGdf.js";
8
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-BkpDV51O.js";
9
+ import { i as parseDrive, n as logReport, r as logText, t as resolveRendering } from "./rendering-options-ByAdi8PF.js";
9
10
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
10
11
  import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-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";
12
- import { t as duplicateNode } from "./duplicate-CqSAtdrh.js";
12
+ import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-BLlsGJW9.js";
13
+ import { t as duplicateNode } from "./duplicate-B8QnX2-r.js";
13
14
  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-ZnfzKDo3.js";
2
2
  import { i as SceneJson } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
@@ -20,6 +20,17 @@ var Signal = class {
20
20
  disconnectAll() {
21
21
  this.connections.length = 0;
22
22
  }
23
+ /**
24
+ * How many listeners are attached.
25
+ *
26
+ * A leak is a SLOPE, and a game had no way to see one: every check in this
27
+ * engine is a snapshot. A handler connected per frame and never disconnected,
28
+ * or a `once` that armed and never fired, accumulates invisibly and is
29
+ * perfectly healthy for the first thirty seconds of every playthrough.
30
+ */
31
+ get count() {
32
+ return this.connections.length;
33
+ }
23
34
  emit(...args) {
24
35
  const snapshot = [...this.connections];
25
36
  for (const conn of snapshot) {
@@ -652,6 +663,16 @@ var Node = class {
652
663
  off(signal, fn) {
653
664
  this._signals.get(signal)?.disconnect(fn);
654
665
  }
666
+ /**
667
+ * How many listeners this node has on `signal` — 0 for one nobody watches.
668
+ *
669
+ * The engine's own leak test reads it, and so can a game: a handler connected
670
+ * every frame and never disconnected is invisible to every other check here,
671
+ * because every other check is a snapshot and a leak is a slope.
672
+ */
673
+ listenerCount(signal) {
674
+ return this._signals.get(signal)?.count ?? 0;
675
+ }
655
676
  emit(signal, ...args) {
656
677
  this.assertDeclared(signal);
657
678
  this._signals.get(signal)?.emit(...args);
@@ -1674,6 +1695,7 @@ const FREED_KEY = "#freed";
1674
1695
  function captureBehaviors(root, source) {
1675
1696
  const state = {};
1676
1697
  const unaddressable = [];
1698
+ const unrecordable = [];
1677
1699
  let saveable = 0;
1678
1700
  const walk = (node) => {
1679
1701
  const behavior = node.behavior;
@@ -1698,12 +1720,45 @@ function captureBehaviors(root, source) {
1698
1720
  mark(root);
1699
1721
  const freed = authoredUids(source).filter((uid) => !alive.has(uid));
1700
1722
  if (freed.length > 0) state[FREED_KEY] = freed;
1723
+ unrecordable.push(...goneWithoutUid(source, root));
1701
1724
  }
1702
1725
  return {
1703
1726
  state,
1704
1727
  unaddressable,
1705
- saveable
1728
+ saveable,
1729
+ unrecordable
1730
+ };
1731
+ }
1732
+ /**
1733
+ * Authored nodes this run CONSUMED that the ledger cannot record.
1734
+ *
1735
+ * `#freed` is authored uids minus alive uids, so a node with no uid can never
1736
+ * enter it — it is silently omitted, and on load it comes back. Every collected
1737
+ * coin in a scene without uids returns, while the score that counted it does
1738
+ * not, and nothing says a word.
1739
+ *
1740
+ * A uid is the only stable handle (it survives a rename and a reparent), so the
1741
+ * engine must not guess a replacement — a saved PATH would point at whatever
1742
+ * node moved into that slot after the next edit. What it can do is notice, by
1743
+ * walking the authored tree and asking the live one whether each node is still
1744
+ * there. Reported, not repaired.
1745
+ */
1746
+ function goneWithoutUid(source, root) {
1747
+ const out = [];
1748
+ const walk = (node, path) => {
1749
+ if (typeof node !== "object" || node === null) return;
1750
+ const n = node;
1751
+ const name = typeof n.name === "string" ? n.name : "";
1752
+ const here = path === "" ? name : `${path}/${name}`;
1753
+ if (!(typeof n.uid === "string" && n.uid !== "") && here !== "" && path !== "") {
1754
+ const live = root.getNodeOrNull(here.slice(here.indexOf("/") + 1));
1755
+ const leaving = live ? live.tree?._isQueuedFree(live) ?? false : false;
1756
+ if (!live || leaving) out.push(here);
1757
+ }
1758
+ if (Array.isArray(n.children)) for (const child of n.children) walk(child, here);
1706
1759
  };
1760
+ walk(source.root, "");
1761
+ return out;
1707
1762
  }
1708
1763
  /** Every uid the scene FILE declares, in document order. */
1709
1764
  function authoredUids(source) {
@@ -3004,6 +3059,7 @@ function buildNodeJson(json, opts) {
3004
3059
  return buildNode(json, opts, [], "", void 0);
3005
3060
  }
3006
3061
  function validateHeader(json) {
3062
+ rejectUnknownKeys(json, SCENE_KEYS, "the scene", "");
3007
3063
  if (json.format !== 1) throw new IncantoError("BAD_FORMAT", `Unsupported scene format ${JSON.stringify(json.format)}; this engine reads format 1.`);
3008
3064
  if (json.type !== "scene") throw new IncantoError("BAD_FORMAT", `Expected "type": "scene", got ${JSON.stringify(json.type)}.`);
3009
3065
  if (typeof json.name !== "string" || json.name === "") throw new IncantoError("BAD_FORMAT", "Scene \"name\" must be a non-empty string.");
@@ -3042,9 +3098,100 @@ function withNodeContext(e, path, nj) {
3042
3098
  });
3043
3099
  return e;
3044
3100
  }
3101
+ /**
3102
+ * Every key a node object may carry.
3103
+ *
3104
+ * The node object was an OPEN dictionary sitting between a dozen closed ones —
3105
+ * `props`, `environment`, `material`, `network` and the environment's own
3106
+ * sub-blocks all reject an unknown key with a did-you-mean. A node accepted
3107
+ * anything, and the one that costs an author a day is `connections`:
3108
+ *
3109
+ * ```jsonc
3110
+ * { "name": "Room", "type": "Node3D",
3111
+ * "connections": [ … ], // <- silently nothing
3112
+ * "children": [ … ] }
3113
+ * ```
3114
+ *
3115
+ * `wireConnections` is reached from the scene header and from a sub-scene's own
3116
+ * header, and NOTHING reads `nodeJson.connections`. Measured on two scenes
3117
+ * differing only in which brace the same wire sits inside:
3118
+ *
3119
+ * ```
3120
+ * inside Dst.n=0 validateScene.ok=true auditWarnings=0
3121
+ * beside Dst.n=1 validateScene.ok=true auditWarnings=0
3122
+ * none Dst.n=0 validateScene.ok=true auditWarnings=0
3123
+ * ```
3124
+ *
3125
+ * The misplaced wire is bit-for-bit indistinguishable from deleting it. A
3126
+ * builder moved 28 of them into `root`, got `1/1 scene(s) valid` with zero
3127
+ * warnings, and found out sixty seconds later from `incanto-playtest`.
3128
+ */
3129
+ const NODE_KEYS = [
3130
+ "name",
3131
+ "uid",
3132
+ "type",
3133
+ "instance",
3134
+ "overrides",
3135
+ "groups",
3136
+ "tags",
3137
+ "props",
3138
+ "script",
3139
+ "network",
3140
+ "children"
3141
+ ];
3142
+ /** Every key the scene HEADER may carry — closed for the same reason. */
3143
+ const SCENE_KEYS = [
3144
+ "format",
3145
+ "type",
3146
+ "name",
3147
+ "dimension",
3148
+ "viewport",
3149
+ "assets",
3150
+ "constants",
3151
+ "orderGroups",
3152
+ "input",
3153
+ "multiplayer",
3154
+ "environment",
3155
+ "strings",
3156
+ "physics",
3157
+ "root",
3158
+ "connections"
3159
+ ];
3160
+ /** Closest known key by edit distance — only a genuinely близкая one. */
3161
+ function nearestKey(key, known) {
3162
+ const lower = key.toLowerCase();
3163
+ let best = null;
3164
+ let bestScore = 3;
3165
+ for (const candidate of known) {
3166
+ let d = 0;
3167
+ const a = lower;
3168
+ const b = candidate.toLowerCase();
3169
+ const rows = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
3170
+ for (let j = 0; j <= b.length; j++) rows[0][j] = j;
3171
+ for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) {
3172
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
3173
+ rows[i][j] = Math.min(rows[i - 1][j] + 1, rows[i][j - 1] + 1, rows[i - 1][j - 1] + cost);
3174
+ }
3175
+ d = rows[a.length][b.length];
3176
+ if (d < bestScore) {
3177
+ bestScore = d;
3178
+ best = candidate;
3179
+ }
3180
+ }
3181
+ return best;
3182
+ }
3183
+ /** Reject a key the shape does not have, the way `props` already does. */
3184
+ function rejectUnknownKeys(obj, known, what, where) {
3185
+ for (const key of Object.keys(obj)) {
3186
+ if (known.includes(key)) continue;
3187
+ const guess = nearestKey(key, known);
3188
+ throw new IncantoError("BAD_FORMAT", `Unknown key "${key}" on ${what}${where ? ` '${where}'` : ""}.` + (guess ? ` Did you mean "${guess}"?` : "") + ` Valid keys: [${known.join(", ")}].` + (key === "connections" ? " Connections live on the SCENE, beside \"root\" — a node cannot carry them, and one placed here does exactly nothing." : ""), { validOptions: [...known] });
3189
+ }
3190
+ }
3045
3191
  function buildNode(nj, opts, stack, parentPath, constants, bands = []) {
3046
3192
  const path = `${parentPath}/${nj.name}`;
3047
3193
  try {
3194
+ rejectUnknownKeys(nj, NODE_KEYS, "node", nj.name ?? "");
3048
3195
  if (nj.instance !== void 0 && nj.type !== void 0) throw new IncantoError("BAD_FORMAT", `Node '${nj.name}' declares both "type" and "instance" — they are mutually exclusive.`);
3049
3196
  if (nj.instance !== void 0) return buildInstance(nj, opts, stack, path, constants, bands);
3050
3197
  if (nj.type === void 0) throw new IncantoError("BAD_FORMAT", `Node '${nj.name}' must declare either "type" or "instance".`);
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-ZnfzKDo3.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-DD5eYIgW.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-DS8RaUV_.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-lQDCwNag.js";
2
+ import { k as diagnose } from "./loader-KZESOZmE.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-vIxU3Xbv.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,
@@ -66,6 +66,7 @@ var Physics2D = class {
66
66
  this.unregisterDebug = registerDebugSource(this);
67
67
  this.disconnect = engine.fixedUpdated.connect((dt) => this.step(dt));
68
68
  this.disconnectScene = engine.sceneChanged.connect(() => {
69
+ this.pruneOverlaps();
69
70
  if (engine.scene) this.syncBodies();
70
71
  });
71
72
  engine.physics = this;
@@ -119,6 +120,8 @@ var Physics2D = class {
119
120
  const b = this.byColliderHandle.get(h2);
120
121
  if (!a || !b) return;
121
122
  const sig = started ? "triggerEnter" : "triggerExit";
123
+ this.trackOverlap(a, b, started);
124
+ this.trackOverlap(b, a, started);
122
125
  try {
123
126
  a.emit(sig, b);
124
127
  b.emit(sig, a);
@@ -284,9 +287,58 @@ var Physics2D = class {
284
287
  else if (joint.type === "rope") data = R.JointData.rope(length, a1, a2);
285
288
  else if (joint.type === "spring") data = R.JointData.spring(length, joint.stiffness, joint.damping, a1, a2);
286
289
  else data = R.JointData.revolute(a1, a2);
287
- this.joints.set(joint, this.world.createImpulseJoint(data, ea.body, eb.body, true));
290
+ const made = this.world.createImpulseJoint(data, ea.body, eb.body, true);
291
+ made.setContactsEnabled(jointContacts(joint.type, joint.collide));
292
+ this.joints.set(joint, made);
288
293
  }
289
294
  }
295
+ /** Who is currently inside each sensor/body, maintained from the event drain. */
296
+ overlaps = /* @__PURE__ */ new Map();
297
+ trackOverlap(self, other, started) {
298
+ let set = this.overlaps.get(self);
299
+ if (started) {
300
+ if (!set) {
301
+ set = /* @__PURE__ */ new Set();
302
+ this.overlaps.set(self, set);
303
+ }
304
+ set.add(other);
305
+ } else if (set) {
306
+ set.delete(other);
307
+ if (set.size === 0) this.overlaps.delete(self);
308
+ }
309
+ }
310
+ /**
311
+ * Forget every overlap whose node has left the tree.
312
+ *
313
+ * The lazy drop inside `overlapping()` only cleans the SETS of a key someone
314
+ * asks about, and only when they ask. This clears the KEYS, which is where the
315
+ * dead nodes were pinned.
316
+ */
317
+ /** Drop one node from the overlap map, both as a key and as a member. */
318
+ forgetOverlaps(node) {
319
+ this.overlaps.delete(node);
320
+ for (const [self, others] of this.overlaps) {
321
+ others.delete(node);
322
+ if (others.size === 0) this.overlaps.delete(self);
323
+ }
324
+ }
325
+ pruneOverlaps() {
326
+ for (const [self, others] of this.overlaps) {
327
+ if (self.tree === null) {
328
+ this.overlaps.delete(self);
329
+ continue;
330
+ }
331
+ for (const other of others) if (other.tree === null) others.delete(other);
332
+ if (others.size === 0) this.overlaps.delete(self);
333
+ }
334
+ }
335
+ /** The bodies currently overlapping `node`. Freed nodes are dropped on read. */
336
+ overlapping(node) {
337
+ const set = this.overlaps.get(node);
338
+ if (!set) return [];
339
+ for (const other of set) if (other.tree === null) set.delete(other);
340
+ return [...set];
341
+ }
290
342
  /**
291
343
  * World-space raycast in PIXELS (y-down). `exclude` skips that body — a
292
344
  * shooter probing from inside its own collider needs it. Sensors (Area2D)
@@ -400,6 +452,7 @@ var Physics2D = class {
400
452
  this.byColliderHandle.delete(e.collider.handle);
401
453
  this.world.removeRigidBody(e.body);
402
454
  this.entries.delete(node);
455
+ this.forgetOverlaps(node);
403
456
  node._physics = null;
404
457
  }
405
458
  }
@@ -1,10 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { k as diagnose } from "./loader-lQDCwNag.js";
2
+ import { k as diagnose } from "./loader-KZESOZmE.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-D-H986A4.js";
4
+ import { H as CharacterBody3D, K as Node3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, q as validateCollider3D } from "./gameplay-CGSv-9XR.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-BvHs6HEk.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-C-5g2pMh.js";
8
8
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
9
9
  //#region src/3d/physics/collider-lines.ts
10
10
  /**
@@ -262,6 +262,7 @@ var Physics3D = class {
262
262
  this.unregisterDebug = registerDebugSource(this);
263
263
  this.disconnect = engine.fixedUpdated.connect((dt) => this.step(dt));
264
264
  this.disconnectScene = engine.sceneChanged.connect(() => {
265
+ this.pruneOverlaps();
265
266
  if (engine.scene) this.syncBodies();
266
267
  });
267
268
  engine.physics = this;
@@ -328,6 +329,11 @@ var Physics3D = class {
328
329
  nv[0] = lv.x;
329
330
  nv[1] = lv.y;
330
331
  nv[2] = lv.z;
332
+ const av = e.body.angvel();
333
+ const na = node.angularVelocity;
334
+ na[0] = av.x;
335
+ na[1] = av.y;
336
+ na[2] = av.z;
331
337
  e.lastV[0] = lv.x;
332
338
  e.lastV[1] = lv.y;
333
339
  e.lastV[2] = lv.z;
@@ -342,6 +348,8 @@ var Physics3D = class {
342
348
  const b = this.byColliderHandle.get(h2);
343
349
  if (!a || !b) return;
344
350
  const sig = started ? "triggerEnter" : "triggerExit";
351
+ this.trackOverlap(a, b, started);
352
+ this.trackOverlap(b, a, started);
345
353
  try {
346
354
  a.emit(sig, b);
347
355
  b.emit(sig, a);
@@ -602,6 +610,7 @@ var Physics3D = class {
602
610
  this.byColliderHandle.delete(e.collider.handle);
603
611
  this.world.removeRigidBody(e.body);
604
612
  this.entries.delete(node);
613
+ this.forgetOverlaps(node);
605
614
  node._physics = null;
606
615
  node._interpPrev = node._interpCurr = null;
607
616
  }
@@ -635,6 +644,11 @@ var Physics3D = class {
635
644
  if (node instanceof RigidBody3D) {
636
645
  desc.setGravityScale(node.gravityScale);
637
646
  desc.setLinvel(node.linearVelocity[0] ?? 0, node.linearVelocity[1] ?? 0, node.linearVelocity[2] ?? 0);
647
+ desc.setAngvel({
648
+ x: node.angularVelocity[0] ?? 0,
649
+ y: node.angularVelocity[1] ?? 0,
650
+ z: node.angularVelocity[2] ?? 0
651
+ });
638
652
  if (node.fixedRotation) desc.lockRotations();
639
653
  }
640
654
  const body = this.world.createRigidBody(desc);
@@ -791,9 +805,64 @@ var Physics3D = class {
791
805
  else if (joint.type === "rope") data = R.JointData.rope(length, a1, a2);
792
806
  else if (joint.type === "spring") data = R.JointData.spring(length, joint.stiffness, joint.damping, a1, a2);
793
807
  else data = R.JointData.spherical(a1, a2);
794
- this.joints.set(joint, this.world.createImpulseJoint(data, ea.body, eb.body, true));
808
+ const made = this.world.createImpulseJoint(data, ea.body, eb.body, true);
809
+ made.setContactsEnabled(jointContacts(joint.type, joint.collide));
810
+ this.joints.set(joint, made);
811
+ }
812
+ }
813
+ /** Who is currently inside each sensor/body, maintained from the event drain. */
814
+ overlaps = /* @__PURE__ */ new Map();
815
+ trackOverlap(self, other, started) {
816
+ let set = this.overlaps.get(self);
817
+ if (started) {
818
+ if (!set) {
819
+ set = /* @__PURE__ */ new Set();
820
+ this.overlaps.set(self, set);
821
+ }
822
+ set.add(other);
823
+ } else if (set) {
824
+ set.delete(other);
825
+ if (set.size === 0) this.overlaps.delete(self);
826
+ }
827
+ }
828
+ /**
829
+ * Forget every overlap whose node has left the tree.
830
+ *
831
+ * The lazy drop inside `overlapping()` only cleans the SETS of a key someone
832
+ * asks about, and only when they ask. This clears the KEYS, which is where the
833
+ * dead nodes were pinned.
834
+ */
835
+ /** Drop one node from the overlap map, both as a key and as a member. */
836
+ forgetOverlaps(node) {
837
+ this.overlaps.delete(node);
838
+ for (const [self, others] of this.overlaps) {
839
+ others.delete(node);
840
+ if (others.size === 0) this.overlaps.delete(self);
795
841
  }
796
842
  }
843
+ pruneOverlaps() {
844
+ for (const [self, others] of this.overlaps) {
845
+ if (self.tree === null) {
846
+ this.overlaps.delete(self);
847
+ continue;
848
+ }
849
+ for (const other of others) if (other.tree === null) others.delete(other);
850
+ if (others.size === 0) this.overlaps.delete(self);
851
+ }
852
+ }
853
+ /**
854
+ * The bodies currently overlapping `node` — the standing answer behind
855
+ * `triggerEnter`/`triggerExit`.
856
+ *
857
+ * Freed nodes are dropped on read rather than tracked, so a hazard that
858
+ * despawns inside a plate cannot leave a ghost holding a door open.
859
+ */
860
+ overlapping(node) {
861
+ const set = this.overlaps.get(node);
862
+ if (!set) return [];
863
+ for (const other of set) if (other.tree === null) set.delete(other);
864
+ return [...set];
865
+ }
797
866
  /**
798
867
  * World-space raycast (meters). `exclude` skips that body's collider —
799
868
  * ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
@@ -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-CmMuQ6CL.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-ZnfzKDo3.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-W2xzTJhs.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-DIyh5Zy9.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-FAXdHta3.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-DMetd3vR.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;