incanto 0.55.0 → 0.57.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 (61) hide show
  1. package/bin/incanto-verify.mjs +9 -2
  2. package/dist/2d.d.ts +59 -7
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +34 -4
  5. package/dist/3d.js +5 -5
  6. package/dist/{behavior-CyQoSu4n.d.ts → behavior-l08AEbq9.d.ts} +5 -0
  7. package/dist/{create-game-CzK9_pzg.js → create-game-C5jQYPah.js} +28 -71
  8. package/dist/{create-game-BRt6XKmP.js → create-game-DpbUrMOQ.js} +146 -7
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-MNLMAcbz.js → duplicate-BPLZDZpd.js} +1 -1
  11. package/dist/{environment-presets-DAbEdEwh.js → environment-presets-CvvQr_bJ.js} +118 -24
  12. package/dist/{frame-report-njybhZon.js → frame-report-BSMny7oe.js} +1 -1
  13. package/dist/{frame-report-DZ70IY26.d.ts → frame-report-DCnHFmto.d.ts} +1 -1
  14. package/dist/{gameplay-CZ2yq37J.js → gameplay-BVphcxmE.js} +163 -14
  15. package/dist/gameplay.d.ts +81 -2
  16. package/dist/gameplay.js +2 -2
  17. package/dist/index.d.ts +45 -5
  18. package/dist/index.js +8 -9
  19. package/dist/{loader-DhI1jFW_.d.ts → loader-BbEMTuWg.d.ts} +1 -1
  20. package/dist/{loader-BTkHYrQn.js → loader-BcrRSjxB.js} +682 -682
  21. package/dist/net.d.ts +2 -2
  22. package/dist/net.js +1 -1
  23. package/dist/{pathfinding-CXGCpRQe.d.ts → pathfinding-mEN4V1CU.d.ts} +1 -1
  24. package/dist/{physics-2d-CfWAggJ1.js → physics-2d-BLcvEFDR.js} +2 -2
  25. package/dist/{physics-3d-C_ZJ6f_d.js → physics-3d-QBrfIT2Y.js} +3 -3
  26. package/dist/react.d.ts +1 -1
  27. package/dist/react.js +1 -1
  28. package/dist/{register-en63AEZO.js → register-C6ZBFRjd.js} +63 -60
  29. package/dist/{register-p48lHE2o.js → register-Ch70uByv.js} +1615 -1502
  30. package/dist/{replay-ePMz26jw.d.ts → replay-Dw6gMlYA.d.ts} +1 -1
  31. package/dist/{replay-t1pP0gQg.js → replay-s7I2GstT.js} +31 -11
  32. package/dist/sheet-grid-BT6N_Bjs.js +59 -0
  33. package/dist/{split-screen-BsdOHbzP.d.ts → split-screen-B0baBwxI.d.ts} +5 -1
  34. package/dist/{split-screen-CSb_uZ6W.js → split-screen-DLsUrleX.js} +14 -3
  35. package/dist/{sprite-animation-7qvUxF6Z.js → sprite-animation-C0wXLBZJ.js} +8 -4
  36. package/dist/{src-BVOVHRL0.js → src-CGjmPw65.js} +55 -2
  37. package/dist/{teardown-C7uVSJvx.js → teardown-Cs113S9F.js} +74 -2
  38. package/dist/{test-Ca5eqELC.js → test-it1VekWs.js} +28 -19
  39. package/dist/test.d.ts +4 -4
  40. package/dist/test.js +2 -2
  41. package/dist/{touch-BnMyy9tr.js → touch-DESwnpOc.js} +173 -1
  42. package/dist/vite.d.ts +1 -1
  43. package/dist/vite.js +3 -3
  44. package/editor/assets/{agent8-Cz4oHPtm.js → agent8-CGT7r3Mb.js} +1 -1
  45. package/editor/assets/{debug-r-Fi9nZh.js → debug-BxWSIHG3.js} +1 -1
  46. package/editor/assets/{index-BfqmvXYD.js → index-CV1m-aX5.js} +62 -62
  47. package/editor/index.html +1 -1
  48. package/package.json +1 -1
  49. package/skills/incanto-assets.md +5 -1
  50. package/skills/incanto-audio.md +7 -1
  51. package/skills/incanto-building-3d-games.md +18 -5
  52. package/skills/incanto-gameplay-behaviors.md +21 -1
  53. package/skills/incanto-hud.md +6 -0
  54. package/skills/incanto-performance.md +5 -0
  55. package/skills/incanto-physics-and-input.md +14 -0
  56. package/skills/incanto-save-slots.md +2 -1
  57. package/skills/incanto-verifying-your-game.md +4 -0
  58. package/templates-app/beacon-isle-3d/package.json +1 -1
  59. package/templates-app/tps-3d/package.json +1 -1
  60. package/templates-app/village-quest-3d/package.json +1 -1
  61. package/dist/log-report-CPFm4OXf.js +0 -173
@@ -1,4 +1,4 @@
1
- import { b as Engine } from "./behavior-CyQoSu4n.js";
1
+ import { b as Engine } from "./behavior-l08AEbq9.js";
2
2
  import { s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/audit.d.ts
@@ -1,6 +1,6 @@
1
- import { E as behaviorSchema, w as parseNodePath } from "./loader-BTkHYrQn.js";
2
- import { P as translationKey } from "./register-p48lHE2o.js";
3
- import { i as getNodeSchema } from "./registry-C7u42TID.js";
1
+ import { E as behaviorSchema, w as parseNodePath } from "./loader-BcrRSjxB.js";
2
+ import { D as HudWidgetBase, F as translationKey } from "./register-Ch70uByv.js";
3
+ import { i as getNodeSchema, o as getNodeType } from "./registry-C7u42TID.js";
4
4
  //#region src/core/node-refs.ts
5
5
  /**
6
6
  * Node-path PROPS, resolved against scene JSON.
@@ -211,13 +211,33 @@ const BODY_TYPES = new Set([
211
211
  "Area3D",
212
212
  "CharacterBody3D"
213
213
  ]);
214
- const HUD_WIDGETS = new Set([
215
- "UiText",
216
- "UiBar",
217
- "UiBanner",
218
- "UiButton",
219
- "UiDialogue"
220
- ]);
214
+ /**
215
+ * Is this node type a HUD widget — one that renders NOTHING outside a HudLayer?
216
+ *
217
+ * This used to be a hand-written list of five, and the kit has sixteen. It went
218
+ * stale the moment UiPanel landed, so the settings menu in the performance
219
+ * skill and the volume menu in the audio skill — both bare `UiPanel`s of
220
+ * uncovered widgets — drew a blank screen while `incanto-check` printed `ok`.
221
+ * The runtime is silent by design (`hud.ts`: `if (!host) return;`), which is
222
+ * exactly why the lint has to be right.
223
+ *
224
+ * Derived from the class hierarchy instead: anything descending from
225
+ * `HudWidgetBase` is one, whatever it is called and whenever it was added.
226
+ */
227
+ function isHudWidget(type) {
228
+ if (type.startsWith("Ui")) return true;
229
+ let ctor;
230
+ try {
231
+ ctor = getNodeType(type);
232
+ } catch {
233
+ return false;
234
+ }
235
+ for (let c = ctor; c; c = Object.getPrototypeOf(c)) {
236
+ if (c.typeName === "HudWidgetBase") return true;
237
+ if (c === HudWidgetBase) return true;
238
+ }
239
+ return false;
240
+ }
221
241
  const LIGHT_TYPES = new Set([
222
242
  "DirectionalLight3D",
223
243
  "OmniLight3D",
@@ -269,7 +289,7 @@ function auditScene(scene) {
269
289
  const collider = props.collider;
270
290
  if (!collider || collider.shape === void 0) warnings.push(`${path}: ${type} has no collider — physics will skip it (add "collider": { "shape": ... }).`);
271
291
  }
272
- if (HUD_WIDGETS.has(type) && !insideHud) warnings.push(`${path}: ${type} works only as a descendant of a HudLayer — it will not render.`);
292
+ if (isHudWidget(type) && !insideHud) warnings.push(`${path}: ${type} works only as a descendant of a HudLayer — it will not render.`);
273
293
  if (staticRoot && ANIMATED_TYPES.has(type)) warnings.push(`${path}: ${type} is frozen inside static subtree '${staticRoot}' — it will not animate/update on screen.`);
274
294
  const nextStatic = staticRoot ?? (props.static === true ? node.name ?? path : null);
275
295
  const nextHud = insideHud || type === "HudLayer";
@@ -0,0 +1,59 @@
1
+ //#region src/core/sheet-grid.ts
2
+ /**
3
+ * Does this spritesheet's grid actually fit the image?
4
+ *
5
+ * `frameWidth`/`frameHeight` are taken on trust, and the frame math floors the
6
+ * division — so a frame size that does not divide the sheet slices every row a
7
+ * little further off, and an animation naming a frame past the end of the grid
8
+ * reads whatever texels are there. Both draw WRONG ART with no error anywhere,
9
+ * which is the one symptom an author cannot tell apart from having picked the
10
+ * wrong sheet in the first place.
11
+ *
12
+ * Pure arithmetic on numbers that are known the moment the image loads, so it
13
+ * costs nothing and it is exact.
14
+ *
15
+ * It lives in core because THREE nodes do this division — AnimatedSprite2D,
16
+ * AnimatedSprite3D and TileMap2D — and for a while the checker sat in `2d/`
17
+ * where only one of them could see it. A checker one directory away from the
18
+ * bug it catches is the same as no checker.
19
+ */
20
+ /**
21
+ * Frame sizes that divide `total` cleanly, biggest first.
22
+ *
23
+ * Bounded to grids of 2–32 cells so the suggestion is a frame size someone
24
+ * might actually have meant, not every factor of the number.
25
+ */
26
+ function divisors(total) {
27
+ const out = [];
28
+ for (let cells = 2; cells <= 32; cells++) if (total % cells === 0) out.push(total / cells);
29
+ return out.sort((a, b) => b - a).slice(0, 6);
30
+ }
31
+ /**
32
+ * The problem with this grid, or null when there is none.
33
+ *
34
+ * `highestFrame` is the largest frame index the scene asks for, when it is
35
+ * known — an animation range that runs off the end is the other half of the
36
+ * same mistake.
37
+ */
38
+ function checkSheetGrid(key, frameWidth, frameHeight, imageWidth, imageHeight, highestFrame) {
39
+ if (!imageWidth || !imageHeight || !frameWidth || !frameHeight) return null;
40
+ if (imageWidth % frameWidth !== 0 || imageHeight % frameHeight !== 0) {
41
+ const parts = [];
42
+ if (imageWidth % frameWidth !== 0) {
43
+ const fits = divisors(imageWidth);
44
+ parts.push(`${imageWidth} px wide is not a whole number of ${frameWidth} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
45
+ }
46
+ if (imageHeight % frameHeight !== 0) {
47
+ const fits = divisors(imageHeight);
48
+ parts.push(`${imageHeight} px tall is not a whole number of ${frameHeight} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
49
+ }
50
+ return `[incanto] spritesheet '${key}' does not fit its frame size: ${parts.join("; ")}. Every row after the first is cut off centre.`;
51
+ }
52
+ if (highestFrame !== void 0) {
53
+ const frames = imageWidth / frameWidth * (imageHeight / frameHeight);
54
+ if (highestFrame >= frames) return `[incanto] spritesheet '${key}' has ${frames} frames (0–${frames - 1}), and an animation asks for frame ${highestFrame}. Those frames draw whatever is at the wrong end of the sheet.`;
55
+ }
56
+ return null;
57
+ }
58
+ //#endregion
59
+ export { checkSheetGrid as t };
@@ -1,4 +1,4 @@
1
- import { At as Node, It as Signal, b as Engine } from "./behavior-CyQoSu4n.js";
1
+ import { At as Node, It as Signal, b as Engine } from "./behavior-l08AEbq9.js";
2
2
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/net/types.d.ts
@@ -317,6 +317,10 @@ declare class NetworkManager {
317
317
  private readonly latestGlobalCollections;
318
318
  private readonly scenes;
319
319
  private readonly lastSent;
320
+ /** Sync keys already reported unreadable, so a per-frame send says it once. */
321
+ private readonly reportedDeadKeys;
322
+ /** Name a sync key the owner cannot read — a typo in the prop or the path. */
323
+ private reportDeadSyncKey;
320
324
  private sendAccumulator;
321
325
  private detachReplication;
322
326
  private lastOwner;
@@ -1,5 +1,5 @@
1
- import { C as diagnose, M as Signal, f as Node, n as loadScene, t as buildNodeJson } from "./loader-BTkHYrQn.js";
2
- import { t as registerCoreNodes, x as Engine } from "./register-p48lHE2o.js";
1
+ import { C as diagnose, M as Signal, S as Node, n as loadScene, t as buildNodeJson } from "./loader-BcrRSjxB.js";
2
+ import { _ as Engine, t as registerCoreNodes } from "./register-Ch70uByv.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { l as registerNode } from "./registry-C7u42TID.js";
@@ -811,6 +811,14 @@ var NetworkManager = class NetworkManager {
811
811
  latestGlobalCollections = /* @__PURE__ */ new Map();
812
812
  scenes = /* @__PURE__ */ new Map();
813
813
  lastSent = /* @__PURE__ */ new Map();
814
+ /** Sync keys already reported unreadable, so a per-frame send says it once. */
815
+ reportedDeadKeys = /* @__PURE__ */ new Set();
816
+ /** Name a sync key the owner cannot read — a typo in the prop or the path. */
817
+ reportDeadSyncKey(owner, key) {
818
+ if (this.reportedDeadKeys.has(key)) return;
819
+ this.reportedDeadKeys.add(key);
820
+ diagnose(owner.tree?.engine ?? null, "error", `[incanto] ${owner.getPath()}: sync key '${key}' reads as undefined — it is never sent, so remote players never see it change. Check the prop name and the node path.`);
821
+ }
814
822
  sendAccumulator = 0;
815
823
  detachReplication = null;
816
824
  lastOwner = null;
@@ -1000,7 +1008,10 @@ var NetworkManager = class NetworkManager {
1000
1008
  const patch = {};
1001
1009
  for (const key of syncKeys) {
1002
1010
  const value = readSyncKey(owner, key);
1003
- if (value === void 0) continue;
1011
+ if (value === void 0) {
1012
+ this.reportDeadSyncKey(owner, key);
1013
+ continue;
1014
+ }
1004
1015
  if (!jsonEquals(this.lastSent.get(key) ?? null, value)) patch[key] = jsonClone(value);
1005
1016
  }
1006
1017
  if (this.sendAccumulator < window) return;
@@ -306,10 +306,14 @@ function resolveAnimation(animations, name, owner) {
306
306
  const via = seen.length > 0 ? ` (via ${seen.join(" → ")})` : "";
307
307
  throw new IncantoError("UNKNOWN_ANIMATION", `No animation '${key}' on '${owner}'${via}. Available: [${Object.keys(animations).join(", ")}].`);
308
308
  }
309
- if (typeof entry !== "string") return {
310
- name: key,
311
- def: entry
312
- };
309
+ if (typeof entry !== "string") {
310
+ const frames = resolveFrames(entry.frames, key);
311
+ if (frames.length > 1 && !(Number.isFinite(entry.fps) && entry.fps > 0)) throw new IncantoError("BAD_FORMAT", `Animation '${key}' on '${owner}' has ${frames.length} frames and no "fps" — it would sit on the first one forever. Add "fps" (the field is fps, not frameRate or speed).`);
312
+ return {
313
+ name: key,
314
+ def: entry
315
+ };
316
+ }
313
317
  if (seen.includes(key)) throw new IncantoError("UNKNOWN_ANIMATION", `Animation aliases on '${owner}' point in a circle: ${[...seen, key].join(" → ")}.`);
314
318
  seen.push(key);
315
319
  key = entry;
@@ -143,6 +143,59 @@ async function preloadUrls(urls, onProgress) {
143
143
  function assetUrls(assets) {
144
144
  return Object.values(assets ?? {}).map((a) => a.url).filter((u) => typeof u === "string" && !u.startsWith("data:"));
145
145
  }
146
+ /**
147
+ * The standard boot screen every template hand-rolled (~125 identical lines
148
+ * of HTML+CSS): black overlay, "Loading" title, progress track + percent.
149
+ * Headless it returns inert no-ops.
150
+ */
151
+ function createLoadingOverlay(title = "Loading") {
152
+ if (typeof document === "undefined") return {
153
+ setProgress: () => {},
154
+ remove: () => {}
155
+ };
156
+ const wrap = document.createElement("div");
157
+ wrap.style.cssText = "position:fixed;inset:0;z-index:10000;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000;color:#fff;font:600 15px system-ui, -apple-system, 'Segoe UI', sans-serif;transition:opacity .3s;";
158
+ const h = document.createElement("div");
159
+ h.textContent = title;
160
+ h.style.cssText = "font-size:1.5rem;font-weight:700;margin-bottom:1rem;";
161
+ const track = document.createElement("div");
162
+ track.style.cssText = "width:256px;height:8px;background:#374151;border-radius:9999px;overflow:hidden;";
163
+ const fill = document.createElement("div");
164
+ fill.style.cssText = "width:0%;height:100%;background:#3b82f6;transition:width .3s;";
165
+ track.appendChild(fill);
166
+ const pct = document.createElement("div");
167
+ pct.textContent = "0%";
168
+ pct.style.cssText = "margin-top:.5rem;font-size:.9rem;";
169
+ wrap.append(h, track, pct);
170
+ document.body.appendChild(wrap);
171
+ let removed = false;
172
+ return {
173
+ setProgress(done, total) {
174
+ const p = total > 0 ? Math.min(100, Math.round(done / total * 100)) : 100;
175
+ fill.style.width = `${p}%`;
176
+ pct.textContent = `${p}%`;
177
+ },
178
+ remove() {
179
+ if (removed) return;
180
+ removed = true;
181
+ wrap.style.opacity = "0";
182
+ setTimeout(() => wrap.remove(), 320);
183
+ }
184
+ };
185
+ }
186
+ /**
187
+ * Preload every scene-asset URL behind the standard overlay, then remove it:
188
+ *
189
+ * await preloadSceneAssets(sceneJson.assets);
190
+ */
191
+ async function preloadSceneAssets(assets, title) {
192
+ const overlay = createLoadingOverlay(title);
193
+ try {
194
+ await preloadUrls(assetUrls(assets), (done, total) => overlay.setProgress(done, total));
195
+ } finally {
196
+ overlay.remove();
197
+ }
198
+ }
146
199
  //#endregion
147
200
  //#region src/core/uid-gen.ts
148
201
  /**
@@ -161,6 +214,6 @@ function newUid() {
161
214
  //#endregion
162
215
  //#region src/index.ts
163
216
  /** Engine version. Kept in sync with package.json by the release pipeline. */
164
- const VERSION = "0.55.0";
217
+ const VERSION = "0.57.0";
165
218
  //#endregion
166
- export { findPath as a, preloadUrls as i, newUid as n, gridFromRows as o, assetUrls as r, VERSION as t };
219
+ export { preloadUrls as a, preloadSceneAssets as i, newUid as n, findPath as o, assetUrls as r, gridFromRows as s, VERSION as t };
@@ -1,4 +1,6 @@
1
- import { b as AudioPlayer } from "./register-p48lHE2o.js";
1
+ import { g as AudioPlayer } from "./register-Ch70uByv.js";
2
+ import { a as logReport, s as parseDrive } from "./touch-DESwnpOc.js";
3
+ import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-BSMny7oe.js";
2
4
  //#region src/core/audio-errors.ts
3
5
  /** Every AudioPlayer under `root` whose source could not be played. */
4
6
  function audioErrors(root) {
@@ -27,6 +29,76 @@ function claimCanvasGestures(canvas) {
27
29
  style.webkitTapHighlightColor = "transparent";
28
30
  }
29
31
  //#endregion
32
+ //#region src/core/dev-channel.ts
33
+ /**
34
+ * Answer the dev server's frame/log requests. Returns the unsubscribes, or an
35
+ * empty list outside a dev server.
36
+ */
37
+ function wireDevChannel(opts) {
38
+ const hot = import.meta.hot;
39
+ const cleanups = [];
40
+ if (!hot) return cleanups;
41
+ const onFrameRequest = (data) => {
42
+ const request = data;
43
+ const id = request?.id;
44
+ hot.send("incanto:frame-ack", { id });
45
+ const drive = request?.drive ? parseDrive(request.drive) : null;
46
+ if (drive?.error) {
47
+ hot.send("incanto:frame-report", {
48
+ id,
49
+ report: { error: drive.error }
50
+ });
51
+ return;
52
+ }
53
+ let droveFrames = null;
54
+ (drive ? opts.applyDrive(drive.steps) : Promise.resolve(null)).then((frames) => {
55
+ droveFrames = frames;
56
+ return opts.captureFramePixels();
57
+ }).then((shot) => hot.send("incanto:frame-report", {
58
+ id,
59
+ report: {
60
+ ...frameStats(shot.pixels, shot.width, shot.height, { grid: request?.grid }),
61
+ signature: frameSignature(shot.pixels, shot.width, shot.height),
62
+ ...droveFrames === null ? {} : { droveFrames },
63
+ ...request?.image ? { image: frameImage(shot.pixels, shot.width, shot.height, (w, h) => Object.assign(document.createElement("canvas"), {
64
+ width: w,
65
+ height: h
66
+ }), request.image) } : {}
67
+ }
68
+ })).catch((error) => hot.send("incanto:frame-report", {
69
+ id,
70
+ report: { error: error instanceof Error ? error.message : String(error) }
71
+ }));
72
+ };
73
+ hot.on("incanto:frame-request", onFrameRequest);
74
+ cleanups.push(() => hot.off?.("incanto:frame-request", onFrameRequest));
75
+ const onLogsRequest = (data) => {
76
+ const id = data?.id;
77
+ try {
78
+ hot.send("incanto:logs-report", {
79
+ id,
80
+ report: logReport({
81
+ entries: opts.engine.log.entries(),
82
+ stats: {
83
+ ...opts.engine.stats(),
84
+ ...opts.rendererStats?.() ?? {}
85
+ },
86
+ assetErrors: opts.assetErrors?.() ?? [],
87
+ hidden: typeof document !== "undefined" && document.hidden
88
+ })
89
+ });
90
+ } catch (error) {
91
+ hot.send("incanto:logs-report", {
92
+ id,
93
+ report: { error: error instanceof Error ? error.message : String(error) }
94
+ });
95
+ }
96
+ };
97
+ hot.on("incanto:logs-request", onLogsRequest);
98
+ cleanups.push(() => hot.off?.("incanto:logs-request", onLogsRequest));
99
+ return cleanups;
100
+ }
101
+ //#endregion
30
102
  //#region src/core/editor-switch.ts
31
103
  /**
32
104
  * Cover the page while it changes what it is.
@@ -324,4 +396,4 @@ function defaultReport(what, error) {
324
396
  console.error(`[incanto] teardown: ${what} failed (continuing)`, error);
325
397
  }
326
398
  //#endregion
327
- export { openBundledEditor as a, audioErrors as c, devServerLibrary as i, pauseWhenHidden as n, poseFromRenderer as o, crossFade as r, claimCanvasGestures as s, teardown as t };
399
+ export { openBundledEditor as a, claimCanvasGestures as c, devServerLibrary as i, audioErrors as l, pauseWhenHidden as n, poseFromRenderer as o, crossFade as r, wireDevChannel as s, teardown as t };
@@ -1,13 +1,13 @@
1
- import { A as registerBehavior, n as loadScene, s as resolveViewport } from "./loader-BTkHYrQn.js";
2
- import { x as Engine } from "./register-p48lHE2o.js";
1
+ import { A as registerBehavior, n as loadScene, s as resolveViewport } from "./loader-BcrRSjxB.js";
2
+ import { _ as Engine } from "./register-Ch70uByv.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { i as getNodeSchema, s as mergeStaticProps } from "./registry-C7u42TID.js";
6
- import { n as startRecording } from "./replay-t1pP0gQg.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-CZ2yq37J.js";
8
- import { t as registerNodes2D } from "./register-en63AEZO.js";
9
- import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-DAbEdEwh.js";
10
- import { n as registerNodesNet, t as createSplitScreen } from "./split-screen-CSb_uZ6W.js";
6
+ import { n as startRecording } from "./replay-s7I2GstT.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-BVphcxmE.js";
8
+ import { t as registerNodes2D } from "./register-C6ZBFRjd.js";
9
+ import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-CvvQr_bJ.js";
10
+ import { n as registerNodesNet, t as createSplitScreen } from "./split-screen-DLsUrleX.js";
11
11
  import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from "three";
12
12
  //#region src/test/framing.ts
13
13
  /**
@@ -1016,6 +1016,10 @@ function facingText(report) {
1016
1016
  const STEP_MS = 1e3 / 60;
1017
1017
  /** Drop height for the buffer probe — long enough to outlast a coyote window. */
1018
1018
  const DROP = 12;
1019
+ /** Metres a 3D probe is lifted/dropped from. */
1020
+ const UNIT_LIFT = 40;
1021
+ /** Pixels a 2D probe is lifted/dropped from — a 2D world is ~30x the numbers. */
1022
+ const PX_LIFT = 1200;
1019
1023
  function positionOf(node) {
1020
1024
  const p = node.position;
1021
1025
  return [
@@ -1062,11 +1066,14 @@ async function makeProbe(json, opts) {
1062
1066
  jump
1063
1067
  };
1064
1068
  }
1069
+ const is2d = session.scene.dimension === "2d";
1065
1070
  return {
1066
1071
  probe: {
1067
1072
  session,
1068
1073
  engine: session.engine,
1069
1074
  player,
1075
+ up: is2d ? -1 : 1,
1076
+ lift: is2d ? PX_LIFT : UNIT_LIFT,
1070
1077
  step: (frames = 1) => session.step(frames * STEP_MS),
1071
1078
  pos: () => positionOf(player),
1072
1079
  vel: () => velocityOf(player),
@@ -1159,7 +1166,7 @@ function jumpShape(probe, jump) {
1159
1166
  let airtime = null;
1160
1167
  for (let f = 2; f <= 600; f++) {
1161
1168
  probe.step();
1162
- const h = probe.pos()[1] - floor;
1169
+ const h = (probe.pos()[1] - floor) * probe.up;
1163
1170
  if (h > apex) {
1164
1171
  apex = h;
1165
1172
  toApex = f;
@@ -1169,7 +1176,8 @@ function jumpShape(probe, jump) {
1169
1176
  break;
1170
1177
  }
1171
1178
  }
1172
- if (apex <= .02) return {
1179
+ const noise = probe.up > 0 ? .02 : 2;
1180
+ if (apex <= noise) return {
1173
1181
  apex: null,
1174
1182
  toApexMs: null,
1175
1183
  airtimeMs: null
@@ -1197,7 +1205,7 @@ async function measureCoyote(json, opts, jump) {
1197
1205
  const p = probe.pos();
1198
1206
  probe.player.position = [
1199
1207
  p[0],
1200
- p[1] + 40,
1208
+ p[1] + probe.lift * probe.up,
1201
1209
  p[2]
1202
1210
  ];
1203
1211
  probe.step(delayFrames);
@@ -1206,7 +1214,7 @@ async function measureCoyote(json, opts, jump) {
1206
1214
  probe.step();
1207
1215
  const after = probe.vel()[1];
1208
1216
  probe.session.dispose();
1209
- return after - before > 1;
1217
+ return (after - before) * probe.up > 1;
1210
1218
  };
1211
1219
  if (!await fired(1)) return 0;
1212
1220
  let lo = 1;
@@ -1231,13 +1239,14 @@ async function measureBuffer(json, opts, jump) {
1231
1239
  if (!probe) return false;
1232
1240
  settle$1(probe);
1233
1241
  const floor = probe.pos()[1];
1242
+ const drop = (probe.up > 0 ? DROP : probe.lift / 3) * probe.up;
1234
1243
  probe.player.position = [
1235
1244
  probe.pos()[0],
1236
- floor + DROP,
1245
+ floor + drop,
1237
1246
  probe.pos()[2]
1238
1247
  ];
1239
1248
  let frames = 0;
1240
- while (probe.pos()[1] > floor + .05 && frames < 200) {
1249
+ while ((probe.pos()[1] - floor) * probe.up > .05 && frames < 200) {
1241
1250
  probe.step();
1242
1251
  frames += 1;
1243
1252
  }
@@ -1249,7 +1258,7 @@ async function measureBuffer(json, opts, jump) {
1249
1258
  settle$1(p2);
1250
1259
  p2.player.position = [
1251
1260
  p2.pos()[0],
1252
- floor + DROP,
1261
+ floor + drop,
1253
1262
  p2.pos()[2]
1254
1263
  ];
1255
1264
  p2.step(Math.max(0, fallFrames - earlyFrames));
@@ -1259,7 +1268,7 @@ async function measureBuffer(json, opts, jump) {
1259
1268
  let rose = false;
1260
1269
  for (let f = 0; f < 40; f++) {
1261
1270
  p2.step();
1262
- if (p2.vel()[1] > 1) rose = true;
1271
+ if (p2.vel()[1] * p2.up > 1) rose = true;
1263
1272
  }
1264
1273
  p2.session.dispose();
1265
1274
  return rose;
@@ -1753,10 +1762,10 @@ async function runScript(json, opts) {
1753
1762
  engine.setScene(scene);
1754
1763
  const physics = opts.physics ?? "auto";
1755
1764
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
1756
- const { enablePhysics2D } = await import("./physics-2d-CfWAggJ1.js").then((n) => n.r);
1765
+ const { enablePhysics2D } = await import("./physics-2d-BLcvEFDR.js").then((n) => n.r);
1757
1766
  await enablePhysics2D(engine);
1758
1767
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
1759
- const { enablePhysics3D } = await import("./physics-3d-C_ZJ6f_d.js").then((n) => n.r);
1768
+ const { enablePhysics3D } = await import("./physics-3d-QBrfIT2Y.js").then((n) => n.r);
1760
1769
  await enablePhysics3D(engine);
1761
1770
  }
1762
1771
  const failures = [];
@@ -1869,10 +1878,10 @@ async function createPlaySession(json, opts = {}) {
1869
1878
  engine.setScene(scene);
1870
1879
  const physics = opts.physics ?? "auto";
1871
1880
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
1872
- const { enablePhysics2D } = await import("./physics-2d-CfWAggJ1.js").then((n) => n.r);
1881
+ const { enablePhysics2D } = await import("./physics-2d-BLcvEFDR.js").then((n) => n.r);
1873
1882
  await enablePhysics2D(engine);
1874
1883
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
1875
- const { enablePhysics3D } = await import("./physics-3d-C_ZJ6f_d.js").then((n) => n.r);
1884
+ const { enablePhysics3D } = await import("./physics-3d-QBrfIT2Y.js").then((n) => n.r);
1876
1885
  await enablePhysics3D(engine);
1877
1886
  }
1878
1887
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
package/dist/test.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { At as Node, Nt as LogEntry, P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-CyQoSu4n.js";
1
+ import { At as Node, Nt as LogEntry, P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-l08AEbq9.js";
2
2
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
3
- import { t as LoadSceneOptions } from "./loader-DhI1jFW_.js";
4
- import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-ePMz26jw.js";
5
- import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-BsdOHbzP.js";
3
+ import { t as LoadSceneOptions } from "./loader-BbEMTuWg.js";
4
+ import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-Dw6gMlYA.js";
5
+ import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-B0baBwxI.js";
6
6
 
7
7
  //#region src/test/facing.d.ts
8
8
  interface FacingOptions {
package/dist/test.js CHANGED
@@ -1,3 +1,3 @@
1
- import { r as auditScene } from "./replay-t1pP0gQg.js";
2
- import { S as framingText, _ as failingReplays, a as registerAllNodes, b as playtestText, c as ladderText, d as multiplayText, f as playMultiplayer, g as facingText, h as facingReport, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as findPlayer, x as describeFraming, y as playtest } from "./test-Ca5eqELC.js";
1
+ import { r as auditScene } from "./replay-s7I2GstT.js";
2
+ import { S as framingText, _ as failingReplays, a as registerAllNodes, b as playtestText, c as ladderText, d as multiplayText, f as playMultiplayer, g as facingText, h as facingReport, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as findPlayer, x as describeFraming, y as playtest } from "./test-it1VekWs.js";
3
3
  export { auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestText, registerAllNodes, runScript, validateScene };