incanto 0.53.0 → 0.55.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 (57) hide show
  1. package/bin/incanto-check.mjs +56 -1
  2. package/bin/incanto-verify.mjs +46 -0
  3. package/dist/2d.d.ts +46 -3
  4. package/dist/2d.js +3 -3
  5. package/dist/3d.d.ts +105 -6
  6. package/dist/3d.js +10 -6
  7. package/dist/{behavior-Do0Da56m.d.ts → behavior-CyQoSu4n.d.ts} +114 -10
  8. package/dist/{create-game-9D87XaiX.js → create-game-BRt6XKmP.js} +10 -6
  9. package/dist/{create-game-CDJ1lVqK.js → create-game-CzK9_pzg.js} +10 -6
  10. package/dist/debug.d.ts +1 -1
  11. package/dist/debug.js +4 -2
  12. package/dist/editor.js +7 -0
  13. package/dist/{environment-presets-SkGanr2s.js → environment-presets-DAbEdEwh.js} +326 -11
  14. package/dist/{gameplay-CRYw_Q-T.js → gameplay-CZ2yq37J.js} +30 -21
  15. package/dist/gameplay.d.ts +8 -3
  16. package/dist/gameplay.js +1 -1
  17. package/dist/index.d.ts +6 -6
  18. package/dist/index.js +5 -5
  19. package/dist/{loader-Cu_7kJDy.d.ts → loader-DhI1jFW_.d.ts} +1 -1
  20. package/dist/net.d.ts +2 -2
  21. package/dist/net.js +1 -1
  22. package/dist/{particle-sim-BzJ1yxoE.d.ts → particle-sim-C5OfBbmU.d.ts} +11 -0
  23. package/dist/{pathfinding-HNGFqGUZ.d.ts → pathfinding-CXGCpRQe.d.ts} +1 -1
  24. package/dist/{physics-2d-BhZZ-HAp.js → physics-2d-CfWAggJ1.js} +1 -1
  25. package/dist/{physics-3d-DmyO2oaN.js → physics-3d-C_ZJ6f_d.js} +2 -2
  26. package/dist/react.d.ts +1 -1
  27. package/dist/react.js +1 -1
  28. package/dist/{register-DROK2l7J.js → register-en63AEZO.js} +163 -4
  29. package/dist/{register-BTomIiYG.js → register-p48lHE2o.js} +130 -13
  30. package/dist/{replay-9Fy6C10F.d.ts → replay-ePMz26jw.d.ts} +1 -1
  31. package/dist/{replay-DYNUL4BU.js → replay-t1pP0gQg.js} +1 -1
  32. package/dist/{split-screen-DNmcX1Pz.d.ts → split-screen-BsdOHbzP.d.ts} +1 -1
  33. package/dist/{split-screen-CaF9hO7g.js → split-screen-CSb_uZ6W.js} +1 -1
  34. package/dist/{sprite-animation-D_p28jwU.js → sprite-animation-7qvUxF6Z.js} +19 -0
  35. package/dist/{src-C9xyZW7M.js → src-BVOVHRL0.js} +1 -1
  36. package/dist/{teardown-Bw2aeyGI.js → teardown-C7uVSJvx.js} +1 -1
  37. package/dist/{test-CJRsciFk.js → test-Ca5eqELC.js} +49 -14
  38. package/dist/test.d.ts +20 -4
  39. package/dist/test.js +2 -2
  40. package/dist/vite.js +2 -2
  41. package/editor/assets/{agent8-PlFHzJsh.js → agent8-Cz4oHPtm.js} +1 -1
  42. package/editor/assets/{debug-CebV7CDW.js → debug-r-Fi9nZh.js} +2 -2
  43. package/editor/assets/{index-CdbsY31G.js → index-BfqmvXYD.js} +91 -91
  44. package/editor/index.html +1 -1
  45. package/package.json +1 -1
  46. package/schemas/scene.schema.json +185 -0
  47. package/skills/incanto-assets.md +45 -2
  48. package/skills/incanto-building-2d-games.md +35 -0
  49. package/skills/incanto-building-3d-games.md +28 -3
  50. package/skills/incanto-environment.md +3 -1
  51. package/skills/incanto-gameplay-behaviors.md +51 -4
  52. package/skills/incanto-node-reference.md +31 -0
  53. package/skills/incanto-performance.md +51 -0
  54. package/skills/incanto-verifying-your-game.md +38 -0
  55. package/templates-app/beacon-isle-3d/package.json +1 -1
  56. package/templates-app/tps-3d/package.json +1 -1
  57. package/templates-app/village-quest-3d/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { $ as T_PREFIX, A as SettingsValues, At as LogEntry, B as captureBehaviors, C as EngineStats, Ct as MusicTrack, Dt as Node, E as DeviceHints, Et as BusName, F as BehaviorState, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, J as effectiveOrder, K as OrderGroup, L as SaveSlot, M as readDeviceHints, Mt as LogManager, N as suggestQuality, Nt as Signal, O as QualityTier, Ot as NodeLifecycle, P as Scene, Pt as SignalListener, Q as Localization, R as SaveSlots, S as Scheduler, St as MusicManager, T as RendererStats, Tt as AudioBuses, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as SfxParams, a as clearBehaviors, at as isAudioContextAvailable, b as Engine, bt as synthSfx, c as registeredBehaviors, ct as Listener, d as PropSchema, dt as SpatialParams, et as suggestLocale, f as clearRegistry, ft as Vec3, g as getNodeType, gt as SFX_PRESET_NAMES, h as getNodeSignals, ht as SFX_PRESETS, i as behaviorSignals, it as SfxPlayOptions, j as qualityEnvironment, jt as LogLevel, k as Settings, kt as SceneTree, l as NodeCtor, lt as ROLLOFF_MODELS, m as getNodeSchema, mt as spatialPan, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as Voice, p as createNode, pt as spatialGain, q as OrderGroupTable, r as behaviorSchema, rt as SfxEngine, s as registerBehavior, st as VoicePreset, t as Behavior, tt as translationKey, u as PropDef, ut as RolloffModel, v as registerNode, vt as SfxWave, w as GameStats, wt as PlayMusicOptions, x as EngineOptions, xt as MusicBackend, y as registeredTypes, yt as SynthOptions, z as behaviorsWithoutSave } from "./behavior-Do0Da56m.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-CyQoSu4n.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-Cu_7kJDy.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-DhI1jFW_.js";
4
4
  import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js";
5
- import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
6
- import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-HNGFqGUZ.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-9Fy6C10F.js";
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-CXGCpRQe.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-ePMz26jw.js";
8
8
 
9
9
  //#region src/core/audio/crossfade.d.ts
10
10
  /**
@@ -1089,4 +1089,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
1089
1089
  /** Engine version. Kept in sync with package.json by the release pipeline. */
1090
1090
  declare const VERSION: string;
1091
1091
  //#endregion
1092
- export { type AnimationDef, type AnimationEntry, type AssetFailure, 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, type DriveScript, type DriveStep, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, 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, type OrderGroupTable, 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 RefNode, type RefProblem, type RefResult, 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, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, 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, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, 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 };
1092
+ export { type AnimationDef, type AnimationEntry, type AssetFailure, 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, type DriveScript, type DriveStep, type EffectEvent, type EffectKind, EffectLog, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, 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, type OrderGroupTable, 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 RefNode, type RefProblem, type RefResult, 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, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, 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, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, 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 };
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { A as registerBehavior, D as behaviorSignals, E as behaviorSchema, M as Signal, O as clearBehaviors, S as InputMap, T as Behavior, _ as savesWithoutUid, a as SCENE_FORMAT, b as effectiveOrder, c as SceneTree, d as resolveConstants, f as Node, g as restoreBehaviors, h as captureBehaviors, i as serializeNode, j as registeredBehaviors, k as getBehavior, l as CONST_REF_KEY, m as behaviorsWithoutSave, n as loadScene, o as computeViewport, p as SaveSlots, r as Scene, s as resolveViewport, u as isConstRef, v as createSaveStore, w as parseNodePath, x as resolveOrderGroups, y as ORDER_GROUP_BASE } from "./loader-BTkHYrQn.js";
2
- import { A as Localization, B as SFX_PRESET_NAMES, D as suggestQuality, E as readDeviceHints, F as isAudioContextAvailable, G as fadeGain, H as MusicManager, I as ROLLOFF_MODELS, K as AudioBuses, L as spatialGain, M as suggestLocale, N as translationKey, O as LogManager, P as SfxEngine, R as spatialPan, S as Settings, U as WebAudioMusicBackend, V as synthSfx, W as crossfadeGains, _ as UiBanner, a as UiMuteToggle, b as AudioPlayer, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as HudLayer, h as UiDialogue, i as UiLanguageSelect, j as T_PREFIX, k as BASE_LOCALE, l as UiSelect, m as UiButton, n as UiFrameCapSelect, o as UiPanel, p as Timer, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as UiBar, w as qualityEnvironment, x as Engine, y as UiText, z as SFX_PRESETS } from "./register-BTomIiYG.js";
2
+ import { A as BASE_LOCALE, B as SFX_PRESETS, D as suggestQuality, E as readDeviceHints, F as SfxEngine, G as crossfadeGains, H as synthSfx, I as isAudioContextAvailable, K as fadeGain, L as ROLLOFF_MODELS, M as T_PREFIX, N as suggestLocale, O as LogManager, P as translationKey, R as spatialGain, S as Settings, U as MusicManager, V as SFX_PRESET_NAMES, W as WebAudioMusicBackend, _ as UiBanner, a as UiMuteToggle, b as AudioPlayer, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as HudLayer, h as UiDialogue, i as UiLanguageSelect, j as Localization, k as EffectLog, l as UiSelect, m as UiButton, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as AudioBuses, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as UiBar, w as qualityEnvironment, x as Engine, y as UiText, z as spatialPan } from "./register-p48lHE2o.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
6
6
  import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-C7u42TID.js";
7
- import { a as nodeRefWarnings, i as describeRefProblem, n as startRecording, o as resolveRefInJson, r as auditScene, t as replay } from "./replay-DYNUL4BU.js";
7
+ import { a as nodeRefWarnings, i as describeRefProblem, n as startRecording, o as resolveRefInJson, r as auditScene, t as replay } from "./replay-t1pP0gQg.js";
8
8
  import { n as logText, r as parseDrive, t as logReport } from "./log-report-CPFm4OXf.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-D_p28jwU.js";
11
- import { a as findPath, i as preloadUrls, n as newUid, o as gridFromRows, r as assetUrls, t as VERSION } from "./src-C9xyZW7M.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-7qvUxF6Z.js";
11
+ import { a as findPath, i as preloadUrls, n as newUid, o as gridFromRows, r as assetUrls, t as VERSION } from "./src-BVOVHRL0.js";
12
12
  import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-BnMyy9tr.js";
13
13
  import { t as duplicateNode } from "./duplicate-MNLMAcbz.js";
14
- 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, 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, 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 };
14
+ export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, 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, 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,4 @@
1
- import { P as Scene, b as Engine } from "./behavior-Do0Da56m.js";
1
+ import { P as Scene, b as Engine } from "./behavior-CyQoSu4n.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 { Dt as Node, d as PropSchema } from "./behavior-Do0Da56m.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-DNmcX1Pz.js";
1
+ import { At as Node, d as PropSchema } from "./behavior-CyQoSu4n.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-BsdOHbzP.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-CaF9hO7g.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-CSb_uZ6W.js";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -49,6 +49,17 @@ declare class ParticleSim {
49
49
  private spawnAccumulator;
50
50
  private everSpawned;
51
51
  constructor(config: ParticleSimConfig, rng: Rng);
52
+ /**
53
+ * Shift every live particle — how world-space emission is done.
54
+ *
55
+ * The pool is in the emitter's LOCAL space, so a moving emitter drags its
56
+ * whole plume with it: dust glued to a running player instead of left behind,
57
+ * and the documented "move the emitter and replay" recipe for a one-shot
58
+ * teleporting the previous explosion across the level. Counter-translating by
59
+ * the emitter's own motion each frame leaves the particles where they were
60
+ * born, exactly, and costs one pass over the live ones.
61
+ */
62
+ translateAll(dx: number, dy: number, dz: number): void;
52
63
  /** Change the emission rate LIVE (particles/sec; 0 pauses emission) — lets an
53
64
  * emitter toggle on/off at runtime (drift smoke, throttle flames). */
54
65
  setRate(rate: number): void;
@@ -1,4 +1,4 @@
1
- import { Dt as Node, ct as Listener, d as PropSchema } from "./behavior-Do0Da56m.js";
1
+ import { At as Node, d as PropSchema, dt as Listener } from "./behavior-CyQoSu4n.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
2
  import { C as diagnose } from "./loader-BTkHYrQn.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-DROK2l7J.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-en63AEZO.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({
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { C as diagnose } from "./loader-BTkHYrQn.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { B as validateCollider3D, F as CharacterBody3D, I as PhysicsBody3D, L as RigidBody3D, P as Area3D, z as Node3D } from "./gameplay-CRYw_Q-T.js";
4
+ import { B as validateCollider3D, F as CharacterBody3D, I as PhysicsBody3D, L as RigidBody3D, P as Area3D, z as Node3D } from "./gameplay-CZ2yq37J.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
- import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-SkGanr2s.js";
6
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-DAbEdEwh.js";
7
7
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
8
8
  //#region src/3d/physics/collider-lines.ts
9
9
  /**
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-Do0Da56m.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-CyQoSu4n.js";
2
2
  import { c as JsonValue } from "./schema-CFeioQRE.js";
3
3
  import { CSSProperties, ReactNode } from "react";
4
4
 
package/dist/react.js CHANGED
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
156
156
  pointer: latest.pointer,
157
157
  ...keyboard !== void 0 ? { keyboard } : {}
158
158
  };
159
- const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-CDJ1lVqK.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-9D87XaiX.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-CzK9_pzg.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-BRt6XKmP.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 { b as effectiveOrder, f as Node } from "./loader-BTkHYrQn.js";
2
- import { t as registerCoreNodes } from "./register-BTomIiYG.js";
1
+ import { C as diagnose, b as effectiveOrder, f as Node } from "./loader-BTkHYrQn.js";
2
+ import { t as registerCoreNodes } from "./register-p48lHE2o.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { 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-D_p28jwU.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-7qvUxF6Z.js";
7
7
  import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry } from "three";
8
8
  //#region src/2d/physics/collider-validate.ts
9
9
  /**
@@ -305,6 +305,59 @@ var Joint2D = class extends Node2D {
305
305
  }
306
306
  };
307
307
  //#endregion
308
+ //#region src/2d/sheet-grid.ts
309
+ /**
310
+ * Does this spritesheet's grid actually fit the image?
311
+ *
312
+ * `frameWidth`/`frameHeight` are taken on trust, and the frame math floors the
313
+ * division — so a frame size that does not divide the sheet slices every row a
314
+ * little further off, and an animation naming a frame past the end of the grid
315
+ * reads whatever texels are there. Both draw WRONG ART with no error anywhere,
316
+ * which is the one symptom an author cannot tell apart from having picked the
317
+ * wrong sheet in the first place.
318
+ *
319
+ * Pure arithmetic on numbers that are known the moment the image loads, so it
320
+ * costs nothing and it is exact.
321
+ */
322
+ /**
323
+ * Frame sizes that divide `total` cleanly, biggest first.
324
+ *
325
+ * Bounded to grids of 2–32 cells so the suggestion is a frame size someone
326
+ * might actually have meant, not every factor of the number.
327
+ */
328
+ function divisors(total) {
329
+ const out = [];
330
+ for (let cells = 2; cells <= 32; cells++) if (total % cells === 0) out.push(total / cells);
331
+ return out.sort((a, b) => b - a).slice(0, 6);
332
+ }
333
+ /**
334
+ * The problem with this grid, or null when there is none.
335
+ *
336
+ * `highestFrame` is the largest frame index the scene asks for, when it is
337
+ * known — an animation range that runs off the end is the other half of the
338
+ * same mistake.
339
+ */
340
+ function checkSheetGrid(key, frameWidth, frameHeight, imageWidth, imageHeight, highestFrame) {
341
+ if (!imageWidth || !imageHeight || !frameWidth || !frameHeight) return null;
342
+ if (imageWidth % frameWidth !== 0 || imageHeight % frameHeight !== 0) {
343
+ const parts = [];
344
+ if (imageWidth % frameWidth !== 0) {
345
+ const fits = divisors(imageWidth);
346
+ parts.push(`${imageWidth} px wide is not a whole number of ${frameWidth} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
347
+ }
348
+ if (imageHeight % frameHeight !== 0) {
349
+ const fits = divisors(imageHeight);
350
+ parts.push(`${imageHeight} px tall is not a whole number of ${frameHeight} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
351
+ }
352
+ return `[incanto] spritesheet '${key}' does not fit its frame size: ${parts.join("; ")}. Every row after the first is cut off centre.`;
353
+ }
354
+ if (highestFrame !== void 0) {
355
+ const frames = imageWidth / frameWidth * (imageHeight / frameHeight);
356
+ if (highestFrame >= frames) return `[incanto] spritesheet '${key}' has ${frames} frames (0–${frames - 1}), and an animation asks for frame ${highestFrame}. Those frames draw whatever is at the wrong end of the sheet.`;
357
+ }
358
+ return null;
359
+ }
360
+ //#endregion
308
361
  //#region src/2d/nodes/sprite-2d.ts
309
362
  /** Shared unit quad — sprites are sized via the quad's scale, never THREE.Sprite. */
310
363
  const UNIT_PLANE$2 = new PlaneGeometry(1, 1);
@@ -452,6 +505,30 @@ var AnimatedSprite2D = class extends Sprite2D {
452
505
  }
453
506
  this.currentFrame = this.frameList[this.frameIndex] ?? 0;
454
507
  }
508
+ /** Sheets already reported, so a bad grid says so ONCE, not every frame. */
509
+ gridChecked = "";
510
+ /**
511
+ * Tell the author their grid does not fit, the first time it is drawn.
512
+ *
513
+ * Both halves of this were silent: a frame size that does not divide the
514
+ * sheet slices every row further off centre, and an animation naming a frame
515
+ * past the end of the grid draws whatever is at the wrong end of the image.
516
+ */
517
+ checkGrid(ref, frameWidth, frameHeight, width, height) {
518
+ if (this.gridChecked === ref) return;
519
+ this.gridChecked = ref;
520
+ let highest = 0;
521
+ for (const [name, entry] of Object.entries(this.animations)) {
522
+ if (typeof entry === "string") continue;
523
+ try {
524
+ for (const frame of resolveFrames(entry.frames, name)) if (frame > highest) highest = frame;
525
+ } catch {
526
+ return;
527
+ }
528
+ }
529
+ const problem = checkSheetGrid(ref, frameWidth, frameHeight, width, height, highest);
530
+ if (problem) diagnose(this.tree?.engine ?? null, "error", problem);
531
+ }
455
532
  resolveTexture(assets) {
456
533
  if (!this.sheet || !assets) return null;
457
534
  const info = assets.getSheet(this.sheet);
@@ -463,6 +540,7 @@ var AnimatedSprite2D = class extends Sprite2D {
463
540
  const tex = this.ownTexture;
464
541
  const image = info.texture.image;
465
542
  if (!image?.width || !image.height) return null;
543
+ this.checkGrid(this.sheet, info.frameWidth, info.frameHeight, image.width, image.height);
466
544
  const cols = Math.max(1, Math.floor(image.width / info.frameWidth));
467
545
  const col = this.currentFrame % cols;
468
546
  const row = Math.floor(this.currentFrame / cols);
@@ -871,6 +949,8 @@ var Particles2D = class Particles2D extends Node2D {
871
949
  options: ["custom", ...PARTICLE_PRESET_NAMES]
872
950
  },
873
951
  emitting: { default: true },
952
+ /** Particles keep the place they were born instead of following the node. */
953
+ worldSpace: { default: false },
874
954
  rate: { default: 40 },
875
955
  burst: { default: 0 },
876
956
  lifetime: { default: [.6, 1.2] },
@@ -895,6 +975,18 @@ var Particles2D = class Particles2D extends Node2D {
895
975
  };
896
976
  preset = "custom";
897
977
  emitting = true;
978
+ /**
979
+ * Where a particle LIVES once it is born.
980
+ *
981
+ * `false` (default) — the emitter's local space: the plume moves with the
982
+ * node, which is what a torch on a moving platform wants.
983
+ *
984
+ * `true` — the world: a particle keeps the place it was born, so dust is left
985
+ * BEHIND a running player instead of glued to it, and moving a one-shot to
986
+ * the next explosion (the documented `p.position = …; p.replay()` recipe)
987
+ * stops dragging the previous one across the level.
988
+ */
989
+ worldSpace = false;
898
990
  rate = 40;
899
991
  burst = 0;
900
992
  lifetime = [.6, 1.2];
@@ -954,11 +1046,38 @@ var Particles2D = class Particles2D extends Node2D {
954
1046
  }
955
1047
  return this.sim;
956
1048
  }
1049
+ /** Particles alive this frame — an emitter that is running, told from one
1050
+ * that is not. `framing()` says where it IS; this says whether it is going. */
1051
+ get aliveCount() {
1052
+ return this.sim?.count ?? 0;
1053
+ }
1054
+ /** A snapshot of the live particles — positions are in whichever space
1055
+ * `worldSpace` selects, relative to the emitter. */
1056
+ particles() {
1057
+ const out = [];
1058
+ this.sim?.forEach((p) => {
1059
+ out.push({ ...p });
1060
+ });
1061
+ return out;
1062
+ }
1063
+ /** The emitter's world position last frame, for world-space counter-motion. */
1064
+ lastWorld = null;
1065
+ /** Last frame's `emitting`, so turning one ON is a reportable event. */
1066
+ wasEmitting = false;
957
1067
  update(dt) {
958
1068
  const sim = this._ensureSim();
959
- if (this.burst > 0 && !this.bursted) {
1069
+ this.holdWorldSpace(sim);
1070
+ sim.setRate(this.emitting ? this.rate : 0);
1071
+ if (this.burst > 0 && !this.bursted && (this.emitting || this.armedByReplay)) {
960
1072
  this.bursted = true;
1073
+ this.armedByReplay = false;
961
1074
  sim.burst(this.burst);
1075
+ this.report("burst", this.burst);
1076
+ }
1077
+ const on = this.emitting && this.rate > 0;
1078
+ if (on !== this.wasEmitting) {
1079
+ this.wasEmitting = on;
1080
+ if (on) this.report("emit", this.rate);
962
1081
  }
963
1082
  sim.update(dt);
964
1083
  if ((!this.emitting || this.rate === 0) && this.bursted && sim.done && !this.finishedEmitted) {
@@ -966,11 +1085,51 @@ var Particles2D = class Particles2D extends Node2D {
966
1085
  this.emit("finished");
967
1086
  }
968
1087
  }
1088
+ /**
1089
+ * Undo the emitter's own motion on everything already alive.
1090
+ *
1091
+ * Ancestor-summed like the physics helpers, so it follows a moving PARENT
1092
+ * (dust under a player) as well as the node's own position.
1093
+ */
1094
+ holdWorldSpace(sim) {
1095
+ if (!this.worldSpace) {
1096
+ this.lastWorld = null;
1097
+ return;
1098
+ }
1099
+ let x = 0;
1100
+ let y = 0;
1101
+ const z = 0;
1102
+ for (let n = this; n; n = n.parent) if (n instanceof Node2D) {
1103
+ x += n.position[0] ?? 0;
1104
+ y += n.position[1] ?? 0;
1105
+ }
1106
+ const prev = this.lastWorld;
1107
+ if (prev) sim.translateAll(prev[0] - x, prev[1] - y, prev[2] - z);
1108
+ this.lastWorld = [
1109
+ x,
1110
+ y,
1111
+ z
1112
+ ];
1113
+ }
1114
+ /** Tell the engine an effect fired — the visual half of the audio record. */
1115
+ report(kind, amount) {
1116
+ const engine = this.tree?.engine;
1117
+ engine?.effects.record({
1118
+ kind,
1119
+ name: this.preset,
1120
+ from: this.getPath(),
1121
+ amount,
1122
+ at: engine.time
1123
+ });
1124
+ }
969
1125
  /** Restart a one-shot (re-burst + allow finished to fire again). */
970
1126
  replay() {
971
1127
  this.bursted = false;
972
1128
  this.finishedEmitted = false;
1129
+ this.armedByReplay = true;
973
1130
  }
1131
+ /** `replay()` asked for this burst, so `emitting: false` does not hold it. */
1132
+ armedByReplay = false;
974
1133
  mesh = null;
975
1134
  /** Parsed `paletteColors`, reused; rebuilt only when the hex list changes. */
976
1135
  paletteCache = [];
@@ -1156,6 +1156,33 @@ function suggestLocale(available, languages) {
1156
1156
  return "en";
1157
1157
  }
1158
1158
  //#endregion
1159
+ //#region src/core/effects-log.ts
1160
+ const EFFECT_LOG_LIMIT = 200;
1161
+ var EffectLog = class {
1162
+ events = [];
1163
+ /** @internal Called by the effects themselves; games read `recent()`. */
1164
+ record(event) {
1165
+ this.events.push(event);
1166
+ if (this.events.length > EFFECT_LOG_LIMIT) this.events.shift();
1167
+ }
1168
+ /** The last `limit` effects, oldest first. */
1169
+ recent(limit = EFFECT_LOG_LIMIT) {
1170
+ return this.events.slice(Math.max(0, this.events.length - limit));
1171
+ }
1172
+ /** How many times an effect with this name fired (`explosion`, `#ff0000`). */
1173
+ countOf(name) {
1174
+ return this.events.reduce((n, e) => e.name === name ? n + 1 : n, 0);
1175
+ }
1176
+ /** How many effects this node fired — the question a wiring check has. */
1177
+ countFrom(path) {
1178
+ return this.events.reduce((n, e) => e.from === path ? n + 1 : n, 0);
1179
+ }
1180
+ /** Forget what has fired so far (between scenes, or between assertions). */
1181
+ clearLog() {
1182
+ this.events.length = 0;
1183
+ }
1184
+ };
1185
+ //#endregion
1159
1186
  //#region src/core/frame-stats.ts
1160
1187
  /**
1161
1188
  * Rolling window of REAL frame timestamps — the math behind `engine.stats()`
@@ -1250,6 +1277,57 @@ var LogManager = class {
1250
1277
  }
1251
1278
  };
1252
1279
  //#endregion
1280
+ //#region src/core/phase-timing.ts
1281
+ const EMPTY = {
1282
+ fixedMs: 0,
1283
+ updateMs: 0,
1284
+ renderMs: 0,
1285
+ otherMs: 0
1286
+ };
1287
+ var PhaseTimings = class {
1288
+ capacity;
1289
+ fixed = [];
1290
+ update = [];
1291
+ render = [];
1292
+ head = 0;
1293
+ constructor(capacity = 60) {
1294
+ this.capacity = capacity;
1295
+ }
1296
+ /** Bank one frame's engine-side work. */
1297
+ push(fixedMs, updateMs, renderMs) {
1298
+ const i = this.head % this.capacity;
1299
+ this.fixed[i] = fixedMs;
1300
+ this.update[i] = updateMs;
1301
+ this.render[i] = renderMs;
1302
+ this.head += 1;
1303
+ }
1304
+ /** Means over the window; `frameMs` (0 headless) fills in `otherMs`. */
1305
+ read(frameMs) {
1306
+ const n = Math.min(this.head, this.capacity);
1307
+ if (n === 0) return { ...EMPTY };
1308
+ const fixedMs = mean(this.fixed, n);
1309
+ const updateMs = mean(this.update, n);
1310
+ const renderMs = mean(this.render, n);
1311
+ return {
1312
+ fixedMs,
1313
+ updateMs,
1314
+ renderMs,
1315
+ otherMs: Math.max(0, frameMs - fixedMs - updateMs - renderMs)
1316
+ };
1317
+ }
1318
+ clear() {
1319
+ this.fixed.length = 0;
1320
+ this.update.length = 0;
1321
+ this.render.length = 0;
1322
+ this.head = 0;
1323
+ }
1324
+ };
1325
+ function mean(values, n) {
1326
+ let total = 0;
1327
+ for (let i = 0; i < n; i++) total += values[i] ?? 0;
1328
+ return total / n;
1329
+ }
1330
+ //#endregion
1253
1331
  //#region src/core/settings.ts
1254
1332
  const DEFAULTS = {
1255
1333
  master: 1,
@@ -1548,6 +1626,12 @@ var Engine = class {
1548
1626
  * routes its volume through this; games set it for global volume/mute. */
1549
1627
  audio = new AudioBuses();
1550
1628
  /**
1629
+ * What SHOWED — the visual half of the audio record. Particle bursts, camera
1630
+ * shake, screen flash and freeze frames land here, so "did the explosion
1631
+ * fire?" is answerable in a run with no renderer.
1632
+ */
1633
+ effects = new EffectLog();
1634
+ /**
1551
1635
  * Persisted player settings — volume, quality, sensitivity.
1552
1636
  *
1553
1637
  * `createSaveStore` was good and had zero callers, so every game's volume
@@ -1590,6 +1674,10 @@ var Engine = class {
1590
1674
  accumulator = 0;
1591
1675
  disposeScheduler = null;
1592
1676
  frameStats = new FrameStatsRing();
1677
+ /** Where each frame's time went — the breakdown `frameMs` alone cannot give. */
1678
+ phaseTimings = new PhaseTimings();
1679
+ /** This frame's render cost, as reported by whoever renders. */
1680
+ renderMs = 0;
1593
1681
  /**
1594
1682
  * Game-time multiplier: 1 = realtime, 0.5 = slow motion, 0 = frozen
1595
1683
  * (hit-stop / pause). Scales BOTH variable and fixed updates — physics,
@@ -1781,12 +1869,24 @@ var Engine = class {
1781
1869
  this.lastFrameError = null;
1782
1870
  return nodes.length;
1783
1871
  }
1872
+ /**
1873
+ * How long the last render took, in ms — called by whoever renders.
1874
+ *
1875
+ * The engine does not render, so this is the one slice it cannot measure for
1876
+ * itself; without it the cost lands in `otherMs` beside the GC and the two
1877
+ * are indistinguishable, which is the confusion this whole breakdown exists
1878
+ * to end.
1879
+ */
1880
+ noteRenderMs(ms) {
1881
+ this.renderMs = ms;
1882
+ }
1784
1883
  stop() {
1785
1884
  this.disposeScheduler?.();
1786
1885
  this.disposeScheduler = null;
1787
1886
  this.lastMs = null;
1788
1887
  this.accumulator = 0;
1789
1888
  this.frameStats.clear();
1889
+ this.phaseTimings.clear();
1790
1890
  }
1791
1891
  /**
1792
1892
  * Live performance counters, queryable at ANY time. fps/frameMs come from a
@@ -1800,7 +1900,8 @@ var Engine = class {
1800
1900
  frameMs: this.frameStats.frameMs(),
1801
1901
  nodes: this._scene?.tree.root ? countNodes(this._scene.tree.root) : 0,
1802
1902
  running: this.disposeScheduler !== null,
1803
- errors: this.errorCount
1903
+ errors: this.errorCount,
1904
+ phases: this.phaseTimings.read(this.frameStats.frameMs())
1804
1905
  };
1805
1906
  }
1806
1907
  /**
@@ -1834,21 +1935,31 @@ var Engine = class {
1834
1935
  }
1835
1936
  /**
1836
1937
  * Advance exactly ONE fixed step and one variable update (both dt = the
1837
- * fixed step), bypassing the wall-clock accumulator entirely. Drift-free
1838
- * by construction — the unit of time for headless harnesses (incanto/test
1839
- * runScript): every input edge is visible to BOTH fixedUpdate and update
1840
- * exactly once.
1938
+ * fixed step scaled by `timeScale`), bypassing the wall-clock accumulator
1939
+ * entirely. Drift-free by construction — the unit of time for headless
1940
+ * harnesses (incanto/test runScript): every input edge is visible to BOTH
1941
+ * fixedUpdate and update exactly once.
1942
+ *
1943
+ * `ignorePause: true` steps a frame at full scale even while the game is
1944
+ * paused — the debug overlay's single-step button, and nothing else.
1841
1945
  */
1842
- step() {
1946
+ step(opts = {}) {
1843
1947
  const scene = this._scene;
1844
1948
  if (!scene) return;
1845
- this._time += this.fixedStep;
1949
+ const dt = this.fixedStep * (opts.ignorePause ? 1 : this.timeScale);
1950
+ this._time += dt;
1846
1951
  this._unscaledTime += this.fixedStep;
1847
- scene.tree.fixedUpdate(this.fixedStep);
1848
- this.fixedUpdated.emit(this.fixedStep);
1849
- scene.tree.update(this.fixedStep);
1850
- this.music.tick(this.fixedStep);
1851
- this.updated.emit(this.fixedStep);
1952
+ const startedFixed = performance.now();
1953
+ scene.tree.fixedUpdate(dt);
1954
+ this.fixedUpdated.emit(dt);
1955
+ const startedUpdate = performance.now();
1956
+ scene.tree.update(dt);
1957
+ this.music.tick(dt);
1958
+ this.updated.emit(dt);
1959
+ const ended = performance.now();
1960
+ const updateMs = Math.max(0, ended - startedUpdate - this.renderMs);
1961
+ this.phaseTimings.push(startedUpdate - startedFixed, updateMs, this.renderMs);
1962
+ this.renderMs = 0;
1852
1963
  this.input.endFrame();
1853
1964
  }
1854
1965
  /** Advance the loop manually. First call after (re)start only primes the clock. */
@@ -1867,6 +1978,7 @@ var Engine = class {
1867
1978
  if (!scene) return;
1868
1979
  this.accumulator += dt;
1869
1980
  let steps = 0;
1981
+ const startedFixed = performance.now();
1870
1982
  while (this.accumulator >= this.fixedStep && steps < this.maxFixedSteps) {
1871
1983
  scene.tree.fixedUpdate(this.fixedStep);
1872
1984
  this.fixedUpdated.emit(this.fixedStep);
@@ -1874,9 +1986,14 @@ var Engine = class {
1874
1986
  steps += 1;
1875
1987
  }
1876
1988
  if (this.accumulator >= this.fixedStep) this.accumulator %= this.fixedStep;
1989
+ const startedUpdate = performance.now();
1877
1990
  scene.tree.update(dt);
1878
1991
  this.music.tick(dt);
1879
1992
  this.updated.emit(dt);
1993
+ const ended = performance.now();
1994
+ const updateMs = Math.max(0, ended - startedUpdate - this.renderMs);
1995
+ this.phaseTimings.push(startedUpdate - startedFixed, updateMs, this.renderMs);
1996
+ this.renderMs = 0;
1880
1997
  this.input.endFrame();
1881
1998
  }
1882
1999
  };
@@ -3664,4 +3781,4 @@ function registerCoreNodes() {
3664
3781
  registerNode(UiMuteToggle);
3665
3782
  }
3666
3783
  //#endregion
3667
- export { Localization as A, SFX_PRESET_NAMES as B, qualityCaps as C, suggestQuality as D, readDeviceHints as E, isAudioContextAvailable as F, fadeGain as G, MusicManager as H, ROLLOFF_MODELS as I, AudioBuses as K, spatialGain as L, suggestLocale as M, translationKey as N, LogManager as O, SfxEngine as P, spatialPan as R, Settings as S, qualityRendering as T, WebAudioMusicBackend as U, synthSfx as V, crossfadeGains as W, UiBanner as _, UiMuteToggle as a, AudioPlayer as b, UiRenderScaleSelect as c, UiToggle as d, UiVolumeSlider as f, HudLayer as g, UiDialogue as h, UiLanguageSelect as i, T_PREFIX as j, BASE_LOCALE as k, UiSelect as l, UiButton as m, UiFrameCapSelect as n, UiPanel as o, Timer as p, UiImage as r, UiQualitySelect as s, registerCoreNodes as t, UiSlider as u, UiBar as v, qualityEnvironment as w, Engine as x, UiText as y, SFX_PRESETS as z };
3784
+ export { BASE_LOCALE as A, SFX_PRESETS as B, qualityCaps as C, suggestQuality as D, readDeviceHints as E, SfxEngine as F, crossfadeGains as G, synthSfx as H, isAudioContextAvailable as I, fadeGain as K, ROLLOFF_MODELS as L, T_PREFIX as M, suggestLocale as N, LogManager as O, translationKey as P, spatialGain as R, Settings as S, qualityRendering as T, MusicManager as U, SFX_PRESET_NAMES as V, WebAudioMusicBackend as W, UiBanner as _, UiMuteToggle as a, AudioPlayer as b, UiRenderScaleSelect as c, UiToggle as d, UiVolumeSlider as f, HudLayer as g, UiDialogue as h, UiLanguageSelect as i, Localization as j, EffectLog as k, UiSelect as l, UiButton as m, UiFrameCapSelect as n, UiPanel as o, Timer as p, AudioBuses as q, UiImage as r, UiQualitySelect as s, registerCoreNodes as t, UiSlider as u, UiBar as v, qualityEnvironment as w, Engine as x, UiText as y, spatialPan as z };
@@ -1,4 +1,4 @@
1
- import { b as Engine } from "./behavior-Do0Da56m.js";
1
+ import { b as Engine } from "./behavior-CyQoSu4n.js";
2
2
  import { s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/audit.d.ts
@@ -1,5 +1,5 @@
1
1
  import { E as behaviorSchema, w as parseNodePath } from "./loader-BTkHYrQn.js";
2
- import { N as translationKey } from "./register-BTomIiYG.js";
2
+ import { P as translationKey } from "./register-p48lHE2o.js";
3
3
  import { i as getNodeSchema } from "./registry-C7u42TID.js";
4
4
  //#region src/core/node-refs.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { Dt as Node, Nt as Signal, b as Engine } from "./behavior-Do0Da56m.js";
1
+ import { At as Node, It as Signal, b as Engine } from "./behavior-CyQoSu4n.js";
2
2
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/net/types.d.ts
@@ -1,5 +1,5 @@
1
1
  import { C as diagnose, M as Signal, f as Node, n as loadScene, t as buildNodeJson } from "./loader-BTkHYrQn.js";
2
- import { t as registerCoreNodes, x as Engine } from "./register-BTomIiYG.js";
2
+ import { t as registerCoreNodes, x as Engine } from "./register-p48lHE2o.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { l as registerNode } from "./registry-C7u42TID.js";