incanto 0.72.0 → 0.74.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 (84) hide show
  1. package/bin/incanto-check.mjs +114 -42
  2. package/bin/incanto-verify.mjs +9 -2
  3. package/dist/2d.d.ts +29 -4
  4. package/dist/2d.js +3 -3
  5. package/dist/3d.d.ts +137 -15
  6. package/dist/3d.js +6 -6
  7. package/dist/{agent8-BqZ0_O1u.js → agent8-CHTT4unP.js} +1 -1
  8. package/dist/{audio-player-ioZ7VKC6.d.ts → audio-player-BOrk4eQx.d.ts} +1 -1
  9. package/dist/{behavior-Bf9P9oB2.d.ts → behavior-J1cmVah0.d.ts} +8 -0
  10. package/dist/{create-game-CmFAtLZp.js → create-game-BAiA-FjP.js} +7 -7
  11. package/dist/{create-game-C9rWqqAC.js → create-game-ssvGvP7W.js} +7 -7
  12. package/dist/debug.d.ts +1 -1
  13. package/dist/debug.js +1 -1
  14. package/dist/editor.js +965 -893
  15. package/dist/{environment-presets-BeHoaGhy.js → environment-presets-BkqlWewf.js} +277 -60
  16. package/dist/{gameplay-BlQe-M07.js → gameplay-Bxe1sMVT.js} +611 -39
  17. package/dist/gameplay.d.ts +141 -7
  18. package/dist/gameplay.js +2 -2
  19. package/dist/index.d.ts +6 -4
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-CBfQzB6S.d.ts → loader-BAeWMYfF.d.ts} +1 -1
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +2 -2
  24. package/dist/{physics-2d-DrIgTlLk.js → physics-2d-C-SfvjzH.js} +94 -11
  25. package/dist/{physics-3d-BzOCw1VS.js → physics-3d-rOzeg890.js} +202 -15
  26. package/dist/{picking-C9McVJrO.js → picking-BMV34Pjl.js} +2 -2
  27. package/dist/react.d.ts +1 -1
  28. package/dist/react.js +1 -1
  29. package/dist/{register-5IYfooAN.js → register-CjociOtt.js} +19 -4
  30. package/dist/{register-C8HuRkOf.js → register-DRQyZKGU.js} +45 -9
  31. package/dist/{replay-datiAE-b.js → replay-B6BbgGM_.js} +42 -6
  32. package/dist/{replay-CBEChqXq.d.ts → replay-wZK5Ok5c.d.ts} +1 -1
  33. package/dist/{save-slots-CEuJPUle.js → save-slots-CEUGeLuB.js} +11 -2
  34. package/dist/{split-screen-Bm5DTElG.js → split-screen-BxZ5rBe7.js} +4 -4
  35. package/dist/{split-screen-DkqLng6q.d.ts → split-screen-D6sx8z3Z.d.ts} +2 -2
  36. package/dist/{sprite-animation-CqR2o3SA.js → sprite-animation-Bz3DHD_4.js} +1 -1
  37. package/dist/{src-DRSsRleD.js → src-CqtvYtSN.js} +1 -1
  38. package/dist/{test-Djuq3Vhq.js → test-CEl0y2Sw.js} +162 -30
  39. package/dist/test.d.ts +56 -5
  40. package/dist/test.js +2 -2
  41. package/dist/{touch-BnCyPA0G.js → touch-CioTZB-y.js} +1 -1
  42. package/dist/vite.js +3 -3
  43. package/editor/assets/{agent8-CMKz2cax.js → agent8-BlzturGi.js} +1 -1
  44. package/editor/assets/{debug-DLrQm-az.js → debug-BMUi8usj.js} +1 -1
  45. package/editor/assets/{index-BEKuxYcE.js → index-CrUCQoaB.js} +53 -53
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +634 -10
  49. package/skills/incanto-3d-character.md +113 -5
  50. package/skills/incanto-assets.md +9 -3
  51. package/skills/incanto-behaviors-and-scripts.md +26 -2
  52. package/skills/incanto-building-2d-games.md +9 -0
  53. package/skills/incanto-building-3d-games.md +45 -0
  54. package/skills/incanto-editor.md +6 -1
  55. package/skills/incanto-environment.md +7 -2
  56. package/skills/incanto-game-feel.md +16 -11
  57. package/skills/incanto-gameplay-behaviors.md +179 -16
  58. package/skills/incanto-node-reference.md +124 -45
  59. package/skills/incanto-physics-and-input.md +67 -5
  60. package/skills/incanto-scene-json-authoring.md +9 -3
  61. package/skills/incanto-verifying-your-game.md +19 -2
  62. package/templates-app/beacon-isle-3d/PROJECT/Status.md +8 -0
  63. package/templates-app/beacon-isle-3d/PROJECT/Structure.md +3 -0
  64. package/templates-app/beacon-isle-3d/package.json +1 -1
  65. package/templates-app/beacon-isle-3d/src/game.scene.json +136 -0
  66. package/templates-app/beacon-isle-3d/verify.ts +144 -0
  67. package/templates-app/molehill-2d/package.json +1 -1
  68. package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
  69. package/templates-app/platformer-2d/PROJECT/Structure.md +3 -0
  70. package/templates-app/platformer-2d/package.json +1 -1
  71. package/templates-app/platformer-2d/src/game.scene.json +126 -0
  72. package/templates-app/platformer-2d/verify.ts +54 -0
  73. package/templates-app/star-survivor/package.json +1 -1
  74. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  75. package/templates-app/tps-3d/PROJECT/Status.md +40 -1
  76. package/templates-app/tps-3d/PROJECT/Structure.md +19 -0
  77. package/templates-app/tps-3d/package.json +1 -1
  78. package/templates-app/tps-3d/src/behaviors.ts +9 -0
  79. package/templates-app/tps-3d/src/cover.scene.json +26 -0
  80. package/templates-app/tps-3d/src/game.scene.json +331 -75
  81. package/templates-app/tps-3d/src/main.ts +14 -0
  82. package/templates-app/tps-3d/src/pillar.scene.json +25 -0
  83. package/templates-app/tps-3d/verify.ts +582 -3
  84. package/templates-app/village-quest-3d/package.json +1 -1
@@ -86,7 +86,7 @@ function collectScenes(entry) {
86
86
  * not. A WARNING, not an error: a url can legitimately be produced at runtime,
87
87
  * and refusing to load a scene over a guess would be worse than the bug.
88
88
  */
89
- function missingArt(file, json) {
89
+ async function missingArt(file, json) {
90
90
  const assets = json && typeof json === 'object' ? json.assets : null;
91
91
  const sceneDir = dirname(file);
92
92
  const root = projectRoot(sceneDir);
@@ -112,9 +112,17 @@ function missingArt(file, json) {
112
112
  url: u.url,
113
113
  })),
114
114
  ];
115
+ const remote = [];
115
116
  for (const { what, kind, url } of named) {
116
117
  if (typeof url !== 'string' || url === '') continue;
117
- // Remote and inline are not this command's business to guess about.
118
+ // A remote url is not guessed about it is ASKED. A melee arena built from
119
+ // the tarball named a model at a path that does not exist on the CDN, and
120
+ // `incanto-verify` printed "its assets resolve" over a 404 only the browser
121
+ // ever reported. Inline (`data:`) and protocol-relative urls stay unchecked.
122
+ if (/^https?:\/\//i.test(url)) {
123
+ remote.push({ what, kind, url });
124
+ continue;
125
+ }
118
126
  if (/^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith('//')) continue;
119
127
  const rel = url.replace(/^\//, '');
120
128
  const candidates = [
@@ -139,6 +147,68 @@ function missingArt(file, json) {
139
147
  `, and beside the scene). It will ${kind === 'audio' ? 'be silent' : 'draw nothing'}.`,
140
148
  );
141
149
  }
150
+ out.push(...(await missingRemoteArt(remote)));
151
+ return out;
152
+ }
153
+
154
+ /** One answer per url per run — a corpus names the same model in every scene. */
155
+ const probed = new Map();
156
+
157
+ /**
158
+ * Does the server have it? A HEAD with a short deadline; servers that refuse
159
+ * HEAD get one ranged GET. Only the answer "no" (404/410) is a warning: an
160
+ * unreachable host is a NOTE on stdout and nothing else, because a check that
161
+ * fails offline is a check people turn off.
162
+ */
163
+ async function probeUrl(url) {
164
+ if (probed.has(url)) return probed.get(url);
165
+ const ask = async (method) => {
166
+ const ctl = new AbortController();
167
+ const timer = setTimeout(() => ctl.abort(), 4000);
168
+ try {
169
+ const res = await fetch(url, {
170
+ method,
171
+ redirect: 'follow',
172
+ signal: ctl.signal,
173
+ headers: method === 'GET' ? { range: 'bytes=0-0' } : {},
174
+ });
175
+ if (method === 'GET') await res.body?.cancel?.();
176
+ return { status: res.status };
177
+ } catch (e) {
178
+ return {
179
+ error:
180
+ e?.name === 'AbortError'
181
+ ? 'timed out after 4 s'
182
+ : (e?.cause?.code ?? e?.message ?? String(e)),
183
+ };
184
+ } finally {
185
+ clearTimeout(timer);
186
+ }
187
+ };
188
+ let answer = await ask('HEAD');
189
+ if (answer.status === 405 || answer.status === 403 || answer.status === 501)
190
+ answer = await ask('GET');
191
+ probed.set(url, answer);
192
+ return answer;
193
+ }
194
+
195
+ async function missingRemoteArt(remote) {
196
+ const answers = await Promise.all(
197
+ remote.map(async (r) => ({ ...r, ...(await probeUrl(r.url)) })),
198
+ );
199
+ const out = [];
200
+ for (const { what, kind, url, status, error } of answers) {
201
+ if (status === 404 || status === 410) {
202
+ out.push(
203
+ `${what} → ${url} is not found on its server (HTTP ${status}). It will ` +
204
+ `${kind === 'audio' ? 'be silent' : 'draw nothing'}.`,
205
+ );
206
+ } else if (error !== undefined) {
207
+ console.log(
208
+ ` note: ${what} → ${url} could not be reached from here (${error}) — not checked.`,
209
+ );
210
+ }
211
+ }
142
212
  return out;
143
213
  }
144
214
 
@@ -266,49 +336,51 @@ for (const file of files) {
266
336
 
267
337
  const instanced = instancedScenes(corpus, (from, ref) => resolve(dirname(from), ref));
268
338
 
269
- const results = files.map((file) => {
270
- let json;
271
- try {
272
- json = readJson(file);
273
- } catch (e) {
274
- return { file, ok: false, code: 'BAD_JSON', message: String(e?.message ?? e) };
275
- }
276
- const res = validateScene(json, {
277
- strictBehaviors: args.strict,
278
- /*
279
- * `validateScene` has always taken `behaviors`; this bin had no way to pass
280
- * any. So `--strict-behaviors` could only ever FAIL on a project with a
281
- * behaviour of its own which is every project past its first hour:
282
- *
283
- * [UNKNOWN_BEHAVIOR] Unknown behavior 'Hunter'. … Did you forget
284
- * registerBehavior('Hunter', Hunter)?
285
- * 0/1 scene(s) valid EXIT=1
286
- *
287
- * A flag whose only outcome is failure is a flag nobody uses, and the check
288
- * it gates a script name that resolves nowhere is worth having.
289
- */
290
- behaviors,
291
- declaredAssets,
292
- resolveScene: (p) => readJson(resolve(dirname(file), p)),
293
- });
294
- if (res.ok) {
339
+ const results = await Promise.all(
340
+ files.map(async (file) => {
341
+ let json;
342
+ try {
343
+ json = readJson(file);
344
+ } catch (e) {
345
+ return { file, ok: false, code: 'BAD_JSON', message: String(e?.message ?? e) };
346
+ }
347
+ const res = validateScene(json, {
348
+ strictBehaviors: args.strict,
349
+ /*
350
+ * `validateScene` has always taken `behaviors`; this bin had no way to pass
351
+ * any. So `--strict-behaviors` could only ever FAIL on a project with a
352
+ * behaviour of its own — which is every project past its first hour:
353
+ *
354
+ * [UNKNOWN_BEHAVIOR] Unknown behavior 'Hunter'. … Did you forget
355
+ * registerBehavior('Hunter', Hunter)?
356
+ * 0/1 scene(s) valid EXIT=1
357
+ *
358
+ * A flag whose only outcome is failure is a flag nobody uses, and the check
359
+ * it gates — a script name that resolves nowhere — is worth having.
360
+ */
361
+ behaviors,
362
+ declaredAssets,
363
+ resolveScene: (p) => readJson(resolve(dirname(file), p)),
364
+ });
365
+ if (res.ok) {
366
+ return {
367
+ file,
368
+ ok: true,
369
+ warnings: [
370
+ ...auditScene(json, { declaredElsewhere, instanced: instanced.has(resolve(file)) }),
371
+ ...(await missingArt(file, json)),
372
+ ],
373
+ };
374
+ }
295
375
  return {
296
376
  file,
297
- ok: true,
298
- warnings: [
299
- ...auditScene(json, { declaredElsewhere, instanced: instanced.has(resolve(file)) }),
300
- ...missingArt(file, json),
301
- ],
377
+ ok: false,
378
+ code: res.error?.code,
379
+ message: res.error?.message,
380
+ details: res.error?.details,
302
381
  };
303
- }
304
- return {
305
- file,
306
- ok: false,
307
- code: res.error?.code,
308
- message: res.error?.message,
309
- details: res.error?.details,
310
- };
311
- });
382
+ }),
383
+ );
312
384
 
313
385
  const failed = results.filter((r) => !r.ok);
314
386
  const warned = results.reduce((n, r) => n + (r.warnings?.length ?? 0), 0);
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * bunx incanto-verify # finds your scene
6
6
  * bunx incanto-verify src/game.scene.json --behaviors src/behaviors.ts
7
+ * bunx incanto-verify src/game.scene.json --runs 16 # more seeds for the plays rung
7
8
  *
8
9
  * The rungs already existed and were used one at a time, because using them
9
10
  * together means knowing three things nobody writes down: the order, that the
@@ -31,8 +32,14 @@ const { feelsRung, firstLine, ladderText, ladderVerdict, saysRung, unmeasuredPla
31
32
 
32
33
  const argv = process.argv.slice(2);
33
34
  const asJson = argv.includes('--json');
35
+ // `--runs N` for the plays rung. Eight seeds see a one-in-eight event 64% of
36
+ // the time; a starter is where a rare fall costs the most, so the sweep asks
37
+ // the six that ship for sixteen. The default stays 8 — every example, every
38
+ // campaign, and the sweep's seven minutes are the reason.
39
+ const runsAt = argv.indexOf('--runs');
40
+ const RUNS = runsAt >= 0 && /^\d+$/.test(argv[runsAt + 1] ?? '') ? argv[runsAt + 1] : '8';
34
41
  if (argv.includes('--help') || argv.includes('-h')) {
35
- console.log(`Usage: incanto verify [scene.json] [--behaviors FILE[,FILE...]] [--json]
42
+ console.log(`Usage: incanto verify [scene.json] [--behaviors FILE[,FILE...]] [--runs N] [--json]
36
43
 
37
44
  Walks the whole ladder and tells you the ONE thing to do next:
38
45
 
@@ -223,7 +230,7 @@ let botPlayed = true;
223
230
 
224
231
  // ---- plays ---------------------------------------------------------------
225
232
  if (rungs[0].status === 'pass') {
226
- const args = [scene, '--json', '--runs', '8'];
233
+ const args = [scene, '--json', '--runs', RUNS];
227
234
  if (behaviors) args.push('--behaviors', behaviors);
228
235
  const r = run('playtest', args);
229
236
  const out = safeJson(r.stdout);
package/dist/2d.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-ioZ7VKC6.js";
2
- import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-Bf9P9oB2.js";
1
+ import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-BOrk4eQx.js";
2
+ import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-J1cmVah0.js";
3
3
  import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
4
4
  import { a as SceneJson, s as JsonObject } from "./rng-Bb-IutXB.js";
5
5
  import { t as EditorSwitchOptions } from "./editor-switch-CFU9mCec.js";
6
6
  import { r as FrameStats } from "./frame-report-DNxDAb1w.js";
7
- import { t as LoadSceneOptions } from "./loader-CBfQzB6S.js";
7
+ import { t as LoadSceneOptions } from "./loader-BAeWMYfF.js";
8
8
  import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
9
9
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
10
10
  import * as RapierNs from "@dimforge/rapier2d-compat";
@@ -159,9 +159,24 @@ declare class PhysicsBody2D extends Node2D {
159
159
  /** @internal Set by Physics2D when the body is created. */
160
160
  _physics: Physics2D | null;
161
161
  }
162
- /** Immovable collider (ground, walls, platforms). */
162
+ /**
163
+ * Immovable collider (ground, walls, platforms).
164
+ *
165
+ * It also carries the SURFACE it is: `friction` and `restitution` used to
166
+ * live on `RigidBody2D` alone, so a trampoline, an ice patch or a sticky
167
+ * landing could not be written in scene JSON, and Rapier's AVERAGE rule
168
+ * halved whatever a ball brought to a plain floor. A static body's authored
169
+ * value WINS the pair — restitution above 0 combines by Max, friction below
170
+ * the default by Min and above it by Max — so the pad decides, whatever lands
171
+ * on it. Left at the defaults, nothing changes for any existing scene.
172
+ */
163
173
  declare class StaticBody2D extends PhysicsBody2D {
164
174
  static override readonly typeName: string;
175
+ static override readonly props: PropSchema;
176
+ /** 0.5 plain · 0 ice (slides on and on) · 1+ glue. Away from 0.5 it wins the pair. */
177
+ friction: number;
178
+ /** 0 dead · 1 trampoline. Above 0 it wins the pair: a dead ball still bounces. */
179
+ restitution: number;
165
180
  }
166
181
  /**
167
182
  * Sensor volume. Emits `triggerEnter(other)` / `triggerExit(other)` — the
@@ -184,6 +199,10 @@ declare class RigidBody2D extends PhysicsBody2D {
184
199
  fixedRotation: boolean;
185
200
  friction: number;
186
201
  restitution: number;
202
+ /** Drag, per second: velocity decays by `e^(-linearDamping·t)`. `0` = none. */
203
+ linearDamping: number;
204
+ /** Rolling resistance, per second, on the spin — see `RigidBody3D.angularDamping`. */
205
+ angularDamping: number;
187
206
  /** px/s, y-down. Read back every step; write to launch. */
188
207
  linearVelocity: number[];
189
208
  /**
@@ -250,6 +269,8 @@ declare class CharacterBody2D extends PhysicsBody2D {
250
269
  * `0` restores the old behaviour.
251
270
  */
252
271
  stepHeight: number;
272
+ /** See CharacterBody3D.collideWithCharacters — off, a crowd ignores itself and costs nothing. */
273
+ collideWithCharacters: boolean;
253
274
  /** @internal Updated by Physics2D.moveAndSlide. */
254
275
  _grounded: boolean;
255
276
  moveAndSlide(): void;
@@ -359,6 +380,10 @@ declare class Physics2D {
359
380
  */
360
381
  /** Drop one node from the overlap map, both as a key and as a member. */
361
382
  private forgetOverlaps;
383
+ /** Every overlap `node` is in ends now — the exits Rapier does not send for a disabled collider. */
384
+ private releaseOverlaps;
385
+ /** Every body the armed sensor is inside of RIGHT NOW enters it — the starts Rapier does not send for a re-enabled collider. */
386
+ private armOverlaps;
362
387
  private pruneOverlaps;
363
388
  /** The bodies currently overlapping `node`. Freed nodes are dropped on read. */
364
389
  overlapping(node: PhysicsBody2D): PhysicsBody2D[];
package/dist/2d.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
2
  import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
3
3
  import { t as isWebGLAvailable } from "./webgl-unavailable-C8aDbGmR.js";
4
- import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-C9rWqqAC.js";
5
- import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-C8HuRkOf.js";
6
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-DrIgTlLk.js";
4
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-ssvGvP7W.js";
5
+ import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-DRQyZKGU.js";
6
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-C-SfvjzH.js";
7
7
  //#region src/2d/library-sprite.ts
8
8
  /**
9
9
  * What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in
package/dist/3d.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-ioZ7VKC6.js";
2
- import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-Bf9P9oB2.js";
1
+ import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-BOrk4eQx.js";
2
+ import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-J1cmVah0.js";
3
3
  import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
4
4
  import { a as SceneJson$1, s as JsonObject } from "./rng-Bb-IutXB.js";
5
5
  import { t as EditorSwitchOptions } from "./editor-switch-CFU9mCec.js";
6
6
  import { a as GridCell, c as diffText, d as frameText, i as FrameStatsOptions, l as frameSignature, n as FrameSignature, o as SIGNATURE_GRID, r as FrameStats, s as diffSignatures, t as FrameDiff, u as frameStats } from "./frame-report-DNxDAb1w.js";
7
- import { t as LoadSceneOptions } from "./loader-CBfQzB6S.js";
7
+ import { t as LoadSceneOptions } from "./loader-BAeWMYfF.js";
8
8
  import { n as PathGrid } from "./pathfinding-_fGrCFmH.js";
9
9
  import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
10
10
  import { VRM } from "@pixiv/three-vrm";
@@ -391,9 +391,24 @@ declare class PhysicsBody3D extends Node3D {
391
391
  /** @internal Set by Physics3D when the body is created. */
392
392
  _physics: Physics3D | null;
393
393
  }
394
- /** Immovable collider (ground, walls, platforms). */
394
+ /**
395
+ * Immovable collider (ground, walls, platforms).
396
+ *
397
+ * It also carries the SURFACE it is: `friction` and `restitution` used to
398
+ * live on `RigidBody3D` alone, so a trampoline, an ice patch or a sticky
399
+ * landing could not be written in scene JSON, and Rapier's AVERAGE rule
400
+ * halved whatever a ball brought to a plain floor. A static body's authored
401
+ * value WINS the pair — restitution above 0 combines by Max, friction below
402
+ * the default by Min and above it by Max — so the pad decides, whatever lands
403
+ * on it. Left at the defaults, nothing changes for any existing scene.
404
+ */
395
405
  declare class StaticBody3D extends PhysicsBody3D {
396
406
  static override readonly typeName: string;
407
+ static override readonly props: PropSchema;
408
+ /** 0.5 plain · 0 ice (slides on and on) · 1+ glue. Away from 0.5 it wins the pair. */
409
+ friction: number;
410
+ /** 0 dead · 1 trampoline. Above 0 it wins the pair: a dead ball still bounces. */
411
+ restitution: number;
397
412
  }
398
413
  /** Sensor volume emitting `triggerEnter(other)` / `triggerExit(other)`. */
399
414
  declare class Area3D extends PhysicsBody3D {
@@ -435,6 +450,17 @@ declare class RigidBody3D extends PhysicsBody3D {
435
450
  fixedRotation: boolean;
436
451
  friction: number;
437
452
  restitution: number;
453
+ /**
454
+ * Drag, per second: velocity decays by `e^(-linearDamping·t)`. `0` = none.
455
+ * A body flying through nothing keeps its speed forever otherwise.
456
+ */
457
+ linearDamping: number;
458
+ /**
459
+ * Rolling resistance, per second, on the spin. Rapier has none by itself:
460
+ * friction only converts slip into spin, so a rolling ball is a wheel and a
461
+ * golf ball rolled to the edge of the world. `1`–`3` stops a ball on a green.
462
+ */
463
+ angularDamping: number;
438
464
  /** m/s, y-up. Read back every step; write to launch. */
439
465
  linearVelocity: number[];
440
466
  /**
@@ -481,6 +507,16 @@ declare class CharacterBody3D extends PhysicsBody3D {
481
507
  /** Max climbable slope angle. */
482
508
  slopeLimitDeg: number;
483
509
  /**
510
+ * Whether OTHER characters are obstacles to this one. On (default), a
511
+ * character is a wall to every other character — Godot's answer, and what a
512
+ * few enemies want. Off, the controller ignores every other kinematic body
513
+ * and a crowd costs what the floor and the walls cost: eighty chasers around
514
+ * a player cost 64–286 ms a step with this on (each slide shape-casts
515
+ * against every neighbour, O(n²) in the crowd) and under a millisecond off.
516
+ * A survivor's horde overlaps itself and nobody minds.
517
+ */
518
+ collideWithCharacters: boolean;
519
+ /**
484
520
  * How high a step this body walks UP without jumping (0 = none).
485
521
  *
486
522
  * Rapier's character controller does not autostep unless asked, and nothing
@@ -637,7 +673,12 @@ declare class Physics3D {
637
673
  * off ONE fixed body — they never move, so there is nothing to step — and the
638
674
  * whole set is rebuilt only when the tree's shape changes.
639
675
  */
640
- /** Where each live scatter field was when its colliders were built. */
676
+ /**
677
+ * Where each live scatter field was when its colliders were built — and what
678
+ * they were built FROM. Rows and place were remembered from the start; the
679
+ * stone itself (`mesh`, `size`, the `collider` spec) was not, so a field
680
+ * grown at runtime, or resized in the inspector, collided as the old stones.
681
+ */
641
682
  private readonly scatterState;
642
683
  /**
643
684
  * Has any scatter field moved, or had its rows replaced, since the build?
@@ -650,6 +691,24 @@ declare class Physics3D {
650
691
  private syncScatters;
651
692
  private syncBodies;
652
693
  /**
694
+ * The stamp an auto body is compared against, reused across every body and
695
+ * step: the walk writes here instead of returning an object, because it runs
696
+ * once per auto body per step and a village of crates must not allocate.
697
+ */
698
+ private readonly stamp;
699
+ /** An auto body whose carried mesh (or mounted model) is not what it was built from. */
700
+ private autoReshaped;
701
+ private rememberAutoShape;
702
+ /**
703
+ * The same choice `meshesUnder` makes — the FIRST carried mesh in tree
704
+ * order, else the first mounted model — reduced to the numbers the collider
705
+ * was built from, so a change in any of them is visible without a rebuild.
706
+ */
707
+ private stampAutoShape;
708
+ private walkAutoShape;
709
+ /** A heightfield body whose Terrain3D regenerated its grid since the build. */
710
+ private heightfieldReshaped;
711
+ /**
653
712
  * Null while a `{"shape":"auto"}` body is waiting on its model — the shape
654
713
  * lives in the model's triangles, and there is nothing honest to guess in the
655
714
  * meantime. syncBodies runs every step, so the body appears the moment the
@@ -676,6 +735,10 @@ declare class Physics3D {
676
735
  */
677
736
  /** Drop one node from the overlap map, both as a key and as a member. */
678
737
  private forgetOverlaps;
738
+ /** Every overlap `node` is in ends now — the exits Rapier does not send for a disabled collider. */
739
+ private releaseOverlaps;
740
+ /** Every body the armed sensor is inside of RIGHT NOW enters it — the starts Rapier does not send for a re-enabled collider. */
741
+ private armOverlaps;
679
742
  private pruneOverlaps;
680
743
  /**
681
744
  * The bodies currently overlapping `node` — the standing answer behind
@@ -1424,12 +1487,8 @@ declare class Camera3D extends Node3D {
1424
1487
  current: boolean;
1425
1488
  lookAt: string;
1426
1489
  /**
1427
- * Aim, every frame, whenever `lookAt` names something.
1428
- *
1429
- * In `update` and not `_syncObject3D`: the second is the renderer's copy pass
1430
- * and a static node is pruned from it, which silently freezes anything driven
1431
- * there. `rotation` is written as an ordinary prop, so the editor, the
1432
- * serializer and `describeFraming` all see what the camera is actually doing.
1490
+ * Aim, every frame, whenever `lookAt` names something — see `aimAt`, which
1491
+ * the spot light shares.
1433
1492
  */
1434
1493
  /** The `lookAt` path already reported as unlookable — cleared by a new one. */
1435
1494
  private warnedLookAt;
@@ -1470,6 +1529,17 @@ declare class CharacterController3D extends Node3D {
1470
1529
  jumpCutMultiplier: number;
1471
1530
  maxJumps: number;
1472
1531
  airControl: number;
1532
+ /**
1533
+ * `[away, up]` m/s: airborne and pressing into a wall, `jump` kicks off it —
1534
+ * away along the wall's normal, up. `[0, 0]` (default) = no wall-jumps.
1535
+ * The controller owns this because a kick written from outside lasted three
1536
+ * frames: the jump cut halved its rise on release and air control steered
1537
+ * the horizontal straight back into the wall. After a kick, air control
1538
+ * stands down for `WALL_LOCKOUT` so the kick lands.
1539
+ */
1540
+ wallJumpImpulse: number[];
1541
+ /** Max fall speed (m/s) while pressing into a wall; 0 = no wall slide. */
1542
+ wallSlideSpeed: number;
1473
1543
  fallGravity: number;
1474
1544
  floatHeight: number;
1475
1545
  /**
@@ -1568,6 +1638,10 @@ declare class CharacterController3D extends Node3D {
1568
1638
  private rising;
1569
1639
  /** Between the jump impulse and the landing — the animation's clock. */
1570
1640
  private jumping;
1641
+ /** Seconds of air control still suppressed after a wall kick. */
1642
+ private wallLockout;
1643
+ /** The outward normal of the wall being pressed into this step, if any. */
1644
+ private wallNormal;
1571
1645
  private get body();
1572
1646
  override onEnterTree(): void;
1573
1647
  override fixedUpdate(dt: number): void;
@@ -2169,6 +2243,44 @@ declare class OmniLight3D extends Node3D {
2169
2243
  protected override _createObject3D(): Object3D;
2170
2244
  override _syncObject3D(): void;
2171
2245
  }
2246
+ /**
2247
+ * A cone of light — a flashlight, a searchlight, a car's headlights, a stage
2248
+ * spot, a guard's torch.
2249
+ *
2250
+ * It AIMS down the node's local −Z, exactly like a camera: at rest it points
2251
+ * along −Z, `rotation: [-90, 0, 0]` points it at the floor, and a headlamp
2252
+ * mounted on a +Z-forward skin wants `rotation: [0, 180, 0]`. `coneDeg` is the
2253
+ * FULL angle of the cone (a "30° beam" is 30), converted to three's half-angle
2254
+ * here so nobody has to halve it by hand. `intensity` is in the same physical
2255
+ * units as `OmniLight3D` — tens, not ones. Shadows are a perspective map from
2256
+ * the cone's apex, so `castShadow` costs one extra render per frame per light.
2257
+ */
2258
+ declare class SpotLight3D extends Node3D {
2259
+ static override readonly typeName: string;
2260
+ static override readonly props: PropSchema;
2261
+ color: string;
2262
+ intensity: number;
2263
+ /** 0 = unlimited (three `distance` semantics). */
2264
+ range: number;
2265
+ /** Full cone angle in degrees, exclusive of 0 and 180. */
2266
+ coneDeg: number;
2267
+ /** 0 = hard edge, 1 = the whole cone is falloff. */
2268
+ penumbra: number;
2269
+ castShadow: boolean;
2270
+ shadowMapSize: number;
2271
+ /**
2272
+ * A node path to AIM at, every frame — the camera's prop, for the same
2273
+ * reason: a searchlight that tracks the player, a stage spot on the boss.
2274
+ * Aiming by hand is Euler arithmetic in XYZ order, where a yaw on the same
2275
+ * node as a pitch swings the cone sideways (at yaw 180 a −55° pitch points
2276
+ * UP). Empty = the authored `rotation` is the aim.
2277
+ */
2278
+ lookAt: string;
2279
+ private warnedLookAt;
2280
+ override update(_dt: number): void;
2281
+ protected override _createObject3D(): Object3D;
2282
+ override _syncObject3D(): void;
2283
+ }
2172
2284
  //#endregion
2173
2285
  //#region src/3d/nodes/loft-mesh-3d.d.ts
2174
2286
  /**
@@ -3395,9 +3507,19 @@ declare class VoxelGrid3D extends Node3D {
3395
3507
  * hard-fails at load; this one silently truncated.
3396
3508
  */
3397
3509
  static validateJson(node: Node): void;
3398
- /** JSON-authored blocks as [x, y, z, tile] tuples (generated terrains bake these). */
3399
- voxels: number[][];
3400
- private appliedVoxels;
3510
+ private _voxels;
3511
+ /**
3512
+ * JSON-authored blocks as [x, y, z, tile] tuples (generated terrains bake
3513
+ * these). The seed lands WHEN THE PROP DOES — at load, before the tree
3514
+ * readies — not on the first `update()` as it used to: a sibling that
3515
+ * builds chunk colliders from `blocks()` at ready (the minecraft template's
3516
+ * Builder) saw an empty map and a baked hut was walked through, and the
3517
+ * late `setBlocks` then WIPED whatever a generator had built at ready.
3518
+ * Now a generator calling `setBlocks` after load is the author replacing
3519
+ * the seed, never the seed replacing the author.
3520
+ */
3521
+ get voxels(): number[][];
3522
+ set voxels(rows: number[][]);
3401
3523
  /** Said once per node: a runtime overflow must not spam a frame loop. */
3402
3524
  private reportedOverflow;
3403
3525
  private readonly map;
@@ -4278,4 +4400,4 @@ declare function createNavDebugNode(nav: TerrainNav, opts?: {
4278
4400
  name?: string;
4279
4401
  }): InstancedMesh3D;
4280
4402
  //#endregion
4281
- export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type FrameDiff, type FrameSignature, type FrameStats, type FrameStatsOptions, type Game3D, type GridCell, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, type ModelFacts, ModelInstance3D, type ModelVerdict, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type Rig, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, SIGNATURE_GRID, type SceneJson, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
4403
+ export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type FrameDiff, type FrameSignature, type FrameStats, type FrameStatsOptions, type Game3D, type GridCell, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, type ModelFacts, ModelInstance3D, type ModelVerdict, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type Rig, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, SIGNATURE_GRID, type SceneJson, type ShadowsEnvironment, type SkyEnvironment, SpotLight3D, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
package/dist/3d.js CHANGED
@@ -1,12 +1,12 @@
1
- import { C as sunDirectionFromSky, S as sunDirectionFromElevationAzimuth, b as horizonColorFromSky, x as parseEnvironment3D } from "./save-slots-CEuJPUle.js";
1
+ import { C as sunDirectionFromSky, S as sunDirectionFromElevationAzimuth, b as horizonColorFromSky, x as parseEnvironment3D } from "./save-slots-CEUGeLuB.js";
2
2
  import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
3
3
  import { t as isWebGLAvailable } from "./webgl-unavailable-C8aDbGmR.js";
4
4
  import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-D-_7YF2G.js";
5
- import { G as StaticBody3D, H as CharacterBody3D, K as Node3D, L as Water3D, R as WATER_CUTOUT_MAX, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, Y as WATER_MAX_RIPPLES, z as WaterCutout3D } from "./gameplay-BlQe-M07.js";
6
- import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-BeHoaGhy.js";
7
- import { a as Environment3D, c as AssetStore3D, i as syncTree, o as applyQualityTier3D, r as Renderer3D, s as setEnvironment3D, t as createGame3D } from "./create-game-CmFAtLZp.js";
5
+ import { B as WATER_CUTOUT_MAX, G as PhysicsBody3D, J as Node3D, K as RigidBody3D, U as Area3D, V as WaterCutout3D, W as CharacterBody3D, Z as WATER_MAX_RIPPLES, q as StaticBody3D, z as Water3D } from "./gameplay-Bxe1sMVT.js";
6
+ import { A as Billboard3D, B as cameraRelative, C as Flowers3D, D as Camera3D, E as CharacterController3D, F as Joint3D, G as acquireOwnTexture, H as movementState, I as InstancedMesh3D, K as acquireTexture, L as MeshInstance3D, M as DEFAULT_TERRAIN_TEXTURE_BASE, N as TERRAIN_THEMES, O as BoneLookAt3D, P as terrainThemeLayers, S as DENSITY_PRESETS, T as FLOWER_VARIETIES, U as rigPose, V as keyboardIntensity, W as TextureCache3D, _ as LoftMesh3D, a as Tree3D, b as SpotLight3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as Terrain3D, k as BoneAttachment3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as resolveFlowerDensity, x as Foliage3D, y as OmniLight3D, z as QUARTER_PITCH } from "./environment-presets-BkqlWewf.js";
7
+ import { a as Environment3D, c as AssetStore3D, i as syncTree, o as applyQualityTier3D, r as Renderer3D, s as setEnvironment3D, t as createGame3D } from "./create-game-BAiA-FjP.js";
8
8
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-BYgD5Edk.js";
9
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-BzOCw1VS.js";
9
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-rOzeg890.js";
10
10
  //#region src/3d/model-verdict.ts
11
11
  /** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
12
12
  const MIXAMO = /^mixamorig[:_]?/i;
@@ -319,4 +319,4 @@ function createNavDebugNode(nav, opts) {
319
319
  return node;
320
320
  }
321
321
  //#endregion
322
- export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
322
+ export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, SpotLight3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
@@ -1,5 +1,5 @@
1
1
  import { o as __exportAll } from "./json-CfTjpvW8.js";
2
- import { Y as diagnose } from "./save-slots-CEuJPUle.js";
2
+ import { Y as diagnose } from "./save-slots-CEUGeLuB.js";
3
3
  //#region src/net/agent8.ts
4
4
  var agent8_exports = /* @__PURE__ */ __exportAll({
5
5
  Agent8TransportForTest: () => Agent8Transport,
@@ -1,4 +1,4 @@
1
- import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-Bf9P9oB2.js";
1
+ import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-J1cmVah0.js";
2
2
  import { t as Rng } from "./rng-Bb-IutXB.js";
3
3
 
4
4
  //#region src/core/boot-failure.d.ts
@@ -1103,6 +1103,12 @@ declare class EffectLog {
1103
1103
  recent(limit?: number): EffectEvent[];
1104
1104
  /** How many times an effect with this name fired (`explosion`, `#ff0000`). */
1105
1105
  countOf(name: string): number;
1106
+ /**
1107
+ * How many effects of this KIND fired (`shake`, `hitstop`, `burst`, `sfx`).
1108
+ * A shake has no name, so `countOf('shake')` answered 0 for a boss whose
1109
+ * every slam shook the camera — the harness that asked reached for the kind.
1110
+ */
1111
+ countKind(kind: string): number;
1106
1112
  /** How many effects this node fired — the question a wiring check has. */
1107
1113
  countFrom(path: string): number;
1108
1114
  /** Forget what has fired so far (between scenes, or between assertions). */
@@ -2949,6 +2955,8 @@ declare abstract class Behavior {
2949
2955
  */
2950
2956
  get physics(): PhysicsQuery | null;
2951
2957
  getNode(path: string): Node;
2958
+ /** The node's `getNodeOrNull`, for the same reason `getNode` is here. */
2959
+ getNodeOrNull(path: string): Node | null;
2952
2960
  emit(signal: string, ...args: unknown[]): void;
2953
2961
  on(signal: string, fn: SignalListener<unknown[]>, opts?: {
2954
2962
  once?: boolean;