incanto 0.74.0 → 0.75.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 (92) hide show
  1. package/bin/incanto-multiplay.mjs +52 -4
  2. package/dist/2d.d.ts +83 -7
  3. package/dist/2d.js +4 -3
  4. package/dist/3d.d.ts +743 -167
  5. package/dist/3d.js +6 -6
  6. package/dist/{agent8-CHTT4unP.js → agent8-B-bh3n0X.js} +1 -1
  7. package/dist/{audio-player-BOrk4eQx.d.ts → audio-player-BRo2uvG6.d.ts} +23 -2
  8. package/dist/{behavior-J1cmVah0.d.ts → behavior-B_245qRy.d.ts} +15 -1
  9. package/dist/{create-game-BAiA-FjP.js → create-game-DVUhHs2B.js} +79 -10
  10. package/dist/{create-game-ssvGvP7W.js → create-game-DnwhJ5nW.js} +83 -7
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-O8mxpgRX.d.ts} +1 -1
  13. package/dist/editor.js +1600 -1529
  14. package/dist/env.d.ts +1 -1
  15. package/dist/{environment-presets-BkqlWewf.js → environment-presets-BP77Dl0K.js} +2005 -60
  16. package/dist/{gameplay-Bxe1sMVT.js → gameplay-D-JuGura.js} +9309 -6595
  17. package/dist/gameplay.d.ts +573 -31
  18. package/dist/gameplay.js +3 -2
  19. package/dist/index.d.ts +150 -6
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-BAeWMYfF.d.ts → loader-CbkVdXL8.d.ts} +2 -2
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +2 -2
  24. package/dist/{physics-2d-C-SfvjzH.js → physics-2d-x3bCrQf0.js} +250 -31
  25. package/dist/{physics-3d-rOzeg890.js → physics-3d-Bz2VPQ6D.js} +673 -51
  26. package/dist/{picking-BMV34Pjl.js → picking-Buita5T9.js} +2 -2
  27. package/dist/{save-slots-CEUGeLuB.js → pose-ByFB_J3O.js} +217 -11
  28. package/dist/react.d.ts +2 -2
  29. package/dist/react.js +1 -1
  30. package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
  31. package/dist/{register-DRQyZKGU.js → register-DxAbTO2T.js} +191 -430
  32. package/dist/{replay-B6BbgGM_.js → replay-DrqQ6dZj.js} +133 -18
  33. package/dist/{replay-wZK5Ok5c.d.ts → replay-W5nCw_cU.d.ts} +21 -2
  34. package/dist/{rng-Bb-IutXB.d.ts → rng-BsXZg3D6.d.ts} +7 -0
  35. package/dist/{split-screen-BxZ5rBe7.js → split-screen-B-dIcQsQ.js} +10 -5
  36. package/dist/{split-screen-D6sx8z3Z.d.ts → split-screen-CiLJf2-C.d.ts} +3 -3
  37. package/dist/{sprite-animation-Bz3DHD_4.js → sprite-animation-SQa5gIu2.js} +8 -6
  38. package/dist/{src-CqtvYtSN.js → src-DLKVc6Hj.js} +1 -1
  39. package/dist/{test-CEl0y2Sw.js → test-HlVUeCSn.js} +487 -82
  40. package/dist/test.d.ts +90 -12
  41. package/dist/test.js +2 -2
  42. package/dist/vite.js +3 -3
  43. package/editor/assets/{agent8-BlzturGi.js → agent8-BXlYM9rv.js} +1 -1
  44. package/editor/assets/{debug-BMUi8usj.js → debug-S42XGP4Y.js} +1 -1
  45. package/editor/assets/index-DgrgUKno.js +11046 -0
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +1630 -299
  49. package/skills/incanto-3d-character.md +358 -20
  50. package/skills/incanto-audio.md +24 -2
  51. package/skills/incanto-building-2d-games.md +21 -2
  52. package/skills/incanto-building-3d-games.md +38 -3
  53. package/skills/incanto-editor.md +17 -6
  54. package/skills/incanto-environment.md +227 -2
  55. package/skills/incanto-game-feel.md +60 -1
  56. package/skills/incanto-gameplay-behaviors.md +668 -20
  57. package/skills/incanto-hud.md +116 -0
  58. package/skills/incanto-multiplayer.md +141 -2
  59. package/skills/incanto-node-reference.md +393 -16
  60. package/skills/incanto-performance.md +32 -0
  61. package/skills/incanto-physics-and-input.md +241 -13
  62. package/skills/incanto-playtesting.md +2 -2
  63. package/skills/incanto-save-slots.md +61 -2
  64. package/skills/incanto-scene-json-authoring.md +13 -0
  65. package/skills/incanto-verifying-your-game.md +86 -3
  66. package/templates-app/beacon-isle-3d/PROJECT/Status.md +12 -0
  67. package/templates-app/beacon-isle-3d/package.json +1 -1
  68. package/templates-app/beacon-isle-3d/src/game.scene.json +151 -2
  69. package/templates-app/molehill-2d/PROJECT/Status.md +7 -0
  70. package/templates-app/molehill-2d/package.json +1 -1
  71. package/templates-app/molehill-2d/src/game.scene.json +129 -2
  72. package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
  73. package/templates-app/platformer-2d/docs/project-2d-rules.md +3 -2
  74. package/templates-app/platformer-2d/package.json +1 -1
  75. package/templates-app/platformer-2d/src/behaviors.ts +40 -26
  76. package/templates-app/platformer-2d/src/game.scene.json +144 -2
  77. package/templates-app/platformer-2d/verify.ts +18 -1
  78. package/templates-app/star-survivor/PROJECT/Status.md +7 -0
  79. package/templates-app/star-survivor/package.json +1 -1
  80. package/templates-app/star-survivor/src/behaviors.ts +9 -8
  81. package/templates-app/star-survivor/src/game.scene.json +142 -4
  82. package/templates-app/star-survivor/verify.ts +13 -0
  83. package/templates-app/tps-3d/PROJECT/Status.md +6 -0
  84. package/templates-app/tps-3d/package.json +1 -1
  85. package/templates-app/tps-3d/src/game.scene.json +71 -10
  86. package/templates-app/village-quest-3d/PROJECT/Status.md +7 -0
  87. package/templates-app/village-quest-3d/package.json +1 -1
  88. package/templates-app/village-quest-3d/src/behaviors.ts +8 -16
  89. package/templates-app/village-quest-3d/src/grove.scene.json +121 -5
  90. package/templates-app/village-quest-3d/src/village.scene.json +126 -2
  91. package/templates-app/village-quest-3d/verify.ts +52 -2
  92. package/editor/assets/index-CrUCQoaB.js +0 -11046
@@ -1,5 +1,5 @@
1
- import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-J1cmVah0.js";
2
- import { c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
1
+ import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-B_245qRy.js";
2
+ import { c as JsonValue, s as JsonObject } from "./rng-BsXZg3D6.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
5
5
  /**
@@ -28,10 +28,14 @@ declare class Chase extends Behavior {
28
28
  speed: number;
29
29
  /** The group member currently chased (`targetGroup`), or null. */
30
30
  private picked;
31
+ /** Read-only: the node being chased this frame, or null (out of sight, none in the group, no `target`). */
32
+ get chasing(): Node | null;
33
+ private current;
31
34
  private sincePick;
32
35
  stopRange: number;
33
36
  loseRange: number;
34
37
  moveParent: boolean;
38
+ requireSight: boolean;
35
39
  ground: boolean | null;
36
40
  facePath: string;
37
41
  /** Signed fall speed accumulated while a ground chaser is off the floor. */
@@ -54,11 +58,44 @@ declare class Chase extends Behavior {
54
58
  private dropIfAirborne;
55
59
  /** The nearest live member of `targetGroup` — re-picked every `retargetEvery` seconds, never the mover itself. */
56
60
  private nearestOfGroup;
61
+ /** `requireSight`: a static-only ray from half a metre over the mover to half a metre over the target reaches it. */
62
+ private canSee;
57
63
  /** See `ground`: an explicit answer, else "is the mover a 3D kinematic capsule". */
58
64
  private walksTheGround;
59
65
  override update(dt: number): void;
60
66
  }
61
67
  //#endregion
68
+ //#region src/gameplay/chase-camera.d.ts
69
+ /**
70
+ * A camera BEHIND something that turns — a car, a boat, a tank, a horse.
71
+ *
72
+ * `FollowCamera` keeps a fixed world offset, which is right for a top-down or
73
+ * side view and wrong for a vehicle: the offset has to swing round with the
74
+ * target's heading, and lazily, or every corner snaps the view. Four examples
75
+ * (a delivery van, a harbour boat, an errand car, a race car) wrote this same
76
+ * behaviour by hand — `back`, `up`, a smoothed yaw — before it existed.
77
+ *
78
+ * The heading comes from the target's rotation BASIS, not from `rotation[1]`:
79
+ * a tilted body's Euler XYZ triple reads `[180, 180−yaw, 180]` past 90° of
80
+ * yaw, and a camera that read the middle number swung to the wrong side. Aim
81
+ * the camera with `Camera3D.lookAt` (or `Camera2D.follow`); this only places it.
82
+ */
83
+ declare class ChaseCamera extends Behavior {
84
+ static readonly props: PropSchema;
85
+ static readonly signals: readonly string[];
86
+ target: string;
87
+ back: number;
88
+ up: number;
89
+ smoothing: number;
90
+ /** The heading the camera currently trails (radians) — a harness reads it. */
91
+ heading: number | null;
92
+ override onReady(): void;
93
+ override update(dt: number): void;
94
+ private placed;
95
+ /** Put the camera on its mark NOW (a scene start, a respawn) instead of flying there. */
96
+ snap(): void;
97
+ }
98
+ //#endregion
62
99
  //#region src/gameplay/collector.d.ts
63
100
  /**
64
101
  * A tally for the "collect N things" pattern, living on the collector itself
@@ -109,8 +146,23 @@ declare class Currency extends Behavior {
109
146
  canAfford(cost: number): boolean;
110
147
  /** Pay `cost` if it is there. Returns whether it went through. */
111
148
  spend(cost: number): boolean;
112
- /** Take payment in. */
149
+ /**
150
+ * Take payment in — and say what did NOT fit.
151
+ *
152
+ * This clamped at `max` and emitted `earned(gain)` anyway, so a purse with a
153
+ * ceiling took a coin it could not hold and told the game it had. Measured on
154
+ * a game whose purse holds four: the fifth pickup vanished off the floor, the
155
+ * HUD said `4 / 4`, and nothing anywhere had the pickup. The game then wrote
156
+ * `if (purse.amount >= purse.max)` itself — the comparison `spend`'s
157
+ * `refused` exists so that nobody repeats.
158
+ *
159
+ * `earned` is now what was actually TAKEN, and `refused` carries the
160
+ * remainder. A purse with no ceiling never refuses, which is every purse
161
+ * written before this.
162
+ */
113
163
  earn(gain: number): void;
164
+ /** How much more this purse can hold (Infinity with no ceiling). */
165
+ get room(): number;
114
166
  /** What has already been complained about, so a loop says it once. */
115
167
  private complained;
116
168
  /**
@@ -154,6 +206,29 @@ declare class Health extends Behavior {
154
206
  invulnerableFor: number;
155
207
  freeOnDeath: boolean;
156
208
  freeParent: boolean;
209
+ knockback: number;
210
+ knockbackSeconds: number;
211
+ knockUp: number;
212
+ staggerSeconds: number;
213
+ applyKnockback: boolean;
214
+ /** Unit direction of the last hit that had a source, source → this body (ground plane). */
215
+ hitDirection: number[];
216
+ private staggerLeft;
217
+ private kickLeft;
218
+ private kick;
219
+ private kickFresh;
220
+ /** True while a hit's stagger holds this body's movers and controllers. */
221
+ get staggered(): boolean;
222
+ /** True while a hit's shove is still moving this body. */
223
+ get knocked(): boolean;
224
+ /** The shove's current velocity (decaying), empty when not knocked — a controller that drives the body reads it. */
225
+ get kickVelocity(): number[];
226
+ /** The vertical part of the shove, ONCE (a lift is an impulse, not a per-frame write); 0 after. */
227
+ takeLift(): number;
228
+ /** A body driven by a character controller gets its shove from the controller, not from here. */
229
+ private drivenByController;
230
+ /** The body a hit moves: this node if it is spatial, else the nearest spatial ancestor (a Health on a child `Hp` node moves its parent). */
231
+ private bodyNode;
157
232
  /** Current hit points (set to `max` on ready). */
158
233
  current: number;
159
234
  private invulnTimer;
@@ -165,7 +240,15 @@ declare class Health extends Behavior {
165
240
  override onReady(): void;
166
241
  override update(dt: number): void;
167
242
  /** Apply `n` damage. No-op while invulnerable or already dead. Emits `damaged`. */
168
- damage(n: number): void;
243
+ /**
244
+ * The shove, frame by frame, decaying linearly — written EVERY frame like a
245
+ * dash, because a controller brakes toward its own pace and a one-frame
246
+ * velocity dies. A dynamic body gets its velocity written (the vertical part
247
+ * once, as a lift); a kinematic or plain node is displaced through
248
+ * `moveBody` with `force`, so it still slides along walls.
249
+ */
250
+ private applyKick;
251
+ damage(n: number, from?: readonly number[] | undefined): void;
169
252
  /**
170
253
  * Be immune for `seconds` from NOW — a dodge roll, a spawn, a cutscene.
171
254
  *
@@ -389,6 +472,7 @@ declare class Interactable extends Behavior {
389
472
  focusedBy: Node | null;
390
473
  /** Actions already reported missing, so a per-frame read says it once. */
391
474
  private readonly reportedActions;
475
+ override onReady(): void;
392
476
  override update(): void;
393
477
  /** Is THIS the Interactable `actor` means — the nearest (that it faces) of every one in reach? */
394
478
  private claims;
@@ -423,6 +507,52 @@ declare class Lifetime extends Behavior {
423
507
  override update(dt: number): void;
424
508
  }
425
509
  //#endregion
510
+ //#region src/gameplay/mount.d.ts
511
+ /**
512
+ * Get on something and drive it; get off beside it.
513
+ *
514
+ * A horse, a car, a boat, a turret seat: the player's controller lets go, the
515
+ * player's body PARKS (collider off, hidden, weightless, riding along so
516
+ * everything that targets the player follows), the player's `skin` moves onto
517
+ * the steed's `saddle`, and the steed's `drive` — a `CharacterController3D` or
518
+ * a `Vehicle3D` — is enabled. `dismount()` puts everything back beside the
519
+ * steed. Two examples (an errand car, a saddled horse) wrote these sixty lines
520
+ * before it existed, and both forgot something the other remembered.
521
+ *
522
+ * Wire it from an `Interactable`: `interacted → Mount.toggle`.
523
+ */
524
+ declare class Mount extends Behavior {
525
+ static readonly props: PropSchema;
526
+ static readonly signals: readonly string[];
527
+ rider: string;
528
+ steed: string;
529
+ drive: string;
530
+ walk: string;
531
+ skin: string;
532
+ saddle: string;
533
+ skinOnSaddle: number[];
534
+ skinOnRider: number[];
535
+ dismountAt: number[];
536
+ /** True while the rider is on. */
537
+ mounted: boolean;
538
+ mounts: number;
539
+ private parked;
540
+ override onReady(): void;
541
+ private riderNode;
542
+ private steedNode;
543
+ /** The first child (or the named one) carrying a boolean `enabled` and a `yaw` or a `throttle` — a controller or a vehicle. */
544
+ private switchOf;
545
+ private skinNode;
546
+ private saddleNode;
547
+ /** Get on. False when already on. */
548
+ mount(): boolean;
549
+ /** Get off beside the steed. False when not on. */
550
+ dismount(): boolean;
551
+ /** On if off, off if on — the handler an `Interactable`'s `interacted` wires to. */
552
+ toggle(): void;
553
+ override update(): void;
554
+ }
555
+ //#endregion
426
556
  //#region src/gameplay/tween.d.ts
427
557
  /** Easing curves shared by the tween behaviors (MoveTo, …). Pure math. */
428
558
  type EaseName = "linear" | "easeIn" | "easeOut" | "easeInOut";
@@ -451,6 +581,12 @@ declare class MoveTo extends Behavior {
451
581
  override onReady(): void;
452
582
  /** Begin (or restart) the move from the node's CURRENT position. */
453
583
  start(): void;
584
+ /**
585
+ * Land on `to` NOW and say `arrived` (once). A skipped cutscene must leave
586
+ * the world as the whole cutscene would have — the door it opens, open.
587
+ * Wire `skipped → Door.finish`.
588
+ */
589
+ finish(): void;
454
590
  override update(dt: number): void;
455
591
  }
456
592
  //#endregion
@@ -565,6 +701,54 @@ declare class Patrol extends Behavior {
565
701
  private advance;
566
702
  }
567
703
  //#endregion
704
+ //#region src/gameplay/phases.d.ts
705
+ /**
706
+ * A boss that changes at two thirds and again at a third.
707
+ *
708
+ * Every other common wiring in this engine became declarative — score to
709
+ * screen, damage to a health bar, waves, win, lose, the save, the restore — and
710
+ * a fight's PHASES stayed the one thing that needed TypeScript. The shipped
711
+ * boss reads `healthChanged(current, max)`, divides, compares against two
712
+ * constants and calls its own `enterPhase`; every boss anyone builds writes
713
+ * those eight lines again.
714
+ *
715
+ * This is the threshold half and only that half. WHAT a phase does is the
716
+ * game's business, and it is reached through connections:
717
+ *
718
+ * ```json
719
+ * { "name": "Mood", "type": "Node", "uid": "n_…",
720
+ * "script": { "name": "Phases", "props": { "at": [0.66, 0.33] } } }
721
+ * ```
722
+ * ```json
723
+ * { "signal": "phase2", "from": "Boss/Mood", "to": "Adds", "handler": "start" },
724
+ * { "signal": "phase3", "from": "Boss/Mood", "to": "Roar", "handler": "play" },
725
+ * { "signal": "phaseChanged", "from": "Boss/Mood", "to": "Hud", "handler": "onPhase" }
726
+ * ```
727
+ *
728
+ * The numbered signals exist because a connection cannot filter on a NUMBER: a
729
+ * `filter` gates on a node's group or tags, so `phaseChanged(2)` could only ever
730
+ * reach one handler that switches. `phase2`…`phase5` is what makes "phase two
731
+ * starts the adds" a line in the scene file.
732
+ */
733
+ declare class Phases extends Behavior {
734
+ static readonly props: PropSchema;
735
+ static readonly signals: readonly string[];
736
+ at: number[];
737
+ health: string;
738
+ /** Which phase the fight is in: 1 until the first threshold is crossed. */
739
+ phase: number;
740
+ private watched;
741
+ override onReady(): void;
742
+ /** Wire this to a `healthChanged` yourself when the health is somewhere odd. */
743
+ read(current: number, max: number): void;
744
+ /** The phase survives a save, like every other run-state number here. */
745
+ override serialize(): {
746
+ phase: number;
747
+ };
748
+ override deserialize(data: unknown): void;
749
+ override announce(): void;
750
+ }
751
+ //#endregion
568
752
  //#region src/gameplay/pickup.d.ts
569
753
  /**
570
754
  * A collectible that vanishes when a collector overlaps it. Must sit on an
@@ -653,7 +837,15 @@ declare class Projectile extends Behavior {
653
837
  * This is what a `spawned(node)` handler calls after turning the clone, and
654
838
  * the only way a `'forward'` projectile ever re-reads its rotation.
655
839
  */
656
- aim(direction?: JsonValue | undefined): void;
840
+ /**
841
+ * Bake the velocity from `direction` × `speed` — plus `carry`, the SHOOTER's
842
+ * velocity, so a shell fired from a tank doing 16 m/s flies with it instead
843
+ * of at the standing tank's speed (a tank could outrun its own shot; a
844
+ * sideways shell landed behind what the barrel pointed at). Added once, at
845
+ * launch; a later `speed` or `direction` write re-bakes with the same carry.
846
+ */
847
+ aim(direction?: JsonValue | undefined, carry?: readonly number[] | undefined): void;
848
+ private carry;
657
849
  override onReady(): void;
658
850
  override update(dt: number): void;
659
851
  /** Resolve `direction` to a unit vector, deriving 'forward' from rotation. */
@@ -692,6 +884,9 @@ declare class SavePoint extends Behavior {
692
884
  scene: string;
693
885
  restoreOnReady: boolean;
694
886
  probeOnReady: boolean;
887
+ keep: string[];
888
+ /** Poses read out of the slot, waiting for `announce()` to put them back. */
889
+ private keptPoses;
695
890
  /** Seconds this run has been playing — written into the slot as `playtime`. */
696
891
  private elapsed;
697
892
  private slots;
@@ -747,9 +942,20 @@ declare class SavePoint extends Behavior {
747
942
  restore(): void;
748
943
  /** Forget this slot — a "delete save" button. */
749
944
  clear(): void;
750
- /** The run's own clock survives a save/continue cycle. */
945
+ /** The run's own clock and the poses the scene asked to keep. */
751
946
  override serialize(): JsonValue;
752
947
  override deserialize(data: JsonValue): void;
948
+ /**
949
+ * Put the kept nodes back where the run left them.
950
+ *
951
+ * In `announce()` and not in `deserialize()`, for the reason the hook exists:
952
+ * a pose written mid-pass can be overwritten by another behavior's restore
953
+ * (a camera rig, a controller reading its target), and announce runs after
954
+ * the whole pass has landed.
955
+ */
956
+ override announce(): void;
957
+ /** Read the poses the scene named — reporting the ones it cannot. */
958
+ private readPoses;
753
959
  }
754
960
  //#endregion
755
961
  //#region src/gameplay/score-keeper.d.ts
@@ -983,6 +1189,88 @@ declare class ZombieAI extends Behavior {
983
1189
  private pickGoal;
984
1190
  }
985
1191
  //#endregion
1192
+ //#region src/gameplay/ballistics.d.ts
1193
+ /**
1194
+ * The two questions a lobbed shot asks: **at what angle**, and **where will it
1195
+ * land**.
1196
+ *
1197
+ * `Projectile.gravity` has flown arcing shells since it shipped, and nothing
1198
+ * could aim one: `Turret` pointed straight at its target, so a gun firing a
1199
+ * falling shell at anything further than a few metres dropped it short — a
1200
+ * measured 66 m short, on an artillery duel built from the package. Every game
1201
+ * that wanted a mortar, a grenade, a catapult or a lobbed spell wrote this
1202
+ * arithmetic itself, and a preview drawn with slightly different arithmetic
1203
+ * lands somewhere the shell does not.
1204
+ */
1205
+ /**
1206
+ * The unit direction that puts a shot from `from` onto `to`, or `null` when
1207
+ * the speed cannot reach it.
1208
+ *
1209
+ * `arc` picks the root: `'low'` is the flat, fast one (a rifle grenade),
1210
+ * `'high'` the mortar's — same landing point, a much taller flight, and the
1211
+ * one that clears a wall in between.
1212
+ *
1213
+ * Dimension-agnostic, like everything else here: a 3D shot spreads over x/z
1214
+ * with +y up, a 2D one over x with −y up (the y-down screen convention
1215
+ * `Projectile.gravity` already follows).
1216
+ */
1217
+ declare function ballisticAim(from: readonly number[], to: readonly number[], speed: number, gravity: number, arc?: "low" | "high"): number[] | null;
1218
+ /** The angle that carries a shot FURTHEST — what a gun fires when it cannot reach. */
1219
+ declare function maxRangeAim(from: readonly number[], to: readonly number[]): number[] | null;
1220
+ interface BallisticPathOptions {
1221
+ /** How long to walk, in seconds (default 12). */
1222
+ seconds?: number;
1223
+ /** The step — leave it at the engine's fixed step so the walk matches the flight. */
1224
+ dt?: number;
1225
+ /** Called with each point; return true to stop (the ground, a wall, the map edge). */
1226
+ stop?: (at: readonly number[]) => boolean;
1227
+ }
1228
+ /**
1229
+ * Walk the flight a `Projectile` with this `speed`, `direction` and `gravity`
1230
+ * WOULD fly — the same integration, step for step.
1231
+ *
1232
+ * That is the point: an aiming line drawn from the closed-form parabola drifts
1233
+ * from the shell the engine actually flies (Euler with the fall accumulated
1234
+ * first), and the player aims by the line. Same arithmetic, same landing.
1235
+ */
1236
+ declare function ballisticPath(from: readonly number[], direction: readonly number[], speed: number, gravity: number, options?: BallisticPathOptions | undefined): number[][];
1237
+ //#endregion
1238
+ //#region src/gameplay/blast.d.ts
1239
+ declare class Blast extends Behavior {
1240
+ static readonly props: PropSchema;
1241
+ static readonly signals: readonly string[];
1242
+ radius: number;
1243
+ damage: number;
1244
+ falloff: "linear" | "none";
1245
+ groups: string;
1246
+ when: string;
1247
+ self: boolean;
1248
+ throughWalls: boolean;
1249
+ private went;
1250
+ /** Has it gone off? A keg explodes once. */
1251
+ get spent(): boolean;
1252
+ private get carrier();
1253
+ override onReady(): void;
1254
+ /** Go off now: everything with a `Health` inside `radius` takes its share. */
1255
+ blast(): number;
1256
+ /** Who is even a candidate: the named groups, or every HEALTH in the tree. */
1257
+ private candidates;
1258
+ /**
1259
+ * `throughWalls: false` — a static-only ray, the same one
1260
+ * `Turret.requireSight` casts, with the two things a BLAST has to know that
1261
+ * a turret does not.
1262
+ *
1263
+ * A bomb lies on the ground, so its ray starts inside the floor's collider
1264
+ * and Rapier reports a hit at zero distance: every victim looked shielded,
1265
+ * by the ground the bomb was standing on. And a crate solid enough to walk
1266
+ * into is a collider between the blast and the crate's own centre, so the
1267
+ * first thing a bomb could never destroy was the box beside it. Neither is a
1268
+ * wall: a hit at the origin is what the blast is sitting on, and a hit that
1269
+ * belongs to the target is the target.
1270
+ */
1271
+ private reaches;
1272
+ }
1273
+ //#endregion
986
1274
  //#region src/gameplay/checkpoint.d.ts
987
1275
  /**
988
1276
  * A checkpoint: touch it and a `Respawn` puts you back HERE from now on.
@@ -1007,7 +1295,38 @@ declare class Checkpoint extends Behavior {
1007
1295
  dropBelow: number;
1008
1296
  /** True after the first activation. */
1009
1297
  lit: boolean;
1298
+ /**
1299
+ * Where this checkpoint sent the catcher, and WHEN it did.
1300
+ *
1301
+ * The position is not derivable at restore: a checkpoint can be lit while its
1302
+ * own node has since moved (a lift, a boat), and `dropBelow` is applied once
1303
+ * at lighting. The clock is what orders three lit checkpoints back into the
1304
+ * order the run walked them.
1305
+ */
1306
+ private sentTo;
1307
+ private litAt;
1010
1308
  override onReady(): void;
1309
+ /**
1310
+ * What a save has to carry: WHICH checkpoint the run had reached.
1311
+ *
1312
+ * `Checkpoint` had none of this, so a continued run came back with every
1313
+ * checkpoint dark and the `Respawn` pointing at the level's start — the first
1314
+ * fall after a continue undid the whole session. It was not in the skill's
1315
+ * list of built-ins that save either, so nothing said it would not.
1316
+ */
1317
+ override serialize(): JsonValue;
1318
+ override deserialize(data: JsonValue): void;
1319
+ /**
1320
+ * Point the catcher back where the run left it.
1321
+ *
1322
+ * Every lit checkpoint sharing a catcher runs this, and they must not fight:
1323
+ * each one writes only if no lit sibling on the same `Respawn` was walked
1324
+ * LATER, so all of them reach the same answer and the order they announce in
1325
+ * does not matter.
1326
+ */
1327
+ override announce(): void;
1328
+ /** Was another lit checkpoint on this same catcher reached after this one? */
1329
+ private laterSiblingLit;
1011
1330
  }
1012
1331
  //#endregion
1013
1332
  //#region src/gameplay/day-night.d.ts
@@ -1194,8 +1513,112 @@ declare class GameFlow extends Behavior {
1194
1513
  * game will have. `UiPanel` appeared in zero template scenes before this.
1195
1514
  */
1196
1515
  pausePanelPath: string;
1516
+ /**
1517
+ * A screen shown at BOOT, holding the world until something resumes.
1518
+ *
1519
+ * '' (the default) is a game that starts playing, which is every game this
1520
+ * engine shipped until now. Naming a panel here is the whole of a title
1521
+ * screen: the flow pauses in `onReady`, shows this instead of the pause
1522
+ * panel, and a `PLAY` button wires straight to `resume` —
1523
+ *
1524
+ * ```json
1525
+ * { "from": "%Play", "signal": "pressed", "to": ".", "handler": "resume" }
1526
+ * ```
1527
+ *
1528
+ * Every shipped shell wrote that in TypeScript, together with the eighty
1529
+ * lines below it, and `incanto-hud.md` told authors to copy one.
1530
+ */
1531
+ titlePanelPath: string;
1532
+ /**
1533
+ * Where this game keeps its saves — a `SaveSlots` namespace. `''` = no saving.
1534
+ *
1535
+ * `SaveSlots`, `captureState` and `restoreState` have existed since the save
1536
+ * round and no shipped starter used any of them: a scaffolded game could not
1537
+ * be closed and come back. The API was never the problem; the SEQUENCE was —
1538
+ * write a slot, reload the scene from source, wait for the reload, restore by
1539
+ * uid — and every game that wanted a save menu had to get that right before it
1540
+ * had one. `restart()` is already the engine's verb for "reload this scene",
1541
+ * wired from a button with no TypeScript; `save()` and `continueFrom()` are
1542
+ * its two siblings.
1543
+ *
1544
+ * WHEN to save is still yours. The skill's rule stands — there is no autosave
1545
+ * — you wire `save` to whatever signal marks the moment: a checkpoint, a level
1546
+ * end, a button in the pause menu.
1547
+ */
1548
+ saveSlots: string;
1549
+ /** Lazily built, so a game with no `saveSlots` never touches storage. */
1550
+ private slots;
1551
+ private store;
1552
+ /**
1553
+ * Write this scene's behaviour state into a slot. `false` = nothing was
1554
+ * written, and the log says why.
1555
+ */
1556
+ save(slot?: unknown): boolean;
1557
+ /** Is there something in this slot to come back to? */
1558
+ hasSave(slot?: unknown): boolean;
1559
+ /**
1560
+ * Reload this scene and hand every behaviour its state back.
1561
+ *
1562
+ * The order is the whole feature: `restoreState` has to run AFTER the reload
1563
+ * and after `onReady`, because `onReady` is where a behaviour sets its
1564
+ * starting values and restoring first would be overwritten.
1565
+ */
1566
+ continueFrom(slot?: unknown): boolean;
1567
+ /**
1568
+ * Screens pushed OVER the base one — options, credits, controls.
1569
+ *
1570
+ * The genuinely hard part of a shell is BACK. Options opens from the title
1571
+ * AND from the pause menu and has to return to whichever asked, which is a
1572
+ * stack; every game that hand-rolls it eventually gets a screen stuck. Two
1573
+ * connection rows now:
1574
+ *
1575
+ * ```json
1576
+ * { "from": "%TitleOptions", "signal": "pressed", "to": ".",
1577
+ * "handler": "screen", "args": ["%Options"] },
1578
+ * { "from": "%Back", "signal": "pressed", "to": ".", "handler": "back" }
1579
+ * ```
1580
+ */
1581
+ private readonly screens;
1582
+ /**
1583
+ * Every screen this flow has ever raised — the set it is allowed to HIDE.
1584
+ *
1585
+ * Popping the stack before syncing would drop the closing screen out of the
1586
+ * list of panels to consider, so `back()` showed the title again and left
1587
+ * Options on top of it. A screen that has been opened stays this flow's to
1588
+ * close.
1589
+ */
1590
+ private readonly everShown;
1591
+ /**
1592
+ * Is the BASE screen the title rather than the pause menu?
1593
+ *
1594
+ * Both are "the world is held and a panel is up", and only this tells them
1595
+ * apart — so `back()` from Options knows which one to bring back.
1596
+ */
1597
+ private onTitle;
1598
+ /**
1599
+ * Push a screen over whatever is showing, and hold the world while it is up.
1600
+ *
1601
+ * A connection handler, so a button reaches it with no script:
1602
+ * `{"handler": "screen", "args": ["%Options"]}` — the bound argument is the
1603
+ * panel's node path.
1604
+ *
1605
+ * Opening a screen during PLAY freezes the world (an inventory, a map), and
1606
+ * `back()` gives it back. Opening one while already paused keeps the pause.
1607
+ */
1608
+ screen(path: unknown): void;
1609
+ /**
1610
+ * Close the top screen and show whoever asked for it.
1611
+ *
1612
+ * With nothing left on the stack this returns to the base screen — the title
1613
+ * at boot, the pause menu while paused — and if there is no base screen
1614
+ * either, it resumes: a screen opened during play closes back into the game.
1615
+ */
1616
+ back(): void;
1197
1617
  state: GameFlowState;
1198
1618
  private frozeScale;
1619
+ /** Dropped when this flow's node leaves the tree — see `onReady`. */
1620
+ private unwatchScene;
1621
+ override onExitTree(): void;
1199
1622
  override onReady(): void;
1200
1623
  /**
1201
1624
  * The panel follows the state; nothing else may own its visibility.
@@ -1230,6 +1653,52 @@ declare class GameFlow extends Behavior {
1230
1653
  override update(): void;
1231
1654
  }
1232
1655
  //#endregion
1656
+ //#region src/gameplay/group-camera.d.ts
1657
+ /**
1658
+ * One camera, several players — the couch co-op camera.
1659
+ *
1660
+ * `FollowCamera` follows ONE target and `ChaseCamera` rides behind ONE
1661
+ * character, so a game with two players at the same keyboard had nothing to
1662
+ * frame them both: whoever the camera followed could walk the other off screen,
1663
+ * and the other player's half of the game went with them.
1664
+ *
1665
+ * This keeps EVERY target in frame: the camera sits at their centroid plus
1666
+ * `offset`, and pulls BACK along that offset until the pair that is furthest
1667
+ * apart fits inside `padding` of the view. It is the Overcooked / Castle
1668
+ * Crashers / Lovers-in-a-Dangerous-Spacetime camera.
1669
+ *
1670
+ * ```json
1671
+ * { "name": "Camera", "type": "Camera3D", "props": { "current": true, "fov": 50 },
1672
+ * "script": { "name": "GroupCamera",
1673
+ * "props": { "targets": "player", "offset": [0, 12, 14],
1674
+ * "minZoom": 0.8, "maxZoom": 2.6, "smoothing": 0.12 } } }
1675
+ * ```
1676
+ *
1677
+ * Pair it with `CharacterController3D`'s `camera: "none"` on every player, or
1678
+ * the controllers will fight it for the camera every frame.
1679
+ */
1680
+ declare class GroupCamera extends Behavior {
1681
+ static readonly props: PropSchema;
1682
+ static readonly signals: readonly string[];
1683
+ targets: string;
1684
+ offset: number[];
1685
+ minZoom: number;
1686
+ maxZoom: number;
1687
+ padding: number;
1688
+ smoothing: number;
1689
+ lookAtGroup: boolean;
1690
+ /** The zoom actually in use — 1 is the authored offset. */
1691
+ zoom: number;
1692
+ /** The point the camera is framing. */
1693
+ centre: number[];
1694
+ override onReady(): void;
1695
+ /** Everyone the camera is trying to keep on screen, right now. */
1696
+ members(): Node[];
1697
+ override update(dt: number): void;
1698
+ /** Snap to the group now — after a teleport, a scene swap, a respawn. */
1699
+ snap(): void;
1700
+ }
1701
+ //#endregion
1233
1702
  //#region src/gameplay/juice.d.ts
1234
1703
  /**
1235
1704
  * Game-feel primitives ("juice"): the weapon cooldown, camera shake, screen
@@ -1339,6 +1808,9 @@ declare class PathFollow extends Behavior {
1339
1808
  speed: number;
1340
1809
  loop: boolean;
1341
1810
  moveParent: boolean;
1811
+ path: string;
1812
+ autoStart: boolean;
1813
+ private declared;
1342
1814
  facePath: string;
1343
1815
  turnSpeed: number;
1344
1816
  private waypoints;
@@ -1346,6 +1818,9 @@ declare class PathFollow extends Behavior {
1346
1818
  /** True while there is somewhere left to go. */
1347
1819
  get moving(): boolean;
1348
1820
  setPath(waypoints: readonly (readonly number[])[]): void;
1821
+ override onReady(): void;
1822
+ /** Walk the declared `path` from its first waypoint — the cue a connection can give. */
1823
+ start(): void;
1349
1824
  /** Stop in place (keeps the node where it is). */
1350
1825
  stop(): void;
1351
1826
  override update(dt: number): void;
@@ -1402,6 +1877,16 @@ declare class PrefabShelf extends Behavior {
1402
1877
  /** What this shelf holds, in authored order. */
1403
1878
  names(): string[];
1404
1879
  /**
1880
+ * The held template itself — to ASK it something, never to change it.
1881
+ *
1882
+ * `Turret.arc` needs the shell's `speed` and `gravity` to solve a firing
1883
+ * angle, and it needs them BEFORE it makes a clone (the barrel elevates
1884
+ * every frame, not only when the gun fires). Re-declaring those numbers on
1885
+ * the turret would be two surfaces free to disagree; reading the one that
1886
+ * flies is the answer. Mutating what comes back changes every future clone.
1887
+ */
1888
+ template(name: string): Node | null;
1889
+ /**
1405
1890
  * A live clone of one template, DETACHED — `addChild` it yourself.
1406
1891
  *
1407
1892
  * Detached for the same reason `duplicateNode` is, and it is not a
@@ -1418,38 +1903,76 @@ declare class PrefabShelf extends Behavior {
1418
1903
  private childNames;
1419
1904
  }
1420
1905
  //#endregion
1421
- //#region src/gameplay/spatial.d.ts
1906
+ //#region src/gameplay/sight.d.ts
1422
1907
  /**
1423
- * A node's position with every spatial ancestor's added in.
1424
- *
1425
- * The tree composes transforms for DRAWING — three does it for free — and every
1426
- * piece of engine logic that reads `node.position` gets the LOCAL number
1427
- * instead. Physics already knew (`worldPosition2D`/`worldPosition3D` in the
1428
- * adapters); the camera did not, and neither did anything that chases a target.
1429
- *
1430
- * A camera parented to the player is the Godot and Phaser idiom, and the
1431
- * authoring skill explicitly permits it — *"EVERY node type can hold children …
1432
- * any node works as a grouping container"*. Measured on exactly that scene:
1908
+ * Eyes for a watcher a guard, a turret, a camera, a beast.
1433
1909
  *
1910
+ * ```json
1911
+ * { "name": "Guard", "type": "CharacterBody3D", "children": [
1912
+ * { "name": "Skin", "type": "ModelInstance3D", "props": { "model": "$base" } },
1913
+ * { "name": "Eyes", "type": "Node",
1914
+ * "script": { "name": "Sight", "props": { "range": 12, "coneDeg": 70, "fillSeconds": 1 } } },
1915
+ * { "name": "Hunt", "type": "Node", "script": { "name": "Chase", "props": { "enabled": false } } }
1916
+ * ] }
1434
1917
  * ```
1435
- * player world position : [1400, 900]
1436
- * renderer view centre : {"x":0,"y":0}
1437
- * framing : camera /Level/Player/Cam centred [1400, 900]
1438
- * 1 in view, 0 outside it
1918
+ * ```jsonc
1919
+ * { "signal": "spotted", "from": "Guard/Eyes", "to": "Guard/Hunt", "handler": "enable" },
1920
+ * { "signal": "lost", "from": "Guard/Eyes", "to": "Guard/Hunt", "handler": "disable" }
1439
1921
  * ```
1440
1922
  *
1441
- * The renderer drew empty world 1600 px from the player while `framing` the
1442
- * instrument the skills tell you to trust before handing a game back —
1443
- * certified the player on screen.
1444
- *
1445
- * Arity comes from the node itself: a 2D node has two components, a 3D node
1446
- * three, and an ancestor that is neither contributes nothing.
1923
+ * On a child of the watcher's body. Every frame it looks the way the node at
1924
+ * `facingPath` faces (the Skin the controller turns, +Z-forward) and sees
1925
+ * anything in `targetGroup` inside `coneDeg` and `range` with nothing
1926
+ * STATIC between its eye and their chest (`los`). `suspicion` fills over
1927
+ * `fillSeconds` while something is seen and drains when nothing is;
1928
+ * `spotted(node)` fires when it reaches 1, `lost(node)` when it empties —
1929
+ * once each, an edge, so a connection can flip a state. `sees(node)`,
1930
+ * `seen()` and `target` (the nearest) answer any frame.
1931
+ *
1932
+ * Five examples wrote this by hand before it existed, and `Chase` still
1933
+ * chases through walls — give an enemy eyes and it stops.
1447
1934
  */
1935
+ declare class Sight extends Behavior {
1936
+ static readonly props: PropSchema;
1937
+ static readonly signals: readonly string[];
1938
+ range: number;
1939
+ coneDeg: number;
1940
+ targetGroup: string;
1941
+ facingPath: string;
1942
+ eyeHeight: number;
1943
+ chestHeight: number;
1944
+ fillSeconds: number;
1945
+ los: boolean;
1946
+ /** 0..1 — how sure it is; 1 is `spotted`. */
1947
+ suspicion: number;
1948
+ /** The nearest node it sees right now, or null. */
1949
+ target: Node | null;
1950
+ private seenNow;
1951
+ private alarmed;
1952
+ private lastTarget;
1953
+ /** Is this node in sight right now? */
1954
+ sees(node: Node): boolean;
1955
+ /** Everything in sight right now, nearest first. */
1956
+ seen(): Node[];
1957
+ override update(dt: number): void;
1958
+ private facing;
1959
+ private distanceTo;
1960
+ }
1961
+ //#endregion
1962
+ //#region src/core/pose.d.ts
1963
+ /** The node's position in WORLD space — its parents' positions and rotations composed. */
1448
1964
  declare function worldPosition(node: Node): number[];
1449
1965
  /**
1450
- * Turn a world position into the local one `node.position` has to hold for the
1451
- * node to END UP there the inverse of `worldPosition` for its parent chain.
1966
+ * A LOCAL vector turned into world space by the node's own rotation and every
1967
+ * ancestor's a barrel's forward, a muzzle's offset, a hardpoint's direction.
1968
+ *
1969
+ * `worldPosition` answers where a node IS; this answers which way it points,
1970
+ * and two games wrote it by hand (a tank's `forwardOf`, an artillery piece's
1971
+ * barrel tip) because nothing else could say it. `worldDirection(node, [0,0,1])`
1972
+ * is the +Z-forward convention every 3D skin in this engine follows.
1452
1973
  */
1974
+ declare function worldDirection(node: Node, local: readonly number[]): number[];
1975
+ /** The local `position` that puts `node` at `world` — the inverse of `worldPosition`. */
1453
1976
  declare function localFromWorld(node: Node, world: readonly number[]): number[];
1454
1977
  //#endregion
1455
1978
  //#region src/gameplay/turret.d.ts
@@ -1468,7 +1991,13 @@ declare function localFromWorld(node: Node, world: readonly number[]): number[];
1468
1991
  * shot takes one, puts it at the turret plus `muzzle`, points its
1469
1992
  * `Projectile` at the target and adds it under `into` (the turret's parent
1470
1993
  * when empty). The clone does the hurting then — give it `DamageOnContact`.
1471
- * - `facePath` turns a barrel toward the target at `turnSpeed`.
1994
+ * - `facePath` turns a barrel toward the target at `turnSpeed` — and the
1995
+ * `muzzle` offset rides THAT node, so a shell leaves the tip of the barrel
1996
+ * wherever it happens to be pointing.
1997
+ * - **`arc`** lobs: a shell with `Projectile.gravity` aimed straight at
1998
+ * anything far away falls out of the sky long before it arrives, so a
1999
+ * lobbing gun solves its launch angle (`'low'` flat and fast, `'high'` over
2000
+ * whatever stands in between) and ELEVATES the barrel to match.
1472
2001
  *
1473
2002
  * Signals: `acquired(target)` when something comes in range, `fired(target)`
1474
2003
  * per shot, `lost()` when nothing live is in range any more.
@@ -1477,6 +2006,7 @@ declare class Turret extends Behavior {
1477
2006
  static readonly props: PropSchema;
1478
2007
  static readonly signals: readonly string[];
1479
2008
  targetGroup: string;
2009
+ requireSight: boolean;
1480
2010
  range: number;
1481
2011
  damage: number;
1482
2012
  cooldown: number;
@@ -1484,6 +2014,7 @@ declare class Turret extends Behavior {
1484
2014
  prefab: string;
1485
2015
  muzzle: number[];
1486
2016
  into: string;
2017
+ arc: "flat" | "low" | "high";
1487
2018
  facePath: string;
1488
2019
  turnSpeed: number;
1489
2020
  /** What it is shooting at right now, or null. */
@@ -1492,7 +2023,18 @@ declare class Turret extends Behavior {
1492
2023
  shots: number;
1493
2024
  private wait;
1494
2025
  override onReady(): void;
2026
+ /** `requireSight`: a static-only ray from the turret to the target reaches it. */
2027
+ private canSee;
1495
2028
  override update(dt: number): void;
2029
+ /** Where a clone actually leaves from: the turret plus its muzzle, turned by the barrel. */
2030
+ private muzzleAt;
2031
+ /**
2032
+ * The heading of this shot: straight at the target, or the arc that lands on
2033
+ * it. Solved from the MUZZLE, which is where the shell starts.
2034
+ */
2035
+ private aimAt;
2036
+ /** The shell's own numbers, read off the template that will be cloned. */
2037
+ private shellBallistics;
1496
2038
  /** One clone from the shelf, at the muzzle, aimed at the target, into the container. */
1497
2039
  private launch;
1498
2040
  }
@@ -1509,4 +2051,4 @@ declare function registerGameplayBehaviors(opts?: {
1509
2051
  replace?: boolean;
1510
2052
  }): void;
1511
2053
  //#endregion
1512
- export { CameraShake, Chase, Checkpoint, Collector, Cooldown, Currency, DamageOnContact, DayNight, type DayPhase, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, PrefabShelf, Projectile, SavePoint, ScoreKeeper, Spawner, Turret, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
2054
+ export { type BallisticPathOptions, Blast, CameraShake, Chase, ChaseCamera, Checkpoint, Collector, Cooldown, Currency, DamageOnContact, DayNight, type DayPhase, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, GroupCamera, Health, Interactable, Lifetime, Mount, MoveTo, Oscillate, PathFollow, Patrol, Phases, Pickup, PrefabShelf, Projectile, SavePoint, ScoreKeeper, Sight, Spawner, Turret, Wander, WaveSpawner, ZombieAI, ballisticAim, ballisticPath, goToScene, hitStop, localFromWorld, maxRangeAim, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldDirection, worldPosition };