incanto 0.58.0 → 0.59.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/bin/incanto-multiplay.mjs +170 -0
  2. package/bin/incanto-verify.mjs +68 -4
  3. package/bin/incanto.mjs +1 -0
  4. package/dist/2d.d.ts +8 -2
  5. package/dist/2d.js +3 -3
  6. package/dist/3d.d.ts +9 -3
  7. package/dist/3d.js +4 -4
  8. package/dist/{behavior-l08AEbq9.d.ts → behavior-DWKTUzKI.d.ts} +10 -0
  9. package/dist/{create-game-D10bU5_J.js → create-game-BiW8Men_.js} +6 -6
  10. package/dist/{create-game-BZwWJIns.js → create-game-CHDLDQsQ.js} +6 -6
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/debug.js +2 -2
  13. package/dist/{duplicate-BPLZDZpd.js → duplicate-DJQd44CD.js} +1 -1
  14. package/dist/{environment-presets-CvvQr_bJ.js → environment-presets-DRAz5EV9.js} +12 -10
  15. package/dist/{gameplay-BVphcxmE.js → gameplay-BBEjPFsR.js} +62 -34
  16. package/dist/gameplay.d.ts +1 -1
  17. package/dist/gameplay.js +1 -1
  18. package/dist/index.d.ts +36 -8
  19. package/dist/index.js +8 -8
  20. package/dist/{json-BLk7H2Qa.js → json-CwwhxQgb.js} +7 -1
  21. package/dist/{loader-BcrRSjxB.js → loader-D8n7TU8W.js} +142 -5
  22. package/dist/{loader-BbEMTuWg.d.ts → loader-TvkRFbyL.d.ts} +1 -1
  23. package/dist/net.d.ts +2 -2
  24. package/dist/net.js +1 -1
  25. package/dist/{pathfinding-mEN4V1CU.d.ts → pathfinding-BqWBb0kh.d.ts} +1 -1
  26. package/dist/{physics-2d-EqA-jddf.js → physics-2d-BaRSRrrZ.js} +12 -2
  27. package/dist/{physics-3d-Dnz4fsXX.js → physics-3d-CYxjh-HW.js} +13 -3
  28. package/dist/react.d.ts +1 -1
  29. package/dist/react.js +2 -2
  30. package/dist/{register-C6ZBFRjd.js → register-BpFcgdcL.js} +57 -27
  31. package/dist/{register-Ch70uByv.js → register-CDrAQqPp.js} +90 -41
  32. package/dist/{registry-C7u42TID.js → registry-WWcQcfMr.js} +1 -1
  33. package/dist/{replay-s7I2GstT.js → replay-CEPyQtF_.js} +31 -7
  34. package/dist/{replay-Dw6gMlYA.d.ts → replay-O-yAGM76.d.ts} +1 -1
  35. package/dist/{split-screen-B0baBwxI.d.ts → split-screen-BQ3tAsf-.d.ts} +38 -1
  36. package/dist/{split-screen-DLsUrleX.js → split-screen-DDMZutQ6.js} +56 -13
  37. package/dist/{sprite-animation-C0wXLBZJ.js → sprite-animation-CY-mrr1L.js} +1 -1
  38. package/dist/{src-C1J09Op6.js → src-CY21B462.js} +1 -1
  39. package/dist/{teardown-Cs113S9F.js → teardown-RApWnM1G.js} +1 -1
  40. package/dist/{test-ZBga8kQ9.js → test-DHYuFyAu.js} +260 -25
  41. package/dist/test.d.ts +103 -5
  42. package/dist/test.js +3 -3
  43. package/dist/vite.js +2 -2
  44. package/editor/assets/{agent8-BQQjE9UQ.js → agent8-BoRGtVxK.js} +1 -1
  45. package/editor/assets/{debug-C9UCsXBS.js → debug-CzdyCg75.js} +1 -1
  46. package/editor/assets/{index-CBgfM3WD.js → index-VesuVEhe.js} +91 -91
  47. package/editor/index.html +1 -1
  48. package/package.json +2 -1
  49. package/schemas/scene.schema.json +1174 -70
  50. package/skills/incanto-localization.md +40 -8
  51. package/skills/incanto-multiplayer.md +57 -4
  52. package/skills/incanto-node-reference.md +18 -0
  53. package/skills/incanto-physics-and-input.md +26 -0
  54. package/skills/incanto-verifying-your-game.md +22 -0
  55. package/templates-app/beacon-isle-3d/package.json +1 -1
  56. package/templates-app/platformer-2d/package.json +1 -1
  57. package/templates-app/star-survivor/package.json +1 -1
  58. package/templates-app/tps-3d/package.json +1 -1
  59. package/templates-app/village-quest-3d/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { At as Node, b as Engine, d as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-l08AEbq9.js";
1
+ import { At as Node, b as Engine, d as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-DWKTUzKI.js";
2
2
  import { c as JsonValue, s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
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-BVphcxmE.js";
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-BBEjPFsR.js";
2
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 };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { $ as T_PREFIX, A as SettingsValues, At as Node, B as captureBehaviors, C as EngineStats, Ct as synthSfx, Dt as PlayMusicOptions, E as DeviceHints, Et as MusicTrack, F as BehaviorState, Ft as LogManager, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as Signal, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as SignalListener, M as readDeviceHints, Mt as SceneTree, N as suggestQuality, Nt as LogEntry, O as QualityTier, Ot as AudioBuses, P as Scene, Pt as LogLevel, Q as Localization, R as SaveSlots, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicManager, 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 NodeLifecycle, k as Settings, kt as BusName, 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 MusicBackend, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-l08AEbq9.js";
1
+ import { $ as T_PREFIX, A as SettingsValues, At as Node, B as captureBehaviors, C as EngineStats, Ct as synthSfx, Dt as PlayMusicOptions, E as DeviceHints, Et as MusicTrack, F as BehaviorState, Ft as LogManager, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as Signal, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as SignalListener, M as readDeviceHints, Mt as SceneTree, N as suggestQuality, Nt as LogEntry, O as QualityTier, Ot as AudioBuses, P as Scene, Pt as LogLevel, Q as Localization, R as SaveSlots, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicManager, 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 NodeLifecycle, k as Settings, kt as BusName, 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 MusicBackend, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-DWKTUzKI.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-BbEMTuWg.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-TvkRFbyL.js";
4
4
  import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js";
5
5
  import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-C5OfBbmU.js";
6
- import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-mEN4V1CU.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-Dw6gMlYA.js";
6
+ import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-BqWBb0kh.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-O-yAGM76.js";
8
8
 
9
9
  //#region src/core/audio/crossfade.d.ts
10
10
  /**
@@ -398,6 +398,27 @@ declare abstract class HudWidgetBase extends Node {
398
398
  * `startsWith` and is returned untouched.
399
399
  */
400
400
  protected _t(value: string): string;
401
+ /**
402
+ * A translation with an English wording to fall back to.
403
+ *
404
+ * For text the ENGINE supplies — "Music", "Mute", "Unlimited" — where there
405
+ * is no author string to carry a `@t:` marker. Asks `declares()`, not
406
+ * `has()`: a key only another locale declares would otherwise come back as
407
+ * the raw key, which is worse than the English.
408
+ */
409
+ /**
410
+ * What this widget's label READS as — resolved every frame, never stored.
411
+ *
412
+ * `UiVolumeSlider` and `UiMuteToggle` used to compute their auto-label in
413
+ * `_build()` and write the result back into `this.label`, which broke them
414
+ * twice over: the per-frame `_t(this.label)` then had a plain string with no
415
+ * marker left to resolve, and `_build` runs from `onReady` — inside
416
+ * `loadScene`, BEFORE `setScene` merges the scene's `strings` — so the table
417
+ * was empty at bake time and the English was permanent even for a game that
418
+ * boots in another language.
419
+ */
420
+ protected _labelText(): string;
421
+ protected _tOr(key: string, fallback: string): string;
401
422
  }
402
423
  /** A text line (score, timer, hints). Set `.text` from behaviors. */
403
424
  declare class UiText extends HudWidgetBase {
@@ -725,7 +746,15 @@ declare class UiSelect extends HudWidgetBase {
725
746
  private lastOptions;
726
747
  private last;
727
748
  protected _build(): HTMLElement;
728
- /** Override point: what an option READS as, when it differs from its value. */
749
+ /**
750
+ * What an option READS as, when it differs from its value.
751
+ *
752
+ * The base resolves `@t:` like every other text prop: the VALUE stays exactly
753
+ * what the author wrote — so a `changed` handler never has to know the active
754
+ * language — and only the displayed words change. Without this a dropdown was
755
+ * the one widget with no way to speak anything but English, and
756
+ * `"options": "@t:diff.easy"` rendered that marker literally on screen.
757
+ */
729
758
  protected _optionLabel(value: string): string;
730
759
  private fillOptions;
731
760
  protected override _sync(): void;
@@ -786,7 +815,6 @@ declare abstract class UiSettingSelect extends UiSelect {
786
815
  * `settings.quality.high` is worse than one reading `High`. English is the
787
816
  * base language and the fallback, always.
788
817
  */
789
- protected _tOr(key: string, fallback: string): string;
790
818
  protected override _sync(): void;
791
819
  }
792
820
  /**
@@ -881,7 +909,7 @@ declare class UiVolumeSlider extends UiSlider {
881
909
  bus: VolumeBus;
882
910
  /** Last value we PUSHED in, to tell an engine-side change from a player drag. */
883
911
  private lastPushed;
884
- protected override _build(): HTMLElement;
912
+ protected override _labelText(): string;
885
913
  protected override _sync(): void;
886
914
  }
887
915
  /**
@@ -898,7 +926,7 @@ declare class UiMuteToggle extends UiToggle {
898
926
  static override readonly typeName: string;
899
927
  static override readonly props: PropSchema;
900
928
  private lastPushed;
901
- protected override _build(): HTMLElement;
929
+ protected override _labelText(): string;
902
930
  protected override _sync(): void;
903
931
  }
904
932
  //#endregion
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { A as registerBehavior, D as behaviorSignals, E as behaviorSchema, M as Signal, O as clearBehaviors, S as Node, T as Behavior, _ as createSaveStore, a as SCENE_FORMAT, b as resolveOrderGroups, c as SceneTree, d as resolveConstants, f as SaveSlots, g as savesWithoutUid, h as restoreBehaviors, i as serializeNode, j as registeredBehaviors, k as getBehavior, l as CONST_REF_KEY, m as captureBehaviors, n as loadScene, o as computeViewport, p as behaviorsWithoutSave, r as Scene, s as resolveViewport, u as isConstRef, v as ORDER_GROUP_BASE, w as parseNodePath, x as InputMap, y as effectiveOrder } from "./loader-BcrRSjxB.js";
2
- import { A as UiText, B as spatialPan, C as suggestQuality, E as HudLayer, F as translationKey, G as WebAudioMusicBackend, H as SFX_PRESET_NAMES, I as SfxEngine, J as AudioBuses, K as crossfadeGains, L as isAudioContextAvailable, M as Localization, N as T_PREFIX, O as UiBanner, P as suggestLocale, R as ROLLOFF_MODELS, S as readDeviceHints, T as EffectLog, U as synthSfx, V as SFX_PRESETS, W as MusicManager, _ as Engine, a as UiMuteToggle, b as qualityEnvironment, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as AudioPlayer, h as UiDialogue, i as UiLanguageSelect, j as BASE_LOCALE, k as UiBar, l as UiSelect, m as UiButton, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as fadeGain, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Settings, w as LogManager, z as spatialGain } from "./register-Ch70uByv.js";
1
+ import { A as getBehavior, C as Node, D as behaviorSchema, E as Behavior, M as registeredBehaviors, N as Signal, O as behaviorSignals, S as InputMap, T as parseNodePath, _ as savesWithoutUid, a as serializeNode, b as effectiveOrder, c as resolveViewport, d as isConstRef, f as resolveConstants, g as restoreBehaviors, h as captureBehaviors, i as Scene, j as registerBehavior, k as clearBehaviors, l as SceneTree, m as behaviorsWithoutSave, n as loadScene, o as SCENE_FORMAT, p as SaveSlots, s as computeViewport, u as CONST_REF_KEY, v as createSaveStore, x as resolveOrderGroups, y as ORDER_GROUP_BASE } from "./loader-D8n7TU8W.js";
2
+ import { A as UiBar, B as ROLLOFF_MODELS, C as readDeviceHints, D as HudLayer, E as EffectLog, F as suggestLocale, G as synthSfx, H as spatialPan, J as crossfadeGains, K as MusicManager, L as translationKey, M as BASE_LOCALE, N as Localization, P as T_PREFIX, R as SfxEngine, T as LogManager, U as SFX_PRESETS, V as spatialGain, W as SFX_PRESET_NAMES, X as AudioBuses, Y as fadeGain, _ as Engine, a as UiMuteToggle, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as AudioPlayer, h as UiDialogue, i as UiLanguageSelect, j as UiText, k as UiBanner, l as UiSelect, m as UiButton, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as WebAudioMusicBackend, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, w as suggestQuality, x as qualityEnvironment, y as Settings, z as isAudioContextAvailable } from "./register-CDrAQqPp.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
- import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
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-C7u42TID.js";
7
- import { a as nodeRefWarnings, i as describeRefProblem, n as startRecording, o as resolveRefInJson, r as auditScene, t as replay } from "./replay-s7I2GstT.js";
5
+ import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-CwwhxQgb.js";
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-WWcQcfMr.js";
7
+ import { a as nodeRefWarnings, i as describeRefProblem, n as startRecording, o as resolveRefInJson, r as auditScene, t as replay } from "./replay-CEPyQtF_.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
- 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-C0wXLBZJ.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-C1J09Op6.js";
12
- import { t as duplicateNode } from "./duplicate-BPLZDZpd.js";
10
+ import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-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-CY21B462.js";
12
+ import { t as duplicateNode } from "./duplicate-DJQd44CD.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, 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, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -1,4 +1,10 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
1
2
  //#region src/core/json.ts
3
+ var json_exports = /* @__PURE__ */ __exportAll({
4
+ jsonClone: () => jsonClone,
5
+ jsonEquals: () => jsonEquals,
6
+ jsonKind: () => jsonKind
7
+ });
2
8
  function jsonKind(value) {
3
9
  if (value === null) return "null";
4
10
  if (Array.isArray(value)) return "array";
@@ -27,4 +33,4 @@ function jsonClone(value) {
27
33
  return JSON.parse(JSON.stringify(value));
28
34
  }
29
35
  //#endregion
30
- export { jsonEquals as n, jsonKind as r, jsonClone as t };
36
+ export { json_exports as i, jsonEquals as n, jsonKind as r, jsonClone as t };
@@ -1,6 +1,7 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
1
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
3
- import { c as mergeStaticSignals, i as getNodeSchema, r as createNode, s as mergeStaticProps, t as applySchemaProps } from "./registry-C7u42TID.js";
3
+ import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
4
+ import { c as mergeStaticSignals, i as getNodeSchema, r as createNode, s as mergeStaticProps, t as applySchemaProps } from "./registry-WWcQcfMr.js";
4
5
  //#region src/core/signal.ts
5
6
  var Signal = class {
6
7
  connections = [];
@@ -1728,6 +1729,67 @@ function computeViewport(canvasW, canvasH, viewport) {
1728
1729
  };
1729
1730
  }
1730
1731
  //#endregion
1732
+ //#region src/core/scene/network-validate.ts
1733
+ /**
1734
+ * Hard-check a node's `network` block at LOAD.
1735
+ *
1736
+ * This block was the one part of a scene the loader deep-cloned without
1737
+ * looking at, and it is the part with the most expensive silent failure in the
1738
+ * engine. Three typos a person makes constantly —
1739
+ *
1740
+ * "sync": "position" (a string where an array belongs)
1741
+ * "mode": "Owner" (capitalised)
1742
+ * "syncs": ["position"] (plural)
1743
+ *
1744
+ * — each mean the same thing at runtime: **nothing replicates, and nothing
1745
+ * says so.** `collectOwners` matches the exact string `'owner'`;
1746
+ * `Array.isArray(net.sync)` yields zero keys, so the send loop's body never
1747
+ * runs and even its dead-key diagnostic cannot fire. Every other player is a
1748
+ * named statue at the spawn point and every layer reports success — measured:
1749
+ * all three variants produce a report byte-identical to the working game's.
1750
+ *
1751
+ * Hard load-time errors over silent warnings is the engine's rule (see
1752
+ * AGENTS.md); this block was simply never held to it.
1753
+ */
1754
+ const MODES = new Set(["owner", "observer"]);
1755
+ function fail$1(message, nodeName, details = {}) {
1756
+ throw new IncantoError("BAD_FORMAT", `${message} (on '${nodeName}')`, details);
1757
+ }
1758
+ /** Throws `BAD_FORMAT` if `network` cannot mean what its author intended. */
1759
+ function validateNetworkBlock(network, nodeName) {
1760
+ if (network === void 0 || network === null) return;
1761
+ if (typeof network !== "object" || Array.isArray(network)) fail$1("\"network\" must be an object like { \"mode\": \"owner\", \"sync\": [\"position\"] }", nodeName);
1762
+ const net = network;
1763
+ const known = new Set([
1764
+ "mode",
1765
+ "sync",
1766
+ "throttleMs"
1767
+ ]);
1768
+ for (const key of Object.keys(net)) {
1769
+ if (known.has(key)) continue;
1770
+ fail$1(`Unknown key "${key}" in "network"${key === "syncs" ? " — did you mean \"sync\"?" : ""}. Valid keys: [mode, sync, throttleMs]`, nodeName, {
1771
+ prop: key,
1772
+ validOptions: [
1773
+ "mode",
1774
+ "sync",
1775
+ "throttleMs"
1776
+ ]
1777
+ });
1778
+ }
1779
+ if (net.mode !== void 0) {
1780
+ if (typeof net.mode !== "string" || !MODES.has(net.mode)) fail$1(`"network.mode" must be one of [owner, observer], not ${JSON.stringify(net.mode)}` + (typeof net.mode === "string" && MODES.has(net.mode.toLowerCase()) ? " — it is lower-case" : ""), nodeName, {
1781
+ prop: "mode",
1782
+ validOptions: [...MODES]
1783
+ });
1784
+ }
1785
+ if (net.sync !== void 0) {
1786
+ if (!Array.isArray(net.sync)) fail$1(`"network.sync" must be an ARRAY of prop names, not ${JSON.stringify(net.sync)}` + (typeof net.sync === "string" ? ` — write ["${net.sync}"]` : ""), nodeName);
1787
+ for (const key of net.sync) if (typeof key !== "string" || key === "") fail$1(`"network.sync" entries must be non-empty prop names, got ${JSON.stringify(key)}`, nodeName);
1788
+ }
1789
+ if (net.throttleMs !== void 0 && typeof net.throttleMs !== "number") fail$1(`"network.throttleMs" must be a number, not ${JSON.stringify(net.throttleMs)}`, nodeName);
1790
+ if (net.mode === "owner" && Array.isArray(net.sync) && net.sync.length === 0) fail$1("\"network.mode\" is \"owner\" but \"sync\" is empty — nothing would replicate", nodeName);
1791
+ }
1792
+ //#endregion
1731
1793
  //#region src/core/scene/schema.ts
1732
1794
  /** Current scene file format version. Defaults are frozen per format version. */
1733
1795
  const SCENE_FORMAT = 1;
@@ -1846,7 +1908,41 @@ var Scene = class {
1846
1908
  }
1847
1909
  };
1848
1910
  //#endregion
1911
+ //#region src/core/scene/strings-validate.ts
1912
+ /**
1913
+ * Hard-check a scene's `strings` header at LOAD.
1914
+ *
1915
+ * `Localization.load` quietly drops anything that is not a string leaf, and
1916
+ * `auditScene` only walks the KEYS it finds — so a table shaped wrongly passed
1917
+ * every check the engine had and then rendered raw keys at runtime. Measured:
1918
+ *
1919
+ * ```
1920
+ * "strings": { "en": { "hud.score": 42 } } → ok, ZERO warnings, exit 0
1921
+ * "strings": "nope" → ok, ZERO warnings, exit 0
1922
+ * ```
1923
+ *
1924
+ * A number where a string belongs is the common one — a score label written as
1925
+ * a default rather than a template — and it is indistinguishable at runtime
1926
+ * from a key nobody declared.
1927
+ */
1928
+ function fail(message, details = {}) {
1929
+ throw new IncantoError("BAD_FORMAT", message, details);
1930
+ }
1931
+ /** Throws `BAD_FORMAT` unless `strings` is `locale → key → string`. */
1932
+ function validateStrings(strings) {
1933
+ if (strings === void 0 || strings === null) return;
1934
+ if (typeof strings !== "object" || Array.isArray(strings)) fail(`Scene "strings" must be an object of locale tables, like { "en": { "hud.score": "Score" } } — got ${JSON.stringify(strings)}.`);
1935
+ for (const [locale, table] of Object.entries(strings)) {
1936
+ if (typeof table !== "object" || table === null || Array.isArray(table)) fail(`Scene "strings.${locale}" must be an object of key → string, got ${JSON.stringify(table)}.`, { prop: locale });
1937
+ for (const [key, value] of Object.entries(table)) if (typeof value !== "string") fail(`Scene "strings.${locale}.${key}" must be a string, got ${JSON.stringify(value)}. A non-string is dropped and the key renders raw on screen.`, { prop: `${locale}.${key}` });
1938
+ }
1939
+ }
1940
+ //#endregion
1849
1941
  //#region src/core/scene/loader.ts
1942
+ var loader_exports = /* @__PURE__ */ __exportAll({
1943
+ buildNodeJson: () => buildNodeJson,
1944
+ loadScene: () => loadScene
1945
+ });
1850
1946
  /**
1851
1947
  * Build a live Scene from scene JSON.
1852
1948
  *
@@ -1861,13 +1957,50 @@ function loadScene(json, opts) {
1861
1957
  if (typeof json !== "object" || json === null) throw new IncantoError("BAD_FORMAT", `Scene JSON must be an object, got ${json === null ? "null" : typeof json}.`);
1862
1958
  const sceneJson = json;
1863
1959
  validateHeader(sceneJson);
1960
+ validateStrings(sceneJson.strings);
1961
+ inheritedStrings = {};
1864
1962
  const root = buildNode(sceneJson.root, opts, [], "", sceneJson.constants, Object.keys(sceneJson.orderGroups ?? {}));
1865
1963
  validateUniqueUids(root);
1866
1964
  const tree = new SceneTree();
1867
1965
  if (opts?.engine) tree._setEngine(opts.engine);
1868
1966
  tree.setRoot(root);
1869
1967
  wireConnections(root, sceneJson.connections ?? [], opts?.declareConnectionSignals);
1870
- return new Scene(sceneJson, root, tree);
1968
+ const collected = inheritedStrings;
1969
+ inheritedStrings = null;
1970
+ return new Scene(withInheritedStrings(sceneJson, collected), root, tree);
1971
+ }
1972
+ /**
1973
+ * Tables contributed by sub-scenes during the current `loadScene`.
1974
+ *
1975
+ * A module-level accumulator rather than a threaded parameter because
1976
+ * `buildInstance` sits five frames down a recursion whose signature is already
1977
+ * long, and a load is synchronous and non-reentrant — the same reason the
1978
+ * constants table is passed the way it is.
1979
+ */
1980
+ let inheritedStrings = null;
1981
+ /** Fold one sub-scene's table into the accumulator. */
1982
+ function inheritStrings(strings) {
1983
+ if (!inheritedStrings || strings === void 0) return;
1984
+ validateStrings(strings);
1985
+ for (const [locale, table] of Object.entries(strings)) {
1986
+ inheritedStrings[locale] ??= {};
1987
+ const into = inheritedStrings[locale];
1988
+ for (const [key, value] of Object.entries(table)) if (into[key] === void 0) into[key] = value;
1989
+ }
1990
+ }
1991
+ /** The parent's own table laid over everything its sub-scenes brought. */
1992
+ function withInheritedStrings(sceneJson, collected) {
1993
+ if (!collected || Object.keys(collected).length === 0) return sceneJson;
1994
+ const merged = {};
1995
+ for (const [locale, table] of Object.entries(collected)) merged[locale] = { ...table };
1996
+ for (const [locale, table] of Object.entries(sceneJson.strings ?? {})) merged[locale] = {
1997
+ ...merged[locale] ?? {},
1998
+ ...table
1999
+ };
2000
+ return {
2001
+ ...sceneJson,
2002
+ strings: merged
2003
+ };
1871
2004
  }
1872
2005
  /** @internal Rebuild a detached node subtree from node JSON (used by duplicateNode).
1873
2006
  * The source comes from a live node's serialized props, which are already resolved
@@ -1936,6 +2069,7 @@ function buildInstance(nj, opts, stack, path, constants, bands) {
1936
2069
  const sub = opts.resolveScene(instancePath);
1937
2070
  if (!sub) throw new IncantoError("UNRESOLVED_INSTANCE", `resolveScene returned nothing for instance '${instancePath}' (node '${nj.name}').`);
1938
2071
  validateHeader(sub);
2072
+ inheritStrings(sub.strings);
1939
2073
  const overrides = resolveConstants(nj.overrides ?? {}, constants);
1940
2074
  const node = buildNode({
1941
2075
  ...sub.root,
@@ -1969,7 +2103,10 @@ function applyCommon(node, nj, opts) {
1969
2103
  node.behavior = behavior;
1970
2104
  for (const s of mergeStaticSignals(behavior.constructor)) node.declareSignal(s);
1971
2105
  }
1972
- if (nj.network) node.network = jsonClone(nj.network);
2106
+ if (nj.network) {
2107
+ validateNetworkBlock(nj.network, node.name);
2108
+ node.network = jsonClone(nj.network);
2109
+ }
1973
2110
  }
1974
2111
  function deepMerge(base, override) {
1975
2112
  const out = { ...base };
@@ -2066,4 +2203,4 @@ function validateUniqueUids(root) {
2066
2203
  walk(root);
2067
2204
  }
2068
2205
  //#endregion
2069
- export { registerBehavior as A, diagnose as C, behaviorSignals as D, behaviorSchema as E, Signal as M, clearBehaviors as O, Node as S, Behavior as T, createSaveStore as _, SCENE_FORMAT as a, resolveOrderGroups as b, SceneTree as c, resolveConstants as d, SaveSlots as f, savesWithoutUid as g, restoreBehaviors as h, serializeNode as i, registeredBehaviors as j, getBehavior as k, CONST_REF_KEY as l, captureBehaviors as m, loadScene as n, computeViewport as o, behaviorsWithoutSave as p, Scene as r, resolveViewport as s, buildNodeJson as t, isConstRef as u, ORDER_GROUP_BASE as v, parseNodePath as w, InputMap as x, effectiveOrder as y };
2206
+ export { getBehavior as A, Node as C, behaviorSchema as D, Behavior as E, registeredBehaviors as M, Signal as N, behaviorSignals as O, InputMap as S, parseNodePath as T, savesWithoutUid as _, serializeNode as a, effectiveOrder as b, resolveViewport as c, isConstRef as d, resolveConstants as f, restoreBehaviors as g, captureBehaviors as h, Scene as i, registerBehavior as j, clearBehaviors as k, SceneTree as l, behaviorsWithoutSave as m, loadScene as n, SCENE_FORMAT as o, SaveSlots as p, loader_exports as r, computeViewport as s, buildNodeJson as t, CONST_REF_KEY as u, createSaveStore as v, diagnose as w, resolveOrderGroups as x, ORDER_GROUP_BASE as y };
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine } from "./behavior-l08AEbq9.js";
1
+ import { P as Scene, b as Engine } from "./behavior-DWKTUzKI.js";
2
2
  import { i as SceneJson } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
package/dist/net.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { At as Node, d as PropSchema } from "./behavior-l08AEbq9.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-B0baBwxI.js";
1
+ import { At as Node, d as PropSchema } from "./behavior-DWKTUzKI.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-BQ3tAsf-.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 applySyncPatch, c as createLocalGameServer, i as NetworkManager, l as LoopbackHub, n as registerNodesNet, o as LocalGameServer, r as NetworkSpawner, s as LocalGameServerTransport, t as createSplitScreen, u as LoopbackTransport } from "./split-screen-DLsUrleX.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-DDMZutQ6.js";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -1,4 +1,4 @@
1
- import { At as Node, d as PropSchema, dt as Listener } from "./behavior-l08AEbq9.js";
1
+ import { At as Node, d as PropSchema, dt as Listener } from "./behavior-DWKTUzKI.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 { C as diagnose } from "./loader-BcrRSjxB.js";
2
+ import { w as diagnose } from "./loader-D8n7TU8W.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-C6ZBFRjd.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-BpFcgdcL.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  import { t as withoutRapierInitNoise } from "./quiet-rapier-BAJ4K94N.js";
7
7
  //#region src/2d/physics/physics-2d.ts
@@ -433,10 +433,20 @@ var Physics2D = class {
433
433
  };
434
434
  this.entries.set(node, e);
435
435
  this.byColliderHandle.set(collider.handle, node);
436
+ if (!node.enabled) {
437
+ collider.setEnabled(false);
438
+ body.setEnabled(false);
439
+ }
436
440
  node._colliderChanged = false;
441
+ node._enabledChanged = false;
437
442
  node._physics = this;
438
443
  return e;
439
444
  }
445
+ if (node._enabledChanged) {
446
+ e.collider.setEnabled(node.enabled);
447
+ e.body.setEnabled(node.enabled);
448
+ node._enabledChanged = false;
449
+ }
440
450
  const [px, py] = worldPosition2D(node);
441
451
  if (px !== e.lastX || py !== e.lastY) {
442
452
  e.body.setTranslation({
@@ -1,10 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { C as diagnose } from "./loader-BcrRSjxB.js";
2
+ import { w as diagnose } from "./loader-D8n7TU8W.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { H as validateCollider3D, I as Area3D, L as CharacterBody3D, R as PhysicsBody3D, V as Node3D, z as RigidBody3D } from "./gameplay-BVphcxmE.js";
4
+ import { H as validateCollider3D, I as Area3D, L as CharacterBody3D, R as PhysicsBody3D, V as Node3D, z as RigidBody3D } from "./gameplay-BBEjPFsR.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
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-CvvQr_bJ.js";
7
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-DRAz5EV9.js";
8
8
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
9
9
  //#region src/3d/physics/collider-lines.ts
10
10
  /**
@@ -652,11 +652,21 @@ var Physics3D = class {
652
652
  };
653
653
  this.entries.set(node, e);
654
654
  this.byColliderHandle.set(collider.handle, node);
655
+ if (!node.enabled) {
656
+ collider.setEnabled(false);
657
+ body.setEnabled(false);
658
+ }
655
659
  node._colliderChanged = false;
660
+ node._enabledChanged = false;
656
661
  node._physics = this;
657
662
  if (node instanceof RigidBody3D) node._physics3d = this;
658
663
  return e;
659
664
  }
665
+ if (node._enabledChanged) {
666
+ e.collider.setEnabled(node.enabled);
667
+ e.body.setEnabled(node.enabled);
668
+ node._enabledChanged = false;
669
+ }
660
670
  const p = worldPosition3D(node);
661
671
  if (p[0] !== e.last[0] || p[1] !== e.last[1] || p[2] !== e.last[2]) {
662
672
  e.body.setTranslation({
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-l08AEbq9.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-DWKTUzKI.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
@@ -1,4 +1,4 @@
1
- import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
1
+ import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
2
2
  import { createContext, useContext, useEffect, useRef, useState } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/react/index.tsx
@@ -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-D10bU5_J.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-BZwWJIns.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-BiW8Men_.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CHDLDQsQ.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1,9 +1,9 @@
1
- import { C as diagnose, S as Node, y as effectiveOrder } from "./loader-BcrRSjxB.js";
2
- import { t as registerCoreNodes } from "./register-Ch70uByv.js";
1
+ import { C as Node, b as effectiveOrder, w as diagnose } from "./loader-D8n7TU8W.js";
2
+ import { I as translateOn, t as registerCoreNodes } from "./register-CDrAQqPp.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
- import { i as getNodeSchema, l as registerNode } from "./registry-C7u42TID.js";
6
- import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-C0wXLBZJ.js";
5
+ import { i as getNodeSchema, l as registerNode } from "./registry-WWcQcfMr.js";
6
+ import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
7
7
  import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
8
8
  import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
9
9
  //#region src/2d/physics/collider-validate.ts
@@ -115,32 +115,51 @@ var Node2D = class extends Node {
115
115
  * `{shape:'capsule', radius, height}` — never child shape nodes.
116
116
  */
117
117
  var PhysicsBody2D = class extends Node2D {
118
- static props = { collider: {
119
- default: {},
120
- variants: {
121
- tag: "shape",
122
- byTag: {
123
- rect: {
124
- shape: "rect",
125
- size: [32, 32]
126
- },
127
- circle: {
128
- shape: "circle",
129
- radius: 16
130
- },
131
- capsule: {
132
- shape: "capsule",
133
- radius: 12,
134
- height: 32
118
+ static props = {
119
+ collider: {
120
+ default: {},
121
+ variants: {
122
+ tag: "shape",
123
+ byTag: {
124
+ rect: {
125
+ shape: "rect",
126
+ size: [32, 32]
127
+ },
128
+ circle: {
129
+ shape: "circle",
130
+ radius: 16
131
+ },
132
+ capsule: {
133
+ shape: "capsule",
134
+ radius: 12,
135
+ height: 32
136
+ }
135
137
  }
136
138
  }
137
- }
138
- } };
139
+ },
140
+ /**
141
+ * Whether this body's collider takes part in physics right now.
142
+ *
143
+ * A melee hitbox is off most of the time and live for a few frames of a
144
+ * swing, and there was no way to say so: games expressed it by REPLACING
145
+ * `collider` with `{}`, which tears the rigid body down and builds a new
146
+ * one twice per swing, and makes the engine warn `has no collider —
147
+ * physics skips it` about a scene that is correct. Keep the real collider
148
+ * and turn it off.
149
+ *
150
+ * Off means no contacts and no `triggerEnter`/`triggerExit`. The body
151
+ * itself stays, so nothing is rebuilt and re-arming is one call.
152
+ */
153
+ enabled: { default: true }
154
+ };
139
155
  /** The unified collision model: every collider participant can emit these. */
140
156
  static signals = ["triggerEnter", "triggerExit"];
141
157
  _collider = {};
142
158
  /** @internal Physics resync flag — raised when `collider` is REPLACED. */
143
159
  _colliderChanged = false;
160
+ _enabled = true;
161
+ /** @internal Physics resync flag — raised when `enabled` is written. */
162
+ _enabledChanged = false;
144
163
  /**
145
164
  * Collider shape prop. Change it by REPLACING the object
146
165
  * (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
@@ -153,6 +172,15 @@ var PhysicsBody2D = class extends Node2D {
153
172
  this._collider = value;
154
173
  this._colliderChanged = true;
155
174
  }
175
+ /** See the `enabled` prop. Writing it arms or disarms the existing collider. */
176
+ get enabled() {
177
+ return this._enabled;
178
+ }
179
+ set enabled(value) {
180
+ if (value === this._enabled) return;
181
+ this._enabled = value;
182
+ this._enabledChanged = true;
183
+ }
156
184
  /** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
157
185
  static validateJson(node) {
158
186
  validateCollider2D(node.collider, node.name);
@@ -825,11 +853,12 @@ var Label = class extends Node2D {
825
853
  _syncObject2D(assets) {
826
854
  super._syncObject2D(assets);
827
855
  const quad = this._quad();
828
- if (typeof document === "undefined" || this.text === "") {
856
+ const text = translateOn(this, this.text);
857
+ if (typeof document === "undefined" || text === "") {
829
858
  quad.visible = false;
830
859
  return;
831
860
  }
832
- const key = `${this.text}\0${this.fontSize}\0${this.color}\0${this.font}\0${this.align}`;
861
+ const key = `${text}\0${this.fontSize}\0${this.color}\0${this.font}\0${this.align}`;
833
862
  if (key !== this.lastKey) {
834
863
  this.lastKey = key;
835
864
  this.rasterize();
@@ -838,13 +867,14 @@ var Label = class extends Node2D {
838
867
  quad.renderOrder = this.effectiveRenderOrder;
839
868
  }
840
869
  rasterize() {
870
+ const text = translateOn(this, this.text);
841
871
  const dpr = Math.min(globalThis.devicePixelRatio ?? 1, 2);
842
872
  this.canvas ??= document.createElement("canvas");
843
873
  const ctx = this.canvas.getContext("2d");
844
874
  if (!ctx) return;
845
875
  const fontSpec = `${this.fontSize * dpr}px ${this.font}`;
846
876
  ctx.font = fontSpec;
847
- const metrics = ctx.measureText(this.text);
877
+ const metrics = ctx.measureText(text);
848
878
  const w = Math.max(1, Math.ceil(metrics.width));
849
879
  const h = Math.ceil(this.fontSize * dpr * 1.4);
850
880
  this.canvas.width = w;
@@ -852,7 +882,7 @@ var Label = class extends Node2D {
852
882
  ctx.font = fontSpec;
853
883
  ctx.fillStyle = this.color;
854
884
  ctx.textBaseline = "middle";
855
- ctx.fillText(this.text, 0, h / 2);
885
+ ctx.fillText(text, 0, h / 2);
856
886
  const quad = this._quad();
857
887
  const mat = quad.material;
858
888
  mat.map?.dispose();