incanto 0.52.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/2d.d.ts +51 -3
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +9 -3
  4. package/dist/3d.js +4 -4
  5. package/dist/{behavior-uPEuZrUB.d.ts → behavior-Do0Da56m.d.ts} +98 -0
  6. package/dist/{create-game-B_e9hJf7.js → create-game-9D87XaiX.js} +26 -11
  7. package/dist/{create-game-CGnoypjL.js → create-game-CDJ1lVqK.js} +21 -13
  8. package/dist/debug.d.ts +1 -1
  9. package/dist/{duplicate-B-OtSRFL.js → duplicate-MNLMAcbz.js} +1 -1
  10. package/dist/editor.js +14 -0
  11. package/dist/{environment-presets-DSZwsKPs.js → environment-presets-SkGanr2s.js} +2 -2
  12. package/dist/{gameplay-B6jqvYeM.js → gameplay-CRYw_Q-T.js} +2 -2
  13. package/dist/gameplay.d.ts +1 -1
  14. package/dist/gameplay.js +1 -1
  15. package/dist/index.d.ts +177 -5
  16. package/dist/index.js +8 -8
  17. package/dist/{loader-B-Gft32x.js → loader-BTkHYrQn.js} +54 -18
  18. package/dist/{loader-B9iTqs27.d.ts → loader-Cu_7kJDy.d.ts} +1 -1
  19. package/dist/{log-report-lxrQY9cH.js → log-report-CPFm4OXf.js} +0 -0
  20. package/dist/net.d.ts +2 -2
  21. package/dist/net.js +1 -1
  22. package/dist/{pathfinding-CAR9DjQQ.d.ts → pathfinding-HNGFqGUZ.d.ts} +24 -1
  23. package/dist/{physics-2d-Dns-oZlE.js → physics-2d-BhZZ-HAp.js} +2 -2
  24. package/dist/{physics-3d-BhI0ehpe.js → physics-3d-DmyO2oaN.js} +3 -3
  25. package/dist/react.d.ts +1 -1
  26. package/dist/react.js +1 -1
  27. package/dist/{register-DVwlnZAZ.js → register-BTomIiYG.js} +304 -24
  28. package/dist/{register-Trx7WHnD.js → register-DROK2l7J.js} +2 -2
  29. package/dist/{replay-BU1CCM15.d.ts → replay-9Fy6C10F.d.ts} +1 -1
  30. package/dist/{replay-BicPOMX0.js → replay-DYNUL4BU.js} +2 -2
  31. package/dist/{split-screen-CZ9ccBBQ.js → split-screen-CaF9hO7g.js} +2 -2
  32. package/dist/{split-screen-paxkQs_q.d.ts → split-screen-DNmcX1Pz.d.ts} +1 -1
  33. package/dist/{src-5gbZO47I.js → src-C9xyZW7M.js} +1 -1
  34. package/dist/{teardown-ks3d5W9n.js → teardown-Bw2aeyGI.js} +30 -1
  35. package/dist/{test-DAojuFdb.js → test-CJRsciFk.js} +11 -11
  36. package/dist/test.d.ts +4 -4
  37. package/dist/test.js +2 -2
  38. package/dist/{touch-BoNg_MnF.js → touch-BnMyy9tr.js} +4 -4
  39. package/dist/vite.js +2 -2
  40. package/editor/assets/{agent8-CCvckvbw.js → agent8-PlFHzJsh.js} +1 -1
  41. package/editor/assets/{debug-CLNCOnbc.js → debug-CebV7CDW.js} +1 -1
  42. package/editor/assets/{index-Cb5Brupb.js → index-CdbsY31G.js} +91 -91
  43. package/editor/index.html +1 -1
  44. package/package.json +1 -1
  45. package/schemas/scene.schema.json +213 -0
  46. package/skills/incanto-audio.md +97 -10
  47. package/skills/incanto-building-2d-games.md +20 -3
  48. package/skills/incanto-hud.md +7 -0
  49. package/skills/incanto-node-reference.md +34 -0
  50. package/skills/incanto-physics-and-input.md +38 -0
  51. package/skills/incanto-verifying-your-game.md +19 -1
  52. package/skills/incanto-web-integration.md +1 -0
  53. package/templates-app/beacon-isle-3d/index.html +3 -3
  54. package/templates-app/beacon-isle-3d/package.json +1 -1
  55. package/templates-app/tps-3d/index.html +3 -3
  56. package/templates-app/tps-3d/package.json +1 -1
  57. package/templates-app/village-quest-3d/index.html +3 -3
  58. package/templates-app/village-quest-3d/package.json +1 -1
package/dist/2d.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Dt 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-uPEuZrUB.js";
1
+ import { Dt 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-Do0Da56m.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
3
3
  import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
4
- import { t as LoadSceneOptions } from "./loader-B9iTqs27.js";
4
+ import { t as LoadSceneOptions } from "./loader-Cu_7kJDy.js";
5
5
  import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
6
6
  import { t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
7
7
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
@@ -304,7 +304,21 @@ interface CreateGame2DOptions {
304
304
  * false disables. They overlay `touchContainer` (default: the canvas's
305
305
  * parent element — give it position: relative).
306
306
  */
307
+ /**
308
+ * Attach pointer input (mouse, finger or pen) to the canvas: positions for
309
+ * `input.pointerPosition()`, buttons for `input.mousePressed()`.
310
+ * `{ lockOnClick: true }` captures the cursor — rare in 2D, so it is off.
311
+ */
312
+ pointer?: boolean | {
313
+ lockOnClick?: boolean;
314
+ };
307
315
  touch?: "auto" | boolean;
316
+ /**
317
+ * Leave the browser's touch gestures (scroll/zoom/pull-to-refresh) on the
318
+ * canvas. Default false: the GAME owns them, or a drag on a phone scrolls
319
+ * the page instead of playing.
320
+ */
321
+ pageGestures?: boolean;
308
322
  touchContainer?: HTMLElement;
309
323
  /** @internal Test seam — replaces `document` for the touch overlay. */
310
324
  _touchDoc?: {
@@ -344,10 +358,32 @@ interface CreateGame2DOptions {
344
358
  _scheduler?: Scheduler;
345
359
  }
346
360
  /** What createGame needs from a renderer (stats is optional for test stubs). */
361
+ /** What a renderer must do for the engine. The test seam supplies this much. */
347
362
  interface GameRenderer {
348
363
  dispose(): void;
349
364
  stats?(): RendererStats;
350
365
  }
366
+ /**
367
+ * What a GAME can ask its renderer for.
368
+ *
369
+ * The two coordinate conversions live here rather than only on `Renderer2D`,
370
+ * because `game.renderer` is what a consumer holds: a tap needs screen→world
371
+ * and a pinned DOM element needs world→screen, both are documented in the
372
+ * skills, and neither typechecked through the narrow interface. Found by
373
+ * following my own documentation in a fresh project.
374
+ */
375
+ interface Game2DRenderer extends GameRenderer {
376
+ /** Canvas px → world px, through the design viewport and the camera. */
377
+ worldFromScreen(sx: number, sy: number): {
378
+ x: number;
379
+ y: number;
380
+ };
381
+ /** World px → canvas px (pin DOM to a node). */
382
+ screenFromWorld(wx: number, wy: number): {
383
+ x: number;
384
+ y: number;
385
+ };
386
+ }
351
387
  interface Game2D {
352
388
  engine: Engine;
353
389
  scene: Scene$1;
@@ -356,13 +392,25 @@ interface Game2D {
356
392
  * A running tree has moved; the editor gets what was written down.
357
393
  */
358
394
  sourceJson: SceneJson;
359
- renderer: GameRenderer;
395
+ renderer: Game2DRenderer;
360
396
  physics: Physics2D | null;
361
397
  /**
362
398
  * The node under a screen pixel, or null. The raycast the renderer already
363
399
  * does — reachable now without holding the renderer.
364
400
  */
365
401
  pick(x: number, y: number): Node | null;
402
+ /**
403
+ * Assets that did not arrive — a texture that 404'd, a sound that cannot be
404
+ * played — with the ref and the reason.
405
+ *
406
+ * The verify loop names this as one of its four signals, and 2D never had it:
407
+ * a 2D game could not ask the question at all.
408
+ */
409
+ assetErrors(): Array<{
410
+ ref: string;
411
+ url: string;
412
+ error: string;
413
+ }>;
366
414
  /** Engine + renderer perf counters in one read (fps/nodes/triangles/…). */
367
415
  stats(): GameStats;
368
416
  /**
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-B_e9hJf7.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-Trx7WHnD.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-Dns-oZlE.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-9D87XaiX.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-DROK2l7J.js";
4
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BhZZ-HAp.js";
5
5
  //#region src/2d/library-sprite.ts
6
6
  /**
7
7
  * What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in
package/dist/3d.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { D as QualityCaps, Dt 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-uPEuZrUB.js";
1
+ import { D as QualityCaps, Dt 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-Do0Da56m.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
3
3
  import { i as SceneJson$1, s as JsonObject } from "./schema-CFeioQRE.js";
4
- import { t as LoadSceneOptions } from "./loader-B9iTqs27.js";
4
+ import { t as LoadSceneOptions } from "./loader-Cu_7kJDy.js";
5
5
  import { t as ParticleSim } from "./particle-sim-BzJ1yxoE.js";
6
6
  import { a as GridCell, c as diffText, d as frameText, i as FrameStatsOptions, l as frameSignature, n as FrameSignature, o as SIGNATURE_GRID, r as FrameStats, s as diffSignatures, t as FrameDiff, u as frameStats } from "./frame-report-DZ70IY26.js";
7
- import { n as PathGrid, s as SpatialPose } from "./pathfinding-CAR9DjQQ.js";
7
+ import { n as PathGrid, s as SpatialPose } from "./pathfinding-HNGFqGUZ.js";
8
8
  import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
9
9
  import { VRM } from "@pixiv/three-vrm";
10
10
  import { Sky } from "three/examples/jsm/objects/Sky.js";
@@ -563,6 +563,12 @@ interface CreateGame3DOptions {
563
563
  * parent element — give it position: relative).
564
564
  */
565
565
  touch?: "auto" | boolean;
566
+ /**
567
+ * Leave the browser's touch gestures (scroll/zoom/pull-to-refresh) on the
568
+ * canvas. Default false: the GAME owns them, or a drag on a phone scrolls
569
+ * the page instead of playing.
570
+ */
571
+ pageGestures?: boolean;
566
572
  touchContainer?: HTMLElement;
567
573
  /** @internal Test seam — replaces `document` for the touch overlay. */
568
574
  _touchDoc?: {
package/dist/3d.js CHANGED
@@ -1,9 +1,9 @@
1
- import { F as CharacterBody3D, H as WATER_MAX_RIPPLES, I as PhysicsBody3D, L as RigidBody3D, M as WATER_CUTOUT_MAX, N as WaterCutout3D, P as Area3D, R as StaticBody3D, j as Water3D, z as Node3D } from "./gameplay-B6jqvYeM.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-DSZwsKPs.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-CGnoypjL.js";
1
+ import { F as CharacterBody3D, H as WATER_MAX_RIPPLES, I as PhysicsBody3D, L as RigidBody3D, M as WATER_CUTOUT_MAX, N as WaterCutout3D, P as Area3D, R as StaticBody3D, j as Water3D, z as Node3D } from "./gameplay-CRYw_Q-T.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-SkGanr2s.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-CDJ1lVqK.js";
4
4
  import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-njybhZon.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-BhI0ehpe.js";
6
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-DmyO2oaN.js";
7
7
  //#region src/3d/model-verdict.ts
8
8
  /** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
9
9
  const MIXAMO = /^mixamorig[:_]?/i;
@@ -325,9 +325,42 @@ type BusName = "sfx" | "music";
325
325
  * engine.audio.muted = true; // mute toggle
326
326
  * ```
327
327
  */
328
+ /** One thing that sounded (or would have, headless). */
329
+ interface AudioEvent {
330
+ /** `preset` (procedural one-shot), `src` (a clip), `music`, or `voice`. */
331
+ kind: "preset" | "src" | "music" | "voice";
332
+ /** The preset name, the clip url, or the voice preset. */
333
+ name: string;
334
+ /** Node path for a node-driven sound; `engine.music` / `engine.sfx` otherwise. */
335
+ from: string;
336
+ bus: BusName;
337
+ /** `engine.time` when it fired, in seconds. */
338
+ at: number;
339
+ }
328
340
  declare class AudioBuses {
329
341
  /** Fires whenever any value changes (drives live re-gain of playing sounds). */
330
342
  readonly changed: Signal<[]>;
343
+ /**
344
+ * What has sounded, most recent last.
345
+ *
346
+ * Audio is a no-op headless, which left "did the coin sound actually fire
347
+ * when the coin was collected?" unanswerable — the audio skill's own advice
348
+ * was to *"assert `play()` doesn't throw and check your gameplay logic
349
+ * instead"*, which checks something else. The CALL still happens with no
350
+ * backend, so recording it answers the wiring question everywhere: in the
351
+ * verify VM, in a playtest, in a test.
352
+ *
353
+ * Bounded (200) — a game running for an hour must not grow a log.
354
+ */
355
+ private readonly events;
356
+ /** @internal Called by the players; games read `recent()`. */
357
+ record(event: AudioEvent): void;
358
+ /** The last `limit` sounds, oldest first. */
359
+ recent(limit?: number): AudioEvent[];
360
+ /** How many times a preset/clip/track sounded — the assertion you want. */
361
+ countOf(name: string): number;
362
+ /** Forget what has sounded so far (between scenes, or between assertions). */
363
+ clearLog(): void;
331
364
  private _master;
332
365
  private _sfx;
333
366
  private _music;
@@ -377,6 +410,8 @@ interface MusicBackend {
377
410
  createTrack(src: string): MusicTrack;
378
411
  /** Resume a gesture-suspended context (wired to the first user gesture). */
379
412
  unlock(): void;
413
+ /** Release the context it owns (optional — teardown). */
414
+ dispose?(): void;
380
415
  }
381
416
  interface PlayMusicOptions {
382
417
  /** Loop the track (default true — music usually loops). */
@@ -420,6 +455,15 @@ declare class MusicManager {
420
455
  get current(): string | null;
421
456
  /** Per-manager volume (0..1), on top of the bus gain. */
422
457
  get volume(): number;
458
+ /**
459
+ * The backend, or an INERT one that mints silent tracks.
460
+ *
461
+ * Never null, on purpose: the manager used to return before touching its
462
+ * state when there was no backend, so `current` stayed null forever in every
463
+ * headless run — including the verify VM, which is the only place a game is
464
+ * checked automatically. "Did the boss music start?" had no answer anywhere.
465
+ * With an inert backend the state machine runs identically and nothing plays.
466
+ */
423
467
  private ensure;
424
468
  /** Resume a gesture-suspended context + (re)start any pending track. */
425
469
  unlock(): void;
@@ -432,6 +476,13 @@ declare class MusicManager {
432
476
  crossfadeTo(src: string, seconds?: number): void;
433
477
  /** Stop the current track, optionally fading out over `fadeOut` seconds. */
434
478
  stop(fadeOut?: number): void;
479
+ /**
480
+ * Stop instantly and release the backend — music must not outlive the game.
481
+ *
482
+ * `dispose()` is the one call that tears an SPA-mounted game down, and it used
483
+ * to leave the track playing with the engine that owned it gone.
484
+ */
485
+ dispose(): void;
435
486
  /** Set the per-manager volume (re-applied to live tracks on the next tick). */
436
487
  setVolume(v: number): void;
437
488
  /** Advance fades by `dt` seconds and (re)apply bus×fade gains. Frame-driven. */
@@ -588,6 +639,14 @@ interface Voice {
588
639
  //#endregion
589
640
  //#region src/core/audio/webaudio-sfx.d.ts
590
641
  /** A spatial emitter: where the sound is + where the listener is. */
642
+ /** The shape `AudioBuses.record` takes (structural — no import cycle). */
643
+ interface AudioRecord {
644
+ kind: "preset" | "src" | "music" | "voice";
645
+ name: string;
646
+ from: string;
647
+ bus: "sfx" | "music";
648
+ at: number;
649
+ }
591
650
  interface SpatialPlay {
592
651
  /** Emitter world position. */
593
652
  position: Vec3;
@@ -615,6 +674,19 @@ declare class SfxEngine {
615
674
  private ctx;
616
675
  private resolved;
617
676
  private readonly pcmCache;
677
+ /**
678
+ * The engine's buses, when this SfxEngine belongs to one — so a continuous
679
+ * voice lands in the same audio record as everything else. A record that
680
+ * covered only SOME sounds would answer "did the engine hum start?" with a
681
+ * confident no.
682
+ */
683
+ private buses;
684
+ /** Live continuous voices, so teardown can silence the ones nobody stopped. */
685
+ private readonly voices;
686
+ /** @internal Engine wiring. */
687
+ _setBuses(buses: {
688
+ record(event: AudioRecord): void;
689
+ }): void;
618
690
  /** True once a backend is present (lazily created on first use). */
619
691
  get available(): boolean;
620
692
  private ensure;
@@ -624,6 +696,18 @@ declare class SfxEngine {
624
696
  * done. Headless returns an inert handle (same call sites, no ifs).
625
697
  */
626
698
  startVoice(preset: VoicePreset, gain?: number): Voice;
699
+ /** How many continuous voices are still running (teardown/leak checks). */
700
+ get liveVoiceCount(): number;
701
+ /** Silence every continuous voice — the hum must not outlive the game. */
702
+ stopAll(seconds?: number): void;
703
+ /**
704
+ * Silence everything and release the AudioContext.
705
+ *
706
+ * A browser allows only a handful of contexts per page, so an SPA that mounts
707
+ * the game a few times must hand this one back — `dispose()` is documented as
708
+ * the call that tears everything down.
709
+ */
710
+ dispose(): void;
627
711
  /** Resume a gesture-suspended context (wired to the first user gesture). */
628
712
  unlock(): void;
629
713
  /**
@@ -760,6 +844,20 @@ declare class InputMap {
760
844
  connect(fn: () => void): () => void;
761
845
  };
762
846
  }): () => void;
847
+ /**
848
+ * Pointer input from a mouse, a FINGER or a pen — one code path.
849
+ *
850
+ * This used to bind `mousedown`/`mousemove`/`mouseup`, which on a phone only
851
+ * arrive as COMPATIBILITY events: delayed until the browser has decided the
852
+ * touch is not a scroll, never fired for drags or multi-touch, and suppressed
853
+ * outright once the surface claims its gestures with `touch-action: none`.
854
+ * So every genre the comment below lists — match-3, tower defense, card,
855
+ * point-and-click, RTS — was mouse-only on the device most of them are played
856
+ * on. Pointer events cover all three input kinds and fire immediately.
857
+ *
858
+ * Only the PRIMARY pointer drives it: a second finger (the one on the virtual
859
+ * stick) must not also swing the camera.
860
+ */
763
861
  attachPointer(target: HTMLElement, opts?: {
764
862
  lockOnClick?: boolean;
765
863
  }): () => void;
@@ -1,13 +1,13 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { A as registerBehavior, C as diagnose, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-B-Gft32x.js";
3
- import { h as Engine, m as AudioPlayer } from "./register-DVwlnZAZ.js";
2
+ import { A as registerBehavior, C as diagnose, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-BTkHYrQn.js";
3
+ import { b as AudioPlayer, x as Engine } from "./register-BTomIiYG.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
- import { i as resolveRendering, n as attachTouchControls } from "./touch-BoNg_MnF.js";
6
- import { a as openBundledEditor, i as devServerLibrary, n as pauseWhenHidden, r as crossFade, t as teardown } from "./teardown-ks3d5W9n.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-B6jqvYeM.js";
8
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-Trx7WHnD.js";
5
+ import { i as resolveRendering, n as attachTouchControls } from "./touch-BnMyy9tr.js";
6
+ import { a as openBundledEditor, c as audioErrors, i as devServerLibrary, n as pauseWhenHidden, r as crossFade, s as claimCanvasGestures, t as teardown } from "./teardown-Bw2aeyGI.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-CRYw_Q-T.js";
8
+ import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-DROK2l7J.js";
9
9
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
10
- import { n as enablePhysics2D } from "./physics-2d-Dns-oZlE.js";
10
+ import { n as enablePhysics2D } from "./physics-2d-BhZZ-HAp.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
  /**
@@ -625,10 +625,15 @@ async function createGame2D(opts) {
625
625
  const cleanups = [];
626
626
  const keyboard = opts.keyboard ?? (typeof window !== "undefined" ? window : false);
627
627
  if (keyboard) engine.input.attachKeyboard(keyboard);
628
+ if (opts.pointer) {
629
+ const lockOnClick = typeof opts.pointer === "object" ? opts.pointer.lockOnClick ?? false : false;
630
+ engine.input.attachPointer(opts.canvas, { lockOnClick });
631
+ }
628
632
  cleanups.push(wireAudioUnlock(engine, scene.root, opts.canvas));
629
633
  cleanups.push(engine.settings.bindAudio(engine.audio));
630
634
  cleanups.push(pauseWhenHidden(engine));
631
635
  cleanups.push(engine.settings.bindLocale(engine.locale));
636
+ if (!opts.pageGestures) claimCanvasGestures(opts.canvas);
632
637
  const touchMode = opts.touch ?? "auto";
633
638
  if (touchMode !== false) {
634
639
  const host = opts.touchContainer ?? opts.canvas.parentElement ?? null;
@@ -718,6 +723,9 @@ async function createGame2D(opts) {
718
723
  pick(x, y) {
719
724
  return engine.picker?.(x, y) ?? null;
720
725
  },
726
+ assetErrors() {
727
+ return [...renderer.assets?.errors() ?? [], ...audioErrors(engine.scene?.tree.root)];
728
+ },
721
729
  stats() {
722
730
  return {
723
731
  triangles: 0,
@@ -755,10 +763,17 @@ function wireAudioUnlock(engine, root, canvas) {
755
763
  target: canvasTarget,
756
764
  type: "pointerdown"
757
765
  });
758
- if (typeof window !== "undefined") sources.push({
759
- target: window,
760
- type: "keydown"
761
- });
766
+ if (typeof window !== "undefined") {
767
+ const win = window;
768
+ sources.push({
769
+ target: win,
770
+ type: "keydown"
771
+ });
772
+ sources.push({
773
+ target: win,
774
+ type: "pointerdown"
775
+ });
776
+ }
762
777
  if (sources.length === 0) return () => {};
763
778
  function detachAll() {
764
779
  for (const s of sources) s.target.removeEventListener?.(s.type, unlock);
@@ -1,15 +1,15 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { A as registerBehavior, C as diagnose, n as loadScene } from "./loader-B-Gft32x.js";
3
- import { _ as qualityCaps, h as Engine, m as AudioPlayer, y as qualityRendering } from "./register-DVwlnZAZ.js";
2
+ import { A as registerBehavior, C as diagnose, n as loadScene } from "./loader-BTkHYrQn.js";
3
+ import { C as qualityCaps, T as qualityRendering, b as AudioPlayer, x as Engine } from "./register-BTomIiYG.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
- import { r as parseDrive, t as logReport } from "./log-report-lxrQY9cH.js";
6
- import { i as resolveRendering, n as attachTouchControls } from "./touch-BoNg_MnF.js";
7
- import { a as openBundledEditor, i as devServerLibrary, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, t as teardown } from "./teardown-ks3d5W9n.js";
8
- import { I as PhysicsBody3D, V as createCausticsQuad, n as registerGameplayBehaviors, z as Node3D } from "./gameplay-B6jqvYeM.js";
5
+ import { r as parseDrive, t as logReport } from "./log-report-CPFm4OXf.js";
6
+ import { i as resolveRendering, n as attachTouchControls } from "./touch-BnMyy9tr.js";
7
+ import { a as openBundledEditor, c as audioErrors, i as devServerLibrary, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as claimCanvasGestures, t as teardown } from "./teardown-Bw2aeyGI.js";
8
+ import { I as PhysicsBody3D, V as createCausticsQuad, n as registerGameplayBehaviors, z as Node3D } from "./gameplay-CRYw_Q-T.js";
9
9
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
10
- import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-DSZwsKPs.js";
10
+ import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-SkGanr2s.js";
11
11
  import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-njybhZon.js";
12
- import { n as enablePhysics3D } from "./physics-3d-BhI0ehpe.js";
12
+ import { n as enablePhysics3D } from "./physics-3d-DmyO2oaN.js";
13
13
  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";
14
14
  import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
15
15
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
@@ -2349,6 +2349,7 @@ async function createGame3D(opts) {
2349
2349
  engine.input.attachPointer(opts.canvas, { lockOnClick });
2350
2350
  }
2351
2351
  cleanups.push(wireAudioUnlock(engine, scene.root, opts.canvas));
2352
+ if (!opts.pageGestures) claimCanvasGestures(opts.canvas);
2352
2353
  const touchMode = opts.touch ?? "auto";
2353
2354
  if (touchMode !== false) {
2354
2355
  const host = opts.touchContainer ?? opts.canvas.parentElement ?? null;
@@ -2575,7 +2576,7 @@ async function createGame3D(opts) {
2575
2576
  renderer,
2576
2577
  physics,
2577
2578
  assetErrors() {
2578
- return renderer.assets?.errors() ?? [];
2579
+ return [...renderer.assets?.errors() ?? [], ...audioErrors(engine.scene?.tree.root)];
2579
2580
  },
2580
2581
  async frame(frameOpts) {
2581
2582
  const capture = renderer.captureFrame;
@@ -2623,10 +2624,17 @@ function wireAudioUnlock(engine, root, canvas) {
2623
2624
  target: canvasTarget,
2624
2625
  type: "pointerdown"
2625
2626
  });
2626
- if (typeof window !== "undefined") sources.push({
2627
- target: window,
2628
- type: "keydown"
2629
- });
2627
+ if (typeof window !== "undefined") {
2628
+ const win = window;
2629
+ sources.push({
2630
+ target: win,
2631
+ type: "keydown"
2632
+ });
2633
+ sources.push({
2634
+ target: win,
2635
+ type: "pointerdown"
2636
+ });
2637
+ }
2630
2638
  if (sources.length === 0) return () => {};
2631
2639
  function detachAll() {
2632
2640
  for (const s of sources) s.target.removeEventListener?.(s.type, unlock);
package/dist/debug.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { T as RendererStats, b as Engine, jt as LogLevel } from "./behavior-uPEuZrUB.js";
1
+ import { T as RendererStats, b as Engine, jt as LogLevel } from "./behavior-Do0Da56m.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-B-Gft32x.js";
1
+ import { i as serializeNode, t as buildNodeJson } from "./loader-BTkHYrQn.js";
2
2
  //#region src/core/scene/duplicate.ts
3
3
  function stripUids(json) {
4
4
  const { uid: _uid, ...rest } = json;
package/dist/editor.js CHANGED
@@ -1181,6 +1181,20 @@ var kt = {
1181
1181
  en: "Options are the locales the scene declares in \"strings\", each labeled with its own endonym. Picking one switches the game live and persists it. English is the base: any key a locale omits falls back to it, on purpose.",
1182
1182
  ko: "옵션은 씬의 \"strings\"가 선언한 로케일이며, 각 언어의 자기 이름으로 표시됩니다. 선택하면 즉시 바뀌고 저장됩니다. 영어가 기준이라 로케일이 빠뜨린 키는 의도적으로 영어로 표시됩니다."
1183
1183
  }),
1184
+ L("UiVolumeSlider", {
1185
+ en: "A volume slider, already wired to a bus.",
1186
+ ko: "볼륨 슬라이더 — 버스에 이미 연결되어 있습니다."
1187
+ }, {
1188
+ en: "bus picks which one it owns: master (everything), sfx or music. It writes engine.audio, which persists itself, so nothing needs saving. Left unlabelled it names its own bus. Two of them (music + sfx) is the audio menu most games ship.",
1189
+ ko: "bus로 어느 볼륨을 맡을지 고릅니다 — master(전체), sfx, music. engine.audio에 쓰면 자동 저장되므로 따로 저장할 게 없습니다. label을 비워두면 버스 이름이 표시됩니다. 대부분의 게임은 music + sfx 두 개면 충분합니다."
1190
+ }),
1191
+ L("UiMuteToggle", {
1192
+ en: "The mute switch — checked means silent.",
1193
+ ko: "음소거 스위치 — 체크하면 소리가 꺼집니다."
1194
+ }, {
1195
+ en: "Writes engine.audio.muted and persists with the volumes. Follows a mute made anywhere else, so two menus can never disagree.",
1196
+ ko: "engine.audio.muted에 쓰고 볼륨과 함께 저장됩니다. 다른 곳에서 음소거해도 따라가므로 두 메뉴가 어긋나지 않습니다."
1197
+ }),
1184
1198
  L("UiQualitySelect", {
1185
1199
  en: "Graphics quality — Low, Medium, High. Already wired.",
1186
1200
  ko: "그래픽 품질 — Low, Medium, High. 이미 연결되어 있습니다."
@@ -1,10 +1,10 @@
1
- import { t as registerCoreNodes } from "./register-DVwlnZAZ.js";
1
+ import { t as registerCoreNodes } from "./register-BTomIiYG.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-C7u42TID.js";
5
5
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
6
6
  import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-D_p28jwU.js";
7
- import { F as CharacterBody3D, I as PhysicsBody3D, L as RigidBody3D, N as WaterCutout3D, P as Area3D, R as StaticBody3D, U as colliderFootDrop, j as Water3D, z as Node3D } from "./gameplay-B6jqvYeM.js";
7
+ import { F as CharacterBody3D, I as PhysicsBody3D, L as RigidBody3D, N as WaterCutout3D, P as Area3D, R as StaticBody3D, U as colliderFootDrop, j as Water3D, z as Node3D } from "./gameplay-CRYw_Q-T.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";
@@ -1,7 +1,7 @@
1
- import { A as registerBehavior, T as Behavior, b as effectiveOrder, f as Node, n as loadScene, p as SaveSlots } from "./loader-B-Gft32x.js";
1
+ import { A as registerBehavior, T as Behavior, b as effectiveOrder, f as Node, n as loadScene, p as SaveSlots } from "./loader-BTkHYrQn.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
4
- import { t as duplicateNode } from "./duplicate-B-OtSRFL.js";
4
+ import { t as duplicateNode } from "./duplicate-MNLMAcbz.js";
5
5
  import { Color, CubeCamera, DepthTexture, DoubleSide, Euler, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
6
6
  //#region src/3d/frustum.ts
7
7
  const scratchFrustum = new Frustum();
@@ -1,4 +1,4 @@
1
- import { Dt as Node, b as Engine, d as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-uPEuZrUB.js";
1
+ import { Dt as Node, b as Engine, d as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-Do0Da56m.js";
2
2
  import { c as JsonValue, s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as Chase, C as restartScene, D as DamageOnContact, E as phaseOf, O as Health, S as goToScene, T as DayNight, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, k as Collector, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, w as FollowCamera, x as GameFlow, y as screenFlash } from "./gameplay-B6jqvYeM.js";
1
+ import { A as Chase, C as restartScene, D as DamageOnContact, E as phaseOf, O as Health, S as goToScene, T as DayNight, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, k as Collector, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, w as FollowCamera, x as GameFlow, y as screenFlash } from "./gameplay-CRYw_Q-T.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };