incanto 0.39.0 → 0.40.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 (41) hide show
  1. package/dist/2d.d.ts +2 -2
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +29 -3
  4. package/dist/3d.js +4 -4
  5. package/dist/{behavior-BNNSIXS5.d.ts → behavior-TA8nySqb.d.ts} +21 -0
  6. package/dist/{create-game-R_8_gTCU.js → create-game-CEKCtnKw.js} +4 -4
  7. package/dist/{create-game-DEtU2PRg.js → create-game-Dc2CEFtD.js} +4 -4
  8. package/dist/debug.d.ts +1 -1
  9. package/dist/{environment-presets-bokhdw_w.js → environment-presets-Z0PkDH62.js} +2 -2
  10. package/dist/{gameplay-BPGomkHR.js → gameplay-CqNfawyn.js} +53 -3
  11. package/dist/gameplay.d.ts +1 -1
  12. package/dist/gameplay.js +1 -1
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +3 -3
  15. package/dist/{loader-mSL65Djg.d.ts → loader-CvSDKC3v.d.ts} +1 -1
  16. package/dist/net.d.ts +1 -1
  17. package/dist/net.js +2 -2
  18. package/dist/{pathfinding-D7W8uqm1.d.ts → pathfinding-_UlKUoox.d.ts} +1 -1
  19. package/dist/{physics-2d-B9ktSWrr.js → physics-2d-CxIqR1cW.js} +1 -1
  20. package/dist/{physics-3d-Cpgc5ZM0.js → physics-3d-CcUaeJXq.js} +2 -2
  21. package/dist/react.d.ts +1 -1
  22. package/dist/react.js +1 -1
  23. package/dist/{register-C2iO_yLv.js → register-CgMO5JBY.js} +1 -1
  24. package/dist/{register-CiXP3-GB.js → register-D5v8iqjZ.js} +1 -1
  25. package/dist/{register-CGf5NKrc.js → register-DMFJI-c_.js} +15 -3
  26. package/dist/{replay-CwdWgz0f.d.ts → replay-DN-kG4va.d.ts} +1 -1
  27. package/dist/{replay-YQ9FW7Yh.js → replay-D_QY5Xm3.js} +1 -1
  28. package/dist/{test-Bf-lUxr2.js → test-CFhY8No9.js} +10 -10
  29. package/dist/test.d.ts +3 -3
  30. package/dist/test.js +2 -2
  31. package/dist/vite.js +1 -1
  32. package/editor/assets/{agent8-DfIcNKb4.js → agent8-COwolmAe.js} +1 -1
  33. package/editor/assets/{debug-ex82CxIM.js → debug-DJSCLmsL.js} +1 -1
  34. package/editor/assets/{index-eVTVArHd.js → index-tRXhfJEm.js} +53 -53
  35. package/editor/index.html +1 -1
  36. package/package.json +1 -1
  37. package/skills/incanto-environment.md +12 -1
  38. package/skills/incanto-performance.md +22 -6
  39. package/templates-app/beacon-isle-3d/package.json +1 -1
  40. package/templates-app/tps-3d/package.json +1 -1
  41. package/templates-app/village-quest-3d/package.json +1 -1
package/dist/2d.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-BNNSIXS5.js";
1
+ import { Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-TA8nySqb.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DVwIGZdK.js";
3
3
  import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
4
- import { t as LoadSceneOptions } from "./loader-mSL65Djg.js";
4
+ import { t as LoadSceneOptions } from "./loader-CvSDKC3v.js";
5
5
  import { t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
6
6
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
7
7
  import * as RapierNs from "@dimforge/rapier2d-compat";
package/dist/2d.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-DEtU2PRg.js";
3
- import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-C2iO_yLv.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-B9ktSWrr.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-Dc2CEFtD.js";
3
+ import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-CgMO5JBY.js";
4
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-CxIqR1cW.js";
5
5
  //#region src/2d/library-sprite.ts
6
6
  /**
7
7
  * Turn a library sprite-animation JSON into a scene-ready spritesheet asset
package/dist/3d.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { D as QualityCaps, Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-BNNSIXS5.js";
1
+ import { D as QualityCaps, Et as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-TA8nySqb.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DVwIGZdK.js";
3
3
  import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
4
- import { t as LoadSceneOptions } from "./loader-mSL65Djg.js";
4
+ import { t as LoadSceneOptions } from "./loader-CvSDKC3v.js";
5
5
  import { t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
6
- import { n as PathGrid, s as SpatialPose } from "./pathfinding-D7W8uqm1.js";
6
+ import { n as PathGrid, s as SpatialPose } from "./pathfinding-_UlKUoox.js";
7
7
  import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
8
8
  import { VRM } from "@pixiv/three-vrm";
9
9
  import { Sky } from "three/examples/jsm/objects/Sky.js";
@@ -3046,6 +3046,32 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3046
3046
  * chose.
3047
3047
  */
3048
3048
  private effectiveQuality;
3049
+ /**
3050
+ * Whether collapsing to the simple shader would still produce a waterline.
3051
+ *
3052
+ * A tier may make a game cheaper; it may not make it look broken. Found by
3053
+ * looking at the four water templates at every tier in a browser: the ocean
3054
+ * at low met the beach in a razor-straight line, because the simple shader's
3055
+ * only shoreline is a fade of its own rectangle and that rectangle's edge was
3056
+ * 800 m out to sea. Big water keeps the fancy shader at low — and still loses
3057
+ * its mirror, so the saving moves rather than disappears.
3058
+ *
3059
+ * This is NOT a veto on the author: a scene that asked for `quality: 'simple'`
3060
+ * gets it at any size. The guardrail only stops a TIER from choosing it.
3061
+ */
3062
+ private canFadeItsOwnRim;
3063
+ /** Whether the planar mirror runs — the tier can take it away, never add it. */
3064
+ private effectiveMirror;
3065
+ /**
3066
+ * How often the reflection cube may refresh, in ms.
3067
+ *
3068
+ * The floor is the whole reason a tier can drop the mirror and keep the
3069
+ * saving: the cube slows to `MIRRORED_CUBE_INTERVAL` only BECAUSE a mirror is
3070
+ * carrying the near reflection, so a capped-away mirror without a floor sends
3071
+ * the cube back to the scene's own interval — six faces of the scene, several
3072
+ * times a second.
3073
+ */
3074
+ private effectiveCubeInterval;
3049
3075
  /** One frame that took no pass; release everything once it has been a while. */
3050
3076
  private noteIdleFrame;
3051
3077
  override free(): void;
package/dist/3d.js CHANGED
@@ -1,8 +1,8 @@
1
- import { A as Water3D, F as PhysicsBody3D, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D, V as WATER_MAX_RIPPLES, j as WATER_CUTOUT_MAX } from "./gameplay-BPGomkHR.js";
2
- import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-bokhdw_w.js";
3
- import { a as Environment3D, c as parseEnvironment3D, d as AssetStore3D, i as syncTree, l as sunDirectionFromElevationAzimuth, o as setEnvironment3D, r as Renderer3D, s as horizonColorFromSky, t as createGame3D, u as sunDirectionFromSky } from "./create-game-R_8_gTCU.js";
1
+ import { A as Water3D, F as PhysicsBody3D, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D, V as WATER_MAX_RIPPLES, j as WATER_CUTOUT_MAX } from "./gameplay-CqNfawyn.js";
2
+ import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-Z0PkDH62.js";
3
+ import { a as Environment3D, c as parseEnvironment3D, d as AssetStore3D, i as syncTree, l as sunDirectionFromElevationAzimuth, o as setEnvironment3D, r as Renderer3D, s as horizonColorFromSky, t as createGame3D, u as sunDirectionFromSky } from "./create-game-CEKCtnKw.js";
4
4
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
5
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-Cpgc5ZM0.js";
5
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-CcUaeJXq.js";
6
6
  //#region src/3d/terrain-nav.ts
7
7
  function buildTerrainNav(terrain, opts) {
8
8
  const cellSize = opts?.cellSize ?? 2;
@@ -1247,6 +1247,27 @@ interface QualityCaps {
1247
1247
  * so the shoreline dissolve, contact foam and depth absorption go with it.
1248
1248
  */
1249
1249
  fancyWater: boolean;
1250
+ /**
1251
+ * Whether a water surface may run its planar MIRROR pass — a whole extra
1252
+ * render of the scene, per water node, per frame.
1253
+ *
1254
+ * The most expensive single thing water does, and the first to go on a phone
1255
+ * that can still afford the rest of the fancy shader. The surface keeps its
1256
+ * cube reflection, so it still mirrors the sky and the far shore; what it
1257
+ * loses is the sharp reflection of nearby geometry.
1258
+ */
1259
+ waterMirror: boolean;
1260
+ /**
1261
+ * Floor (ms) on how often a reflection may be re-rendered. 0 = the scene
1262
+ * decides.
1263
+ *
1264
+ * This exists because of a trap: the reflection cube runs on a SLOW schedule
1265
+ * precisely because a mirror is present. Suppress the mirror without holding
1266
+ * that schedule and the cube starts refreshing at the scene's own interval —
1267
+ * six faces of the scene, several times a second — which hands most of the
1268
+ * saving straight back.
1269
+ */
1270
+ minReflectionIntervalMs: number;
1250
1271
  }
1251
1272
  //#endregion
1252
1273
  //#region src/core/stats.d.ts
@@ -1,13 +1,13 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { n as loadScene, v as diagnose, w as registerBehavior } from "./loader-r49nDwB4.js";
3
- import { _ as qualityCaps, h as Engine, m as AudioPlayer, y as qualityRendering } from "./register-CGf5NKrc.js";
3
+ import { _ as qualityCaps, h as Engine, m as AudioPlayer, y as qualityRendering } from "./register-DMFJI-c_.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-BoNg_MnF.js";
6
6
  import { a as poseFromRenderer, i as openBundledEditor, n as crossFade, r as devServerLibrary, t as teardown } from "./teardown-CCtAMDLB.js";
7
- import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-BPGomkHR.js";
7
+ import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-CqNfawyn.js";
8
8
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
9
- import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-bokhdw_w.js";
10
- import { n as enablePhysics3D } from "./physics-3d-Cpgc5ZM0.js";
9
+ import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-Z0PkDH62.js";
10
+ import { n as enablePhysics3D } from "./physics-3d-CcUaeJXq.js";
11
11
  import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
12
12
  import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
13
13
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
@@ -1,13 +1,13 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { n as loadScene, o as computeViewport, s as resolveViewport, v as diagnose, w as registerBehavior } from "./loader-r49nDwB4.js";
3
- import { h as Engine, m as AudioPlayer } from "./register-CGf5NKrc.js";
3
+ import { h as Engine, m as AudioPlayer } from "./register-DMFJI-c_.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-BoNg_MnF.js";
6
6
  import { i as openBundledEditor, n as crossFade, r as devServerLibrary, t as teardown } from "./teardown-CCtAMDLB.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-BPGomkHR.js";
8
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-C2iO_yLv.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-CqNfawyn.js";
8
+ import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-CgMO5JBY.js";
9
9
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
10
- import { n as enablePhysics2D } from "./physics-2d-B9ktSWrr.js";
10
+ import { n as enablePhysics2D } from "./physics-2d-CxIqR1cW.js";
11
11
  import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
12
12
  //#region src/2d/assets.ts
13
13
  /**
package/dist/debug.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { At as LogLevel, T as RendererStats, b as Engine } from "./behavior-BNNSIXS5.js";
1
+ import { At as LogLevel, T as RendererStats, b as Engine } from "./behavior-TA8nySqb.js";
2
2
 
3
3
  //#region src/debug/panel.d.ts
4
4
  /** Minimal document surface the overlay needs (injectable for tests). */
@@ -1,10 +1,10 @@
1
- import { t as registerCoreNodes } from "./register-CGf5NKrc.js";
1
+ import { t as registerCoreNodes } from "./register-DMFJI-c_.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { t as Rng } from "./rng-DP-SR7eg.js";
4
4
  import { i as getNodeSchema, l as registerNode } from "./registry-IyWCGe4q.js";
5
5
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
6
6
  import { i as applyParticlePreset, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
7
- import { A as Water3D, F as PhysicsBody3D, H as colliderFootDrop, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D } from "./gameplay-BPGomkHR.js";
7
+ import { A as Water3D, F as PhysicsBody3D, H as colliderFootDrop, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D } from "./gameplay-CqNfawyn.js";
8
8
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
9
9
  import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, ClampToEdgeWrapping, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NoColorSpace, NormalBlending, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector3, Vector4, VectorKeyframeTrack } from "three";
10
10
  import { clone } from "three/addons/utils/SkeletonUtils.js";
@@ -2833,6 +2833,22 @@ const FANCY_SEGMENTS_MIN = 24;
2833
2833
  const FANCY_SEGMENTS_MAX = 160;
2834
2834
  /** Simple quality: coarse enough to animate every vertex on the CPU. */
2835
2835
  const SIMPLE_SEGMENTS = 32;
2836
+ /**
2837
+ * The fraction of its half-extent over which the simple shader dissolves its
2838
+ * own rim — `smoothstep(0.82, 1.0, …)` in lake.ts, so the outer 18%.
2839
+ */
2840
+ const SIMPLE_EDGE_FADE_FRACTION = .18;
2841
+ /**
2842
+ * How wide that dissolve may be and still read as a SHORE, in metres.
2843
+ *
2844
+ * The simple shader has no depth texture, so fading its own rectangle is the
2845
+ * only waterline it can draw — it dissolves the plane's EDGE, not the place
2846
+ * where the ground rises. Fine when the water fills its basin (a 12 m pond
2847
+ * fades over 1.1 m, a 70 m lake over 6.3 m); useless when it does not (a
2848
+ * 1920 m ocean fades over 173 m, 800 m out to sea from the island it surrounds,
2849
+ * and meets the beach in a razor-straight line with no surf).
2850
+ */
2851
+ const SIMPLE_SHORE_BAND_MAX = 10;
2836
2852
  /** Bodies must rise this far above the waterline to 'exit' (no surface flicker). */
2837
2853
  const SURFACE_HYSTERESIS = .05;
2838
2854
  /** Entry splash amplitude: base + per-(m/s of |vy|), capped. */
@@ -3209,7 +3225,7 @@ var Water3D = class Water3D extends Node3D {
3209
3225
  this.cubeCamera = new CubeCamera(near, 1e3, this.cubeTarget);
3210
3226
  this.lastReflectionAt = 0;
3211
3227
  }
3212
- const cubeInterval = this.mirror ? Math.max(this.rp("reflectionInterval"), MIRRORED_CUBE_INTERVAL) : this.rp("reflectionInterval");
3228
+ const cubeInterval = this.effectiveCubeInterval();
3213
3229
  if (this.lastReflectionAt === 0 || now - this.lastReflectionAt >= cubeInterval) {
3214
3230
  this.lastReflectionAt = now;
3215
3231
  const center = worldTranslationOf(this);
@@ -3225,7 +3241,7 @@ var Water3D = class Water3D extends Node3D {
3225
3241
  u.uUseEnvironmentMap.value = 1;
3226
3242
  }
3227
3243
  }
3228
- if (this.mirror && u.uMirrorMap && u.uMirrorMatrix && u.uUseMirror) {
3244
+ if (this.effectiveMirror() && u.uMirrorMap && u.uMirrorMatrix && u.uUseMirror) {
3229
3245
  ctx.gl.getDrawingBufferSize(drawingBufferScratch);
3230
3246
  const mirrorW = Math.max(1, Math.min(Math.round(drawingBufferScratch.x / MIRROR_PASS_SCALE), MIRROR_PASS_MAX));
3231
3247
  const mirrorH = Math.max(1, Math.min(Math.round(drawingBufferScratch.y / MIRROR_PASS_SCALE), MIRROR_PASS_MAX));
@@ -3423,9 +3439,43 @@ var Water3D = class Water3D extends Node3D {
3423
3439
  * chose.
3424
3440
  */
3425
3441
  effectiveQuality() {
3426
- if (this.caps && !this.caps.fancyWater) return "simple";
3442
+ if (this.caps && !this.caps.fancyWater && this.canFadeItsOwnRim()) return "simple";
3427
3443
  return this.quality;
3428
3444
  }
3445
+ /**
3446
+ * Whether collapsing to the simple shader would still produce a waterline.
3447
+ *
3448
+ * A tier may make a game cheaper; it may not make it look broken. Found by
3449
+ * looking at the four water templates at every tier in a browser: the ocean
3450
+ * at low met the beach in a razor-straight line, because the simple shader's
3451
+ * only shoreline is a fade of its own rectangle and that rectangle's edge was
3452
+ * 800 m out to sea. Big water keeps the fancy shader at low — and still loses
3453
+ * its mirror, so the saving moves rather than disappears.
3454
+ *
3455
+ * This is NOT a veto on the author: a scene that asked for `quality: 'simple'`
3456
+ * gets it at any size. The guardrail only stops a TIER from choosing it.
3457
+ */
3458
+ canFadeItsOwnRim() {
3459
+ return Math.max(this.size[0] ?? 1, this.size[1] ?? 1) / 2 * SIMPLE_EDGE_FADE_FRACTION <= SIMPLE_SHORE_BAND_MAX;
3460
+ }
3461
+ /** Whether the planar mirror runs — the tier can take it away, never add it. */
3462
+ effectiveMirror() {
3463
+ return this.mirror && (this.caps ? this.caps.waterMirror : true);
3464
+ }
3465
+ /**
3466
+ * How often the reflection cube may refresh, in ms.
3467
+ *
3468
+ * The floor is the whole reason a tier can drop the mirror and keep the
3469
+ * saving: the cube slows to `MIRRORED_CUBE_INTERVAL` only BECAUSE a mirror is
3470
+ * carrying the near reflection, so a capped-away mirror without a floor sends
3471
+ * the cube back to the scene's own interval — six faces of the scene, several
3472
+ * times a second.
3473
+ */
3474
+ effectiveCubeInterval() {
3475
+ const authored = this.rp("reflectionInterval");
3476
+ const base = this.effectiveMirror() ? Math.max(authored, MIRRORED_CUBE_INTERVAL) : authored;
3477
+ return Math.max(base, this.caps?.minReflectionIntervalMs ?? 0);
3478
+ }
3429
3479
  /** One frame that took no pass; release everything once it has been a while. */
3430
3480
  noteIdleFrame() {
3431
3481
  this.idleFrames += 1;
@@ -1,4 +1,4 @@
1
- import { Et as Node, b as Engine, d as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-BNNSIXS5.js";
1
+ import { Et as Node, b as Engine, d as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-TA8nySqb.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 { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-BPGomkHR.js";
1
+ import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-CqNfawyn.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { $ as suggestLocale, A as SettingsValues, At as LogLevel, B as captureBehaviors, C as EngineStats, Ct as PlayMusicOptions, Dt as NodeLifecycle, E as DeviceHints, Et as Node, F as BehaviorState, G as OrderGroup, H as SaveStore, I as RestoreReport, J as resolveOrderGroups, K as OrderGroupTable, L as SaveSlot, M as readDeviceHints, Mt as Signal, N as suggestQuality, Nt as SignalListener, O as QualityTier, Ot as SceneTree, P as Scene, Q as T_PREFIX, R as SaveSlots, S as Scheduler, St as MusicTrack, T as RendererStats, Tt as BusName, U as createSaveStore, V as restoreBehaviors, W as ORDER_GROUP_BASE, X as LocaleTables, Y as BASE_LOCALE, Z as Localization, _ as mergeStaticSignals, _t as SfxWave, a as clearBehaviors, at as Voice, b as Engine, bt as MusicBackend, c as registeredBehaviors, ct as ROLLOFF_MODELS, d as PropSchema, dt as Vec3, et as translationKey, f as clearRegistry, ft as spatialGain, g as getNodeType, gt as SfxParams, h as getNodeSignals, ht as SFX_PRESET_NAMES, i as behaviorSignals, it as isAudioContextAvailable, j as qualityEnvironment, jt as LogManager, k as Settings, kt as LogEntry, l as NodeCtor, lt as RolloffModel, m as getNodeSchema, mt as SFX_PRESETS, n as BehaviorCtor, nt as SfxEngine, o as getBehavior, ot as VoicePreset, p as createNode, pt as spatialPan, q as effectiveOrder, r as behaviorSchema, rt as SfxPlayOptions, s as registerBehavior, st as Listener, t as Behavior, tt as InputMap, u as PropDef, ut as SpatialParams, v as registerNode, vt as SynthOptions, w as GameStats, wt as AudioBuses, x as EngineOptions, xt as MusicManager, y as registeredTypes, yt as synthSfx, z as behaviorsWithoutSave } from "./behavior-BNNSIXS5.js";
1
+ import { $ as suggestLocale, A as SettingsValues, At as LogLevel, B as captureBehaviors, C as EngineStats, Ct as PlayMusicOptions, Dt as NodeLifecycle, E as DeviceHints, Et as Node, F as BehaviorState, G as OrderGroup, H as SaveStore, I as RestoreReport, J as resolveOrderGroups, K as OrderGroupTable, L as SaveSlot, M as readDeviceHints, Mt as Signal, N as suggestQuality, Nt as SignalListener, O as QualityTier, Ot as SceneTree, P as Scene, Q as T_PREFIX, R as SaveSlots, S as Scheduler, St as MusicTrack, T as RendererStats, Tt as BusName, U as createSaveStore, V as restoreBehaviors, W as ORDER_GROUP_BASE, X as LocaleTables, Y as BASE_LOCALE, Z as Localization, _ as mergeStaticSignals, _t as SfxWave, a as clearBehaviors, at as Voice, b as Engine, bt as MusicBackend, c as registeredBehaviors, ct as ROLLOFF_MODELS, d as PropSchema, dt as Vec3, et as translationKey, f as clearRegistry, ft as spatialGain, g as getNodeType, gt as SfxParams, h as getNodeSignals, ht as SFX_PRESET_NAMES, i as behaviorSignals, it as isAudioContextAvailable, j as qualityEnvironment, jt as LogManager, k as Settings, kt as LogEntry, l as NodeCtor, lt as RolloffModel, m as getNodeSchema, mt as SFX_PRESETS, n as BehaviorCtor, nt as SfxEngine, o as getBehavior, ot as VoicePreset, p as createNode, pt as spatialPan, q as effectiveOrder, r as behaviorSchema, rt as SfxPlayOptions, s as registerBehavior, st as Listener, t as Behavior, tt as InputMap, u as PropDef, ut as SpatialParams, v as registerNode, vt as SynthOptions, w as GameStats, wt as AudioBuses, x as EngineOptions, xt as MusicManager, y as registeredTypes, yt as synthSfx, z as behaviorsWithoutSave } from "./behavior-TA8nySqb.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-mSL65Djg.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-CvSDKC3v.js";
4
4
  import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
5
- import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-D7W8uqm1.js";
6
- 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-CwdWgz0f.js";
5
+ import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-_UlKUoox.js";
6
+ 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-DN-kG4va.js";
7
7
 
8
8
  //#region src/core/audio/crossfade.d.ts
9
9
  /**
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { C as getBehavior, E as Signal, S as clearBehaviors, T as registeredBehaviors, _ as InputMap, a as SCENE_FORMAT, b as behaviorSchema, c as SceneTree, d as resolveConstants, f as Node, g as resolveOrderGroups, h as effectiveOrder, i as serializeNode, l as CONST_REF_KEY, m as ORDER_GROUP_BASE, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, w as registerBehavior, x as behaviorSignals, y as Behavior } from "./loader-r49nDwB4.js";
2
- import { A as T_PREFIX, B as synthSfx, C as behaviorsWithoutSave, D as LogManager, E as createSaveStore, F as ROLLOFF_MODELS, G as AudioBuses, H as WebAudioMusicBackend, I as spatialGain, L as spatialPan, M as translationKey, N as SfxEngine, O as BASE_LOCALE, P as isAudioContextAvailable, R as SFX_PRESETS, S as SaveSlots, T as restoreBehaviors, U as crossfadeGains, V as MusicManager, W as fadeGain, a as UiSlider, b as readDeviceHints, c as UiButton, d as UiBanner, f as UiBar, g as Settings, h as Engine, i as UiSelect, j as suggestLocale, k as Localization, l as UiDialogue, m as AudioPlayer, n as UiImage, o as UiToggle, p as UiText, r as UiPanel, s as Timer, t as registerCoreNodes, u as HudLayer, v as qualityEnvironment, w as captureBehaviors, x as suggestQuality, z as SFX_PRESET_NAMES } from "./register-CGf5NKrc.js";
2
+ import { A as T_PREFIX, B as synthSfx, C as behaviorsWithoutSave, D as LogManager, E as createSaveStore, F as ROLLOFF_MODELS, G as AudioBuses, H as WebAudioMusicBackend, I as spatialGain, L as spatialPan, M as translationKey, N as SfxEngine, O as BASE_LOCALE, P as isAudioContextAvailable, R as SFX_PRESETS, S as SaveSlots, T as restoreBehaviors, U as crossfadeGains, V as MusicManager, W as fadeGain, a as UiSlider, b as readDeviceHints, c as UiButton, d as UiBanner, f as UiBar, g as Settings, h as Engine, i as UiSelect, j as suggestLocale, k as Localization, l as UiDialogue, m as AudioPlayer, n as UiImage, o as UiToggle, p as UiText, r as UiPanel, s as Timer, t as registerCoreNodes, u as HudLayer, v as qualityEnvironment, w as captureBehaviors, x as suggestQuality, z as SFX_PRESET_NAMES } from "./register-DMFJI-c_.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 startRecording, r as auditScene, t as replay } from "./replay-YQ9FW7Yh.js";
5
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-D_QY5Xm3.js";
6
6
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
7
7
  import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-IyWCGe4q.js";
8
8
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
@@ -172,6 +172,6 @@ function newUid() {
172
172
  //#endregion
173
173
  //#region src/index.ts
174
174
  /** Engine version. Kept in sync with package.json by the release pipeline. */
175
- const VERSION = "0.39.0";
175
+ const VERSION = "0.40.0";
176
176
  //#endregion
177
177
  export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveOrderGroups, resolveRendering, resolveViewport, restoreBehaviors, serializeNode, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine } from "./behavior-BNNSIXS5.js";
1
+ import { P as Scene, b as Engine } from "./behavior-TA8nySqb.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,4 +1,4 @@
1
- import { Et as Node, Mt as Signal, b as Engine, d as PropSchema } from "./behavior-BNNSIXS5.js";
1
+ import { Et as Node, Mt as Signal, b as Engine, d as PropSchema } from "./behavior-TA8nySqb.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
package/dist/net.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { n as loadScene } from "./loader-r49nDwB4.js";
2
- import { h as Engine } from "./register-CGf5NKrc.js";
2
+ import { h as Engine } from "./register-DMFJI-c_.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
6
- import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-CiXP3-GB.js";
6
+ import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-D5v8iqjZ.js";
7
7
  //#region src/net/loopback.ts
8
8
  /**
9
9
  * In-memory multiplayer hub implementing the SAME kernel contract as
@@ -1,4 +1,4 @@
1
- import { Et as Node, d as PropSchema, st as Listener } from "./behavior-BNNSIXS5.js";
1
+ import { Et as Node, d as PropSchema, st as Listener } from "./behavior-TA8nySqb.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 { v as diagnose } from "./loader-r49nDwB4.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-C2iO_yLv.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-CgMO5JBY.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 { v as diagnose } from "./loader-r49nDwB4.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-BPGomkHR.js";
4
+ import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-CqNfawyn.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-bokhdw_w.js";
6
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-Z0PkDH62.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-BNNSIXS5.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-TA8nySqb.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-R_8_gTCU.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-DEtU2PRg.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-CEKCtnKw.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-Dc2CEFtD.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1,5 +1,5 @@
1
1
  import { f as Node, h as effectiveOrder } from "./loader-r49nDwB4.js";
2
- import { t as registerCoreNodes } from "./register-CGf5NKrc.js";
2
+ import { t as registerCoreNodes } from "./register-DMFJI-c_.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-IyWCGe4q.js";
@@ -1,5 +1,5 @@
1
1
  import { E as Signal, f as Node, t as buildNodeJson } from "./loader-r49nDwB4.js";
2
- import { t as registerCoreNodes } from "./register-CGf5NKrc.js";
2
+ import { t as registerCoreNodes } from "./register-DMFJI-c_.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-IyWCGe4q.js";
@@ -1599,9 +1599,21 @@ function qualityEnvironment(tier) {
1599
1599
  };
1600
1600
  }
1601
1601
  const QUALITY_CAPS = Object.freeze({
1602
- low: Object.freeze({ fancyWater: false }),
1603
- medium: Object.freeze({ fancyWater: true }),
1604
- high: Object.freeze({ fancyWater: true })
1602
+ low: Object.freeze({
1603
+ fancyWater: false,
1604
+ waterMirror: false,
1605
+ minReflectionIntervalMs: 2e3
1606
+ }),
1607
+ medium: Object.freeze({
1608
+ fancyWater: true,
1609
+ waterMirror: false,
1610
+ minReflectionIntervalMs: 2e3
1611
+ }),
1612
+ high: Object.freeze({
1613
+ fancyWater: true,
1614
+ waterMirror: true,
1615
+ minReflectionIntervalMs: 0
1616
+ })
1605
1617
  });
1606
1618
  /**
1607
1619
  * The caps for a tier — the same frozen object every call.
@@ -1,4 +1,4 @@
1
- import { b as Engine } from "./behavior-BNNSIXS5.js";
1
+ import { b as Engine } from "./behavior-TA8nySqb.js";
2
2
  import { s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/audit.d.ts
@@ -1,4 +1,4 @@
1
- import { M as translationKey } from "./register-CGf5NKrc.js";
1
+ import { M as translationKey } from "./register-DMFJI-c_.js";
2
2
  //#region src/core/audit.ts
3
3
  const ANIMATED_TYPES = new Set([
4
4
  "Particles2D",
@@ -1,13 +1,13 @@
1
1
  import { n as loadScene, w as registerBehavior } from "./loader-r49nDwB4.js";
2
- import { h as Engine } from "./register-CGf5NKrc.js";
2
+ import { h as Engine } from "./register-DMFJI-c_.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { n as startRecording } from "./replay-YQ9FW7Yh.js";
4
+ import { n as startRecording } from "./replay-D_QY5Xm3.js";
5
5
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
6
6
  import { i as getNodeSchema, s as mergeStaticProps } from "./registry-IyWCGe4q.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-BPGomkHR.js";
8
- import { t as registerNodes2D } from "./register-C2iO_yLv.js";
9
- import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-bokhdw_w.js";
10
- import { t as registerNodesNet } from "./register-CiXP3-GB.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-CqNfawyn.js";
8
+ import { t as registerNodes2D } from "./register-CgMO5JBY.js";
9
+ import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-Z0PkDH62.js";
10
+ import { t as registerNodesNet } from "./register-D5v8iqjZ.js";
11
11
  import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from "three";
12
12
  //#region src/test/framing.ts
13
13
  /**
@@ -1244,10 +1244,10 @@ async function runScript(json, opts) {
1244
1244
  engine.setScene(scene);
1245
1245
  const physics = opts.physics ?? "auto";
1246
1246
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
1247
- const { enablePhysics2D } = await import("./physics-2d-B9ktSWrr.js").then((n) => n.r);
1247
+ const { enablePhysics2D } = await import("./physics-2d-CxIqR1cW.js").then((n) => n.r);
1248
1248
  await enablePhysics2D(engine);
1249
1249
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
1250
- const { enablePhysics3D } = await import("./physics-3d-Cpgc5ZM0.js").then((n) => n.r);
1250
+ const { enablePhysics3D } = await import("./physics-3d-CcUaeJXq.js").then((n) => n.r);
1251
1251
  await enablePhysics3D(engine);
1252
1252
  }
1253
1253
  const failures = [];
@@ -1360,10 +1360,10 @@ async function createPlaySession(json, opts = {}) {
1360
1360
  engine.setScene(scene);
1361
1361
  const physics = opts.physics ?? "auto";
1362
1362
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
1363
- const { enablePhysics2D } = await import("./physics-2d-B9ktSWrr.js").then((n) => n.r);
1363
+ const { enablePhysics2D } = await import("./physics-2d-CxIqR1cW.js").then((n) => n.r);
1364
1364
  await enablePhysics2D(engine);
1365
1365
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
1366
- const { enablePhysics3D } = await import("./physics-3d-Cpgc5ZM0.js").then((n) => n.r);
1366
+ const { enablePhysics3D } = await import("./physics-3d-CcUaeJXq.js").then((n) => n.r);
1367
1367
  await enablePhysics3D(engine);
1368
1368
  }
1369
1369
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
package/dist/test.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Et as Node, P as Scene, b as Engine, kt as LogEntry, n as BehaviorCtor } from "./behavior-BNNSIXS5.js";
1
+ import { Et as Node, P as Scene, b as Engine, kt as LogEntry, n as BehaviorCtor } from "./behavior-TA8nySqb.js";
2
2
  import { c as JsonValue, i as SceneJson } from "./schema-CFeioQRE.js";
3
- import { t as LoadSceneOptions } from "./loader-mSL65Djg.js";
4
- import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-CwdWgz0f.js";
3
+ import { t as LoadSceneOptions } from "./loader-CvSDKC3v.js";
4
+ import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-DN-kG4va.js";
5
5
 
6
6
  //#region src/test/framing.d.ts
7
7
  /** Where a node's origin sits relative to the current camera. */
package/dist/test.js CHANGED
@@ -1,3 +1,3 @@
1
- import { r as auditScene } from "./replay-YQ9FW7Yh.js";
2
- import { a as registerAllNodes, c as feelReport, d as findPlayer, f as playtest, h as framingText, i as findFloatingProps, l as feelText, m as describeFraming, n as createPlaySession, o as runScript, p as playtestText, r as describeCapture, s as validateScene, t as captureScene, u as failingReplays } from "./test-Bf-lUxr2.js";
1
+ import { r as auditScene } from "./replay-D_QY5Xm3.js";
2
+ import { a as registerAllNodes, c as feelReport, d as findPlayer, f as playtest, h as framingText, i as findFloatingProps, l as feelText, m as describeFraming, n as createPlaySession, o as runScript, p as playtestText, r as describeCapture, s as validateScene, t as captureScene, u as failingReplays } from "./test-CFhY8No9.js";
3
3
  export { auditScene, captureScene, createPlaySession, describeCapture, describeFraming, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, playtest, playtestText, registerAllNodes, runScript, validateScene };
package/dist/vite.js CHANGED
@@ -1,4 +1,4 @@
1
- import { s as validateScene } from "./test-Bf-lUxr2.js";
1
+ import { s as validateScene } from "./test-CFhY8No9.js";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
4
4
  //#region src/vite/index.ts
@@ -1 +1 @@
1
- import{n as e}from"./index-eVTVArHd.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
1
+ import{n as e}from"./index-tRXhfJEm.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
@@ -1,3 +1,3 @@
1
- import{i as e,r as t,t as n}from"./index-eVTVArHd.js";function r(e,t,n,r,i,a,o=180,s=120){let c=Math.max(o,n),l=Math.max(s,r);return{x:Math.min(Math.max(0,e),Math.max(0,i-c)),y:Math.min(Math.max(0,t),Math.max(0,a-l)),w:c,h:l}}function i(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var a=`rgba(18, 20, 26, 0.92)`,o=`1px solid rgba(255,255,255,0.14)`,s=`12px ui-monospace, SFMono-Regular, Menlo, monospace`,c=class{host;el;body;onClose=()=>{};x;y;w;h;constructor(e,t,n,r){this.host=t,this.x=r.x,this.y=r.y,this.w=r.w,this.h=r.h,this.el=e.createElement(`div`),i(this.el,{position:`absolute`,background:a,border:o,borderRadius:`8px`,color:`rgba(255,255,255,0.88)`,font:s,display:`flex`,flexDirection:`column`,overflow:`hidden`,zIndex:`40`,pointerEvents:`auto`,boxShadow:`0 8px 28px rgba(0,0,0,0.45)`});let c=e.createElement(`div`);c.textContent=n,i(c,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(c);let l=e.createElement(`div`);l.textContent=`×`,l.title=`close`,i(l,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),l.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(l),this.body=e.createElement(`div`),i(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let u=e.createElement(`div`);u.textContent=`◢`,i(u,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(u),this.wireDrag(c,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(u,(e,t)=>{this.w+=e,this.h+=t,this.layout()}),this.layout(),t.appendChild(this.el)}remove(){this.el.remove()}layout(){let e=this.host.getBoundingClientRect(),t=r(this.x,this.y,this.w,this.h,e.width,e.height);this.x=t.x,this.y=t.y,this.w=t.w,this.h=t.h,i(this.el,{left:`${t.x}px`,top:`${t.y}px`,width:`${t.w}px`,height:`${t.h}px`})}wireDrag(e,t){let n=null,r=0,i=0;e.addEventListener(`pointerdown`,t=>{n=t.pointerId,r=t.clientX,i=t.clientY,e.setPointerCapture?.(t.pointerId)}),e.addEventListener(`pointermove`,e=>{e.pointerId===n&&(t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY)});let a=e=>{e.pointerId===n&&(n=null)};e.addEventListener(`pointerup`,a),e.addEventListener(`pointercancel`,a)}},l=96,u=200,d=8192;function f(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function p(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,u).map(e=>JSON.stringify(e)),n=e.length-u;return t.join(`
1
+ import{i as e,r as t,t as n}from"./index-tRXhfJEm.js";function r(e,t,n,r,i,a,o=180,s=120){let c=Math.max(o,n),l=Math.max(s,r);return{x:Math.min(Math.max(0,e),Math.max(0,i-c)),y:Math.min(Math.max(0,t),Math.max(0,a-l)),w:c,h:l}}function i(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var a=`rgba(18, 20, 26, 0.92)`,o=`1px solid rgba(255,255,255,0.14)`,s=`12px ui-monospace, SFMono-Regular, Menlo, monospace`,c=class{host;el;body;onClose=()=>{};x;y;w;h;constructor(e,t,n,r){this.host=t,this.x=r.x,this.y=r.y,this.w=r.w,this.h=r.h,this.el=e.createElement(`div`),i(this.el,{position:`absolute`,background:a,border:o,borderRadius:`8px`,color:`rgba(255,255,255,0.88)`,font:s,display:`flex`,flexDirection:`column`,overflow:`hidden`,zIndex:`40`,pointerEvents:`auto`,boxShadow:`0 8px 28px rgba(0,0,0,0.45)`});let c=e.createElement(`div`);c.textContent=n,i(c,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(c);let l=e.createElement(`div`);l.textContent=`×`,l.title=`close`,i(l,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),l.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(l),this.body=e.createElement(`div`),i(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let u=e.createElement(`div`);u.textContent=`◢`,i(u,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(u),this.wireDrag(c,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(u,(e,t)=>{this.w+=e,this.h+=t,this.layout()}),this.layout(),t.appendChild(this.el)}remove(){this.el.remove()}layout(){let e=this.host.getBoundingClientRect(),t=r(this.x,this.y,this.w,this.h,e.width,e.height);this.x=t.x,this.y=t.y,this.w=t.w,this.h=t.h,i(this.el,{left:`${t.x}px`,top:`${t.y}px`,width:`${t.w}px`,height:`${t.h}px`})}wireDrag(e,t){let n=null,r=0,i=0;e.addEventListener(`pointerdown`,t=>{n=t.pointerId,r=t.clientX,i=t.clientY,e.setPointerCapture?.(t.pointerId)}),e.addEventListener(`pointermove`,e=>{e.pointerId===n&&(t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY)});let a=e=>{e.pointerId===n&&(n=null)};e.addEventListener(`pointerup`,a),e.addEventListener(`pointercancel`,a)}},l=96,u=200,d=8192;function f(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function p(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,u).map(e=>JSON.stringify(e)),n=e.length-u;return t.join(`
2
2
  `)+(n>0?`\n… ${n} more`:``)}return t.length>d?`${t.slice(0,d)} … (${t.length-d} more chars)`:t}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t={}){let n=t.doc??(typeof document<`u`?document:null);if(!n)return null;let r=t.container??(typeof document<`u`?document.body:null);return!r||typeof r.appendChild!=`function`?null:new v(e,r,n,t.statsSource,t.actions)}var g=300,_=[`log`,`info`,`warn`,`error`,`debug`],v=class{engine;container;doc;statsSource;actions;panels=new Map;cleanups=[];menuButton;dropdown=null;selected=null;collapsedFlags=new Map;statsChip=null;logRows=[];levelEnabled={debug:!0,info:!0,warn:!0,error:!0};consoleCapture=!1;consolePatched=[];frame=0;editing=0;detailOpen=new WeakMap;hovering=!1;constructor(e,t,n,r,a=[]){this.engine=e,this.container=t,this.doc=n,this.statsSource=r,this.actions=a,this.menuButton=n.createElement(`div`),this.menuButton.textContent=`☰ debug`,i(this.menuButton,{position:`absolute`,top:`8px`,left:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,cursor:`pointer`,userSelect:`none`,zIndex:`50`,pointerEvents:`auto`}),this.menuButton.addEventListener(`click`,()=>this.toggleDropdown()),t.style.position||(t.style.position=`relative`),t.appendChild(this.menuButton),this.cleanups.push(e.log.added.connect(e=>{this.pushLog({level:e.level,source:`engine`,text:e.parts.map(x).join(` `)})})),this.cleanups.push(e.sceneChanged.connect(()=>{this.selected=null,this.applyColliderScope(),this.renderExplorer(),this.renderInspector()})),this.cleanups.push(e.updated.connect(()=>{this.frame+=1,this.frame%30==0&&(this.renderExplorer(),this.renderStats(),this.editing===0&&!this.hovering&&this.renderInspector())}))}isOpen(e){return e===`colliders`?this.colliderMode!==`off`:e===`stats`?this.statsChip!==null:this.panels.has(e)}colliderMode=`off`;setColliders(e){this.colliderMode=e,this.applyColliderScope()}applyColliderScope(){let e=this.colliderMode;for(let t of n(`2d`).concat(n(`3d`)))t.debugDraw=e!==`off`,t.debugScope=e===`selected`?this.selected:null}open(e){if(e===`stats`){this.openStatsChip();return}if(this.panels.has(e))return;let t=new c(this.doc,this.container,{explorer:`Explorer`,inspector:`Inspector`,logs:`Logs`}[e],{explorer:{x:12,y:44,w:240,h:320},inspector:{x:264,y:44,w:280,h:320},logs:{x:12,y:380,w:532,h:200}}[e]);t.onClose=()=>this.close(e),e===`inspector`&&(t.body.addEventListener(`pointerenter`,()=>{this.hovering=!0}),t.body.addEventListener(`pointerleave`,()=>{this.hovering=!1})),this.panels.set(e,t),e===`explorer`&&this.renderExplorer(),e===`inspector`&&this.renderInspector(),e===`logs`&&this.renderLogs()}close(e){if(e===`inspector`&&(this.hovering=!1),e===`stats`){this.statsChip?.remove(),this.statsChip=null;return}let t=this.panels.get(e);t&&(t.remove(),this.panels.delete(e))}toggle(e){if(e===`colliders`){this.setColliders({off:`all`,all:`selected`,selected:`off`}[this.colliderMode]);return}this.isOpen(e)?this.close(e):this.open(e)}setLevelEnabled(e,t){this.levelEnabled[e]=t,this.renderLogs()}setConsoleCapture(e){if(e!==this.consoleCapture)if(this.consoleCapture=e,e){this.renderLogs();let e=console;for(let t of _){let n=e[t];e[t]=(...e)=>{n.apply(console,e);let r=t===`log`?`info`:t;this.pushLog({level:r,source:`console`,text:e.map(x).join(` `)})},this.consolePatched.push(()=>{e[t]=n})}}else{for(let e of this.consolePatched)e();this.consolePatched=[],this.renderLogs()}}dispose(){this.engine.debugSelection=null,this.setColliders(`off`),this.setConsoleCapture(!1);for(let e of this.cleanups)e();for(let e of[...this.panels.keys()])this.close(e);this.close(`stats`),this.dropdown?.remove(),this.dropdown=null,this.menuButton.remove()}menuLabel(e,t){let n=e===`colliders`&&this.colliderMode!==`off`?` · ${this.colliderMode}`:``;return`${this.isOpen(e)?`✓ `:``}${t}${n}`}toggleDropdown(){if(this.dropdown){this.dropdown.remove(),this.dropdown=null;return}let e=this.doc.createElement(`div`);i(e,{position:`absolute`,top:`34px`,left:`8px`,background:`rgba(18,20,26,0.95)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,font:`12px ui-monospace, Menlo, monospace`,color:`rgba(255,255,255,0.85)`,zIndex:`60`,pointerEvents:`auto`,overflow:`hidden`});for(let[t,n]of[[`explorer`,`Explorer`],[`inspector`,`Inspector`],[`logs`,`Logs`],[`stats`,`Stats`],[`colliders`,`Colliders`]]){let r=this.doc.createElement(`div`);r.textContent=this.menuLabel(t,n),i(r,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`}),r.addEventListener(`click`,()=>{if(this.toggle(t),t===`colliders`){r.textContent=this.menuLabel(t,n);return}this.dropdown?.remove(),this.dropdown=null}),e.appendChild(r)}for(let t of this.actions){let n=this.doc.createElement(`div`);n.textContent=t.label,i(n,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`,borderTop:`1px solid rgba(255,255,255,0.14)`,color:`rgba(158,232,220,0.95)`}),n.addEventListener(`click`,()=>{this.dropdown?.remove(),this.dropdown=null,t.run()}),e.appendChild(n)}this.container.appendChild(e),this.dropdown=e}openStatsChip(){if(this.statsChip)return;let e=this.doc.createElement(`div`);i(e,{position:`absolute`,top:`8px`,right:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,textAlign:`right`,whiteSpace:`pre`,userSelect:`none`,pointerEvents:`none`,zIndex:`70`}),this.container.appendChild(e),this.statsChip=e,this.renderStats()}renderStats(){let e=this.statsChip;if(!e)return;let t=this.engine.stats(),n=this.statsSource?.()??{},r=[`nodes ${t.nodes}`,...n.triangles===void 0?[]:[`tris ${b(n.triangles)}`],...n.drawCalls===void 0?[]:[`calls ${n.drawCalls}`]].join(` · `);e.textContent=`${Math.round(t.fps)} fps · ${t.frameMs.toFixed(1)} ms\n${r}`}renderExplorer(){let e=this.panels.get(`explorer`);if(!e)return;let t=e.body.scrollTop;y(e.body);let n=this.engine.scene?.root;if(!n){e.body.scrollTop=t;return}let r=(e,t)=>{let n=e.constructor,a=e.children.length>0,o=this.collapsedFlags.get(e)===!0,s=this.doc.createElement(`div`);i(s,{display:`flex`,alignItems:`center`,cursor:`pointer`,padding:`1px 2px`,borderRadius:`3px`,background:e===this.selected?`rgba(110,160,255,0.25)`:`transparent`});let c=this.doc.createElement(`span`);c.textContent=a?o?`▸`:`▾`:`·`,i(c,{width:`14px`,flex:`none`,textAlign:`center`,opacity:a?`0.85`:`0.25`,userSelect:`none`}),a&&c.addEventListener(`click`,t=>{t.stopPropagation?.(),this.collapsedFlags.set(e,!o),this.renderExplorer()}),s.appendChild(c);let l=this.doc.createElement(`span`);l.textContent=e.name,i(l,{whiteSpace:`nowrap`}),s.appendChild(l);let u=this.doc.createElement(`span`);if(u.textContent=` ${n.typeName}`,i(u,{opacity:`0.45`,whiteSpace:`nowrap`,fontSize:`10px`}),s.appendChild(u),a&&o){let t=this.doc.createElement(`span`);t.textContent=` (${e.children.length})`,i(t,{opacity:`0.35`,fontSize:`10px`}),s.appendChild(t)}if(s.addEventListener(`click`,()=>{this.selected=e,this.engine.debugSelection=e,this.applyColliderScope(),this.open(`inspector`),this.renderExplorer(),this.renderInspector()}),t.appendChild(s),a&&!o){let n=this.doc.createElement(`div`);i(n,{marginLeft:`8px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});for(let t of e.children)r(t,n);t.appendChild(n)}};r(n,e.body),e.body.scrollTop=t}renderInspector(){let e=this.panels.get(`inspector`);if(!e)return;let n=e.body.scrollTop;y(e.body);let r=this.selected;if(r&&r.tree===null&&(this.selected=null,this.engine.debugSelection=null,this.applyColliderScope(),r=null),!r){let t=this.doc.createElement(`div`);t.textContent=`select a node in the Explorer`,i(t,{opacity:`0.6`}),e.body.appendChild(t);return}let a=r.constructor,o=this.doc.createElement(`div`);if(o.textContent=`${r.getPath()} · ${a.typeName}${r.uid?` · ${r.uid}`:``}`,i(o,{fontWeight:`700`,marginBottom:`6px`,whiteSpace:`pre-wrap`}),e.body.appendChild(o),r.groups.size>0){let t=this.doc.createElement(`div`);t.textContent=`groups: ${[...r.groups].join(`, `)}`,i(t,{opacity:`0.7`,marginBottom:`6px`}),e.body.appendChild(t)}let s=t(a),c=r;for(let t of Object.keys(s)){let n=s[t];this.renderValueRow(e.body,r,t,{read:()=>c[t],write:e=>{c[t]=e},options:n?.options,variants:n?.variants})}e.body.scrollTop=n}renderValueRow(t,n,r,a){let o=a.read(),s=a.options,c={get[r](){return a.read()},set[r](e){a.write(e)}},u=this.doc.createElement(`div`);i(u,{display:`flex`,gap:`6px`,alignItems:`center`,margin:`2px 0`});let d=this.doc.createElement(`div`);d.textContent=r,i(d,{minWidth:`84px`,opacity:`0.75`}),u.appendChild(d);let h=(e,t)=>{let a=this.doc.createElement(`input`);return a.type=`number`,a.value=String(e),i(a,S(`70px`)),this.trackEditing(a),a.addEventListener(`change`,()=>{let e=Number(a.value);Number.isFinite(e)?t(e):a.value=String(n[r])}),a};if(typeof o==`number`)u.appendChild(h(o,e=>{c[r]=e}));else if(typeof o==`boolean`){let e=this.doc.createElement(`input`);e.type=`checkbox`,e.checked=o,this.trackEditing(e),e.addEventListener(`change`,()=>{c[r]=e.checked}),u.appendChild(e)}else if(typeof o==`string`&&s&&s.length>0){let e=this.doc.createElement(`select`);for(let t of s.includes(o)?s:[o,...s]){let n=this.doc.createElement(`option`);n.value=t,n.textContent=t,t===o&&(n.selected=!0),e.appendChild(n)}e.value=o,this.trackEditing(e),e.addEventListener(`change`,()=>{c[r]=e.value}),i(e,S(`110px`)),u.appendChild(e)}else if(typeof o==`string`){let e=this.doc.createElement(`input`);e.type=`text`,e.value=o,this.trackEditing(e),i(e,S(`140px`)),e.addEventListener(`change`,()=>{c[r]=e.value}),u.appendChild(e)}else if(Array.isArray(o)&&o.length<=8&&o.every(e=>typeof e==`number`))for(let e=0;e<o.length;e++)u.appendChild(h(o[e],t=>{let n=[...c[r]];n[e]=t,c[r]=n}));else if(m(o)){let s=this.doc.createElement(`div`);i(s,{marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});let c=a.variants?.tag,l=Object.keys(o);c&&!l.includes(c)&&l.unshift(c);let d=a.path??r;for(let t of l){let r=t===c;this.renderValueRow(s,n,t,{path:`${d}.${t}`,read:()=>a.read()[t]??(r?``:null),write:n=>{if(r&&a.variants){let t=a.variants.byTag[String(n)];if(t!==void 0){a.write(e(t));return}}a.write({...a.read(),[t]:n})},options:r&&a.variants?Object.keys(a.variants.byTag):void 0})}t.appendChild(u),t.appendChild(s);return}else{let s=JSON.stringify(e(o));if(s.length<=l){let e=this.doc.createElement(`div`);e.textContent=s,i(e,{opacity:`0.65`,whiteSpace:`pre-wrap`,wordBreak:`break-all`}),u.appendChild(e)}else{let e=a.path??r,c=this.detailOpen.get(n)?.has(e)??!1,l=this.doc.createElement(`div`);if(l.textContent=`${c?`▾`:`▸`} ${f(o)}`,i(l,{opacity:`0.75`,cursor:`pointer`,userSelect:`none`}),l.addEventListener(`click`,()=>{let t=this.detailOpen.get(n);t||(t=new Set,this.detailOpen.set(n,t)),c?t.delete(e):t.add(e),this.renderInspector()}),u.appendChild(l),t.appendChild(u),c){let e=this.doc.createElement(`div`);e.textContent=p(o,s),i(e,{opacity:`0.6`,whiteSpace:`pre-wrap`,wordBreak:`break-all`,marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`}),t.appendChild(e)}return}}t.appendChild(u)}trackEditing(e){e.addEventListener(`focus`,()=>{this.editing+=1}),e.addEventListener(`blur`,()=>{this.editing=Math.max(0,this.editing-1)})}pushLog(e){this.logRows.push(e),this.logRows.length>g&&this.logRows.splice(0,this.logRows.length-g),this.renderLogs()}renderLogs(){let e=this.panels.get(`logs`);if(!e)return;y(e.body);let t=this.doc.createElement(`div`);i(t,{display:`flex`,gap:`8px`,marginBottom:`4px`,flexWrap:`wrap`});for(let e of[`debug`,`info`,`warn`,`error`]){let n=this.doc.createElement(`div`);n.textContent=`${this.levelEnabled[e]?`✓`:`·`}${e}`,i(n,{cursor:`pointer`,opacity:this.levelEnabled[e]?`1`:`0.45`}),n.addEventListener(`click`,()=>this.setLevelEnabled(e,!this.levelEnabled[e])),t.appendChild(n)}let n=this.doc.createElement(`div`);n.textContent=`${this.consoleCapture?`✓`:`·`}console`,i(n,{cursor:`pointer`,marginLeft:`auto`}),n.addEventListener(`click`,()=>this.setConsoleCapture(!this.consoleCapture)),t.appendChild(n),e.body.appendChild(t);let r={debug:`rgba(255,255,255,0.5)`,info:`rgba(255,255,255,0.85)`,warn:`#ffc861`,error:`#ff6b6b`};for(let t of this.logRows){if(!this.levelEnabled[t.level])continue;let n=this.doc.createElement(`div`);n.textContent=`[${t.source===`console`?`console`:t.level}] ${t.text}`,i(n,{color:r[t.level],whiteSpace:`pre-wrap`}),e.body.appendChild(n)}}};function y(e){for(let t of[...e.children])t.remove()}function b(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function x(e){if(typeof e==`string`)return e;if(e instanceof Error){let t=e.stack?.split(`
3
3
  `)[1]?.trim();return`${e.name}: ${e.message}${t?` (${t})`:``}`}try{let t=JSON.stringify(e);return t===`{}`||t===void 0?String(e):t}catch{return String(e)}}function S(e){return{width:e,background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.2)`,borderRadius:`4px`,color:`inherit`,font:`inherit`,padding:`2px 4px`}}export{h as attachDebugOverlay};