incanto 0.69.0 → 0.70.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 (146) hide show
  1. package/bin/_behaviors-loader.mjs +22 -0
  2. package/bin/_read-json.mjs +28 -0
  3. package/bin/incanto-assets.mjs +5 -4
  4. package/bin/incanto-check.mjs +58 -31
  5. package/bin/incanto-editor.mjs +128 -5
  6. package/bin/incanto-env.mjs +3 -2
  7. package/bin/incanto-feel.mjs +24 -13
  8. package/bin/incanto-frame.mjs +8 -1
  9. package/bin/incanto-multiplay.mjs +11 -9
  10. package/bin/incanto-new.mjs +105 -10
  11. package/bin/incanto-play.mjs +158 -16
  12. package/bin/incanto-playtest.mjs +66 -23
  13. package/bin/incanto-skills.mjs +14 -2
  14. package/bin/incanto-verify.mjs +125 -44
  15. package/bin/incanto.mjs +3 -2
  16. package/dist/2d.d.ts +281 -34
  17. package/dist/2d.js +4 -4
  18. package/dist/3d.d.ts +157 -9
  19. package/dist/3d.js +7 -7
  20. package/dist/{agent8-CvsfVskX.js → agent8-CmNF01gA.js} +61 -8
  21. package/dist/{audio-player-C0j2k-yB.d.ts → audio-player-DaMxqfNE.d.ts} +2 -2
  22. package/dist/{behavior-rm8vVzu_.d.ts → behavior-DZExDn9o.d.ts} +744 -46
  23. package/dist/{create-game-CwxEDlCm.js → create-game-Bwvh6q8A.js} +142 -59
  24. package/dist/{create-game-CvJ1wSRP.js → create-game-C7ffQWW7.js} +97 -47
  25. package/dist/debug.d.ts +1 -1
  26. package/dist/debug.js +2 -3
  27. package/dist/diagnostics-Cu85N3tL.d.ts +12 -0
  28. package/dist/{editor-switch-CnIOiyNJ.d.ts → editor-switch-CFU9mCec.d.ts} +22 -13
  29. package/dist/editor.js +1065 -863
  30. package/dist/env.d.ts +1 -1
  31. package/dist/env.js +5 -3
  32. package/dist/{environment-presets-BO-7XoFV.js → environment-presets-D6Q5BxeE.js} +298 -45
  33. package/dist/{gameplay-D1KixWK8.js → gameplay-BfHkuzVb.js} +809 -211
  34. package/dist/gameplay.d.ts +187 -7
  35. package/dist/gameplay.js +1 -1
  36. package/dist/{heightmap-CRK0M4jT.js → heightmap-BYgD5Edk.js} +1 -1
  37. package/dist/index.d.ts +156 -13
  38. package/dist/index.js +10 -12
  39. package/dist/json-CfTjpvW8.js +67 -0
  40. package/dist/{loader-DadV2QCT.d.ts → loader-Cff09LMm.d.ts} +2 -2
  41. package/dist/net.d.ts +27 -3
  42. package/dist/net.js +2 -2
  43. package/dist/{noise-CGUMx44x.js → noise-D3nPpmFg.js} +1 -1
  44. package/dist/{physics-2d-CF5yyjbj.js → physics-2d-CE0Qvy3V.js} +136 -11
  45. package/dist/{physics-3d-DbYaEcZy.js → physics-3d-CpH-2gn5.js} +104 -23
  46. package/dist/{teardown-CMqFVxO9.js → picking-CQJ_PJKh.js} +103 -12
  47. package/dist/react.d.ts +2 -2
  48. package/dist/react.js +2 -2
  49. package/dist/{register-mmfugpaQ.js → register-6DYnKZcy.js} +546 -817
  50. package/dist/{register-DbVArsns.js → register-Bkk0wSDB.js} +332 -23
  51. package/dist/{replay-BEHLDcQV.js → replay-DjAkAzMq.js} +224 -14
  52. package/dist/{replay-BQfkX4x7.d.ts → replay-Dmw-PKQu.d.ts} +20 -3
  53. package/dist/{schema-B6ugCV1Q.d.ts → rng-Bb-IutXB.d.ts} +38 -21
  54. package/dist/{rng-DP-SR7eg.js → rng-CDOMybym.js} +22 -0
  55. package/dist/{loader-DZE7B07H.js → save-slots-BXVg148r.js} +4570 -2322
  56. package/dist/{split-screen-Cg76MCV5.d.ts → split-screen--k-XpBjr.d.ts} +36 -4
  57. package/dist/{split-screen-CauMwu6y.js → split-screen-PL78oVXP.js} +159 -26
  58. package/dist/{sprite-animation-CY-mrr1L.js → sprite-animation-CqR2o3SA.js} +39 -8
  59. package/dist/{src-CYEu-_S9.js → src-Cxfiv1Hg.js} +2 -17
  60. package/dist/test-iHYVUcDK.js +4036 -0
  61. package/dist/test.d.ts +531 -30
  62. package/dist/test.js +3 -3
  63. package/dist/touch-BnCyPA0G.js +519 -0
  64. package/dist/vite.d.ts +20 -37
  65. package/dist/vite.js +176 -6
  66. package/dist/{webgl-unavailable-Z5_g8x9H.js → webgl-unavailable-C8aDbGmR.js} +44 -0
  67. package/editor/assets/agent8-D0MS174y.js +1 -0
  68. package/editor/assets/{debug-CMULinBW.js → debug-BnXkKuYu.js} +2 -2
  69. package/editor/assets/index-CIu3uc3l.js +11046 -0
  70. package/editor/index.html +1 -1
  71. package/package.json +4 -14
  72. package/schemas/scene.schema.json +30 -3
  73. package/skills/incanto-3d-character.md +14 -1
  74. package/skills/incanto-3d-models.md +12 -0
  75. package/skills/incanto-assets.md +9 -1
  76. package/skills/incanto-audio.md +8 -2
  77. package/skills/incanto-behaviors-and-scripts.md +83 -6
  78. package/skills/incanto-building-2d-games.md +77 -8
  79. package/skills/incanto-building-3d-games.md +58 -4
  80. package/skills/incanto-editor.md +46 -7
  81. package/skills/incanto-environment.md +19 -1
  82. package/skills/incanto-game-feel.md +70 -0
  83. package/skills/incanto-gameplay-behaviors.md +109 -19
  84. package/skills/incanto-hud.md +128 -7
  85. package/skills/incanto-localization.md +13 -5
  86. package/skills/incanto-multiplayer.md +83 -3
  87. package/skills/incanto-node-reference.md +222 -58
  88. package/skills/incanto-performance.md +52 -0
  89. package/skills/incanto-physics-and-input.md +123 -24
  90. package/skills/incanto-playtesting.md +78 -2
  91. package/skills/incanto-save-slots.md +161 -6
  92. package/skills/incanto-scene-json-authoring.md +69 -12
  93. package/skills/incanto-verifying-your-game.md +196 -10
  94. package/skills/incanto-web-integration.md +28 -0
  95. package/skills/incanto-your-first-game.md +3 -1
  96. package/templates-app/beacon-isle-3d/generate-world.ts +77 -9
  97. package/templates-app/beacon-isle-3d/package.json +2 -2
  98. package/templates-app/beacon-isle-3d/src/behaviors.ts +22 -0
  99. package/templates-app/beacon-isle-3d/src/game.scene.json +103 -378
  100. package/templates-app/beacon-isle-3d/src/main.ts +11 -1
  101. package/templates-app/beacon-isle-3d/tsconfig.json +1 -1
  102. package/templates-app/beacon-isle-3d/verify.ts +3 -1
  103. package/templates-app/molehill-2d/.incanto/playtest/lost-seed1.json +4277 -0
  104. package/templates-app/molehill-2d/PROJECT/Context.md +58 -0
  105. package/templates-app/molehill-2d/PROJECT/Requirements.md +39 -0
  106. package/templates-app/molehill-2d/PROJECT/Status.md +27 -0
  107. package/templates-app/molehill-2d/PROJECT/Structure.md +48 -0
  108. package/templates-app/molehill-2d/docs/project-2d-rules.md +44 -0
  109. package/templates-app/molehill-2d/index.html +73 -0
  110. package/templates-app/molehill-2d/package.json +23 -0
  111. package/templates-app/molehill-2d/src/behaviors.ts +198 -0
  112. package/templates-app/molehill-2d/src/game.scene.json +1255 -0
  113. package/templates-app/molehill-2d/src/main.ts +41 -0
  114. package/templates-app/molehill-2d/tsconfig.json +13 -0
  115. package/templates-app/molehill-2d/verify.ts +247 -0
  116. package/templates-app/molehill-2d/vite.config.ts +12 -0
  117. package/templates-app/platformer-2d/package.json +2 -2
  118. package/templates-app/platformer-2d/src/game.scene.json +128 -600
  119. package/templates-app/platformer-2d/src/main.ts +11 -1
  120. package/templates-app/platformer-2d/tsconfig.json +1 -1
  121. package/templates-app/star-survivor/package.json +2 -2
  122. package/templates-app/star-survivor/src/game.scene.json +41 -195
  123. package/templates-app/star-survivor/src/main.ts +11 -1
  124. package/templates-app/star-survivor/tsconfig.json +1 -1
  125. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  126. package/templates-app/tps-3d/package.json +2 -2
  127. package/templates-app/tps-3d/src/behaviors.ts +19 -1
  128. package/templates-app/tps-3d/src/game.scene.json +78 -217
  129. package/templates-app/tps-3d/src/main.ts +13 -2
  130. package/templates-app/tps-3d/tsconfig.json +1 -1
  131. package/templates-app/village-quest-3d/.incanto/playtest/swapped-seed1.json +1735 -0
  132. package/templates-app/village-quest-3d/package.json +2 -2
  133. package/templates-app/village-quest-3d/src/behaviors.ts +21 -0
  134. package/templates-app/village-quest-3d/src/grove.scene.json +54 -221
  135. package/templates-app/village-quest-3d/src/main.ts +11 -1
  136. package/templates-app/village-quest-3d/src/village.scene.json +199 -838
  137. package/templates-app/village-quest-3d/tsconfig.json +1 -1
  138. package/templates-app/village-quest-3d/verify.ts +14 -1
  139. package/dist/duplicate-Dj4L-5AA.js +0 -22
  140. package/dist/json-CwwhxQgb.js +0 -36
  141. package/dist/registry-CF70EArN.js +0 -212
  142. package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
  143. package/dist/test-D6ISmuFE.js +0 -2383
  144. package/dist/touch-DEAmqGdf.js +0 -225
  145. package/editor/assets/agent8-BDvaZMQJ.js +0 -1
  146. package/editor/assets/index-CZIRr5EL.js +0 -10958
@@ -1,5 +1,5 @@
1
- import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-rm8vVzu_.js";
2
- import { c as JsonValue, s as JsonObject } from "./schema-B6ugCV1Q.js";
1
+ import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-DZExDn9o.js";
2
+ import { c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
5
5
  /**
@@ -58,6 +58,8 @@ declare class Collector extends Behavior {
58
58
  /** How many have been picked up so far. */
59
59
  override serialize(): JsonValue;
60
60
  override deserialize(data: JsonValue): void;
61
+ /** The counter on screen. `collect(0)` used to be the only way to repaint it. */
62
+ override announce(): void;
61
63
  }
62
64
  //#endregion
63
65
  //#region src/gameplay/currency.d.ts
@@ -85,10 +87,21 @@ declare class Currency extends Behavior {
85
87
  spend(cost: number): boolean;
86
88
  /** Take payment in. */
87
89
  earn(gain: number): void;
90
+ /** What has already been complained about, so a loop says it once. */
91
+ private complained;
92
+ /**
93
+ * A zero is ordinary; a NEGATIVE is a caller that flipped a sign.
94
+ *
95
+ * Reported rather than thrown: this is called from game code inside a frame,
96
+ * and a throw here quarantines the node holding the player's money.
97
+ */
98
+ private refuseNonsense;
88
99
  /** Set it outright (a shop that grants, a cheat, a restore). */
89
100
  setAmount(next: number): void;
90
101
  override serialize(): JsonValue;
91
102
  override deserialize(data: JsonValue): void;
103
+ /** The wallet on screen — `setAmount`'s own doc comment already named a restore. */
104
+ override announce(): void;
92
105
  }
93
106
  //#endregion
94
107
  //#region src/gameplay/health.d.ts
@@ -116,6 +129,7 @@ declare class Health extends Behavior {
116
129
  regenPerSec: number;
117
130
  invulnerableFor: number;
118
131
  freeOnDeath: boolean;
132
+ freeParent: boolean;
119
133
  /** Current hit points (set to `max` on ready). */
120
134
  current: number;
121
135
  private invulnTimer;
@@ -194,6 +208,14 @@ declare class Health extends Behavior {
194
208
  /** Current HP and whether death already fired — `max` comes back from JSON. */
195
209
  override serialize(): JsonValue;
196
210
  override deserialize(data: JsonValue): void;
211
+ /**
212
+ * Repaint the bar. Not `died` — a save is being read, nobody just died.
213
+ *
214
+ * This one needed the hook most: `setCurrent` is private AND early-returns
215
+ * when the value is unchanged, so no call a game could make would repaint a
216
+ * restored bar. It read 100 at 38 HP until the next hit teleported it to 37.
217
+ */
218
+ override announce(): void;
197
219
  }
198
220
  //#endregion
199
221
  //#region src/gameplay/damage-on-contact.d.ts
@@ -297,6 +319,7 @@ declare class FollowCamera extends Behavior {
297
319
  offset: number[];
298
320
  smoothing: number;
299
321
  deadzone: number;
322
+ private shakeRng;
300
323
  override onReady(): void;
301
324
  private shakeMag;
302
325
  private shakeT;
@@ -321,6 +344,8 @@ declare class Interactable extends Behavior {
321
344
  action: string;
322
345
  range: number;
323
346
  actorGroup: string;
347
+ /** Actions already reported missing, so a per-frame read says it once. */
348
+ private readonly reportedActions;
324
349
  override update(): void;
325
350
  }
326
351
  //#endregion
@@ -466,7 +491,31 @@ declare class Patrol extends Behavior {
466
491
  * this one is invisible to it.
467
492
  */
468
493
  private reportDeadWaypoint;
469
- /** Resolve waypoint `i` to a position, reading node paths live. */
494
+ /**
495
+ * Resolve waypoint `i` to a position in the MOVER's frame, reading node paths
496
+ * live.
497
+ *
498
+ * A node-path waypoint used to return the marker's raw `position`, which is
499
+ * parent-relative — and it was then fed to `moveToward(mover.position, …)`,
500
+ * i.e. read in the MOVER's frame. So a walker and its markers under different
501
+ * parents walked a shifted copy of the path, silently, with the marker nodes
502
+ * sitting right there in the scene. Measured on a tower-defence lane
503
+ * organised the ordinary way (markers under `/root/Path`, spawns under a
504
+ * container at [-50, 90]): every creep stacked motionless 90 px below and
505
+ * 72 px short of the keep, `incanto-check` `1/1 scene(s) valid`, zero errors,
506
+ * zero warnings, and the wave could be neither won nor lost.
507
+ *
508
+ * `Chase` has converted through world space since the identical bug was found
509
+ * there ("comparing two nodes' raw `position` only worked while they happened
510
+ * to share a parent"), and a `FollowCamera` aimed at a node uses its world
511
+ * position too. Patrol was the outlier.
512
+ *
513
+ * A LITERAL array stays local, because that is the only frame an author can
514
+ * mean when they type numbers into a behaviour's props.
515
+ *
516
+ * `reportDeadWaypoint` could not see this: it fires when a path resolves to
517
+ * NOTHING, and a path that resolves into the wrong frame resolves fine.
518
+ */
470
519
  private pointAt;
471
520
  private advance;
472
521
  }
@@ -508,14 +557,58 @@ declare class Pickup extends Behavior {
508
557
  * `null` (the engine's "any JSON" escape hatch) and `null` means `'forward'`.
509
558
  * `gravity` adds a constant downward (+y, the 2D y-down convention) pull, for
510
559
  * lobbed/arcing shots.
560
+ *
561
+ * **Every prop here is live.** `speed` and `direction` re-bake the heading when
562
+ * written, and the heading is resolved on the FIRST STEP rather than at
563
+ * `onReady` — because `onReady` is before the one place a game aims a spawned
564
+ * bullet. `SpawnSource.spawn` ends with `parent.addChild(clone)` and `Spawner`
565
+ * emits `spawned(node)` after that, so a handler that sets a rotation or a
566
+ * direction was always too late. Measured at speed 100 over 60 frames:
567
+ *
568
+ * ```
569
+ * A rotation 0 direction [0,1] -> pos [ 0, 100] authored: obeyed
570
+ * B rotation 90 direction "forward" -> pos [ 0, 100] authored: obeyed
571
+ * C rotation 90 set after addChild -> pos [100, 0] IGNORED
572
+ * D direction [0,1] set after add -> pos [100, 0] IGNORED — and it read
573
+ * back [0,1] while
574
+ * travelling +x
575
+ * ```
576
+ *
577
+ * What is deliberately NOT live is `rotation` after that first step: a
578
+ * projectile that spins for looks would have its spin become its trajectory.
579
+ * Call `aim()` to re-derive from the current rotation on purpose.
511
580
  */
512
581
  declare class Projectile extends Behavior {
513
582
  static readonly props: PropSchema;
514
583
  static readonly signals: readonly string[];
515
- speed: number;
516
- direction: JsonValue;
584
+ private _speed;
585
+ private _direction;
517
586
  gravity: number;
587
+ /** The straight-line part, `direction × speed`. Empty until the first step. */
518
588
  private velocity;
589
+ /**
590
+ * The +y accumulated by `gravity`, kept APART from `velocity`.
591
+ *
592
+ * Re-aiming replaces the straight-line part; the fall is a fact about the
593
+ * world, not about the shot, so an arrow re-aimed halfway down keeps falling
594
+ * rather than snapping back to level.
595
+ */
596
+ private fall;
597
+ /** Units per second along `direction`. Writable at any time. */
598
+ get speed(): number;
599
+ set speed(value: number);
600
+ /** A vector (`[x,y(,z)]`) or `'forward'`. Writable at any time. */
601
+ get direction(): JsonValue;
602
+ set direction(value: JsonValue);
603
+ private get armed();
604
+ /**
605
+ * Re-derive the heading from `direction` (or, for `'forward'`, from the
606
+ * node's CURRENT rotation) and bake it into the velocity.
607
+ *
608
+ * This is what a `spawned(node)` handler calls after turning the clone, and
609
+ * the only way a `'forward'` projectile ever re-reads its rotation.
610
+ */
611
+ aim(direction?: JsonValue | undefined): void;
519
612
  override onReady(): void;
520
613
  override update(dt: number): void;
521
614
  /** Resolve `direction` to a unit vector, deriving 'forward' from rotation. */
@@ -642,6 +735,14 @@ declare class ScoreKeeper extends Behavior {
642
735
  /** The run's progress. `scoreToWin` is balance and comes back from JSON. */
643
736
  override serialize(): JsonValue;
644
737
  override deserialize(data: JsonValue): void;
738
+ /**
739
+ * The score line and the lives counter. NOT `won`/`lost`.
740
+ *
741
+ * Re-firing those on load is how a Continue lands straight on the game-over
742
+ * screen it was loaded to escape — the flags come back through `hasWon` /
743
+ * `hasLost` and the game reads them, rather than being told again.
744
+ */
745
+ override announce(): void;
645
746
  }
646
747
  //#endregion
647
748
  //#region src/gameplay/spawner.d.ts
@@ -679,6 +780,7 @@ declare class Spawner extends Behavior {
679
780
  get liveCount(): number;
680
781
  /** @internal The detached template node (test/debug only). */
681
782
  _templateForTest(): Node;
783
+ override onEnterTree(): void;
682
784
  override onReady(): void;
683
785
  /** Begin (or resume) interval spawning. */
684
786
  start(): void;
@@ -740,9 +842,27 @@ declare class WaveSpawner extends Behavior {
740
842
  private timer;
741
843
  private spawnedThisWave;
742
844
  private announced;
845
+ override onEnterTree(): void;
743
846
  override onReady(): void;
744
847
  /** Start (or restart from) wave 0. */
745
848
  start(): void;
849
+ /**
850
+ * Which wave you were on — and nothing else.
851
+ *
852
+ * `behaviorsWithoutSave` is documented as "the list to read before shipping",
853
+ * and nobody had read it against this repo's own examples. Five of them put a
854
+ * `WaveSpawner` at the centre of the game (`fps-3d`, `tps-3d`, `tps-3d-neon`,
855
+ * `tps-3d-ruins`, `dogfight-3d`), and every one of them saved a run on wave 3
856
+ * and continued it on wave 1 — silently, because a save reloads the scene
857
+ * from source and a fresh `onReady` honestly starts at the beginning.
858
+ *
859
+ * The wave RESTARTS rather than resuming mid-spawn, which is the same
860
+ * checkpoint rule the rest of the save system follows: spawned entities are
861
+ * never restored, so resuming "four enemies into wave 3" would mean resuming
862
+ * a wave whose enemies do not exist. `waveStarted` fires again for the HUD.
863
+ */
864
+ override serialize(): JsonValue;
865
+ override deserialize(data: JsonValue): void;
746
866
  override update(dt: number): void;
747
867
  private beginWave;
748
868
  private prune;
@@ -834,6 +954,18 @@ declare class DayNight extends Behavior {
834
954
  private baseAmbient;
835
955
  private baseExposure;
836
956
  private captured;
957
+ /** Said once: there was no sky to move when this first looked. */
958
+ private reportedNoSky;
959
+ /**
960
+ * What time it is. A save that forgets it returns you at dawn.
961
+ *
962
+ * `startHour` is the scene's opening, not the run's clock: a fresh `onReady`
963
+ * cannot know that this player has been out all night. `paused` goes with it
964
+ * because a game that stops the clock for a cutscene must not resume ticking
965
+ * on load.
966
+ */
967
+ override serialize(): JsonValue;
968
+ override deserialize(data: JsonValue): void;
837
969
  override onReady(): void;
838
970
  override update(dt: number): void;
839
971
  private applyHour;
@@ -904,6 +1036,25 @@ type GameFlowState = "playing" | "paused" | "gameover" | "won";
904
1036
  /**
905
1037
  * Swap to another scene behind a black fade (title→game→next level).
906
1038
  * Headless (no DOM) the swap is immediate. Restores timeScale to 1.
1039
+ *
1040
+ * ONE AT A TIME. A door is usually a trigger, and a player who walks along a
1041
+ * wall of them fires several inside one fade. Measured on three doorways passed
1042
+ * in about a second:
1043
+ *
1044
+ * ```
1045
+ * setScene calls: 3 | order: ["crypt","garden","forge"]
1046
+ * final scene : forge
1047
+ * ```
1048
+ *
1049
+ * Three complete scenes loaded and the player landed in the third — a level
1050
+ * they never walked to, from a door they never reached. Further calls while a
1051
+ * travel is in flight are IGNORED rather than queued: you already went through
1052
+ * a door, and the second one is the same input arriving twice.
1053
+ *
1054
+ * The outgoing scene also stops while the screen is black. It kept simulating
1055
+ * through the fade, so a player who triggered the door mid-fall died in the
1056
+ * level they were leaving — and the checkpoint that death wrote was for the
1057
+ * level they had left.
907
1058
  */
908
1059
  declare function goToScene(engine: Engine, sceneJson: JsonObject, opts?: {
909
1060
  fadeSeconds?: number;
@@ -961,8 +1112,24 @@ declare class GameFlow extends Behavior {
961
1112
  state: GameFlowState;
962
1113
  private frozeScale;
963
1114
  override onReady(): void;
964
- /** The panel follows the state; nothing else may own its visibility. */
1115
+ /**
1116
+ * The panel follows the state; nothing else may own its visibility.
1117
+ *
1118
+ * …and while it is up, the layer it lives on takes ARROW KEYS AND THE PAD.
1119
+ * `HudLayer.focusNavigation` is off by default and rightly so — a game whose
1120
+ * HUD happens to contain a button must not lose its arrow keys the moment one
1121
+ * exists — and `incanto-hud.md` says "turn it on for the screens that ARE
1122
+ * menus and off again when play resumes". A pause panel IS that screen, and
1123
+ * this is the one place in the engine that knows when it opens and closes, so
1124
+ * every game with a pause menu was writing the same two lines or shipping a
1125
+ * menu no controller could reach.
1126
+ *
1127
+ * The layer's own value is remembered and put back: a game that turned
1128
+ * navigation on for its own reasons keeps it after a pause.
1129
+ */
965
1130
  private syncPausePanel;
1131
+ /** The layer's own `focusNavigation`, while this flow is borrowing it. */
1132
+ private navigationWas;
966
1133
  gameOver(text?: string, color?: string): void;
967
1134
  win(text?: string, color?: string): void;
968
1135
  pause(): void;
@@ -971,7 +1138,7 @@ declare class GameFlow extends Behavior {
971
1138
  /** Fade to another scene (next level, back to title). */
972
1139
  goToScene(sceneJson: JsonObject, opts?: {
973
1140
  fadeSeconds?: number;
974
- }): void;
1141
+ } | undefined): void;
975
1142
  private transition;
976
1143
  private freeze;
977
1144
  private thaw;
@@ -1025,6 +1192,19 @@ declare class CameraShake extends Behavior {
1025
1192
  private t;
1026
1193
  private prev;
1027
1194
  /**
1195
+ * Decoration draws from its OWN stream — see `effectRng`.
1196
+ *
1197
+ * A shake drew from `engine.rng`, the stream game logic reads and
1198
+ * `captureState` saves, once per axis per RENDERED frame. So the player's
1199
+ * frame rate decided the game's random numbers, and toggling the
1200
+ * `reduceMotion` accessibility switch changed the rolls: a replay recorded at
1201
+ * 60 fps does not reproduce on a laptop that dropped to 30, and two players
1202
+ * with the same seed diverge because one of them was hit. The particle
1203
+ * siblings were moved off `engine.rng` for exactly this; both camera shakes
1204
+ * were left behind.
1205
+ */
1206
+ private shakeRng;
1207
+ /**
1028
1208
  * Start a shake — unless the player asked for no motion.
1029
1209
  *
1030
1210
  * `settings.reduceMotion` is an accessibility switch, and camera movement the
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as DamageOnContact, C as restartScene, D as FaceTarget, E as FloatAway, F as localFromWorld, I as worldPosition, M as Currency, N as Collector, O as DayNight, P as Chase, S as goToScene, T as FollowCamera, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as Health, k as phaseOf, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-D1KixWK8.js";
1
+ import { A as phaseOf, C as restartScene, D as FloatAway, F as Chase, M as Health, N as Currency, O as FaceTarget, P as Collector, S as goToScene, T as FollowCamera, X as localFromWorld, Z as worldPosition, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as DamageOnContact, k as DayNight, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-BfHkuzVb.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
@@ -1,4 +1,4 @@
1
- import { t as createNoise2D } from "./noise-CGUMx44x.js";
1
+ import { t as createNoise2D } from "./noise-D3nPpmFg.js";
2
2
  //#region src/3d/terrain/heightmap.ts
3
3
  /**
4
4
  * Pure heightfield + splat-weight math for `Terrain3D` — NO three imports, so
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as ParticleSimConfig, c as showBootFailure, i as ParticleSim, n as AudioPlayer, o as ParticleView, s as isWebGLAvailable, t as AudioElementLike } from "./audio-player-C0j2k-yB.js";
2
- import { $ as T_PREFIX, A as SettingsValues, At as BusName, B as captureBehaviors, C as EngineStats, Ct as sfxDuration, Dt as MusicTrack, E as DeviceHints, Et as MusicManager, F as BehaviorState, Ft as LogLevel, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as LogManager, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as Signal, M as readDeviceHints, Mt as NodeLifecycle, N as suggestQuality, Nt as SceneTree, O as QualityTier, Ot as PlayMusicOptions, P as Scene, Pt as LogEntry, Q as Localization, R as SaveSlots, Rt as SignalListener, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicBackend, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as spatialPan, a as clearBehaviors, at as EffectLog, b as Engine, bt as SfxParams, c as registeredBehaviors, ct as isAudioContextAvailable, d as PropSchema, dt as Listener, et as suggestLocale, f as clearRegistry, ft as ROLLOFF_MODELS, g as getNodeType, gt as spatialGain, h as getNodeSignals, ht as Vec3, i as behaviorSignals, it as EffectKind, j as qualityEnvironment, jt as Node, k as Settings, kt as AudioBuses, l as NodeCtor, lt as Voice, m as getNodeSchema, mt as SpatialParams, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as SfxEngine, p as createNode, pt as RolloffModel, q as OrderGroupTable, r as behaviorSchema, rt as EffectEvent, s as registerBehavior, st as SfxPlayOptions, t as Behavior, tt as translationKey, u as PropDef, ut as VoicePreset, v as registerNode, vt as SFX_PRESETS, w as GameStats, wt as synthSfx, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-rm8vVzu_.js";
3
- 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-B6ugCV1Q.js";
4
- import { n as loadScene, t as LoadSceneOptions } from "./loader-DadV2QCT.js";
1
+ import { a as ParticleSimConfig, c as showBootFailure, i as ParticleSim, n as AudioPlayer, o as ParticleView, s as isWebGLAvailable, t as AudioElementLike } from "./audio-player-DaMxqfNE.js";
2
+ import { $ as T_PREFIX, A as SettingsValues, At as BusName, B as captureBehaviors, C as EngineStats, Ct as sfxDuration, Dt as MusicTrack, E as DeviceHints, Et as MusicManager, F as BehaviorState, Ft as LogLevel, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as LogManager, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as Signal, M as readDeviceHints, Mt as NodeLifecycle, N as suggestQuality, Nt as SceneTree, O as QualityTier, Ot as PlayMusicOptions, P as Scene, Pt as LogEntry, Q as Localization, R as SaveSlots, Rt as SignalListener, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicBackend, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as spatialPan, a as clearBehaviors, at as EffectLog, b as Engine, bt as SfxParams, c as registeredBehaviors, ct as isAudioContextAvailable, d as PropSchema, dt as Listener, et as suggestLocale, f as clearRegistry, ft as ROLLOFF_MODELS, g as getNodeType, gt as spatialGain, h as getNodeSignals, ht as Vec3, i as behaviorSignals, it as EffectKind, j as qualityEnvironment, jt as Node, k as Settings, kt as AudioBuses, l as NodeCtor, lt as Voice, m as getNodeSchema, mt as SpatialParams, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as SfxEngine, p as createNode, pt as RolloffModel, q as OrderGroupTable, r as behaviorSchema, rt as EffectEvent, s as registerBehavior, st as SfxPlayOptions, t as Behavior, tt as translationKey, u as PropDef, ut as VoicePreset, v as registerNode, vt as SFX_PRESETS, w as GameStats, wt as synthSfx, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-DZExDn9o.js";
3
+ import { a as SceneJson, c as JsonValue, d as jsonKind, i as SCENE_FORMAT, l as jsonClone, n as ConnectionJson, o as JsonKind, r as NodeJson, s as JsonObject, t as Rng, u as jsonEquals } from "./rng-Bb-IutXB.js";
4
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-Cff09LMm.js";
5
5
  import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js";
6
6
  import { i as gridFromRows, n as PathGrid, r as findPath, t as FindPathOptions } from "./pathfinding-_fGrCFmH.js";
7
- import { a as startRecording, c as IncantoErrorDetails, i as replay, l as auditScene, n as ReplayEvent, o as IncantoError, r as ReplayJson, s as IncantoErrorCode, t as Recorder } from "./replay-BQfkX4x7.js";
7
+ import { a as startRecording, c as IncantoErrorDetails, i as replay, l as auditScene, n as ReplayEvent, o as IncantoError, r as ReplayJson, s as IncantoErrorCode, t as Recorder } from "./replay-Dmw-PKQu.js";
8
8
 
9
9
  //#region src/core/audio/crossfade.d.ts
10
10
  /**
@@ -109,6 +109,20 @@ type DriveStep = {
109
109
  kind: "pointer";
110
110
  dx: number;
111
111
  dy: number;
112
+ } | {
113
+ kind: "at";
114
+ x: number;
115
+ y: number;
116
+ } | {
117
+ kind: "atNode";
118
+ path: string;
119
+ } | {
120
+ kind: "click";
121
+ button: number;
122
+ } | {
123
+ kind: "mouse";
124
+ button: number;
125
+ down: boolean;
112
126
  } | {
113
127
  kind: "step";
114
128
  ms: number;
@@ -245,13 +259,26 @@ type RefResult = {
245
259
  declare function resolveRefInJson(root: RefNode | undefined, owner: readonly number[], value: string): RefResult;
246
260
  /** The problem in English, for `incanto-check` and as the editor's base text. */
247
261
  declare function describeRefProblem(problem: RefProblem, value: string): string;
262
+ interface RefWarningOptions {
263
+ /**
264
+ * This file is a PREFAB another scene drops in, so a path may legitimately
265
+ * leave it.
266
+ *
267
+ * `/root/Bolts` is the fragment's own root only while the fragment is alone;
268
+ * attached, it resolves. Four such warnings stood forever on four of five
269
+ * files of a real project whose main scene audited clean. The verdict is
270
+ * reworded rather than suppressed: a typo'd path is MOST likely in a file
271
+ * written away from the scene that gives it meaning, so it still gets named.
272
+ */
273
+ fragment?: boolean;
274
+ }
248
275
  /**
249
276
  * Scene lints for node-path props that point at nothing.
250
277
  *
251
278
  * An EMPTY value is not reported: `''` is the default of most of these props and
252
279
  * means "not set", which is a choice rather than a typo.
253
280
  */
254
- declare function nodeRefWarnings(scene: JsonObject): string[];
281
+ declare function nodeRefWarnings(scene: JsonObject, opts?: RefWarningOptions): string[];
255
282
  //#endregion
256
283
  //#region src/core/nodes/hud.d.ts
257
284
  /**
@@ -305,6 +332,22 @@ declare class HudLayer extends Node {
305
332
  override onExitTree(): void;
306
333
  override update(): void;
307
334
  /**
335
+ * Is this the layer the keyboard is talking to?
336
+ *
337
+ * Keyboard focus is singular by nature — a browser has ONE focused element —
338
+ * and `focusNavigation` is per LAYER, so a game with a HUD and a modal that
339
+ * armed both had a single Enter activate the focused widget in EACH: the
340
+ * pause menu's RESUME and whatever the screen underneath happened to have
341
+ * focused, which is "the modal does not block the screen behind it" arriving
342
+ * through the keyboard.
343
+ *
344
+ * The TOPMOST armed layer wins — last in document order, which is the one
345
+ * drawn on top, and the one a modal is. A hidden layer is not armed at all: a
346
+ * closed menu must not eat the key from the HUD it covers. With one layer,
347
+ * which is the usual case, this is always true.
348
+ */
349
+ private ownsFocus;
350
+ /**
308
351
  * Arrow keys / d-pad move the focus, Enter / A activates it.
309
352
  *
310
353
  * Off by default: a game whose HUD has a button must not lose its arrow keys
@@ -361,6 +404,19 @@ declare abstract class HudWidgetBase extends Node {
361
404
  * A or an arrow ON this widget means. Default: nothing.
362
405
  */
363
406
  _activate(_dir: number): void;
407
+ /**
408
+ * @internal What a MOUSE CLICK on this widget does. Defaults to `_activate`.
409
+ *
410
+ * For a button, a toggle and a select the two are the same thing, which is
411
+ * why one entry point served both for a while. For `UiDialogue` they are
412
+ * not: Enter on a waiting choice ANSWERS it, and a click on the box does
413
+ * not — `advance()` returns early while `current.choices` is set, with the
414
+ * comment "must pick — clicks don't skip choices".
415
+ *
416
+ * A harness whose `click:` answered the question would pass a test the real
417
+ * game fails, which is the one thing the ladder must not do.
418
+ */
419
+ _click(): void;
364
420
  /** @internal Draw the focus ring. Overridable if a widget wants its own. */
365
421
  _paintFocus(): void;
366
422
  /** @internal */
@@ -379,6 +435,27 @@ declare abstract class HudWidgetBase extends Node {
379
435
  */
380
436
  _mountWidget(): void;
381
437
  /**
438
+ * The gesture, without a pointer — the `press()` of drag and drop.
439
+ *
440
+ * All four signals lived on `_wireDrag`, which is wired to a DOM element and
441
+ * therefore does not exist headless. So the one screen the HUD skill says
442
+ * needs more than a click — an inventory — was the one screen no harness
443
+ * could drive: `dragStarted`, `droppedOn`, `dropped` and `dragCancelled`
444
+ * were unreachable from `runScript`, `incanto-playtest` and every check in
445
+ * the ladder, and a game that moves items between slots could only be tested
446
+ * by hand in a browser.
447
+ *
448
+ * Same rules as the pointer path, so a test that passes here is a test about
449
+ * the game: a non-`draggable` source does nothing, a target that is not a
450
+ * `dropTarget` CANCELS, and dropping a thing back where it came from — on
451
+ * itself, or on the slot it is already in — is not a move.
452
+ *
453
+ * Returns whether the drop was taken.
454
+ */
455
+ dropOnto(target: HudWidgetBase | null): boolean;
456
+ /** The slot this widget is sitting in, if any — its origin for a drop. */
457
+ private dropTargetAbove;
458
+ /**
382
459
  * Drag and drop, on the DOM the engine already builds.
383
460
  *
384
461
  * An inventory is the one screen where "click it" is not enough, and every
@@ -409,6 +486,12 @@ declare abstract class HudWidgetBase extends Node {
409
486
  * did nothing at all, forever.
410
487
  */
411
488
  private _followAnchor;
489
+ /**
490
+ * Follow the engine, and let a change made HERE reach it — with or without a
491
+ * DOM element. No element access allowed: this is the half that runs
492
+ * headless.
493
+ */
494
+ protected _pull(): void;
412
495
  protected _sync(): void;
413
496
  /**
414
497
  * Resolve a text prop through the engine's locale.
@@ -487,6 +570,20 @@ declare class UiText extends HudWidgetBase {
487
570
  * authored opening line. Filling the slot with nothing instead would put
488
571
  * `"Gems / 8"` on screen for every game's first frame, and a score line
489
572
  * that starts blank looks broken rather than empty.
573
+ *
574
+ * Unless there IS no opening line. `format` alone painted `""`:
575
+ *
576
+ * ```
577
+ * format = 'Gems {} / 8'
578
+ * shown before setText "" <- nothing on screen
579
+ * shown after setText "Gems 3 / 8"
580
+ * ```
581
+ *
582
+ * and the signals a HUD is wired to — `scoreChanged`, `healthChanged` — only
583
+ * fire on CHANGE, so the row stays invisible through the whole opening of the
584
+ * game and forever if the value never moves. An empty slot in a visible
585
+ * template is the lesser of those two: it shows the author what they
586
+ * declared, where they declared it.
490
587
  */
491
588
  get shown(): string;
492
589
  override _paintedText(): string | undefined;
@@ -522,7 +619,7 @@ declare class UiBar extends HudWidgetBase {
522
619
  * already has, so a bar whose ceiling moves (a max-HP upgrade) needs no
523
620
  * second wire.
524
621
  */
525
- setValue(value: number, max?: number): void;
622
+ setValue(value: number, max?: number | undefined): void;
526
623
  /** Move the ceiling on its own, leaving the fill where it is. */
527
624
  setMax(max: number): void;
528
625
  protected _build(): HTMLElement;
@@ -554,7 +651,7 @@ declare class UiBanner extends HudWidgetBase {
554
651
  show(text: string, opts?: {
555
652
  color?: string;
556
653
  seconds?: number;
557
- }): void;
654
+ } | undefined): void;
558
655
  /** Drop everything (scene transitions). */
559
656
  clear(): void;
560
657
  protected _build(): HTMLElement;
@@ -626,7 +723,7 @@ declare class UiDialogue extends HudWidgetBase {
626
723
  /** What the box currently shows (grows while typing) — test-friendly. */
627
724
  get showing(): string;
628
725
  /** Queue a line; optional `choices` renders buttons after the line types out. */
629
- say(speaker: string, text: string, choices?: string[]): void;
726
+ say(speaker: string, text: string, choices?: string[] | undefined): void;
630
727
  /**
631
728
  * A dialogue takes focus while it is up.
632
729
  *
@@ -647,6 +744,14 @@ declare class UiDialogue extends HudWidgetBase {
647
744
  * `dir` is 0 for "activate" and ±1 for a nudge, the same shape `UiSelect`
648
745
  * uses — so one key layout drives a menu, a slider and a conversation.
649
746
  */
747
+ /**
748
+ * A click on the BOX is `advance()`, which refuses to skip a choice.
749
+ *
750
+ * The DOM handler is `box.addEventListener('click', () => this.advance())`
751
+ * and `advance` returns early while `current.choices` is set. Enter is a
752
+ * different verb — it answers — so a harness clicking the box must not.
753
+ */
754
+ override _click(): void;
650
755
  override _activate(dir: number): void;
651
756
  /** Show which option the keyboard is on — a ring nobody can see is not one. */
652
757
  private paintCursor;
@@ -776,6 +881,16 @@ declare class Timer extends Node {
776
881
  private remaining;
777
882
  start(time?: number): void;
778
883
  stop(): void;
884
+ /**
885
+ * Scene-load validation: a period of zero is a timer that never fires.
886
+ *
887
+ * `update` guards it — a non-positive period "would spin forever on any dt",
888
+ * so it calls `stop()` — and the guard is right. Being SILENT about it is
889
+ * what a load error is for: a scene asking for a tick every zero seconds got
890
+ * no ticks and no word said. The runtime guard stays, for code that zeroes
891
+ * the period from a handler.
892
+ */
893
+ static validateJson(node: Node): void;
779
894
  override onReady(): void;
780
895
  override update(dt: number): void;
781
896
  }
@@ -890,6 +1005,22 @@ declare class UiSelect extends HudWidgetBase {
890
1005
  options: string;
891
1006
  value: string;
892
1007
  override focusable: boolean;
1008
+ /** The options, as the list of values it actually offers. */
1009
+ private choices;
1010
+ /**
1011
+ * A value the list cannot show is a setting the game and the screen disagree
1012
+ * about.
1013
+ *
1014
+ * The browser cannot select an option that does not exist, so it shows the
1015
+ * FIRST one — while `node.value` keeps answering with what it was given, and
1016
+ * `changed` only fires when a person moves it, so nothing ever reconciles
1017
+ * them. That is the shape a saved setting has after a patch removes a quality
1018
+ * tier, and it is silent for the rest of the session.
1019
+ *
1020
+ * A hard error at LOAD, like every other enum in this engine. An empty value
1021
+ * is "nothing chosen yet", which is a choice.
1022
+ */
1023
+ static validateJson(node: Node): void;
893
1024
  /** Left/right (and confirm) walk the list — a select has no other gesture. */
894
1025
  override _activate(dir: number): void;
895
1026
  private select;
@@ -909,7 +1040,18 @@ declare class UiSelect extends HudWidgetBase {
909
1040
  */
910
1041
  protected _optionLabel(value: string): string;
911
1042
  private fillOptions;
1043
+ /** What was already reported, so a stuck value is said once and not per frame. */
1044
+ private complainedAbout;
1045
+ /**
1046
+ * The runtime half of `validateJson`.
1047
+ *
1048
+ * A behaviour may legitimately write the value first and widen `options`
1049
+ * after — so this reports rather than refuses, and goes quiet again when the
1050
+ * list catches up.
1051
+ */
1052
+ private reportMismatch;
912
1053
  protected override _sync(): void;
1054
+ protected override _pull(): void;
913
1055
  }
914
1056
  /**
915
1057
  * The language picker, done — because every game needs the same one.
@@ -935,7 +1077,7 @@ declare class UiLanguageSelect extends UiSelect {
935
1077
  override label: string;
936
1078
  /** Last locale we PUSHED into `value`, to tell an engine change from a player one. */
937
1079
  private lastLocale;
938
- protected override _sync(): void;
1080
+ protected override _pull(): void;
939
1081
  protected override _optionLabel(value: string): string;
940
1082
  }
941
1083
  /**
@@ -967,6 +1109,7 @@ declare abstract class UiSettingSelect extends UiSelect {
967
1109
  * `settings.quality.high` is worse than one reading `High`. English is the
968
1110
  * base language and the fallback, always.
969
1111
  */
1112
+ protected override _pull(): void;
970
1113
  protected override _sync(): void;
971
1114
  }
972
1115
  /**
@@ -1062,7 +1205,7 @@ declare class UiVolumeSlider extends UiSlider {
1062
1205
  /** Last value we PUSHED in, to tell an engine-side change from a player drag. */
1063
1206
  private lastPushed;
1064
1207
  protected override _labelText(): string;
1065
- protected override _sync(): void;
1208
+ protected override _pull(): void;
1066
1209
  }
1067
1210
  /**
1068
1211
  * The mute switch.
@@ -1079,7 +1222,7 @@ declare class UiMuteToggle extends UiToggle {
1079
1222
  static override readonly props: PropSchema;
1080
1223
  private lastPushed;
1081
1224
  protected override _labelText(): string;
1082
- protected override _sync(): void;
1225
+ protected override _pull(): void;
1083
1226
  }
1084
1227
  //#endregion
1085
1228
  //#region src/core/noise.d.ts
@@ -1208,7 +1351,7 @@ declare function resolveConstants<T extends JsonValue | undefined>(value: T, con
1208
1351
  * duplicating a uid'd template can never mint colliding ids. Names ARE
1209
1352
  * kept — rename clones you need to look up individually.
1210
1353
  */
1211
- declare function duplicateNode(node: Node): Node;
1354
+ declare function duplicateNode(node: Node, opts?: LoadSceneOptions): Node;
1212
1355
  //#endregion
1213
1356
  //#region src/core/scene/serializer.d.ts
1214
1357
  /**