incanto 0.31.0 → 0.33.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 (54) hide show
  1. package/bin/incanto-play.mjs +7 -2
  2. package/dist/2d.d.ts +59 -2
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +28 -3
  5. package/dist/3d.js +4 -4
  6. package/dist/{behavior-D_jMpFh8.d.ts → behavior-CKwTCjfR.d.ts} +140 -1
  7. package/dist/{create-game-z5XaB1p5.js → create-game-D2QU5x7S.js} +16 -8
  8. package/dist/{create-game-DqOjMBUS.js → create-game-sFuTLqjD.js} +22 -10
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-KRPtUtzl.js → duplicate-BgnG1Lqz.js} +1 -1
  11. package/dist/editor.js +35 -0
  12. package/dist/{environment-presets--DigHNg4.js → environment-presets-CQtEGogB.js} +2 -2
  13. package/dist/{gameplay-BftxM_It.js → gameplay-BpQCbABv.js} +90 -2
  14. package/dist/gameplay.d.ts +1 -1
  15. package/dist/gameplay.js +1 -1
  16. package/dist/index.d.ts +135 -23
  17. package/dist/index.js +6 -59
  18. package/dist/{loader-BlaRQGaA.js → loader-Buk8Bu1h.js} +53 -0
  19. package/dist/{loader-BYBrqTxP.d.ts → loader-COn5fS0o.d.ts} +1 -1
  20. package/dist/net.d.ts +1 -1
  21. package/dist/net.js +3 -3
  22. package/dist/{pathfinding-BwhqPD3i.d.ts → pathfinding-DUw9mir9.d.ts} +1 -1
  23. package/dist/{physics-2d-D9wquBvK.js → physics-2d-DjXR5DMu.js} +12 -2
  24. package/dist/{physics-3d-CnPygVGo.js → physics-3d-DF8npb1O.js} +55 -9
  25. package/dist/react.d.ts +1 -1
  26. package/dist/react.js +1 -1
  27. package/dist/{register-Dzkd6-os.js → register-CscIzJEO.js} +551 -13
  28. package/dist/{register-CUY284Is.js → register-CtI-itec.js} +2 -2
  29. package/dist/{register-tkR_8tWg.js → register-FIJtNbub.js} +145 -8
  30. package/dist/test-BRxLd2jH.js +642 -0
  31. package/dist/test.d.ts +55 -3
  32. package/dist/test.js +2 -392
  33. package/dist/{touch-031PxtCR.js → touch-BoNg_MnF.js} +2 -1
  34. package/dist/vite.js +1 -1
  35. package/editor/assets/{agent8-o27_Y1xN.js → agent8-BdDP3xKW.js} +1 -1
  36. package/editor/assets/{debug-DfcWX3uW.js → debug-DbjTyTlC.js} +1 -1
  37. package/editor/assets/{index-C9fb5QcT.js → index-BWCudoz1.js} +90 -90
  38. package/editor/index.html +1 -1
  39. package/package.json +1 -1
  40. package/schemas/scene.schema.json +520 -0
  41. package/skills/README.md +14 -2
  42. package/skills/incanto-audio.md +43 -0
  43. package/skills/incanto-building-2d-games.md +52 -0
  44. package/skills/incanto-building-3d-games.md +16 -0
  45. package/skills/incanto-hud.md +41 -0
  46. package/skills/incanto-node-reference.md +89 -0
  47. package/skills/incanto-physics-and-input.md +50 -0
  48. package/skills/incanto-verifying-your-game.md +86 -30
  49. package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +40 -19
  50. package/templates-app/beacon-isle-3d/package.json +1 -1
  51. package/templates-app/tps-3d/docs/project-3d-rules.md +41 -19
  52. package/templates-app/tps-3d/package.json +1 -1
  53. package/templates-app/village-quest-3d/docs/project-3d-rules.md +40 -19
  54. package/templates-app/village-quest-3d/package.json +1 -1
@@ -17,6 +17,7 @@ import { dirname, join, resolve } from 'node:path';
17
17
  * pointer <dx> <dy> look deltas wheel <dy>
18
18
  * capture [file] current state as scene JSON (stdout or file)
19
19
  * describe one line per node, non-default props only
20
+ * framing what the camera SEES: on/off screen, lights, overlaps
20
21
  * logs engine.log entries since the last call
21
22
  * quit
22
23
  *
@@ -56,7 +57,7 @@ if (args.help || !args.scene) {
56
57
  console.error(`Usage: incanto-play <scene.json> [--behaviors <file.ts|.js>] [--seed N] [--fixed-hz 60] [--commands <file>]
57
58
 
58
59
  Headless play: stdin commands (step/press/release/vector/key/mouse/pointer/
59
- wheel/capture/describe/logs/quit), JSON-line responses on stdout.`);
60
+ wheel/capture/describe/framing/logs/quit), JSON-line responses on stdout.`);
60
61
  process.exit(args.help && args.scene !== undefined ? 0 : 1);
61
62
  }
62
63
 
@@ -175,6 +176,10 @@ function handle(line) {
175
176
  case 'describe':
176
177
  out({ ok: true, cmd, text: session.describe() });
177
178
  break;
179
+ case 'framing':
180
+ // what the camera can SEE — on/off screen, lit or black, overlaps
181
+ out({ ok: true, cmd, text: session.framing() });
182
+ break;
178
183
  case 'logs':
179
184
  out({ ok: true, cmd, entries: session.drainLogs() });
180
185
  break;
@@ -188,7 +193,7 @@ function handle(line) {
188
193
  cmd,
189
194
  error: {
190
195
  code: 'UNKNOWN_COMMAND',
191
- message: `unknown command '${cmd}'. Commands: step press release vector key mouse pointer wheel capture describe logs quit`,
196
+ message: `unknown command '${cmd}'. Commands: step press release vector key mouse pointer wheel capture describe framing logs quit`,
192
197
  },
193
198
  });
194
199
  }
package/dist/2d.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { C as RendererStats, S as GameStats, X as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-D_jMpFh8.js";
1
+ import { C as RendererStats, N as Scene$1, S as GameStats, at as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine } from "./behavior-CKwTCjfR.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-BJb-CWfA.js";
3
3
  import { i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
4
- import { t as LoadSceneOptions } from "./loader-BYBrqTxP.js";
4
+ import { t as LoadSceneOptions } from "./loader-COn5fS0o.js";
5
5
  import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
6
6
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
7
7
  import * as RapierNs from "@dimforge/rapier2d-compat";
@@ -144,6 +144,22 @@ declare class CharacterBody2D extends PhysicsBody2D {
144
144
  /** @internal Updated by Physics2D.moveAndSlide. */
145
145
  _grounded: boolean;
146
146
  moveAndSlide(): void;
147
+ /** @internal Set by the physics step from the KCC's resolved collisions. */
148
+ _onWall: number;
149
+ /** @internal */
150
+ _onCeiling: boolean;
151
+ /**
152
+ * Touching a wall, and which side (-1 left, +1 right, 0 neither).
153
+ *
154
+ * Wall jump and wall slide are unbuildable without it — not awkward to build,
155
+ * impossible: `isOnFloor()` was the only surface a character could report, so
156
+ * even a hand-written behavior had nothing to ask.
157
+ */
158
+ isOnWall(): boolean;
159
+ /** -1 the wall is to the left, +1 to the right, 0 no wall. */
160
+ wallSide(): number;
161
+ /** Head hit something — cut the jump short, the way every platformer does. */
162
+ isOnCeiling(): boolean;
147
163
  isOnFloor(): boolean;
148
164
  }
149
165
  //#endregion
@@ -321,6 +337,11 @@ interface Game2D {
321
337
  sourceJson: SceneJson;
322
338
  renderer: GameRenderer;
323
339
  physics: Physics2D | null;
340
+ /**
341
+ * The node under a screen pixel, or null. The raycast the renderer already
342
+ * does — reachable now without holding the renderer.
343
+ */
344
+ pick(x: number, y: number): Node | null;
324
345
  /** Engine + renderer perf counters in one read (fps/nodes/triangles/…). */
325
346
  stats(): GameStats;
326
347
  /**
@@ -463,18 +484,54 @@ declare class Camera2D extends Node2D {
463
484
  * - `platformer`: x movement + gravity (from the scene `physics.gravity[1]`,
464
485
  * default 980) + jump (`jumpHeight` px → impulse √(2·g·h))
465
486
  * - `topDown`: full-axis movement, no gravity
487
+ *
488
+ * ## Why the extra platformer props exist
489
+ *
490
+ * A jump that only works while `isOnFloor()` is true feels BROKEN, and players
491
+ * do not report it as "the coyote time is missing" — they report the game as
492
+ * unresponsive. The whole schema used to be five props, so a JSON-only game got
493
+ * a character that felt wrong and could not drive `AnimatedSprite2D.play('run')`
494
+ * either. `examples/platformer-2d` hand-rolls all of this in a 541-line
495
+ * behavior; these are those constants, as props.
496
+ *
497
+ * Every one defaults to OFF (0 / 1 / false), so an existing scene behaves
498
+ * exactly as before until it asks for something.
466
499
  */
467
500
  declare class CharacterController2D extends Node {
468
501
  static override readonly typeName: string;
469
502
  static readonly props: PropSchema;
503
+ static readonly signals: readonly string[];
470
504
  mode: string;
471
505
  maxSpeed: number;
472
506
  /** Pixels (platformer mode). */
473
507
  jumpHeight: number;
474
508
  moveAction: string;
475
509
  jumpAction: string;
510
+ coyoteSeconds: number;
511
+ jumpBufferSeconds: number;
512
+ jumpCutMultiplier: number;
513
+ maxJumps: number;
514
+ dashSpeed: number;
515
+ dashSeconds: number;
516
+ dashAction: string;
517
+ wallSlideSpeed: number;
518
+ wallJumpImpulse: number[];
519
+ /** What the character is doing — drive `AnimatedSprite2D.play(state)` off it. */
520
+ state: "idle" | "run" | "jump" | "fall" | "wallSlide" | "dash";
521
+ private coyoteLeft;
522
+ private bufferLeft;
523
+ private jumpsUsed;
524
+ private dashLeft;
525
+ private dashDir;
526
+ private rising;
476
527
  override onReady(): void;
477
528
  override fixedUpdate(dt: number): void;
529
+ /**
530
+ * Emits `movementStateChanged(state)` on a change — the 3D sibling has had
531
+ * this since it shipped, and it is what drives `AnimatedSprite2D.play(...)`
532
+ * from scene JSON instead of a behavior polling velocities.
533
+ */
534
+ private setState;
478
535
  }
479
536
  //#endregion
480
537
  //#region src/2d/nodes/color-rect-2d.d.ts
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-z5XaB1p5.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-tkR_8tWg.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-D9wquBvK.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-D2QU5x7S.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-FIJtNbub.js";
4
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-DjXR5DMu.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 { C as RendererStats, S as GameStats, X as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-D_jMpFh8.js";
1
+ import { C as RendererStats, N as Scene$1, S as GameStats, at as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine } from "./behavior-CKwTCjfR.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-BJb-CWfA.js";
3
3
  import { i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
4
- import { t as LoadSceneOptions } from "./loader-BYBrqTxP.js";
4
+ import { t as LoadSceneOptions } from "./loader-COn5fS0o.js";
5
5
  import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
6
- import { n as PathGrid, s as SpatialPose } from "./pathfinding-BwhqPD3i.js";
6
+ import { n as PathGrid, s as SpatialPose } from "./pathfinding-DUw9mir9.js";
7
7
  import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Vector3, WebGLRenderer } from "three";
8
8
  import { VRM } from "@pixiv/three-vrm";
9
9
  import { Sky } from "three/examples/jsm/objects/Sky.js";
@@ -488,6 +488,16 @@ interface CreateGame3DOptions {
488
488
  /** Keep the frame rate by rendering fewer pixels while frames run slow
489
489
  * (default ON). `false` pins the resolution — screenshots, capture, tests. */
490
490
  adaptiveResolution?: boolean;
491
+ /**
492
+ * Keep the drawing buffer readable so the canvas can be screenshot.
493
+ *
494
+ * WebGL clears it the instant a frame composites, so `toDataURL()` — and every
495
+ * screenshot built on it — is BLANK without this. Off by default: it costs a
496
+ * buffer copy per frame and only a run that means to look at the output should
497
+ * pay for it. `environment.rendering.preserveDrawingBuffer` does the same from
498
+ * the scene.
499
+ */
500
+ preserveDrawingBuffer?: boolean;
491
501
  resolveScene?: LoadSceneOptions["resolveScene"];
492
502
  /**
493
503
  * Boot progress, for the loading screen an app already has.
@@ -534,6 +544,11 @@ interface Game3D {
534
544
  sourceJson: SceneJson;
535
545
  renderer: GameRenderer;
536
546
  physics: Physics3D | null;
547
+ /**
548
+ * The node under a screen pixel, or null. The raycast the renderer already
549
+ * does — reachable now without holding the renderer.
550
+ */
551
+ pick(x: number, y: number): Node | null;
537
552
  /** Engine + renderer perf counters in one read (fps/nodes/triangles/…). */
538
553
  stats(): GameStats;
539
554
  /**
@@ -2998,6 +3013,16 @@ interface Renderer3DOptions {
2998
3013
  * the resolution — for screenshots, video capture and pixel-diff tests.
2999
3014
  */
3000
3015
  adaptiveResolution?: boolean;
3016
+ /**
3017
+ * Keep the drawing buffer readable so the canvas can be SCREENSHOT.
3018
+ *
3019
+ * WebGL clears it the moment a frame composites, so `canvas.toDataURL()` and
3020
+ * anything built on it (a Playwright screenshot, a pixel-diff test) comes
3021
+ * back blank without this. Off by default — it costs a buffer copy per frame
3022
+ * and only a run that intends to look at the output should pay it. The scene
3023
+ * can ask for it too: `environment.rendering.preserveDrawingBuffer`.
3024
+ */
3025
+ preserveDrawingBuffer?: boolean;
3001
3026
  }
3002
3027
  /**
3003
3028
  * WebGL presentation layer: subscribes to `engine.updated`, mirrors the active
package/dist/3d.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- 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-BftxM_It.js";
3
- import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-DqOjMBUS.js";
4
- import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, 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, V as movementState, _ 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--DigHNg4.js";
2
+ 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-BpQCbABv.js";
3
+ import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-sFuTLqjD.js";
4
+ import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, 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, V as movementState, _ 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-CQtEGogB.js";
5
5
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
6
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-CnPygVGo.js";
6
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-DF8npb1O.js";
7
7
  //#region src/3d/environment-runtime.ts
8
8
  /**
9
9
  * Live environment editing — the renderer re-applies `scene.environment`
@@ -298,6 +298,14 @@ declare class AudioBuses {
298
298
  /** Final gain for a sound on `bus` with its own `sourceVolume` (all clamped). */
299
299
  effectiveVolume(bus: BusName, sourceVolume: number): number;
300
300
  /** Clamp + finite-guard a new value; emit only on a real change. */
301
+ /**
302
+ * Write THEN emit.
303
+ *
304
+ * This used to emit from inside the expression assigning the field, so every
305
+ * listener of `changed` read the OLD value — including the thing the signal
306
+ * exists for ("live re-gain of playing sounds"), which therefore re-gained to
307
+ * the volume you just left.
308
+ */
301
309
  private assign;
302
310
  }
303
311
  //#endregion
@@ -595,6 +603,25 @@ declare class InputMap {
595
603
  private detach;
596
604
  /** Load (or extend with) scene-JSON action declarations. Hard-validates shape. */
597
605
  declare(decls: Record<string, unknown>): void;
606
+ /**
607
+ * Where the pointer is, in CANVAS pixels (top-left origin), or null when it
608
+ * has never been over the canvas.
609
+ *
610
+ * This is what a click-driven game asks for. `pointerDelta()` answers "how far
611
+ * did the mouse move" — the mouse-look question — and for a whole class of
612
+ * genres that is the wrong question and the only one the engine could answer.
613
+ * Pair it with `renderer.pick(x, y)` to get the node under the cursor.
614
+ */
615
+ pointerPosition(): {
616
+ x: number;
617
+ y: number;
618
+ } | null;
619
+ /**
620
+ * @internal Feed a pointer position. `target` scales client → canvas pixels
621
+ * so a CSS-stretched canvas still reports coordinates its renderer can use.
622
+ */
623
+ setPointerPosition(clientX: number, clientY: number, target?: HTMLElement): void;
624
+ private pointerPos;
598
625
  /** Feed a key state change (code = KeyboardEvent.code, e.g. 'Space', 'KeyW'). */
599
626
  handleKey(code: string, isDown: boolean): void;
600
627
  private readonly injectedDown;
@@ -699,6 +726,17 @@ declare class InputMap {
699
726
  isPressed(action: string): boolean;
700
727
  justPressed(action: string): boolean;
701
728
  justReleased(action: string): boolean;
729
+ /**
730
+ * Mouse button state WITHOUT declaring an action for it.
731
+ *
732
+ * A click on a tile is not a bindable action — there is no key to remap, and
733
+ * asking a match-3 to declare `"click": { "keys": ["Mouse0"] }` in every scene
734
+ * is ceremony. Buttons already live in the key set as `Mouse0`/`Mouse1`/…;
735
+ * these read them directly. 0 left, 1 middle, 2 right.
736
+ */
737
+ mousePressed(button?: number): boolean;
738
+ mouseJustPressed(button?: number): boolean;
739
+ mouseJustReleased(button?: number): boolean;
702
740
  /** Normalized direction for a vector2 action (y-down: up = -y). */
703
741
  getVector(action: string): {
704
742
  x: number;
@@ -753,6 +791,89 @@ declare class Scene {
753
791
  toJSON(): SceneJson;
754
792
  }
755
793
  //#endregion
794
+ //#region src/core/save.d.ts
795
+ /**
796
+ * Namespaced game persistence — high scores, unlocks, settings. localStorage
797
+ * in the browser, in-memory headless (tests and SSR stay green). Values are
798
+ * JSON round-tripped, so what you get back is what you saved.
799
+ *
800
+ * const save = createSaveStore('my-game');
801
+ * save.set('highScore', 4200);
802
+ * const best = save.get('highScore', 0);
803
+ */
804
+ interface SaveStore {
805
+ get<T extends JsonValue>(key: string, fallback: T): T;
806
+ set(key: string, value: JsonValue): void;
807
+ remove(key: string): void;
808
+ /** Wipe THIS namespace only. */
809
+ clear(): void;
810
+ }
811
+ declare function createSaveStore(namespace: string): SaveStore;
812
+ //#endregion
813
+ //#region src/core/settings.d.ts
814
+ /** One of three tiers, resolved once at boot and adjustable in an options menu. */
815
+ type QualityTier = "low" | "medium" | "high";
816
+ interface SettingsValues {
817
+ master: number;
818
+ sfx: number;
819
+ music: number;
820
+ muted: boolean;
821
+ quality: QualityTier;
822
+ /** Mouse/stick look sensitivity multiplier. */
823
+ sensitivity: number;
824
+ invertY: boolean;
825
+ /** Reduce camera shake, flashes and other motion. */
826
+ reduceMotion: boolean;
827
+ }
828
+ /** What a device probe can see without a GPU context. */
829
+ interface DeviceHints {
830
+ hardwareConcurrency?: number;
831
+ deviceMemory?: number;
832
+ coarsePointer?: boolean;
833
+ maxTextureSize?: number;
834
+ }
835
+ /**
836
+ * A default tier from what the browser will admit to.
837
+ *
838
+ * Deliberately crude: this picks a STARTING point, and the options menu is the
839
+ * real answer. A phone that reports four cores and a coarse pointer should not
840
+ * open on the setting tuned for a desktop GPU.
841
+ */
842
+ declare function suggestQuality(hints: DeviceHints): QualityTier;
843
+ /** Read what this environment will tell us, safely, without touching WebGL. */
844
+ declare function readDeviceHints(): DeviceHints;
845
+ declare class Settings {
846
+ /** Fires after any change, with the key that changed. */
847
+ readonly changed: Signal<[keyof SettingsValues]>;
848
+ private readonly store;
849
+ private readonly values;
850
+ constructor(namespace?: string, store?: SaveStore);
851
+ get<K extends keyof SettingsValues>(key: K): SettingsValues[K];
852
+ set<K extends keyof SettingsValues>(key: K, value: SettingsValues[K]): void;
853
+ /** Every value at once — for an options screen, or a save-file dump. */
854
+ all(): SettingsValues;
855
+ /** Back to defaults, on disk too. */
856
+ reset(): void;
857
+ /**
858
+ * Push the saved volumes into the engine's buses, and keep writing them back
859
+ * whenever anything changes them.
860
+ *
861
+ * The subscription is the point: a game that sets `engine.audio.music = 0.4`
862
+ * from a slider has already persisted it, without knowing this class exists.
863
+ */
864
+ bindAudio(audio: AudioBuses): () => void;
865
+ }
866
+ /**
867
+ * What a quality tier MEANS, as an environment patch.
868
+ *
869
+ * Only the levers that actually cost: shadows, post, clouds, and the resolution
870
+ * the renderer draws at. Deliberately an `environment` patch, because
871
+ * `setEnvironment3D(engine, patch)` already applies one live and validated —
872
+ * this is not a second rendering pipeline, it is three presets for the one that
873
+ * exists.
874
+ */
875
+ declare function qualityEnvironment(tier: QualityTier): Record<string, JsonValue>;
876
+ //#endregion
756
877
  //#region src/core/stats.d.ts
757
878
  /**
758
879
  * Performance-stats shapes — pure data, importable everywhere (core stays
@@ -824,9 +945,27 @@ declare class Engine {
824
945
  readonly rng: Rng;
825
946
  /** The engine log channel (debug overlay + headless harness tail this). */
826
947
  readonly log: LogManager;
948
+ /**
949
+ * Screen pixel → the node under it, when a renderer has offered one.
950
+ *
951
+ * The raycast has always existed (`Renderer3D.pick` / `Renderer2D.pick`) and
952
+ * was reachable only from whoever held the renderer — so a Behavior, which
953
+ * holds a node, could not use it. Every mouse-driven genre needs exactly this
954
+ * and had no way to ask. Instance-scoped, set by the renderer on construction
955
+ * and cleared on dispose; null when nothing renders (headless).
956
+ */
957
+ picker: ((x: number, y: number) => Node | null) | null;
827
958
  /** Global volume buses — `master` × `sfx`/`music` gain + `muted`. AudioPlayer
828
959
  * routes its volume through this; games set it for global volume/mute. */
829
960
  readonly audio: AudioBuses;
961
+ /**
962
+ * Persisted player settings — volume, quality, sensitivity.
963
+ *
964
+ * `createSaveStore` was good and had zero callers, so every game's volume
965
+ * reset on reload. Reading it here costs nothing (localStorage, once) and
966
+ * `bindAudio` makes the volumes write themselves.
967
+ */
968
+ readonly settings: Settings;
830
969
  /** Low-latency procedural-SFX player (WebAudio, headless-safe). AudioPlayer
831
970
  * presets play through it; games may call `engine.sfx.play(...)` directly. */
832
971
  readonly sfx: SfxEngine;
@@ -1035,4 +1174,4 @@ declare function registeredBehaviors(): string[];
1035
1174
  /** Test isolation helper. */
1036
1175
  declare function clearBehaviors(): void;
1037
1176
  //#endregion
1038
- export { LogEntry as $, VoicePreset as A, SfxParams as B, RendererStats as C, SfxPlayOptions as D, SfxEngine as E, Vec3 as F, MusicManager as G, SynthOptions as H, spatialGain as I, AudioBuses as J, MusicTrack as K, spatialPan as L, ROLLOFF_MODELS as M, RolloffModel as N, isAudioContextAvailable as O, SpatialParams as P, SceneTree as Q, SFX_PRESETS as R, GameStats as S, InputMap as T, synthSfx as U, SfxWave as V, MusicBackend as W, Node as X, BusName as Y, NodeLifecycle as Z, registeredTypes as _, registerBehavior as a, Scheduler as b, PropDef as c, createNode as d, LogLevel as et, getNodeSchema as f, registerNode as g, mergeStaticSignals as h, getBehavior as i, Listener as j, Voice as k, PropSchema as l, getNodeType as m, BehaviorCtor as n, Signal as nt, registeredBehaviors as o, getNodeSignals as p, PlayMusicOptions as q, clearBehaviors as r, SignalListener as rt, NodeCtor as s, Behavior as t, LogManager as tt, clearRegistry as u, Engine as v, Scene as w, EngineStats as x, EngineOptions as y, SFX_PRESET_NAMES as z };
1177
+ export { MusicBackend as $, suggestQuality as A, Listener as B, RendererStats as C, SettingsValues as D, Settings as E, SfxEngine as F, spatialGain as G, RolloffModel as H, SfxPlayOptions as I, SFX_PRESET_NAMES as J, spatialPan as K, isAudioContextAvailable as L, createSaveStore as M, Scene as N, qualityEnvironment as O, InputMap as P, synthSfx as Q, Voice as R, GameStats as S, QualityTier as T, SpatialParams as U, ROLLOFF_MODELS as V, Vec3 as W, SfxWave as X, SfxParams as Y, SynthOptions as Z, registeredTypes as _, registerBehavior as a, Node as at, Scheduler as b, PropDef as c, LogEntry as ct, createNode as d, Signal as dt, MusicManager as et, getNodeSchema as f, SignalListener as ft, registerNode as g, mergeStaticSignals as h, getBehavior as i, BusName as it, SaveStore as j, readDeviceHints as k, PropSchema as l, LogLevel as lt, getNodeType as m, BehaviorCtor as n, PlayMusicOptions as nt, registeredBehaviors as o, NodeLifecycle as ot, getNodeSignals as p, SFX_PRESETS as q, clearBehaviors as r, AudioBuses as rt, NodeCtor as s, SceneTree as st, Behavior as t, MusicTrack as tt, clearRegistry as u, LogManager as ut, Engine as v, DeviceHints as w, EngineStats as x, EngineOptions as y, VoicePreset as z };
@@ -1,13 +1,13 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { m as diagnose, n as loadScene, o as computeViewport, s as resolveViewport, y as registerBehavior } from "./loader-BlaRQGaA.js";
3
- import { l as AudioPlayer, u as Engine } from "./register-Dzkd6-os.js";
2
+ import { m as diagnose, n as loadScene, o as computeViewport, s as resolveViewport, y as registerBehavior } from "./loader-Buk8Bu1h.js";
3
+ import { h as Engine, m as AudioPlayer } from "./register-CscIzJEO.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
- import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
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-ByzfDPyu.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-BftxM_It.js";
8
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-tkR_8tWg.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-BpQCbABv.js";
8
+ import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-FIJtNbub.js";
9
9
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
10
- import { n as enablePhysics2D } from "./physics-2d-D9wquBvK.js";
10
+ import { n as enablePhysics2D } from "./physics-2d-DjXR5DMu.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
  /**
@@ -269,13 +269,16 @@ var Renderer2D = class {
269
269
  this.assets = opts.assets ?? new AssetStore2D(void 0, this.engine);
270
270
  const rendering = resolveRendering(opts.engine.scene?.environment, {
271
271
  antialias: true,
272
- pixelRatio: 1
272
+ pixelRatio: 1,
273
+ preserveDrawingBuffer: false
273
274
  }, globalThis.devicePixelRatio ?? 1, opts);
274
275
  this.webgl = new WebGLRenderer({
275
276
  canvas: opts.canvas,
276
- antialias: rendering.antialias
277
+ antialias: rendering.antialias,
278
+ preserveDrawingBuffer: rendering.preserveDrawingBuffer
277
279
  });
278
280
  this.webgl.info.autoReset = false;
281
+ this.engine.picker = (x, y) => this.pick(x, y);
279
282
  this.webgl.setPixelRatio(rendering.pixelRatio);
280
283
  this.webgl.info.autoReset = false;
281
284
  this.debugLines = new LineSegments(new BufferGeometry(), new LineBasicMaterial({
@@ -483,6 +486,7 @@ var Renderer2D = class {
483
486
  };
484
487
  }
485
488
  dispose() {
489
+ if (this.engine.picker) this.engine.picker = null;
486
490
  this.disconnect();
487
491
  for (const scene of [this.worldScene, this.uiScene]) scene.traverse((obj) => {
488
492
  const mesh = obj;
@@ -540,6 +544,7 @@ async function createGame2D(opts) {
540
544
  const keyboard = opts.keyboard ?? (typeof window !== "undefined" ? window : false);
541
545
  if (keyboard) engine.input.attachKeyboard(keyboard);
542
546
  cleanups.push(wireAudioUnlock(engine, scene.root, opts.canvas));
547
+ cleanups.push(engine.settings.bindAudio(engine.audio));
543
548
  const touchMode = opts.touch ?? "auto";
544
549
  if (touchMode !== false) {
545
550
  const host = opts.touchContainer ?? opts.canvas.parentElement ?? null;
@@ -626,6 +631,9 @@ async function createGame2D(opts) {
626
631
  sourceJson,
627
632
  renderer,
628
633
  physics,
634
+ pick(x, y) {
635
+ return engine.picker?.(x, y) ?? null;
636
+ },
629
637
  stats() {
630
638
  return {
631
639
  triangles: 0,
@@ -1,13 +1,13 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { m as diagnose, n as loadScene, y as registerBehavior } from "./loader-BlaRQGaA.js";
3
- import { l as AudioPlayer, u as Engine } from "./register-Dzkd6-os.js";
2
+ import { m as diagnose, n as loadScene, y as registerBehavior } from "./loader-Buk8Bu1h.js";
3
+ import { h as Engine, m as AudioPlayer } from "./register-CscIzJEO.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
- import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
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-ByzfDPyu.js";
7
- import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-BftxM_It.js";
7
+ import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-BpQCbABv.js";
8
8
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
9
- import { E as Camera3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets--DigHNg4.js";
10
- import { n as enablePhysics3D } from "./physics-3d-CnPygVGo.js";
9
+ import { E as Camera3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-CQtEGogB.js";
10
+ import { n as enablePhysics3D } from "./physics-3d-DF8npb1O.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";
@@ -1554,13 +1554,19 @@ var Renderer3D = class {
1554
1554
  this.assets = opts.assets ?? new AssetStore3D(this.engine);
1555
1555
  const rendering = resolveRendering(opts.engine.scene?.environment, {
1556
1556
  antialias: true,
1557
- pixelRatio: Math.min(globalThis.devicePixelRatio ?? 1, 2)
1558
- }, globalThis.devicePixelRatio ?? 1, { pixelRatio: opts.pixelRatio });
1557
+ pixelRatio: Math.min(globalThis.devicePixelRatio ?? 1, 2),
1558
+ preserveDrawingBuffer: false
1559
+ }, globalThis.devicePixelRatio ?? 1, {
1560
+ pixelRatio: opts.pixelRatio,
1561
+ preserveDrawingBuffer: opts.preserveDrawingBuffer
1562
+ });
1559
1563
  this.webgl = new WebGLRenderer({
1560
1564
  canvas: opts.canvas,
1561
- antialias: rendering.antialias
1565
+ antialias: rendering.antialias,
1566
+ preserveDrawingBuffer: rendering.preserveDrawingBuffer
1562
1567
  });
1563
1568
  this.webgl.info.autoReset = false;
1569
+ this.engine.picker = (x, y) => this.pick(x, y);
1564
1570
  this.basePixelRatio = rendering.pixelRatio;
1565
1571
  this.webgl.setPixelRatio(rendering.pixelRatio);
1566
1572
  this.adaptive = opts.adaptiveResolution === false ? null : new AdaptiveResolution();
@@ -2077,6 +2083,7 @@ var Renderer3D = class {
2077
2083
  this.webgl.shadowMap.needsUpdate = true;
2078
2084
  }
2079
2085
  dispose() {
2086
+ if (this.engine.picker) this.engine.picker = null;
2080
2087
  this.disconnect();
2081
2088
  this.threeScene.traverse((obj) => {
2082
2089
  const mesh = obj;
@@ -2194,7 +2201,8 @@ async function createGame3D(opts) {
2194
2201
  canvas: opts.canvas,
2195
2202
  engine,
2196
2203
  ...opts.pixelRatio !== void 0 ? { pixelRatio: opts.pixelRatio } : {},
2197
- ...opts.adaptiveResolution !== void 0 ? { adaptiveResolution: opts.adaptiveResolution } : {}
2204
+ ...opts.adaptiveResolution !== void 0 ? { adaptiveResolution: opts.adaptiveResolution } : {},
2205
+ ...opts.preserveDrawingBuffer !== void 0 ? { preserveDrawingBuffer: opts.preserveDrawingBuffer } : {}
2198
2206
  });
2199
2207
  await report(.14, "renderer");
2200
2208
  const wantDebug = opts.debug ?? false;
@@ -2254,6 +2262,7 @@ async function createGame3D(opts) {
2254
2262
  opts.onProgress?.(.16 + f * .82, "shaders");
2255
2263
  });
2256
2264
  } catch {}
2265
+ cleanups.push(engine.settings.bindAudio(engine.audio));
2257
2266
  await report(1, "ready");
2258
2267
  function disposeGame() {
2259
2268
  teardown([
@@ -2274,6 +2283,9 @@ async function createGame3D(opts) {
2274
2283
  assetErrors() {
2275
2284
  return renderer.assets?.errors() ?? [];
2276
2285
  },
2286
+ pick(x, y) {
2287
+ return engine.picker?.(x, y) ?? null;
2288
+ },
2277
2289
  stats() {
2278
2290
  return {
2279
2291
  triangles: 0,
package/dist/debug.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as RendererStats, et as LogLevel, v as Engine } from "./behavior-D_jMpFh8.js";
1
+ import { C as RendererStats, lt as LogLevel, v as Engine } from "./behavior-CKwTCjfR.js";
2
2
 
3
3
  //#region src/debug/panel.d.ts
4
4
  /** Minimal document surface the overlay needs (injectable for tests). */
@@ -1,4 +1,4 @@
1
- import { i as serializeNode, t as buildNodeJson } from "./loader-BlaRQGaA.js";
1
+ import { i as serializeNode, t as buildNodeJson } from "./loader-Buk8Bu1h.js";
2
2
  //#region src/core/order-groups.ts
3
3
  const ORDER_GROUP_BASE = {
4
4
  background: -2e3,
package/dist/editor.js CHANGED
@@ -1075,6 +1075,41 @@ var dt = {
1075
1075
  en: "Child of HudLayer. Set text/size/color/background; disabled greys it out. Emits the 'pressed' signal (behaviors: node.on('pressed', ...)); press() triggers it programmatically (gamepad menus, tests).",
1076
1076
  ko: "HudLayer의 자식. text/size/color/background를 설정하고 disabled로 비활성화합니다. 'pressed' 시그널을 내며(비헤이비어에서 node.on('pressed', ...)), press()로 코드에서도 누를 수 있습니다(게임패드 메뉴, 테스트)."
1077
1077
  }),
1078
+ F("UiPanel", {
1079
+ en: "A box that holds other widgets — the thing a menu, options screen or inventory actually is.",
1080
+ ko: "다른 위젯을 담는 상자 — 메뉴·설정 화면·인벤토리가 실제로 이것입니다."
1081
+ }, {
1082
+ en: "Child of HudLayer. Widgets under it mount INSIDE it, so tree structure becomes screen structure; panels nest. layout column|row|grid with columns (an inventory is a grid panel, one child per slot), plus gap/padding/background/radius/width/height/border. width or height 0 = size to the contents.",
1083
+ ko: "HudLayer의 자식. 그 아래 위젯들이 이 안에 마운트되어 트리 구조가 화면 구조가 됩니다(중첩 가능). layout은 column|row|grid이고 columns와 함께 씁니다(인벤토리 = grid 패널, 슬롯당 자식 하나). gap/padding/background/radius/width/height/border 지원. width·height가 0이면 내용에 맞춰집니다."
1084
+ }),
1085
+ F("UiImage", {
1086
+ en: "A picture in the HUD — item icons, portraits, a logo.",
1087
+ ko: "HUD 안의 그림 — 아이템 아이콘, 초상화, 로고."
1088
+ }, {
1089
+ en: "src takes a url or a $assetKey (declare the icon once like any other asset). fit contain|cover|fill; tint multiplies through the picture, which is how an unaffordable item greys out in one prop; opacity for disabled states.",
1090
+ ko: "src는 url 또는 $assetKey(아이콘도 다른 에셋처럼 한 번만 선언). fit은 contain|cover|fill, tint는 그림에 곱해져서 살 수 없는 아이템을 프롭 하나로 회색 처리합니다. opacity로 비활성 표현."
1091
+ }),
1092
+ F("UiSlider", {
1093
+ en: "A value the player sets — volume, sensitivity, difficulty.",
1094
+ ko: "플레이어가 조절하는 값 — 볼륨, 감도, 난이도."
1095
+ }, {
1096
+ en: "Emits changed(value). label/value/min/max/step/width/color. Setting .value from a behavior updates the control WITHOUT re-emitting, so restoring a saved setting does not fire the handler that saved it.",
1097
+ ko: "changed(value) 시그널. label/value/min/max/step/width/color. 비헤이비어에서 .value를 설정하면 컨트롤만 갱신되고 changed는 다시 발생하지 않습니다 — 저장된 설정을 복원할 때 저장 핸들러가 다시 도는 것을 막습니다."
1098
+ }),
1099
+ F("UiToggle", {
1100
+ en: "An on/off switch — mute, invert-Y, fullscreen.",
1101
+ ko: "켜기/끄기 스위치 — 음소거, Y축 반전, 전체화면."
1102
+ }, {
1103
+ en: "Emits changed(boolean). label/value. Same no-re-emit rule as UiSlider when set from code.",
1104
+ ko: "changed(boolean) 시그널. label/value. 코드에서 설정할 때 재발생하지 않는 규칙은 UiSlider와 같습니다."
1105
+ }),
1106
+ F("UiSelect", {
1107
+ en: "One of several — quality, resolution, language.",
1108
+ ko: "여러 개 중 하나 — 품질, 해상도, 언어."
1109
+ }, {
1110
+ en: "options is a comma-separated list (\"low,medium,high\"); value is the current one. Emits changed(value).",
1111
+ ko: "options는 쉼표로 구분된 목록(\"low,medium,high\"), value가 현재 선택입니다. changed(value) 시그널."
1112
+ }),
1078
1113
  F("UiDialogue", {
1079
1114
  en: "A typewriter dialogue box with a queue and choice buttons — the RPG conversation layer.",
1080
1115
  ko: "타자기 효과·큐·선택지 버튼을 갖춘 대화창 — RPG 대화 레이어."
@@ -1,10 +1,10 @@
1
- import { t as registerCoreNodes } from "./register-Dzkd6-os.js";
1
+ import { t as registerCoreNodes } from "./register-CscIzJEO.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-CJdGpT2V.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-BftxM_It.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-BpQCbABv.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, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, 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";