incanto 0.33.0 → 0.35.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 (58) hide show
  1. package/dist/2d.d.ts +5 -5
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +122 -14
  4. package/dist/3d.js +5 -5
  5. package/dist/{audit-C4kmDK0o.js → audit-C5oZGAru.js} +37 -0
  6. package/dist/{behavior-CKwTCjfR.d.ts → behavior-CGSCWOHB.d.ts} +319 -22
  7. package/dist/{create-game-D2QU5x7S.js → create-game-B0Wfhi2-.js} +6 -5
  8. package/dist/{create-game-sFuTLqjD.js → create-game-BUD89Kqh.js} +20 -6
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-BgnG1Lqz.js → duplicate-C716f-97.js} +1 -1
  11. package/dist/{editor-switch-BJb-CWfA.d.ts → editor-switch-DyXEtH36.d.ts} +1 -1
  12. package/dist/editor.js +1739 -1312
  13. package/dist/env.d.ts +1 -1
  14. package/dist/{environment-presets-CQtEGogB.js → environment-presets-TAGvmM3z.js} +240 -74
  15. package/dist/{errors-1dXlIwoR.d.ts → errors-BY2kL0hv.d.ts} +1 -1
  16. package/dist/{gameplay-BpQCbABv.js → gameplay-BvhcQbfJ.js} +75 -11
  17. package/dist/gameplay.d.ts +12 -5
  18. package/dist/gameplay.js +1 -1
  19. package/dist/index.d.ts +72 -7
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-COn5fS0o.d.ts → loader-CUcj00M8.d.ts} +19 -2
  22. package/dist/{loader-Buk8Bu1h.js → loader-Mig5fY4n.js} +110 -18
  23. package/dist/net.d.ts +2 -2
  24. package/dist/net.js +3 -3
  25. package/dist/{particle-sim-CwJ5rI_P.d.ts → particle-sim-B-vZBF5R.d.ts} +1 -1
  26. package/dist/{pathfinding-DUw9mir9.d.ts → pathfinding-DgOo2KNF.d.ts} +1 -1
  27. package/dist/{physics-2d-DjXR5DMu.js → physics-2d-DqAclql-.js} +22 -9
  28. package/dist/{physics-3d-DF8npb1O.js → physics-3d-DxBH4sIF.js} +27 -11
  29. package/dist/react.d.ts +2 -2
  30. package/dist/react.js +1 -1
  31. package/dist/{register-CscIzJEO.js → register-BFLg0-_i.js} +697 -21
  32. package/dist/{register-CtI-itec.js → register-BjbPMA5B.js} +2 -2
  33. package/dist/{register-FIJtNbub.js → register-DSmIRAf7.js} +11 -5
  34. package/dist/{schema-CcoWb32N.d.ts → schema-3ywbdlrv.d.ts} +10 -0
  35. package/dist/{test-BRxLd2jH.js → test-9EokzbRd.js} +10 -10
  36. package/dist/test.d.ts +4 -4
  37. package/dist/test.js +2 -2
  38. package/dist/vite.js +1 -1
  39. package/editor/assets/{agent8-BdDP3xKW.js → agent8-D3_GWeuh.js} +1 -1
  40. package/editor/assets/{debug-DbjTyTlC.js → debug-CX0LDd1r.js} +1 -1
  41. package/editor/assets/index-CMlKFT0C.js +10773 -0
  42. package/editor/index.html +1 -1
  43. package/package.json +1 -1
  44. package/schemas/scene.schema.json +251 -6
  45. package/skills/incanto-assets.md +21 -0
  46. package/skills/incanto-behaviors-and-scripts.md +16 -0
  47. package/skills/incanto-editor.md +17 -1
  48. package/skills/incanto-gameplay-behaviors.md +6 -0
  49. package/skills/incanto-hud.md +73 -1
  50. package/skills/incanto-localization.md +132 -0
  51. package/skills/incanto-node-reference.md +78 -24
  52. package/skills/incanto-save-slots.md +152 -0
  53. package/templates-app/beacon-isle-3d/package.json +1 -1
  54. package/templates-app/tps-3d/package.json +1 -1
  55. package/templates-app/village-quest-3d/PROJECT/Context.md +16 -0
  56. package/templates-app/village-quest-3d/package.json +1 -1
  57. package/templates-app/village-quest-3d/src/village.scene.json +25 -3
  58. package/editor/assets/index-BWCudoz1.js +0 -10696
@@ -1,5 +1,5 @@
1
- import { at as Node, l as PropSchema, n as BehaviorCtor, t as Behavior, v as Engine } from "./behavior-CKwTCjfR.js";
2
- import { c as JsonValue, s as JsonObject } from "./schema-CcoWb32N.js";
1
+ import { b as Engine, bt as Node, d as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-CGSCWOHB.js";
2
+ import { c as JsonValue, s as JsonObject } from "./schema-3ywbdlrv.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
5
5
  /**
@@ -51,6 +51,9 @@ declare class Collector extends Behavior {
51
51
  override onReady(): void;
52
52
  /** Add `value` to the tally and emit `totalChanged`. */
53
53
  collect(value: number): void;
54
+ /** How many have been picked up so far. */
55
+ override serialize(): JsonValue;
56
+ override deserialize(data: JsonValue): void;
54
57
  }
55
58
  //#endregion
56
59
  //#region src/gameplay/health.d.ts
@@ -96,6 +99,9 @@ declare class Health extends Behavior {
96
99
  kill(): void;
97
100
  private setCurrent;
98
101
  private die;
102
+ /** Current HP and whether death already fired — `max` comes back from JSON. */
103
+ override serialize(): JsonValue;
104
+ override deserialize(data: JsonValue): void;
99
105
  }
100
106
  //#endregion
101
107
  //#region src/gameplay/damage-on-contact.d.ts
@@ -377,6 +383,9 @@ declare class ScoreKeeper extends Behavior {
377
383
  setScore(value: number): void;
378
384
  /** Lose one life (no-op when lives disabled or already lost). Emits `lifeLost`, then `lost` at 0. */
379
385
  loseLife(): void;
386
+ /** The run's progress. `scoreToWin` is balance and comes back from JSON. */
387
+ override serialize(): JsonValue;
388
+ override deserialize(data: JsonValue): void;
380
389
  }
381
390
  //#endregion
382
391
  //#region src/gameplay/spawner.d.ts
@@ -606,9 +615,7 @@ declare function goToScene(engine: Engine, sceneJson: JsonObject, opts?: {
606
615
  */
607
616
  declare class GameFlow extends Behavior {
608
617
  static readonly signals: readonly string[];
609
- static readonly props: Record<string, {
610
- default: string | boolean;
611
- }>;
618
+ static readonly props: PropSchema;
612
619
  /** Input action that restarts from gameover/won (declare it in `input{}`). */
613
620
  restartAction: string;
614
621
  /** Freeze `engine.timeScale` on gameover/won (banner UI keeps rendering). */
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-BpQCbABv.js";
1
+ import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-BvhcQbfJ.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { $ as MusicBackend, A as suggestQuality, B as Listener, C as RendererStats, D as SettingsValues, E as Settings, F as SfxEngine, G as spatialGain, H as RolloffModel, I as SfxPlayOptions, J as SFX_PRESET_NAMES, K as spatialPan, L as isAudioContextAvailable, M as createSaveStore, N as Scene, O as qualityEnvironment, P as InputMap, Q as synthSfx, R as Voice, S as GameStats, T as QualityTier, U as SpatialParams, V as ROLLOFF_MODELS, W as Vec3, X as SfxWave, Y as SfxParams, Z as SynthOptions, _ as registeredTypes, a as registerBehavior, at as Node, b as Scheduler, c as PropDef, ct as LogEntry, d as createNode, dt as Signal, et as MusicManager, f as getNodeSchema, ft as SignalListener, g as registerNode, h as mergeStaticSignals, i as getBehavior, it as BusName, j as SaveStore, k as readDeviceHints, l as PropSchema, lt as LogLevel, m as getNodeType, n as BehaviorCtor, nt as PlayMusicOptions, o as registeredBehaviors, ot as NodeLifecycle, p as getNodeSignals, q as SFX_PRESETS, r as clearBehaviors, rt as AudioBuses, s as NodeCtor, st as SceneTree, t as Behavior, tt as MusicTrack, u as clearRegistry, ut as LogManager, v as Engine, w as DeviceHints, x as EngineStats, y as EngineOptions, z as VoicePreset } from "./behavior-CKwTCjfR.js";
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-CcoWb32N.js";
3
- import { n as loadScene, t as LoadSceneOptions } from "./loader-COn5fS0o.js";
4
- import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
5
- import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-DUw9mir9.js";
6
- import { i as auditScene, n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-1dXlIwoR.js";
1
+ import { $ as Voice, A as qualityEnvironment, B as restoreBehaviors, C as EngineStats, Ct as LogEntry, D as QualityTier, Dt as SignalListener, E as DeviceHints, Et as Signal, F as RestoreReport, G as Localization, H as createSaveStore, I as SaveSlot, J as translationKey, K as T_PREFIX, L as SaveSlots, M as suggestQuality, N as Scene, O as Settings, P as BehaviorState, Q as isAudioContextAvailable, R as behaviorsWithoutSave, S as Scheduler, St as SceneTree, T as RendererStats, Tt as LogManager, U as BASE_LOCALE, V as SaveStore, W as LocaleTables, X as SfxEngine, Y as InputMap, Z as SfxPlayOptions, _ as mergeStaticSignals, _t as PlayMusicOptions, a as clearBehaviors, at as Vec3, b as Engine, bt as Node, c as registeredBehaviors, ct as SFX_PRESETS, d as PropSchema, dt as SfxWave, et as VoicePreset, f as clearRegistry, ft as SynthOptions, g as getNodeType, gt as MusicTrack, h as getNodeSignals, ht as MusicManager, i as behaviorSignals, it as SpatialParams, j as readDeviceHints, k as SettingsValues, l as NodeCtor, lt as SFX_PRESET_NAMES, m as getNodeSchema, mt as MusicBackend, n as BehaviorCtor, nt as ROLLOFF_MODELS, o as getBehavior, ot as spatialGain, p as createNode, pt as synthSfx, q as suggestLocale, r as behaviorSchema, rt as RolloffModel, s as registerBehavior, st as spatialPan, t as Behavior, tt as Listener, u as PropDef, ut as SfxParams, v as registerNode, vt as AudioBuses, w as GameStats, wt as LogLevel, x as EngineOptions, xt as NodeLifecycle, y as registeredTypes, yt as BusName, z as captureBehaviors } from "./behavior-CGSCWOHB.js";
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-3ywbdlrv.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-CUcj00M8.js";
4
+ import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-B-vZBF5R.js";
5
+ import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-DgOo2KNF.js";
6
+ import { i as auditScene, n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-BY2kL0hv.js";
7
7
 
8
8
  //#region src/core/audio/crossfade.d.ts
9
9
  /**
@@ -120,6 +120,7 @@ declare class HudLayer extends Node {
120
120
  static readonly props: PropSchema;
121
121
  zIndex: number;
122
122
  visible: boolean;
123
+ focusNavigation: boolean;
123
124
  /** @internal root overlay element (null headless). */
124
125
  _element: HTMLElement | null;
125
126
  private readonly slots;
@@ -138,14 +139,40 @@ declare class HudLayer extends Node {
138
139
  private _mount;
139
140
  override onExitTree(): void;
140
141
  override update(): void;
142
+ /**
143
+ * Arrow keys / d-pad move the focus, Enter / A activates it.
144
+ *
145
+ * Off by default: a game whose HUD has a button must not lose its arrow keys
146
+ * to a menu the moment one exists. Turn it on for the screens that ARE menus
147
+ * (`"focusNavigation": true` on the pause panel's layer), and off again when
148
+ * play resumes.
149
+ */
150
+ private stepFocus;
151
+ /** Focusable widgets under this layer, in tree order. */
152
+ private focusables;
153
+ /** Move focus to a widget by hand — opening a menu should start somewhere. */
154
+ focus(widget: HudWidgetBase | null): void;
141
155
  /** @internal Widgets mount into per-anchor flex columns. */
142
156
  _slot(anchor: HudAnchor): HTMLElement | null;
143
157
  }
144
158
  /** Shared plumbing: mount into the parent HudLayer's anchor slot. */
145
159
  declare abstract class HudWidgetBase extends Node {
146
160
  static readonly props: PropSchema;
161
+ static readonly signals: readonly string[];
147
162
  anchor: HudAnchor;
148
163
  visible: boolean;
164
+ focusable: boolean;
165
+ draggable: boolean;
166
+ dropTarget: boolean;
167
+ /** @internal Set by the owning HudLayer while this widget has focus. */
168
+ _focused: boolean;
169
+ /**
170
+ * @internal Confirm (`dir` 0) or nudge (-1 left / +1 right) — what pressing
171
+ * A or an arrow ON this widget means. Default: nothing.
172
+ */
173
+ _activate(_dir: number): void;
174
+ /** @internal Draw the focus ring. Overridable if a widget wants its own. */
175
+ _paintFocus(): void;
149
176
  /** @internal */
150
177
  _element: HTMLElement | null;
151
178
  protected abstract _build(): HTMLElement;
@@ -161,11 +188,31 @@ declare abstract class HudWidgetBase extends Node {
161
188
  * anything attached at runtime before its host existed.
162
189
  */
163
190
  _mountWidget(): void;
191
+ /**
192
+ * Drag and drop, on the DOM the engine already builds.
193
+ *
194
+ * An inventory is the one screen where "click it" is not enough, and every
195
+ * game that wanted one dropped out of scene JSON to hand-roll pointer
196
+ * handlers. The whole gesture is four signals and no new node type: a
197
+ * `draggable` widget emits `dragStarted` and, if it lands on one,
198
+ * `droppedOn(target)`; a `dropTarget` emits `dropped(source)`. Who owns the
199
+ * ITEM is the game's business — this reports the gesture, not a model.
200
+ */
201
+ private _wireDrag;
164
202
  /** The nearest ancestor that holds widgets itself (a UiPanel). */
165
203
  private host;
166
204
  override onExitTree(): void;
167
205
  override update(_dt: number): void;
168
206
  protected _sync(): void;
207
+ /**
208
+ * Resolve a text prop through the engine's locale.
209
+ *
210
+ * Called every frame from `_sync`, which is what makes switching language
211
+ * repaint with nothing to invalidate: widgets already re-read their props, so
212
+ * the new string simply appears on the next frame. A plain string costs one
213
+ * `startsWith` and is returned untouched.
214
+ */
215
+ protected _t(value: string): string;
169
216
  }
170
217
  /** A text line (score, timer, hints). Set `.text` from behaviors. */
171
218
  declare class UiText extends HudWidgetBase {
@@ -193,6 +240,8 @@ declare class UiBar extends HudWidgetBase {
193
240
  background: string;
194
241
  label: string;
195
242
  private fill;
243
+ private tag;
244
+ private lastLabel;
196
245
  private lastRatio;
197
246
  /** Current fill ratio 0..1 (what the bar shows). */
198
247
  get ratio(): number;
@@ -244,6 +293,9 @@ declare class UiButton extends HudWidgetBase {
244
293
  color: string;
245
294
  background: string;
246
295
  disabled: boolean;
296
+ override focusable: boolean;
297
+ /** Enter / A on a focused button is a press. */
298
+ override _activate(_dir: number): void;
247
299
  private lastText;
248
300
  /** Programmatic press — same path as a click (tests, gamepad menus). */
249
301
  press(): void;
@@ -392,6 +444,9 @@ declare class UiSlider extends HudWidgetBase {
392
444
  step: number;
393
445
  width: number;
394
446
  color: string;
447
+ override focusable: boolean;
448
+ /** Left/right nudge by one step — the only sane thing a d-pad can mean here. */
449
+ override _activate(dir: number): void;
395
450
  private input;
396
451
  private text;
397
452
  private last;
@@ -405,6 +460,9 @@ declare class UiToggle extends HudWidgetBase {
405
460
  static override readonly signals: readonly string[];
406
461
  label: string;
407
462
  value: boolean;
463
+ override focusable: boolean;
464
+ /** Confirm flips it; left/right set it explicitly, which reads better on a pad. */
465
+ override _activate(dir: number): void;
408
466
  private input;
409
467
  private last;
410
468
  protected _build(): HTMLElement;
@@ -418,10 +476,17 @@ declare class UiSelect extends HudWidgetBase {
418
476
  label: string;
419
477
  options: string;
420
478
  value: string;
479
+ override focusable: boolean;
480
+ /** Left/right (and confirm) walk the list — a select has no other gesture. */
481
+ override _activate(dir: number): void;
421
482
  private select;
483
+ private labelEl;
484
+ private lastLabel;
422
485
  private lastOptions;
423
486
  private last;
424
487
  protected _build(): HTMLElement;
488
+ /** Override point: what an option READS as, when it differs from its value. */
489
+ protected _optionLabel(value: string): string;
425
490
  private fillOptions;
426
491
  protected override _sync(): void;
427
492
  }
@@ -708,4 +773,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
708
773
  /** Engine version. Kept in sync with package.json by the release pipeline. */
709
774
  declare const VERSION: string;
710
775
  //#endregion
711
- export { AudioBuses, type AudioElementLike, AudioPlayer, Behavior, type BehaviorCtor, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LoadSceneOptions, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, suggestQuality, synthSfx };
776
+ export { AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import { _ as clearBehaviors, a as SCENE_FORMAT, b as registeredBehaviors, c as SceneTree, d as resolveConstants, f as Node, g as Behavior, h as InputMap, i as serializeNode, l as CONST_REF_KEY, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as getBehavior, x as Signal, y as registerBehavior } from "./loader-Buk8Bu1h.js";
2
- import { A as MusicManager, C as isAudioContextAvailable, D as SFX_PRESETS, E as spatialPan, M as crossfadeGains, N as fadeGain, O as SFX_PRESET_NAMES, P as AudioBuses, S as SfxEngine, T as spatialGain, _ as qualityEnvironment, a as UiSlider, b as createSaveStore, c as UiButton, d as UiBanner, f as UiBar, g as Settings, h as Engine, i as UiSelect, j as WebAudioMusicBackend, k as synthSfx, l as UiDialogue, m as AudioPlayer, n as UiImage, o as UiToggle, p as UiText, r as UiPanel, s as Timer, t as registerCoreNodes, u as HudLayer, v as readDeviceHints, w as ROLLOFF_MODELS, x as LogManager, y as suggestQuality } from "./register-CscIzJEO.js";
1
+ import { C as Signal, S as registeredBehaviors, _ as behaviorSchema, a as SCENE_FORMAT, b as getBehavior, c as SceneTree, d as resolveConstants, f as Node, g as Behavior, i as serializeNode, l as CONST_REF_KEY, m as InputMap, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as behaviorSignals, x as registerBehavior, y as clearBehaviors } from "./loader-Mig5fY4n.js";
2
+ import { A as translationKey, B as WebAudioMusicBackend, C as restoreBehaviors, D as Localization, E as BASE_LOCALE, F as spatialPan, H as fadeGain, I as SFX_PRESETS, L as SFX_PRESET_NAMES, M as isAudioContextAvailable, N as ROLLOFF_MODELS, O as T_PREFIX, P as spatialGain, R as synthSfx, S as captureBehaviors, T as LogManager, U as AudioBuses, V as crossfadeGains, _ as qualityEnvironment, a as UiSlider, b as SaveSlots, c as UiButton, d as UiBanner, f as UiBar, g as Settings, h as Engine, i as UiSelect, j as SfxEngine, k as suggestLocale, l as UiDialogue, m as AudioPlayer, n as UiImage, o as UiToggle, p as UiText, r as UiPanel, s as Timer, t as registerCoreNodes, u as HudLayer, v as readDeviceHints, w as createSaveStore, x as behaviorsWithoutSave, y as suggestQuality, z as MusicManager } from "./register-BFLg0-_i.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
- import { t as auditScene } from "./audit-C4kmDK0o.js";
5
+ import { t as auditScene } from "./audit-C5oZGAru.js";
6
6
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
7
7
  import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-CJdGpT2V.js";
8
8
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
9
- import { n as ORDER_GROUP_BASE, r as effectiveOrder, t as duplicateNode } from "./duplicate-BgnG1Lqz.js";
9
+ import { n as ORDER_GROUP_BASE, r as effectiveOrder, t as duplicateNode } from "./duplicate-C716f-97.js";
10
10
  import { i as applyParticlePreset, n as PARTICLE_PRESETS, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
11
11
  import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-BoNg_MnF.js";
12
12
  //#region src/core/pathfinding.ts
@@ -242,6 +242,6 @@ function newUid() {
242
242
  //#endregion
243
243
  //#region src/index.ts
244
244
  /** Engine version. Kept in sync with package.json by the release pipeline. */
245
- const VERSION = "0.33.0";
245
+ const VERSION = "0.35.0";
246
246
  //#endregion
247
- export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, Settings, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, suggestQuality, synthSfx };
247
+ export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -1,5 +1,5 @@
1
- import { N as Scene, v as Engine } from "./behavior-CKwTCjfR.js";
2
- import { i as SceneJson } from "./schema-CcoWb32N.js";
1
+ import { N as Scene, b as Engine } from "./behavior-CGSCWOHB.js";
2
+ import { i as SceneJson } from "./schema-3ywbdlrv.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
5
5
  interface LoadSceneOptions {
@@ -20,6 +20,23 @@ interface LoadSceneOptions {
20
20
  */
21
21
  stubMissingBehaviors?: boolean;
22
22
  /**
23
+ * Editor mode: EVERY script becomes a no-op stub, registered or not.
24
+ *
25
+ * The editor must not run the game while you edit it — a behavior's `onReady`
26
+ * moving the player, or `update` walking it off the terrain, is not something
27
+ * an edit view should do. It used to get that by deleting `script` outright,
28
+ * which also meant deleting `connections` (a behavior handler cannot resolve
29
+ * on a node with no behavior) — so the editor validated a scene the game
30
+ * would reject, and reported green.
31
+ *
32
+ * Stubbing instead keeps connections loadable and hard-validated: endpoints
33
+ * must still resolve, so DANGLING_CONNECTION fires in the editor exactly
34
+ * where it fires in the game. Handlers are accepted (they live in the game's
35
+ * TypeScript, which the editor does not load) and props are not applied.
36
+ * Games should NOT set this.
37
+ */
38
+ stubAllBehaviors?: boolean;
39
+ /**
23
40
  * Attach the engine BEFORE onEnterTree/onReady fire, so behaviors can use
24
41
  * `this.engine` / `this.rng` / `this.log` in onReady. `createGame` does
25
42
  * this for you; pair it with `engine.setScene(scene)` afterwards.
@@ -103,6 +103,23 @@ function getBehavior(name) {
103
103
  }
104
104
  return ctor;
105
105
  }
106
+ /**
107
+ * A registered behavior's merged prop schema, `{}` for one nobody registered.
108
+ *
109
+ * The node side has had `getNodeSchema` for a long time; a behavior's props were
110
+ * reachable only by whoever already held the class, which a tool reading scene
111
+ * JSON never does. Deliberately non-throwing: a tool inspecting a scene that
112
+ * names a behavior from a game it did not load should degrade, not blow up.
113
+ */
114
+ function behaviorSchema(name) {
115
+ const ctor = behaviors.get(name);
116
+ return ctor ? mergeStaticProps(ctor) : {};
117
+ }
118
+ /** A registered behavior's signals (own + inherited), `[]` for an unknown one. */
119
+ function behaviorSignals(name) {
120
+ const ctor = behaviors.get(name);
121
+ return ctor ? mergeStaticSignals(ctor) : [];
122
+ }
106
123
  function registeredBehaviors() {
107
124
  return [...behaviors.keys()];
108
125
  }
@@ -118,6 +135,14 @@ function createBehavior(name, props) {
118
135
  return behavior;
119
136
  }
120
137
  //#endregion
138
+ //#region src/core/diagnostics.ts
139
+ function diagnose(engine, level, ...parts) {
140
+ engine?.log[level](...parts);
141
+ if (level === "error") console.error(...parts);
142
+ else if (level === "warn") console.warn(...parts);
143
+ else console.info(...parts);
144
+ }
145
+ //#endregion
121
146
  //#region src/core/input.ts
122
147
  /** Keys typed into editable elements belong to the page UI, not the game. */
123
148
  function isEditableTarget(target) {
@@ -453,6 +478,19 @@ var InputMap = class {
453
478
  * is ceremony. Buttons already live in the key set as `Mouse0`/`Mouse1`/…;
454
479
  * these read them directly. 0 left, 1 middle, 2 right.
455
480
  */
481
+ /**
482
+ * Is this raw CODE down / just pressed? (`'ArrowUp'`, `'Enter'`, `'Pad12'`…)
483
+ *
484
+ * Menu navigation is not a bindable action — there is no key to remap, and
485
+ * asking every scene to declare `"menuUp": { "keys": ["ArrowUp", "Pad12"] }`
486
+ * is ceremony for something every menu on every platform does the same way.
487
+ */
488
+ keyPressed(code) {
489
+ return this.down.has(code);
490
+ }
491
+ keyJustPressed(code) {
492
+ return this.pressedEdge.has(code);
493
+ }
456
494
  mousePressed(button = 0) {
457
495
  return this.down.has(`Mouse${button}`);
458
496
  }
@@ -559,14 +597,6 @@ function parseInputDecls(decls) {
559
597
  return out;
560
598
  }
561
599
  //#endregion
562
- //#region src/core/diagnostics.ts
563
- function diagnose(engine, level, ...parts) {
564
- engine?.log[level](...parts);
565
- if (level === "error") console.error(...parts);
566
- else if (level === "warn") console.warn(...parts);
567
- else console.info(...parts);
568
- }
569
- //#endregion
570
600
  //#region src/core/node-path.ts
571
601
  function parseNodePath(path) {
572
602
  if (path === "") throw new IncantoError("BAD_NODE_PATH", "Node path must not be empty.");
@@ -644,10 +674,7 @@ var Node = class {
644
674
  const old = this._name;
645
675
  if (this._parent) this._name = uniqueSiblingName(value, this._parent._children, this);
646
676
  else this._name = value;
647
- if (this._tree && this._name !== old) {
648
- this._tree._unindexName(this, old);
649
- this._tree._indexName(this);
650
- }
677
+ if (this._tree && this._name !== old) this._tree._reindexName(this, old);
651
678
  }
652
679
  get parent() {
653
680
  return this._parent;
@@ -1042,7 +1069,7 @@ function resolveConstants(value, constants) {
1042
1069
  *
1043
1070
  * Headless by design — tests step it manually; the render loop (M2) calls it.
1044
1071
  */
1045
- var SceneTree = class {
1072
+ var SceneTree = class SceneTree {
1046
1073
  _root = null;
1047
1074
  _engine = null;
1048
1075
  freeQueue = /* @__PURE__ */ new Set();
@@ -1058,6 +1085,47 @@ var SceneTree = class {
1058
1085
  * change shape?" check for per-step body gathers and similar caches.
1059
1086
  */
1060
1087
  _structureVersion = 0;
1088
+ /**
1089
+ * WHAT changed, not just that something did.
1090
+ *
1091
+ * The version alone forces every consumer into a full-tree re-walk, and the
1092
+ * physics step is gated on it: one bullet spawned per frame means
1093
+ * `collectBodies` + `collectJoints` + `syncScatters` walk the entire scene at
1094
+ * 60 Hz. A bullet-hell or a survivors-like does nothing else all game.
1095
+ *
1096
+ * This log lets a consumer replay just the attaches and detaches it has not
1097
+ * seen. It is bounded — a scene load appends thousands of entries and nobody
1098
+ * benefits from replaying those — so a consumer that falls behind
1099
+ * `_structureLogBase` takes the full walk, exactly as before. Correctness does
1100
+ * not depend on the log; only the fast path does.
1101
+ */
1102
+ _structureLog = [];
1103
+ /** The `_structureVersion` the first entry in the log corresponds to. */
1104
+ _structureLogBase = 0;
1105
+ static STRUCTURE_LOG_MAX = 2048;
1106
+ /**
1107
+ * Attaches and detaches since `sinceVersion`, or null when the log no longer
1108
+ * reaches back that far (take the full walk).
1109
+ *
1110
+ * The returned array is the live buffer's tail — do not hold it across frames.
1111
+ */
1112
+ _structureSince(sinceVersion) {
1113
+ if (sinceVersion > this._structureVersion) return null;
1114
+ if (sinceVersion < this._structureLogBase) return null;
1115
+ return this._structureLog.slice(sinceVersion - this._structureLogBase);
1116
+ }
1117
+ _logStructure(node, added) {
1118
+ this._structureVersion++;
1119
+ this._structureLog.push({
1120
+ node,
1121
+ added
1122
+ });
1123
+ const overflow = this._structureLog.length - SceneTree.STRUCTURE_LOG_MAX;
1124
+ if (overflow > 0) {
1125
+ this._structureLog.splice(0, overflow);
1126
+ this._structureLogBase += overflow;
1127
+ }
1128
+ }
1061
1129
  _frameId = 0;
1062
1130
  /**
1063
1131
  * Monotonic update-frame counter — bumped once per `update()`. Per-frame
@@ -1069,7 +1137,7 @@ var SceneTree = class {
1069
1137
  }
1070
1138
  /** @internal Node attach hook. */
1071
1139
  _indexName(node) {
1072
- this._structureVersion++;
1140
+ this._logStructure(node, true);
1073
1141
  let set = this._nameIndex.get(node.name);
1074
1142
  if (!set) {
1075
1143
  set = /* @__PURE__ */ new Set();
@@ -1077,9 +1145,29 @@ var SceneTree = class {
1077
1145
  }
1078
1146
  set.add(node);
1079
1147
  }
1080
- /** @internal Node detach/rename hook (`name` = the entry to remove). */
1148
+ /**
1149
+ * @internal A RENAME: same node, same place, different key in the name index.
1150
+ *
1151
+ * Deliberately not logged as a detach + attach. It is not a structural change,
1152
+ * and treating it as one would make the physics step tear down and rebuild the
1153
+ * node's Rapier body for a name edit.
1154
+ */
1155
+ _reindexName(node, oldName) {
1156
+ const set = this._nameIndex.get(oldName);
1157
+ if (set) {
1158
+ set.delete(node);
1159
+ if (set.size === 0) this._nameIndex.delete(oldName);
1160
+ }
1161
+ let next = this._nameIndex.get(node.name);
1162
+ if (!next) {
1163
+ next = /* @__PURE__ */ new Set();
1164
+ this._nameIndex.set(node.name, next);
1165
+ }
1166
+ next.add(node);
1167
+ }
1168
+ /** @internal Node detach hook (`name` = the entry to remove). */
1081
1169
  _unindexName(node, name = node.name) {
1082
- this._structureVersion++;
1170
+ this._logStructure(node, false);
1083
1171
  const set = this._nameIndex.get(name);
1084
1172
  if (set) {
1085
1173
  set.delete(node);
@@ -1278,6 +1366,8 @@ var Scene = class {
1278
1366
  constants;
1279
1367
  /** Input action declarations (loaded into `Engine.input` on setScene). */
1280
1368
  input;
1369
+ /** Translations by locale (merged into `Engine.locale` on setScene). */
1370
+ strings;
1281
1371
  /** Physics config (gravity etc. — interpreted by the physics modules). */
1282
1372
  physics;
1283
1373
  /** Multiplayer config (room etc. — interpreted by incanto/net). */
@@ -1296,6 +1386,7 @@ var Scene = class {
1296
1386
  this.assets = source.assets ? jsonClone(source.assets) : void 0;
1297
1387
  this.constants = source.constants ? jsonClone(source.constants) : void 0;
1298
1388
  this.input = source.input ? jsonClone(source.input) : void 0;
1389
+ this.strings = source.strings ? jsonClone(source.strings) : void 0;
1299
1390
  this.physics = source.physics ? jsonClone(source.physics) : void 0;
1300
1391
  this.multiplayer = source.multiplayer ? jsonClone(source.multiplayer) : void 0;
1301
1392
  this.environment = source.environment ? jsonClone(source.environment) : void 0;
@@ -1312,6 +1403,7 @@ var Scene = class {
1312
1403
  ...this.assets ? { assets: jsonClone(this.assets) } : {},
1313
1404
  ...this.constants ? { constants: jsonClone(this.constants) } : {},
1314
1405
  ...this.input ? { input: jsonClone(this.input) } : {},
1406
+ ...this.strings ? { strings: jsonClone(this.strings) } : {},
1315
1407
  ...this.physics ? { physics: jsonClone(this.physics) } : {},
1316
1408
  ...this.multiplayer ? { multiplayer: jsonClone(this.multiplayer) } : {},
1317
1409
  ...this.environment ? { environment: jsonClone(this.environment) } : {},
@@ -1435,7 +1527,7 @@ function applyCommon(node, nj, opts) {
1435
1527
  node.script = jsonClone(nj.script);
1436
1528
  const name = nj.script.name;
1437
1529
  if (typeof name !== "string" || name === "") throw new IncantoError("BAD_FORMAT", `Node '${nj.name}': "script" needs a non-empty "name" (a registered behavior).`);
1438
- const behavior = opts?.stubMissingBehaviors && !hasBehavior(name) ? new class extends StubBehavior {}() : createBehavior(name, nj.script.props);
1530
+ const behavior = opts?.stubAllBehaviors || opts?.stubMissingBehaviors && !hasBehavior(name) ? new class extends StubBehavior {}() : createBehavior(name, nj.script.props);
1439
1531
  behavior.node = node;
1440
1532
  node.behavior = behavior;
1441
1533
  for (const s of mergeStaticSignals(behavior.constructor)) node.declareSignal(s);
@@ -1507,4 +1599,4 @@ function validateUniqueUids(root) {
1507
1599
  walk(root);
1508
1600
  }
1509
1601
  //#endregion
1510
- export { clearBehaviors as _, SCENE_FORMAT as a, registeredBehaviors as b, SceneTree as c, resolveConstants as d, Node as f, Behavior as g, InputMap as h, serializeNode as i, CONST_REF_KEY as l, diagnose as m, loadScene as n, computeViewport as o, parseNodePath as p, Scene as r, resolveViewport as s, buildNodeJson as t, isConstRef as u, getBehavior as v, Signal as x, registerBehavior as y };
1602
+ export { Signal as C, registeredBehaviors as S, behaviorSchema as _, SCENE_FORMAT as a, getBehavior as b, SceneTree as c, resolveConstants as d, Node as f, Behavior as g, diagnose as h, serializeNode as i, CONST_REF_KEY as l, InputMap as m, loadScene as n, computeViewport as o, parseNodePath as p, Scene as r, resolveViewport as s, buildNodeJson as t, isConstRef as u, behaviorSignals as v, registerBehavior as x, clearBehaviors as y };
package/dist/net.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { at as Node, dt as Signal, l as PropSchema, v as Engine } from "./behavior-CKwTCjfR.js";
2
- import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
1
+ import { Et as Signal, b as Engine, bt as Node, d as PropSchema } from "./behavior-CGSCWOHB.js";
2
+ import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-3ywbdlrv.js";
3
3
 
4
4
  //#region src/net/types.d.ts
5
5
  type Unsubscribe = () => void;
package/dist/net.js CHANGED
@@ -1,9 +1,9 @@
1
- import { n as loadScene } from "./loader-Buk8Bu1h.js";
2
- import { h as Engine } from "./register-CscIzJEO.js";
1
+ import { n as loadScene } from "./loader-Mig5fY4n.js";
2
+ import { h as Engine } from "./register-BFLg0-_i.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
6
- import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-CtI-itec.js";
6
+ import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-BjbPMA5B.js";
7
7
  //#region src/net/loopback.ts
8
8
  /**
9
9
  * In-memory multiplayer hub implementing the SAME kernel contract as
@@ -1,4 +1,4 @@
1
- import { a as Rng } from "./schema-CcoWb32N.js";
1
+ import { a as Rng } from "./schema-3ywbdlrv.js";
2
2
 
3
3
  //#region src/core/particle-sim.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { B as Listener, at as Node, l as PropSchema } from "./behavior-CKwTCjfR.js";
1
+ import { bt as Node, d as PropSchema, tt as Listener } from "./behavior-CGSCWOHB.js";
2
2
 
3
3
  //#region src/core/nodes/audio-player.d.ts
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { m as diagnose } from "./loader-Buk8Bu1h.js";
2
+ import { h as diagnose } from "./loader-Mig5fY4n.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-FIJtNbub.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-DSmIRAf7.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  //#region src/2d/physics/physics-2d.ts
7
7
  var physics_2d_exports = /* @__PURE__ */ __exportAll({
@@ -334,16 +334,29 @@ var Physics2D = class {
334
334
  const scene = this.engine.scene;
335
335
  if (!scene) return;
336
336
  const version = scene.tree._structureVersion;
337
- const structureChanged = version !== this.lastStructure || scene.root !== this.lastRoot;
337
+ const sameRoot = scene.root === this.lastRoot;
338
+ const structureChanged = version !== this.lastStructure || !sameRoot;
338
339
  if (structureChanged) {
340
+ const delta = sameRoot ? scene.tree._structureSince(this.lastStructure) : null;
339
341
  this.lastStructure = version;
340
342
  this.lastRoot = scene.root;
341
- this.bodySet.clear();
342
- collectBodies(scene.root, this.bodySet);
343
- }
344
- if (structureChanged) {
345
- this.jointSet.clear();
346
- collectJoints(scene.root, this.jointSet);
343
+ if (delta) {
344
+ let joints = false;
345
+ for (const { node, added } of delta) {
346
+ if (node instanceof PhysicsBody2D) if (added) this.bodySet.add(node);
347
+ else this.bodySet.delete(node);
348
+ if (node instanceof Joint2D) joints = true;
349
+ }
350
+ if (joints) {
351
+ this.jointSet.clear();
352
+ collectJoints(scene.root, this.jointSet);
353
+ }
354
+ } else {
355
+ this.bodySet.clear();
356
+ collectBodies(scene.root, this.bodySet);
357
+ this.jointSet.clear();
358
+ collectJoints(scene.root, this.jointSet);
359
+ }
347
360
  }
348
361
  const seen = this.bodySet;
349
362
  for (const node of seen) {