incanto 0.68.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 (153) hide show
  1. package/assets/catalog.json +9 -5
  2. package/bin/_behaviors-loader.mjs +22 -0
  3. package/bin/_read-json.mjs +28 -0
  4. package/bin/incanto-assets.mjs +19 -5
  5. package/bin/incanto-check.mjs +92 -15
  6. package/bin/incanto-editor.mjs +128 -5
  7. package/bin/incanto-env.mjs +3 -2
  8. package/bin/incanto-feel.mjs +24 -13
  9. package/bin/incanto-frame.mjs +8 -1
  10. package/bin/incanto-multiplay.mjs +11 -9
  11. package/bin/incanto-new.mjs +128 -5
  12. package/bin/incanto-play.mjs +158 -16
  13. package/bin/incanto-playtest.mjs +66 -23
  14. package/bin/incanto-serve.mjs +160 -0
  15. package/bin/incanto-skills.mjs +14 -2
  16. package/bin/incanto-verify.mjs +165 -44
  17. package/bin/incanto.mjs +4 -2
  18. package/dist/2d.d.ts +285 -36
  19. package/dist/2d.js +4 -4
  20. package/dist/3d.d.ts +158 -10
  21. package/dist/3d.js +8 -8
  22. package/dist/{agent8-CvsfVskX.js → agent8-CmNF01gA.js} +61 -8
  23. package/dist/{audio-player-DOrq7sP-.d.ts → audio-player-DaMxqfNE.d.ts} +33 -14
  24. package/dist/{behavior-DoFPYrgo.d.ts → behavior-DZExDn9o.d.ts} +809 -44
  25. package/dist/{create-game-IZIydDwI.js → create-game-Bwvh6q8A.js} +148 -61
  26. package/dist/{create-game-DbWtVTxD.js → create-game-C7ffQWW7.js} +103 -49
  27. package/dist/debug.d.ts +1 -1
  28. package/dist/debug.js +2 -3
  29. package/dist/diagnostics-Cu85N3tL.d.ts +12 -0
  30. package/dist/{editor-switch-CnIOiyNJ.d.ts → editor-switch-CFU9mCec.d.ts} +22 -13
  31. package/dist/editor.js +1066 -864
  32. package/dist/env.d.ts +1 -1
  33. package/dist/env.js +5 -3
  34. package/dist/{environment-presets-CybQXNqS.js → environment-presets-D6Q5BxeE.js} +299 -46
  35. package/dist/{frame-report-DCnHFmto.d.ts → frame-report-DNxDAb1w.d.ts} +8 -0
  36. package/dist/{frame-report-BSMny7oe.js → frame-report-Dlq13Gyj.js} +1 -0
  37. package/dist/{gameplay-DM1eu_cV.js → gameplay-BfHkuzVb.js} +825 -221
  38. package/dist/gameplay.d.ts +187 -7
  39. package/dist/gameplay.js +1 -1
  40. package/dist/{heightmap-CRK0M4jT.js → heightmap-BYgD5Edk.js} +1 -1
  41. package/dist/index.d.ts +156 -13
  42. package/dist/index.js +10 -12
  43. package/dist/json-CfTjpvW8.js +67 -0
  44. package/dist/{loader-CcB533FR.d.ts → loader-Cff09LMm.d.ts} +2 -2
  45. package/dist/net.d.ts +27 -3
  46. package/dist/net.js +2 -2
  47. package/dist/{noise-CGUMx44x.js → noise-D3nPpmFg.js} +1 -1
  48. package/dist/{physics-2d-B7Y6dPZO.js → physics-2d-CE0Qvy3V.js} +136 -11
  49. package/dist/{physics-3d-bG3n70Ky.js → physics-3d-CpH-2gn5.js} +104 -23
  50. package/dist/{teardown-D2NEmxPB.js → picking-CQJ_PJKh.js} +106 -14
  51. package/dist/react.d.ts +2 -2
  52. package/dist/react.js +2 -2
  53. package/dist/{register-3ta-2Xig.js → register-6DYnKZcy.js} +652 -831
  54. package/dist/{register-ibCjm-wH.js → register-Bkk0wSDB.js} +348 -30
  55. package/dist/{replay-CYvhVHHN.js → replay-DjAkAzMq.js} +224 -14
  56. package/dist/{replay-Dvn8aeBd.d.ts → replay-Dmw-PKQu.d.ts} +20 -3
  57. package/dist/{schema-B6ugCV1Q.d.ts → rng-Bb-IutXB.d.ts} +38 -21
  58. package/dist/{rng-DP-SR7eg.js → rng-CDOMybym.js} +22 -0
  59. package/dist/{loader-BC4PNtJX.js → save-slots-BXVg148r.js} +4558 -2294
  60. package/dist/{sheet-grid-BT6N_Bjs.js → sheet-grid-Cea343VO.js} +6 -2
  61. package/dist/{split-screen-DhrSzZIB.d.ts → split-screen--k-XpBjr.d.ts} +36 -4
  62. package/dist/{split-screen-CYwDkbLF.js → split-screen-PL78oVXP.js} +159 -26
  63. package/dist/{sprite-animation-CY-mrr1L.js → sprite-animation-CqR2o3SA.js} +39 -8
  64. package/dist/{src-D7RIqXYF.js → src-Cxfiv1Hg.js} +2 -17
  65. package/dist/test-iHYVUcDK.js +4036 -0
  66. package/dist/test.d.ts +542 -30
  67. package/dist/test.js +3 -3
  68. package/dist/touch-BnCyPA0G.js +519 -0
  69. package/dist/vite.d.ts +54 -3
  70. package/dist/vite.js +349 -17
  71. package/dist/{webgl-unavailable-N9nQqesw.js → webgl-unavailable-C8aDbGmR.js} +56 -1
  72. package/editor/assets/agent8-D0MS174y.js +1 -0
  73. package/editor/assets/{debug-BBhKuBNV.js → debug-BnXkKuYu.js} +2 -2
  74. package/editor/assets/index-CIu3uc3l.js +11046 -0
  75. package/editor/index.html +1 -1
  76. package/package.json +7 -16
  77. package/schemas/scene.schema.json +30 -3
  78. package/skills/incanto-3d-character.md +14 -1
  79. package/skills/incanto-3d-models.md +12 -0
  80. package/skills/incanto-assets.md +25 -3
  81. package/skills/incanto-audio.md +27 -5
  82. package/skills/incanto-behaviors-and-scripts.md +83 -6
  83. package/skills/incanto-building-2d-games.md +106 -8
  84. package/skills/incanto-building-3d-games.md +118 -6
  85. package/skills/incanto-editor.md +46 -7
  86. package/skills/incanto-environment.md +19 -1
  87. package/skills/incanto-game-feel.md +70 -0
  88. package/skills/incanto-gameplay-behaviors.md +121 -14
  89. package/skills/incanto-hud.md +128 -7
  90. package/skills/incanto-localization.md +13 -5
  91. package/skills/incanto-multiplayer.md +83 -3
  92. package/skills/incanto-node-reference.md +222 -58
  93. package/skills/incanto-performance.md +52 -0
  94. package/skills/incanto-physics-and-input.md +123 -24
  95. package/skills/incanto-playtesting.md +78 -2
  96. package/skills/incanto-save-slots.md +188 -6
  97. package/skills/incanto-scene-json-authoring.md +69 -12
  98. package/skills/incanto-verifying-your-game.md +292 -12
  99. package/skills/incanto-web-integration.md +28 -0
  100. package/skills/incanto-your-first-game.md +5 -2
  101. package/templates-app/beacon-isle-3d/generate-world.ts +77 -9
  102. package/templates-app/beacon-isle-3d/package.json +2 -2
  103. package/templates-app/beacon-isle-3d/src/behaviors.ts +22 -0
  104. package/templates-app/beacon-isle-3d/src/game.scene.json +103 -378
  105. package/templates-app/beacon-isle-3d/src/main.ts +24 -4
  106. package/templates-app/beacon-isle-3d/tsconfig.json +1 -1
  107. package/templates-app/beacon-isle-3d/verify.ts +3 -1
  108. package/templates-app/molehill-2d/.incanto/playtest/lost-seed1.json +4277 -0
  109. package/templates-app/molehill-2d/PROJECT/Context.md +58 -0
  110. package/templates-app/molehill-2d/PROJECT/Requirements.md +39 -0
  111. package/templates-app/molehill-2d/PROJECT/Status.md +27 -0
  112. package/templates-app/molehill-2d/PROJECT/Structure.md +48 -0
  113. package/templates-app/molehill-2d/docs/project-2d-rules.md +44 -0
  114. package/templates-app/molehill-2d/index.html +73 -0
  115. package/templates-app/molehill-2d/package.json +23 -0
  116. package/templates-app/molehill-2d/src/behaviors.ts +198 -0
  117. package/templates-app/molehill-2d/src/game.scene.json +1255 -0
  118. package/templates-app/molehill-2d/src/main.ts +41 -0
  119. package/templates-app/molehill-2d/tsconfig.json +13 -0
  120. package/templates-app/molehill-2d/verify.ts +247 -0
  121. package/templates-app/molehill-2d/vite.config.ts +12 -0
  122. package/templates-app/platformer-2d/index.html +0 -23
  123. package/templates-app/platformer-2d/package.json +2 -2
  124. package/templates-app/platformer-2d/src/behaviors.ts +26 -16
  125. package/templates-app/platformer-2d/src/game.scene.json +143 -625
  126. package/templates-app/platformer-2d/src/main.ts +35 -13
  127. package/templates-app/platformer-2d/tsconfig.json +1 -1
  128. package/templates-app/star-survivor/package.json +2 -2
  129. package/templates-app/star-survivor/src/game.scene.json +41 -195
  130. package/templates-app/star-survivor/src/main.ts +28 -7
  131. package/templates-app/star-survivor/tsconfig.json +1 -1
  132. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  133. package/templates-app/tps-3d/package.json +2 -2
  134. package/templates-app/tps-3d/src/behaviors.ts +19 -1
  135. package/templates-app/tps-3d/src/game.scene.json +78 -217
  136. package/templates-app/tps-3d/src/main.ts +39 -17
  137. package/templates-app/tps-3d/tsconfig.json +1 -1
  138. package/templates-app/village-quest-3d/.incanto/playtest/swapped-seed1.json +1735 -0
  139. package/templates-app/village-quest-3d/package.json +2 -2
  140. package/templates-app/village-quest-3d/src/behaviors.ts +21 -0
  141. package/templates-app/village-quest-3d/src/grove.scene.json +54 -221
  142. package/templates-app/village-quest-3d/src/main.ts +24 -4
  143. package/templates-app/village-quest-3d/src/village.scene.json +199 -838
  144. package/templates-app/village-quest-3d/tsconfig.json +1 -1
  145. package/templates-app/village-quest-3d/verify.ts +14 -1
  146. package/dist/duplicate-IWIqk0HJ.js +0 -22
  147. package/dist/json-CwwhxQgb.js +0 -36
  148. package/dist/registry-CF70EArN.js +0 -212
  149. package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
  150. package/dist/test-8hoHeRmo.js +0 -2340
  151. package/dist/touch-DEAmqGdf.js +0 -225
  152. package/editor/assets/agent8-BrrHOjMJ.js +0 -1
  153. package/editor/assets/index-eVd0BToA.js +0 -10958
package/dist/test.d.ts CHANGED
@@ -1,9 +1,30 @@
1
- import { P as Scene, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-DoFPYrgo.js";
2
- import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-B6ugCV1Q.js";
3
- import { t as LoadSceneOptions } from "./loader-CcB533FR.js";
4
- import { l as auditScene, o as IncantoError, r as ReplayJson, u as instancedScenes } from "./replay-Dvn8aeBd.js";
5
- import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-DhrSzZIB.js";
1
+ import { P as Scene$1, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-DZExDn9o.js";
2
+ import { a as SceneJson, c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
3
+ import { t as LoadSceneOptions } from "./loader-Cff09LMm.js";
4
+ import { l as auditScene, o as IncantoError, r as ReplayJson, u as instancedScenes } from "./replay-Dmw-PKQu.js";
5
+ import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen--k-XpBjr.js";
6
+ import { Matrix4, Vector3 } from "three";
6
7
 
8
+ //#region src/core/asset-urls.d.ts
9
+ interface SceneAssetUrl {
10
+ /** `/Root/Enemies/Slime` — where in the tree it was written. */
11
+ path: string;
12
+ /** `texture`, `sheet`, `material.map`, … */
13
+ prop: string;
14
+ /** The asset spec's `kind` — `texture`, `spritesheet`, `model`, `audio`, … */
15
+ kind: string;
16
+ url: string;
17
+ }
18
+ /**
19
+ * Walk the tree and collect every URL-valued asset prop.
20
+ *
21
+ * `$ref` values are somebody else's problem (`validateAssetRefs` resolves them
22
+ * against `assets{}` at load). Unknown node types are skipped rather than
23
+ * thrown on: this is a warning path, and the loader reports an unknown type far
24
+ * better than a walker could.
25
+ */
26
+ declare function sceneAssetUrls(json: unknown): SceneAssetUrl[];
27
+ //#endregion
7
28
  //#region src/test/facing.d.ts
8
29
  interface FacingOptions {
9
30
  behaviors?: Record<string, BehaviorCtor>;
@@ -26,6 +47,20 @@ interface FacingReport {
26
47
  backwards: boolean;
27
48
  /** How far it actually moved, so a report on a stuck character is not trusted. */
28
49
  travelled: number;
50
+ /**
51
+ * WHICH property was read to get a heading — `yaw` (3D, and 2D top-down),
52
+ * `flipX` (a 2D side view), or null when the skin declares neither.
53
+ *
54
+ * It exists because the answer used to be "none of them, silently": in 2D
55
+ * both values this read were `undefined`, so `dot` came out exactly 0,
56
+ * `backwards` could never fire, and the line printed was
57
+ * `faces its travel (dot 0.00)` for any 2D character whatever way its art
58
+ * pointed. A check that cannot fail is worse than no check, so a report that
59
+ * measured nothing now SAYS it measured nothing.
60
+ */
61
+ basis: "yaw" | "flipX" | null;
62
+ /** When there is no `dot`, which of the four reasons it is. */
63
+ noAnswer: "no-player" | "no-skin" | "did-not-move" | "no-heading" | null;
29
64
  }
30
65
  /**
31
66
  * Drive the player forward and compare where the skin points with where it went.
@@ -40,14 +75,34 @@ declare function facingText(report: FacingReport): string;
40
75
  //#endregion
41
76
  //#region src/test/framing.d.ts
42
77
  /** Where a node's origin sits relative to the current camera. */
43
- type Where = "onScreen" | "offscreen" | "behind";
78
+ /**
79
+ * `hidden` is not a position — it is the renderer declining to draw at all.
80
+ *
81
+ * It is its own state rather than a flavour of `offscreen` because the fix
82
+ * differs: an offscreen node is in the wrong PLACE, a hidden one is switched
83
+ * off, and a 2D screen drawing absolutely nothing used to report
84
+ * `2 in view, 0 outside it` — the two numbers an author reads to conclude the
85
+ * frame is fine.
86
+ */
87
+ type Where = "onScreen" | "offscreen" | "behind" | "hidden" | "blank";
44
88
  interface FramingEntry {
45
89
  path: string;
46
90
  type: string;
47
91
  /** World-space origin. */
48
92
  at: [number, number, number];
49
93
  where: Where;
50
- /** Normalised device coords, x/y in −1..1 (right/up). Absent when `behind`. */
94
+ /**
95
+ * Normalised coords, x/y in −1..1. Absent when `behind`.
96
+ *
97
+ * The Y AXIS DIFFERS BY REPORT, because the two spaces do: in the 3D report
98
+ * this is NDC and +y is UP; in the 2D report it is the design-space window
99
+ * and +y is DOWN, like every other 2D coordinate in this engine. The one
100
+ * docstring here used to promise "(right/up)" for both, and the comment above
101
+ * the 2D computation claimed it was "the same reading as the 3D report" — so
102
+ * anything comparing a 2D `screen.y` against a 3D one was reading a sign
103
+ * that was never there. `FramingReport.dimension` already tells you which
104
+ * report you have; this follows it.
105
+ */
51
106
  screen?: [number, number];
52
107
  /** Metres from the camera's eye. */
53
108
  distance: number;
@@ -70,9 +125,55 @@ interface FramingReport {
70
125
  fovDeg?: number; /** 2D only: world pixels visible, after `zoom`. */
71
126
  view?: [number, number]; /** 2D only. */
72
127
  zoom?: number;
128
+ /**
129
+ * 3D: which way is UP on screen — the camera's local +Y in world space.
130
+ *
131
+ * Roll changes nothing else: `at` and `forward` of a camera rolled 180°
132
+ * are identical to an upright one's, so the whole world hanging upside
133
+ * down printed a byte-identical header.
134
+ */
135
+ up?: [number, number, number];
136
+ /**
137
+ * 3D: how far the horizon is tilted, in degrees. 0 is level, 180 inverted.
138
+ *
139
+ * A number rather than a verdict: a dutch angle is a real choice. But the
140
+ * engine's OWN suggested default — `position [7, 6, 9], rotation
141
+ * [-26, 36, 0]` — measures 16° of roll, and nothing anywhere reported it,
142
+ * because no two-angle Euler-XYZ recipe can produce a level camera in the
143
+ * first place (`[pitch, yaw, 0]` is Rx·Ry; a level camera is Ry·Rx).
144
+ */
145
+ rollDeg?: number;
146
+ /**
147
+ * The frame is upside down (`up.y < 0`), near-vertical shots excepted.
148
+ *
149
+ * A plain top-down camera (`rotation: [-90, 0, 0]`) measures
150
+ * `up = (0, 0, -1)` — `up.y` is 0 EXACTLY and the frame is correct, so the
151
+ * test is `< 0` and skips |forward.y| > 0.98 entirely.
152
+ */
153
+ upsideDown?: boolean;
154
+ /**
155
+ * The projection matrix has a non-finite element, so NOTHING is drawn.
156
+ *
157
+ * `fov: 0` / `near: 0` / `near >= far`. Every entry is reported offscreen
158
+ * rather than passed through with `screen [NaN, NaN]` under an `ok`.
159
+ */
160
+ degenerate?: boolean;
73
161
  } | null;
74
162
  entries: FramingEntry[];
75
163
  /**
164
+ * Widgets a HudLayer puts on screen, by path — the part of the picture the
165
+ * camera has nothing to do with.
166
+ *
167
+ * The walk stops at a `HudLayer` on purpose ("asking whether the camera can
168
+ * see it is a category error"), and nothing counted what was inside. So a
169
+ * shop screen made entirely of widgets and a scene file with a root node and
170
+ * nothing else BOTH read `0/0 on screen`, and the check whose header says
171
+ * "N scene(s) a person would call broken on sight" passed the second one:
172
+ * its rule is "the camera sees none of the things there are", and there were
173
+ * none. Hidden widgets are left out, like every other `visible: false` node.
174
+ */
175
+ widgets: string[];
176
+ /**
76
177
  * What lights the scene: light nodes, plus the environment header when it
77
178
  * carries a sky/hdri/preset/ambient. Empty means the render is BLACK.
78
179
  */
@@ -94,10 +195,82 @@ interface FramingOptions {
94
195
  */
95
196
  viewport?: readonly [number, number];
96
197
  }
97
- declare function describeFraming(scene: Scene, opts?: FramingOptions): FramingReport;
198
+ declare function describeFraming(scene: Scene$1, opts?: FramingOptions): FramingReport;
98
199
  /** The report as something to read in a terminal. */
99
200
  declare function framingText(report: FramingReport): string;
100
201
  //#endregion
202
+ //#region src/test/picker.d.ts
203
+ /**
204
+ * "What node is under this point" — from GEOMETRY, with no renderer.
205
+ *
206
+ * `Clickable` reads `engine.picker`, which the renderer installs as a GPU
207
+ * raycast. Headless there is no renderer, so `engine.picker` is null and every
208
+ * mouse-driven genre — match-3, tower defense, cards, point-and-click, RTS,
209
+ * idle, a flip puzzle — was inert in the one place it was supposed to be
210
+ * proved. The recipe for it was published (write your own picker) and every
211
+ * such game therefore hand-wrote the same box hit-test.
212
+ *
213
+ * The boxes come from the same machinery `describeFraming` uses, so a node's
214
+ * pickable extent and its on-screen extent are the same fact rather than two
215
+ * that drift.
216
+ */
217
+ interface PickerOptions {
218
+ /**
219
+ * The pixel rect the coordinates live in.
220
+ *
221
+ * 2D: defaults to the scene's `viewport.design`, then 960×540 — the same
222
+ * fallback the framing report makes, because it is the same question.
223
+ * 3D: the size the ray is cast through; only the ASPECT changes an answer.
224
+ */
225
+ viewport?: readonly [number, number];
226
+ }
227
+ /**
228
+ * A picker over `scene`, in the scene's own units.
229
+ *
230
+ * 2D takes WORLD pixels — headless there is no canvas to convert from, and the
231
+ * design window is the coordinate space every 2D position in the file is
232
+ * already written in. 3D takes pixels within `viewport` and casts a ray from
233
+ * the scene's current camera.
234
+ *
235
+ * A hidden node is never picked, which matches what the player can click.
236
+ */
237
+ declare function geometricPicker(scene: Scene$1 | (() => Scene$1), opts?: PickerOptions): (x: number, y: number) => Node | null;
238
+ /**
239
+ * `engine.toWorld` / `engine.toScreen` from GEOMETRY, with no renderer — the
240
+ * aiming half of a mouse game, the way `geometricPicker` is the clicking half.
241
+ *
242
+ * A renderer installs these on the engine so a Behavior can ask where the
243
+ * cursor IS; headless nothing did, so a drag-and-launch, a tower placed on the
244
+ * map or a unit steered by the cursor could be clicked in a harness and never
245
+ * aimed. 2D is the identity (the picker's coordinates are already world pixels
246
+ * — there is no canvas to convert from); 3D unprojects onto the `groundY`
247
+ * plane, which is where a 3D game puts the thing you pointed at.
248
+ */
249
+ declare function geometricView(scene: Scene$1 | (() => Scene$1), opts?: PickerOptions & {
250
+ groundY?: number;
251
+ }): {
252
+ toWorld: (x: number, y: number) => number[] | null;
253
+ toScreen: (world: readonly number[]) => {
254
+ x: number;
255
+ y: number;
256
+ behind: boolean;
257
+ } | null;
258
+ screenRay: (x: number, y: number) => {
259
+ origin: number[];
260
+ dir: number[];
261
+ } | null;
262
+ };
263
+ /**
264
+ * Where a node is, in the coordinates `geometricPicker` answers in.
265
+ *
266
+ * The click half of a headless driver: `at %Mole` has to become a point before
267
+ * a press can land on it, and the space differs by dimension — 2D pixels are
268
+ * the world, 3D pixels are a projection through the scene's camera. Both
269
+ * drivers that click headlessly (`runScript` and `incanto-play`) need exactly
270
+ * this, and the first one had it inline.
271
+ */
272
+ declare function pointAtNode(scene: Scene$1, path: string, opts?: PickerOptions): [number, number] | null;
273
+ //#endregion
101
274
  //#region src/test/agreement.d.ts
102
275
  /**
103
276
  * Do two clients hold the same value for the same replicated thing?
@@ -126,8 +299,43 @@ interface AgreementSnapshot {
126
299
  * materialised this account at all.
127
300
  */
128
301
  seen: Record<string, Record<string, JsonValue | undefined> | null>;
302
+ /**
303
+ * Declared sync keys the OWNER cannot read.
304
+ *
305
+ * These used to be dropped from `truth` entirely — "the sender's own problem,
306
+ * already diagnosed there". It is diagnosed there, to `engine.log.error`,
307
+ * which does not touch `stats().errors` and so reaches no verdict; and the
308
+ * sender's report is gated on 500 ms of replication, which a short match may
309
+ * not reach. Meanwhile dropping the key means every observer agrees perfectly
310
+ * about a value that is never sent, and `agrees` grades a dead sync key
311
+ * PASS. A key that cannot be read is the most complete failure of
312
+ * replication there is, so it is a finding, not a silence.
313
+ */
314
+ unreadable?: string[];
315
+ }
316
+ /**
317
+ * One `collection:` spawner on one client: what the server sent it, and what it
318
+ * made of that.
319
+ *
320
+ * `NetworkSpawner` has two sources and only `users` was ever compared. A
321
+ * `collection:` spawner — bullets, pickups, every entity a server owns — could
322
+ * receive a hundred entities and materialise none, and the whole `agrees` rung
323
+ * had nothing to say: the comparison is keyed by ACCOUNT, and a bullet has no
324
+ * account.
325
+ */
326
+ interface CollectionSnapshot {
327
+ /** The client this spawner belongs to. */
328
+ account: string;
329
+ /** Node path of the spawner. */
330
+ spawner: string;
331
+ /** The collection id, from `source: "collection:<id>"`. */
332
+ id: string;
333
+ /** Entity ids this client's manager last received for that collection. */
334
+ received: string[];
335
+ /** Child instance names the spawner actually materialised. */
336
+ materialised: string[];
129
337
  }
130
- type DisagreementKind = "missing" | "absent" | "mismatch" | "shape" | "erased";
338
+ type DisagreementKind = "missing" | "absent" | "mismatch" | "shape" | "erased" | "unreadable" | "unspawned";
131
339
  interface Disagreement {
132
340
  kind: DisagreementKind;
133
341
  /** Whose value it is. */
@@ -152,6 +360,14 @@ interface AgreementOptions {
152
360
  * @param late the same, for a client that joined AFTER everything settled
153
361
  */
154
362
  declare function agreementProblems(live: AgreementSnapshot[], late?: AgreementSnapshot[], opts?: AgreementOptions): Disagreement[];
363
+ /**
364
+ * Collections that arrived and became nothing, and collections two clients
365
+ * disagree about.
366
+ *
367
+ * Pure over snapshots, like `agreementProblems`, and reported in the same
368
+ * shape so one verdict covers both halves of what a spawner does.
369
+ */
370
+ declare function collectionProblems(snapshots: CollectionSnapshot[]): Disagreement[];
155
371
  //#endregion
156
372
  //#region src/test/agreement-snapshot.d.ts
157
373
  /**
@@ -175,6 +391,15 @@ interface AgreementClient {
175
391
  * @param observers the clients whose beliefs to record (defaults to `clients`)
176
392
  */
177
393
  declare function takeAgreementSnapshots(clients: AgreementClient[], observers?: AgreementClient[]): AgreementSnapshot[];
394
+ /**
395
+ * What every `collection:` spawner RECEIVED, and what it made of it.
396
+ *
397
+ * The account-keyed comparison above cannot see these: a bullet has no account.
398
+ * A spawner can take a hundred entities off the wire and materialise none of
399
+ * them — an unregistered `scene` key does exactly that — and until this
400
+ * existed the `agrees` rung had nothing to say about it.
401
+ */
402
+ declare function takeCollectionSnapshots(clients: AgreementClient[]): CollectionSnapshot[];
178
403
  //#endregion
179
404
  //#region src/test/feel.d.ts
180
405
  interface FeelOptions {
@@ -191,6 +416,28 @@ interface FeelReport {
191
416
  /** null when the scene has no player this can drive. */
192
417
  player: string | null;
193
418
  moveAction: string | null;
419
+ /**
420
+ * WHICH WAY the move action was pushed to get these numbers.
421
+ *
422
+ * Every probe used to push `(1, 0)` and only `(1, 0)`. A game whose movement
423
+ * reads the other axis of the same action — the engine's own documented
424
+ * forward is `-y` — measured nothing and printed the silence as `0 u/s` with
425
+ * every derived row blank, exit 0. Null means nothing moved on any axis.
426
+ */
427
+ moveAxis: [number, number] | null;
428
+ /** Every vector2 action this run actually pushed, in the order it tried them. */
429
+ moveTried: string[];
430
+ /**
431
+ * What the engine said while these numbers were being taken.
432
+ *
433
+ * A feel report is a page of confident numbers, and it printed them for a
434
+ * game whose controller's sibling script threw on the first frame and was
435
+ * quarantined. The controller still works, so the numbers are honest for what
436
+ * they measure — and a script dying during the measurement is worth a line.
437
+ * Deduped across probes: each probe is a fresh session.
438
+ */
439
+ errorMessages: string[];
440
+ warnings: string[];
194
441
  jumpAction: string | null;
195
442
  /** Top horizontal speed reached, world units per second. */
196
443
  topSpeed: number;
@@ -209,8 +456,38 @@ interface FeelReport {
209
456
  timeToApexMs: number | null;
210
457
  /** Launch → back on the ground. */
211
458
  airtimeMs: number | null;
459
+ /**
460
+ * The fastest single frame, when it is well above the sustained `topSpeed`.
461
+ *
462
+ * Null when they agree. When they do not, something OTHER than the character's
463
+ * own movement moved it during the measurement — a knockback from an enemy the
464
+ * probe walked into, a launcher, a conveyor — and every number in the block is
465
+ * about that instead.
466
+ */
467
+ peakSpeed: number | null;
212
468
  /** MEASURED, not read: how long after leaving ground a jump still fires. */
213
469
  coyoteMs: number | null;
470
+ /**
471
+ * Can this character get off the ground a full second after leaving it?
472
+ *
473
+ * The coyote probe reads a jump as "vertical velocity went up on the frame I
474
+ * pressed", and TWO of the six shipped starters make that true forever, for
475
+ * two different reasons: `platformer-2d` has a double jump, and
476
+ * `beacon-isle-3d` has a glide that clamps the fall while `jump` is held. So
477
+ * both reported `coyote window always (no window)` — which reads as "your
478
+ * ground check is infinitely generous" — while one of them declares
479
+ * `coyoteSeconds: 0.12` in the very same file.
480
+ *
481
+ * A window cannot be measured this way on a character that can jump in
482
+ * mid-air, and saying that is worth more than a number that is wrong.
483
+ */
484
+ jumpsInMidair: boolean;
485
+ /**
486
+ * `coyoteSeconds` as the scene DECLARES it, in ms — the only number in this
487
+ * report that is read rather than measured, and it is here to be CONTRASTED
488
+ * with `coyoteMs` rather than to stand in for it.
489
+ */
490
+ declaredCoyoteMs: number | null;
214
491
  /** MEASURED: how long before landing a press still survives. */
215
492
  jumpBufferMs: number | null;
216
493
  /** Frames between the press and the first movement. */
@@ -258,6 +535,20 @@ interface MultiplayPlayer {
258
535
  room: string;
259
536
  /** Nodes each `NetworkSpawner` in this client's scene ended up holding. */
260
537
  spawned: Record<string, number>;
538
+ /**
539
+ * The same, with each spawner's `source`.
540
+ *
541
+ * "materialised nothing" used to be asked of the CLIENT — every spawner it
542
+ * has, all at zero — so a client with a working spawner and a dead one
543
+ * beside it was silent, and the whole check was additionally gated on nobody
544
+ * being blind. A spawner is the unit that fails, so a spawner is the unit
545
+ * that is asked.
546
+ */
547
+ spawners: Array<{
548
+ name: string;
549
+ source: string;
550
+ children: number;
551
+ }>;
261
552
  /** Other accounts this client's room state actually carried. */
262
553
  sawAccounts: string[];
263
554
  /** Frame errors on this client's engine. */
@@ -330,6 +621,24 @@ interface PlaytestRun {
330
621
  /** How many times the player's Health went down. */
331
622
  damageTaken: number;
332
623
  errors: number;
624
+ /**
625
+ * What the engine WARNED about — one line per distinct warning.
626
+ *
627
+ * The warnings are the things that leave a game running and wrong: a camera
628
+ * whose `follow` resolves to a node with no position, a `DayNight` with no
629
+ * sky, a body with no collider. `incanto-verify`'s `says` rung reads them off
630
+ * the browser; a headless run had them in `engine.log` and printed none.
631
+ */
632
+ warnings: string[];
633
+ /**
634
+ * What threw, in the engine's own words — one line per distinct error.
635
+ *
636
+ * `errors` is a COUNT, and the count was all a run carried. The message that
637
+ * names the behavior, the node and the exception went to the console only,
638
+ * so `playtestText` — what the bin prints and what an agent reads back —
639
+ * said `✗ error in 2/2` and nothing an author could act on.
640
+ */
641
+ errorMessages: string[];
333
642
  /** Only for a run worth watching — replay it with `incanto-play`. */
334
643
  replay: ReplayJson | null;
335
644
  /** Where the player ended up, for the fall report. */
@@ -338,6 +647,48 @@ interface PlaytestRun {
338
647
  heard: string[];
339
648
  /** Node paths that fired a visible effect during the run. */
340
649
  shown: string[];
650
+ /**
651
+ * Mouse clicks this run landed on a `Clickable`.
652
+ *
653
+ * The bot drives the mouse; nothing counted it, so a game played entirely by
654
+ * clicking looked identical to a game nobody touched.
655
+ */
656
+ clicks: number;
657
+ /**
658
+ * Mouse DRAGS this run — press, move, let go.
659
+ *
660
+ * A game can be entirely mouse-driven with no `Clickable` in it: a slingshot,
661
+ * a drawing game, a box-select, a swipe. The bot could only click a listed
662
+ * node, so it reported the whole class as `nothing here is drivable`.
663
+ */
664
+ drags: number;
665
+ /**
666
+ * HUD widgets this run pressed — a menu game's only verb.
667
+ *
668
+ * Counted separately from `clicks` because they are different gestures on
669
+ * different surfaces: a click lands on a world node through the picker, a
670
+ * press lands on a DOM overlay widget through the path a controller takes.
671
+ */
672
+ presses: number;
673
+ /**
674
+ * Declared BUTTON ACTIONS this run pressed — the keyboard, which is the only
675
+ * verb a rhythm game, a typing game, a QTE or a keyboard puzzle has.
676
+ *
677
+ * The bot has always pressed them (`steer` flips a coin per button per
678
+ * frame); nothing counted them. So a game with no character and four declared
679
+ * lanes was reported as one where "there was nobody to move and nothing to
680
+ * press", in the same run that pressed its buttons three hundred times.
681
+ */
682
+ actionPresses: number;
683
+ /**
684
+ * HUD DRAGS this run — an ingredient into a pot, an item between slots.
685
+ *
686
+ * The bot drove the world mouse and the widgets' activation, and never the
687
+ * one gesture the HUD skill says a click cannot express. So a game whose
688
+ * every verb is a drag reported `nothing here is drivable` and `stuck in
689
+ * 6/6`, about a game that plays.
690
+ */
691
+ widgetDrags: number;
341
692
  }
342
693
  interface PlaytestReport {
343
694
  runs: PlaytestRun[];
@@ -347,9 +698,39 @@ interface PlaytestReport {
347
698
  signals: string[];
348
699
  /** Declared actions the driver had available. */
349
700
  actions: string[];
350
- /** Actions that never changed the world in any run. */
701
+ /**
702
+ * Declared actions NOTHING read, in any run — a control the scene promises
703
+ * and no code implements.
704
+ *
705
+ * This field shipped hard-coded to `[]`, with a comment claiming it held
706
+ * "actions that never changed the world". Nothing ever measured that, so the
707
+ * report answered "no dead controls" for every scene ever passed to it —
708
+ * including `examples/village-quest-3d`, which declares
709
+ * `"restart": { "keys": ["KeyR"] }` and never reads it, so its own control
710
+ * list says R restarts and R does nothing.
711
+ *
712
+ * What is measured now is exact rather than heuristic: `engine.input` records
713
+ * every action something ASKED about, and an action nobody asked about cannot
714
+ * have changed anything. Pressing each button and diffing the world — the
715
+ * original wording — is unmeasurable in a scene where NPCs walk and physics
716
+ * settles; this is free and it cannot be fooled by noise.
717
+ *
718
+ * Advisory, not a failure: an action polled only in a state no run reached
719
+ * (a pause menu the bot never opened) lands here honestly.
720
+ */
351
721
  inertActions: string[];
352
722
  /**
723
+ * Every distinct thing that threw, across every run, in the engine's own
724
+ * words. Empty when nothing did.
725
+ */
726
+ errorMessages: string[];
727
+ /**
728
+ * Every distinct WARNING the engine made, across every run — the things
729
+ * that leave a game running and wrong (a camera following a node with no
730
+ * position, a body with no collider). Empty when there were none.
731
+ */
732
+ warnings: string[];
733
+ /**
353
734
  * Does anything in this scene declare an END?
354
735
  *
355
736
  * "No run reached a win" has two causes that need opposite fixes: a goal
@@ -361,6 +742,17 @@ interface PlaytestReport {
361
742
  /** How many nodes are wired to hurt the player — see the `danger:` line. */
362
743
  hazards: number;
363
744
  /**
745
+ * Actions the bot stopped pressing because they STOPPED THE CLOCK.
746
+ *
747
+ * It drove every declared button, and `incanto-your-first-game.md` tells you
748
+ * to declare `pause`. Measured on one losable scene: 0/10 losses with `pause`
749
+ * declared, 10/10 without, everything else identical — a frozen game cannot
750
+ * hurt you, so the same run also reported the game as having no way to lose.
751
+ * Named here rather than guessed from the word "pause": a game may call it
752
+ * `menu` or `escape`, and the bot notices the way a player would.
753
+ */
754
+ froze: string[];
755
+ /**
364
756
  * Did `findPlayer` find anyone to drive?
365
757
  *
366
758
  * `findPlayer`'s own comment says a scene it cannot drive "says so rather
@@ -418,6 +810,30 @@ declare function playtest(json: unknown, opts?: PlaytestOptions): Promise<Playte
418
810
  * of green is a wall nobody reads.
419
811
  */
420
812
  declare function playtestText(report: PlaytestReport): string;
813
+ /**
814
+ * Did this playtest find something WRONG? — the exit code, as a function.
815
+ *
816
+ * `incanto-playtest` exited 1 whenever no run reached a win, and its own report
817
+ * says when that is not a failure. Measured on a walkabout scene:
818
+ *
819
+ * ```
820
+ * · no win declared in this scene — nothing emits won/lost, so there is
821
+ * nothing to reach
822
+ * · 2/2 ran out of time still playing
823
+ * $ echo $?
824
+ * 1
825
+ * ```
826
+ *
827
+ * The `·` is this file's own marker for "not applicable", printed above an exit
828
+ * code that says failure — so a CI step on a walkabout, a title screen, or any
829
+ * scene whose ending is a quest a random walker cannot finish is red forever,
830
+ * and its author learns to stop reading it. `incanto-verify` already reads the
831
+ * JSON and calls those `unmeasured`; only the standalone bin disagreed.
832
+ *
833
+ * What stays a failure regardless of a declared win: a run that THREW, and a
834
+ * run that left the world. Neither is a matter of what the scene set out to be.
835
+ */
836
+ declare function playtestFailed(report: PlaytestReport): boolean;
421
837
  /** The failing runs worth saving, newest problem first. */
422
838
  declare function failingReplays(report: PlaytestReport): Array<{
423
839
  seed: number;
@@ -427,22 +843,13 @@ declare function failingReplays(report: PlaytestReport): Array<{
427
843
  //#endregion
428
844
  //#region src/test/verify-ladder.d.ts
429
845
  /**
430
- * The verification ladder, as one answer.
431
- *
432
- * The rungs already exist — `incanto-check` says the scene loads,
433
- * `incanto-playtest` says it can be finished, `incanto-frame` says something
434
- * was drawn. They are documented together and used apart, because using them
435
- * together means knowing three things nobody writes down:
436
- *
437
- * the ORDER (a scene that does not load cannot be played or drawn, so the
438
- * first red rung is the only one worth reading)
439
- * that an unmeasured rung is not a failing one — "no dev server" means the
440
- * question was never asked, and answering it "fail" sends an agent editing
441
- * a scene that is fine
442
- * what to do NEXT, which is one sentence and never the whole report
846
+ * What a spawned tool actually SAID, given its stderr.
443
847
  *
444
- * Pure: the rungs are run by the CLI, this decides what they add up to.
848
+ * Used by every rung in `incanto-verify`; it lives here rather than in the bin
849
+ * so it can be tested without a built `dist` — the bin loads the library at
850
+ * runtime, so a regex only the bin knows is a regex nothing checks.
445
851
  */
852
+ declare function firstLine(text: string | null | undefined): string | null;
446
853
  type RungStatus = "pass" | "fail" | "unmeasured" | "skipped";
447
854
  interface RungResult {
448
855
  name: string;
@@ -479,11 +886,61 @@ interface LadderVerdict {
479
886
  next: string | null;
480
887
  /** Scenes found when none was named (empty once one is being verified). */
481
888
  candidates: string[];
889
+ /**
890
+ * Did anything get asked at all?
891
+ *
892
+ * `ok` answers "is anything WRONG", and with no rungs the honest answer to
893
+ * that is yes-nothing-is-wrong — which is how `incanto-verify` printed
894
+ * *"2 scenes here, and guessing between them would verify the wrong one"*
895
+ * and exited 0. A CI step whose whole job is this command then goes green
896
+ * having looked at nothing, which is the exact shape the gate exists to
897
+ * prevent. Callers that need a verdict rather than an absence read this.
898
+ */
899
+ measured: boolean;
482
900
  }
483
901
  interface LadderContext {
484
902
  /** Scenes found when none was named — none, or more than one to choose from. */
485
903
  candidates?: string[];
486
904
  }
905
+ /**
906
+ * The `says` rung, from `incanto-logs`' report — the one rung whose whole job is
907
+ * "what is the running game SAYING".
908
+ *
909
+ * It read `ok` and printed "no errors, no failed assets", which threw away
910
+ * every WARNING the page had reported. The engine warns about exactly the
911
+ * things that leave a game running and wrong — a camera whose `follow` resolves
912
+ * to a node with no position, a `DayNight` with no sky to move, a `Buoyancy`
913
+ * with no water, a body with no collider — and the headline command showed a
914
+ * tick over all of it.
915
+ *
916
+ * Warnings are NOT a failure (an author may have chosen to live with one), so
917
+ * this keeps `pass` and carries them the way `loads` has always carried
918
+ * `incanto-check`'s: as notes under the rung, printed with a `!`.
919
+ *
920
+ * Pure, so it can be tested without a browser — the bin owns fetching the
921
+ * report, this owns what it means.
922
+ */
923
+ declare function saysRung(report: {
924
+ ok?: boolean;
925
+ counts?: {
926
+ warn?: number;
927
+ };
928
+ grouped?: readonly {
929
+ level: string;
930
+ message: string;
931
+ count: number;
932
+ }[];
933
+ assetErrors?: readonly {
934
+ ref?: string;
935
+ error?: string;
936
+ }[];
937
+ worst?: {
938
+ message?: string;
939
+ } | null;
940
+ stats?: {
941
+ errors?: number;
942
+ };
943
+ } | null): RungResult;
487
944
  declare function ladderVerdict(rungs: RungResult[], ctx?: LadderContext): LadderVerdict;
488
945
  /** The ladder as a person or an agent reads it: every rung, then the one action. */
489
946
  declare function ladderText(v: LadderVerdict): string;
@@ -548,7 +1005,7 @@ interface SceneCapture {
548
1005
  nodes: NodeCapture[];
549
1006
  }
550
1007
  /** Snapshot the live tree: every node, every schema prop's current value. */
551
- declare function captureScene(scene: Scene): SceneCapture;
1008
+ declare function captureScene(scene: Scene$1): SceneCapture;
552
1009
  /**
553
1010
  * Render a capture as one grep-able line per node, showing only NON-DEFAULT
554
1011
  * props — the same delta discipline as scene JSON, so the interesting state
@@ -574,7 +1031,7 @@ interface ValidationResult {
574
1031
  declare function validateScene(json: unknown, opts?: ValidateSceneOptions): ValidationResult;
575
1032
  interface RunContext {
576
1033
  engine: Engine;
577
- scene: Scene;
1034
+ scene: Scene$1;
578
1035
  /** Simulated time of the current step, in ms. */
579
1036
  timeMs: number;
580
1037
  /** Resolve a node path relative to the scene root. */
@@ -593,10 +1050,65 @@ interface ScriptStep {
593
1050
  vector?: [string, number, number];
594
1051
  /** Raw key-code fallback: [code, isDown]. */
595
1052
  key?: [string, boolean];
1053
+ /**
1054
+ * Click a node by PATH, or a point `[x, y]` — the mouse, as one step.
1055
+ *
1056
+ * `Clickable` reads the mouse BUTTON, not an action, and needs
1057
+ * `engine.picker`, which headless is null because there is no renderer to
1058
+ * raycast with. So every mouse-driven genre — match-3, tower defense, cards,
1059
+ * point-and-click, RTS, idle, a flip puzzle — could not be played by its own
1060
+ * harness: `press`/`release`/`vector`/`key` are all ACTIONS.
1061
+ *
1062
+ * The press and the release land on two different frames, which is what
1063
+ * `Clickable` requires and the part a hand-rolled version forgets. A
1064
+ * `geometricPicker` is installed for the run unless the game already has one
1065
+ * of its own — the game's answer always wins.
1066
+ *
1067
+ * A FUNCTION for the case a mouse game is usually in: click whatever is there
1068
+ * NOW. A whack-a-mole cannot name its target when the script is written, and
1069
+ * `null` means "nothing to click this time", which is not a failure.
1070
+ */
1071
+ click?: string | [number, number] | ((ctx: RunContext) => string | [number, number] | null);
1072
+ /**
1073
+ * Move the cursor, without pressing anything.
1074
+ *
1075
+ * A node path or a point, resolved exactly as `click` resolves one. On its
1076
+ * own this is a HOVER — which is a state a game shows (a highlighted tile, a
1077
+ * tooltip, a cursor that changes) and nothing could reach.
1078
+ *
1079
+ * With `mouse` it is the other half of a DRAG, which `click` cannot express
1080
+ * at all: `click` presses and releases at one point, and a slingshot, a
1081
+ * drawn selection box, a swipe and an inventory item carried between slots
1082
+ * are all "press HERE, move THERE, let go". Measured on a drag-and-launch
1083
+ * game: the launch fired on every shot and the drag vector was always zero,
1084
+ * so the ball dropped where it stood and the harness said the run was fine.
1085
+ */
1086
+ at?: string | [number, number] | ((ctx: RunContext) => string | [number, number] | null);
1087
+ /**
1088
+ * Press or release the mouse where the cursor is, as separate steps.
1089
+ *
1090
+ * `'down'` holds the button until a later `'up'` — across as many frames and
1091
+ * moves as the script likes. `Clickable` still fires on the release over the
1092
+ * node the press started on, so a drag that wanders off is not a click, which
1093
+ * is what the player already expects.
1094
+ */
1095
+ mouse?: "down" | "up";
1096
+ /** Which mouse button `mouse` (and `click`) uses: 0 left · 1 middle · 2 right. */
1097
+ button?: number;
596
1098
  /** Arbitrary imperative hook. */
597
1099
  do?: (ctx: RunContext) => void;
598
- /** Inline check: return false or throw to record a failure (run continues). */
599
- assert?: (ctx: RunContext) => boolean | undefined;
1100
+ /**
1101
+ * Inline check. A failure is `false`, a non-empty STRING (used as the
1102
+ * message), or a throw; the run continues either way.
1103
+ *
1104
+ * The string form exists because it is what people write. A harness that
1105
+ * says WHY it failed reads `(ctx) => problem ?? null` — and this API took
1106
+ * only `false`, so every one of those checks passed by not being `false`,
1107
+ * and a tower-defense harness printed `VERIFY OK` over a keep that had
1108
+ * fallen. Anything else — a number, an object — is recorded as a failure
1109
+ * naming the contract, because a check nobody can read is not a check.
1110
+ */
1111
+ assert?: (ctx: RunContext) => boolean | string | null | undefined;
600
1112
  /** Names this step in failure reports. */
601
1113
  label?: string;
602
1114
  }
@@ -660,7 +1172,7 @@ interface PlaySessionOptions {
660
1172
  }
661
1173
  interface PlaySession {
662
1174
  engine: Engine;
663
- scene: Scene;
1175
+ scene: Scene$1;
664
1176
  /** Simulated time so far, ms. */
665
1177
  readonly timeMs: number;
666
1178
  /** Advance the simulation by ~ms (whole fixed steps). */
@@ -689,4 +1201,4 @@ interface PlaySession {
689
1201
  */
690
1202
  declare function createPlaySession(json: unknown, opts?: PlaySessionOptions): Promise<PlaySession>;
691
1203
  //#endregion
692
- export { type AgreementSnapshot, type Disagreement, type DisagreementKind, type FacingOptions, type FacingReport, type FeelOptions, type FeelReport, type FramingEntry, type FramingOptions, type FramingReport, GroundingIssue, GroundingOptions, type LadderVerdict, type MultiplayOptions, type MultiplayPlayer, type MultiplayReport, NodeCapture, type Outcome, PlaySession, PlaySessionOptions, type PlaytestOptions, type PlaytestReport, type PlaytestRun, RunContext, RunFailure, RunResult, RunScriptOptions, type RungResult, type RungStatus, SceneCapture, ScriptStep, ValidateSceneOptions, ValidationResult, type Where, agreementProblems, auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestText, registerAllNodes, runScript, takeAgreementSnapshots, validateScene };
1204
+ export { type AgreementSnapshot, type CollectionSnapshot, type Disagreement, type DisagreementKind, type FacingOptions, type FacingReport, type FeelOptions, type FeelReport, type FramingEntry, type FramingOptions, type FramingReport, GroundingIssue, GroundingOptions, type LadderVerdict, type MultiplayOptions, type MultiplayPlayer, type MultiplayReport, NodeCapture, type Outcome, type PickerOptions, PlaySession, PlaySessionOptions, type PlaytestOptions, type PlaytestReport, type PlaytestRun, RunContext, RunFailure, RunResult, RunScriptOptions, type RungResult, type RungStatus, type SceneAssetUrl, SceneCapture, ScriptStep, ValidateSceneOptions, ValidationResult, type Where, agreementProblems, auditScene, captureScene, collectionProblems, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, validateScene };