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
@@ -1,7 +1,7 @@
1
- import { Lt as Signal, b as Engine, jt as Node } from "./behavior-Bf9P9oB2.js";
1
+ import { Lt as Signal, b as Engine, jt as Node } from "./behavior-J1cmVah0.js";
2
2
  import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
3
3
  import { a as SceneJson, c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
4
- import { t as LoadSceneOptions } from "./loader-CBfQzB6S.js";
4
+ import { t as LoadSceneOptions } from "./loader-BAeWMYfF.js";
5
5
 
6
6
  //#region src/net/types.d.ts
7
7
  type Unsubscribe = () => void;
@@ -1,6 +1,6 @@
1
1
  import { n as jsonEquals } from "./json-CfTjpvW8.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
- import { h as didYouMean } from "./touch-BnCyPA0G.js";
3
+ import { h as didYouMean } from "./touch-CioTZB-y.js";
4
4
  //#region src/core/particle-presets.ts
5
5
  const PARTICLE_PRESETS = {
6
6
  fire: {
@@ -199,6 +199,6 @@ async function preloadSceneAssets(assets, title) {
199
199
  //#endregion
200
200
  //#region src/index.ts
201
201
  /** Engine version. Kept in sync with package.json by the release pipeline. */
202
- const VERSION = "0.72.0";
202
+ const VERSION = "0.74.0";
203
203
  //#endregion
204
204
  export { findPath as a, preloadUrls as i, assetUrls as n, gridFromRows as o, preloadSceneAssets as r, VERSION as t };
@@ -1,14 +1,14 @@
1
1
  import { n as jsonEquals, t as jsonClone } from "./json-CfTjpvW8.js";
2
- import { B as HudLayer, M as resetMemorySaves, O as resolveViewport, V as HudWidgetBase, c as isCloned, h as loadScene, ot as registerBehavior, tt as StubBehavior } from "./save-slots-CEuJPUle.js";
3
- import { v as Engine } from "./register-5IYfooAN.js";
2
+ import { B as HudLayer, M as resetMemorySaves, O as resolveViewport, V as HudWidgetBase, c as isCloned, h as loadScene, ot as registerBehavior, tt as StubBehavior } from "./save-slots-CEUGeLuB.js";
3
+ import { v as Engine } from "./register-CjociOtt.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { t as Rng } from "./rng-CDOMybym.js";
6
- import { c as getNodeSchema, d as mergeStaticProps, u as getNodeType } from "./touch-BnCyPA0G.js";
7
- import { n as startRecording } from "./replay-datiAE-b.js";
8
- import { Q as worldPosition, n as registerGameplayBehaviors } from "./gameplay-BlQe-M07.js";
9
- import { t as registerNodes2D } from "./register-C8HuRkOf.js";
10
- import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-BeHoaGhy.js";
11
- import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-Bm5DTElG.js";
6
+ import { a as LIGHT_NODE_TYPES, n as startRecording } from "./replay-B6BbgGM_.js";
7
+ import { c as getNodeSchema, d as mergeStaticProps, u as getNodeType } from "./touch-CioTZB-y.js";
8
+ import { et as worldPosition, n as registerGameplayBehaviors } from "./gameplay-Bxe1sMVT.js";
9
+ import { t as registerNodes2D } from "./register-DRQyZKGU.js";
10
+ import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-BkqlWewf.js";
11
+ import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-BxZ5rBe7.js";
12
12
  import { Box3, Euler, Frustum, Matrix4, PerspectiveCamera, Quaternion, Ray, Vector3 } from "three";
13
13
  //#region src/core/asset-urls.ts
14
14
  /** Keys inside a `material` object that hold a texture URL. */
@@ -90,7 +90,7 @@ function sceneAssetUrls(json) {
90
90
  */
91
91
  const DEG = Math.PI / 180;
92
92
  const CAMERA_TYPES = new Set(["Camera3D"]);
93
- const LIGHT_TYPES = new Set(["DirectionalLight3D", "OmniLight3D"]);
93
+ const LIGHT_TYPES = LIGHT_NODE_TYPES;
94
94
  /** Screen-space subtrees: they ignore the camera, so framing has nothing to say. */
95
95
  const SCREEN_SPACE$1 = new Set(["UILayer", "HudLayer"]);
96
96
  /** Depth a 2D collider is given on the axis it does not have. */
@@ -846,7 +846,7 @@ function framingText(report) {
846
846
  //#region src/test/picker.ts
847
847
  const DEFAULT_VIEW = [960, 540];
848
848
  const CAMERAS = new Set(["Camera3D"]);
849
- const LIGHTS = new Set(["DirectionalLight3D", "OmniLight3D"]);
849
+ const LIGHTS = LIGHT_NODE_TYPES;
850
850
  /**
851
851
  * A picker over `scene`, in the scene's own units.
852
852
  *
@@ -2759,12 +2759,14 @@ async function makeProbe(json, opts) {
2759
2759
  };
2760
2760
  const move = opts.moveAction ?? named("moveAction", "vector2") ?? moveOptions[0] ?? null;
2761
2761
  const jump = opts.jumpAction ?? named("jumpAction", "button") ?? (declared.some((a) => a.name === "jump" && a.type === "button") ? "jump" : null);
2762
+ const sprint = named("sprintAction", "button") ?? (declared.some((a) => a.name === "sprint" && a.type === "button") ? "sprint" : null);
2762
2763
  if (!player) {
2763
2764
  session.dispose();
2764
2765
  return {
2765
2766
  probe: null,
2766
2767
  move,
2767
2768
  jump,
2769
+ sprint,
2768
2770
  moveOptions
2769
2771
  };
2770
2772
  }
@@ -2783,10 +2785,15 @@ async function makeProbe(json, opts) {
2783
2785
  speed: () => {
2784
2786
  const v = velocityOf(player);
2785
2787
  return Math.hypot(v[0], v[2]);
2788
+ },
2789
+ onFloor: () => {
2790
+ const body = player;
2791
+ return typeof body.isOnFloor === "function" ? body.isOnFloor() : null;
2786
2792
  }
2787
2793
  },
2788
2794
  move,
2789
2795
  jump,
2796
+ sprint,
2790
2797
  moveOptions
2791
2798
  };
2792
2799
  }
@@ -2951,6 +2958,42 @@ function jumpShape(probe, jump, hold = false) {
2951
2958
  airtimeMs: airtime
2952
2959
  };
2953
2960
  }
2961
+ /** Frames between presses in a multi-jump probe — long enough for the rise to slow. */
2962
+ const BETWEEN_PRESSES = 15;
2963
+ /**
2964
+ * How high the character goes with EVERY jump spent: press, let the rise slow,
2965
+ * press again, as many times as `maxJumps` allows.
2966
+ *
2967
+ * The presses are spaced because a second press taken at launch speed re-sets
2968
+ * the same velocity and reads as nothing having happened.
2969
+ */
2970
+ function multiJumpShape(probe, jump, jumps) {
2971
+ settle$1(probe);
2972
+ const floor = probe.pos()[1];
2973
+ let apex = 0;
2974
+ let airtime = null;
2975
+ let pressesLeft = jumps;
2976
+ for (let f = 1; f <= 900; f++) {
2977
+ if (pressesLeft > 0 && (f - 1) % BETWEEN_PRESSES === 0) {
2978
+ probe.engine.input.pressAction(jump);
2979
+ probe.step();
2980
+ probe.engine.input.releaseAction(jump);
2981
+ pressesLeft -= 1;
2982
+ } else probe.step();
2983
+ const h = (probe.pos()[1] - floor) * probe.up;
2984
+ if (h > apex) apex = h;
2985
+ if (pressesLeft === 0 && apex > .02 && h <= .02) {
2986
+ airtime = Math.round(f * STEP_MS);
2987
+ break;
2988
+ }
2989
+ }
2990
+ const noise = probe.up > 0 ? .02 : 2;
2991
+ if (apex <= noise) return null;
2992
+ return {
2993
+ apex: Number(apex.toFixed(3)),
2994
+ airtimeMs: airtime
2995
+ };
2996
+ }
2954
2997
  /**
2955
2998
  * How long after leaving the ground a jump still fires — measured, not read.
2956
2999
  *
@@ -2960,7 +3003,7 @@ function jumpShape(probe, jump, hold = false) {
2960
3003
  * which is the whole point — that was a real bug in this engine's 3D controller
2961
3004
  * until 0.37 and the schema looked correct the entire time.
2962
3005
  */
2963
- async function measureCoyote(json, opts, jump) {
3006
+ async function measureCoyote(json, opts, jump, jumps = 1) {
2964
3007
  const fired = async (delayFrames) => {
2965
3008
  const { probe } = await makeProbe(json, opts);
2966
3009
  if (!probe) return false;
@@ -2971,16 +3014,26 @@ async function measureCoyote(json, opts, jump) {
2971
3014
  p[1] + probe.lift * probe.up,
2972
3015
  p[2]
2973
3016
  ];
3017
+ for (let f = 0; f < 4 && probe.onFloor() === true; f++) probe.step();
2974
3018
  probe.step(delayFrames);
2975
- const before = probe.vel()[1];
2976
- probe.engine.input.pressAction(jump);
2977
- probe.step();
2978
- const after = probe.vel()[1];
3019
+ const pressFires = () => {
3020
+ const before = probe.vel()[1];
3021
+ probe.engine.input.pressAction(jump);
3022
+ probe.step();
3023
+ probe.engine.input.releaseAction(jump);
3024
+ const after = probe.vel()[1];
3025
+ return after * probe.up > 1 && (after - before) * probe.up > 1;
3026
+ };
3027
+ let all = pressFires();
3028
+ for (let extra = 1; all && extra < jumps; extra++) {
3029
+ probe.step(BETWEEN_PRESSES);
3030
+ all = pressFires();
3031
+ }
2979
3032
  probe.session.dispose();
2980
- return (after - before) * probe.up > 1;
3033
+ return all;
2981
3034
  };
2982
- if (!await fired(1)) return 0;
2983
- let lo = 1;
3035
+ if (!await fired(0)) return 0;
3036
+ let lo = 0;
2984
3037
  let hi = 60;
2985
3038
  if (await fired(hi)) return null;
2986
3039
  while (hi - lo > 1) {
@@ -2988,7 +3041,7 @@ async function measureCoyote(json, opts, jump) {
2988
3041
  if (await fired(mid)) lo = mid;
2989
3042
  else hi = mid;
2990
3043
  }
2991
- return Math.round(lo * STEP_MS);
3044
+ return Math.round((lo + 1) * STEP_MS);
2992
3045
  }
2993
3046
  /**
2994
3047
  * `coyoteSeconds` as written in the scene, in ms — for CONTRAST, never instead.
@@ -3012,13 +3065,36 @@ function declaredCoyote(json) {
3012
3065
  walk(json?.root);
3013
3066
  return found;
3014
3067
  }
3068
+ /** `maxJumps` as written in the scene; 1 when it says nothing. */
3069
+ function declaredMaxJumps(json) {
3070
+ let found = 1;
3071
+ const walk = (node) => {
3072
+ if (!node || typeof node !== "object") return;
3073
+ const n = node;
3074
+ if (typeof n.type === "string" && /^CharacterController[23]D$/.test(n.type)) {
3075
+ const value = n.props?.maxJumps;
3076
+ if (typeof value === "number" && value > 1) found = Math.floor(value);
3077
+ }
3078
+ for (const child of n.children ?? []) walk(child);
3079
+ };
3080
+ walk(json?.root);
3081
+ return found;
3082
+ }
3015
3083
  /**
3016
3084
  * How long BEFORE landing a press still survives.
3017
3085
  *
3018
3086
  * Same shape: drop the character, press at T frames before it would touch down,
3019
3087
  * and check whether it left the ground again afterwards.
3020
3088
  */
3021
- async function measureBuffer(json, opts, jump) {
3089
+ async function measureBuffer(json, opts, jump, jumps = 1) {
3090
+ const exhaust = (probe) => {
3091
+ for (let i = 0; i < jumps; i++) {
3092
+ probe.step(BETWEEN_PRESSES);
3093
+ probe.engine.input.pressAction(jump);
3094
+ probe.step();
3095
+ probe.engine.input.releaseAction(jump);
3096
+ }
3097
+ };
3022
3098
  const survived = async (earlyFrames) => {
3023
3099
  const { probe } = await makeProbe(json, opts);
3024
3100
  if (!probe) return false;
@@ -3030,8 +3106,9 @@ async function measureBuffer(json, opts, jump) {
3030
3106
  floor + drop,
3031
3107
  probe.pos()[2]
3032
3108
  ];
3109
+ if (jumps > 1) exhaust(probe);
3033
3110
  let frames = 0;
3034
- while ((probe.pos()[1] - floor) * probe.up > .05 && frames < 200) {
3111
+ while ((probe.pos()[1] - floor) * probe.up > .05 && frames < 400) {
3035
3112
  probe.step();
3036
3113
  frames += 1;
3037
3114
  }
@@ -3046,6 +3123,7 @@ async function measureBuffer(json, opts, jump) {
3046
3123
  floor + drop,
3047
3124
  p2.pos()[2]
3048
3125
  ];
3126
+ if (jumps > 1) exhaust(p2);
3049
3127
  p2.step(Math.max(0, fallFrames - earlyFrames));
3050
3128
  p2.engine.input.pressAction(jump);
3051
3129
  p2.step();
@@ -3073,6 +3151,7 @@ async function feelReport(json, opts = {}) {
3073
3151
  const first = await makeProbe(json, opts);
3074
3152
  const move = first.move;
3075
3153
  const jump = first.jump;
3154
+ const sprintAction = first.sprint;
3076
3155
  if (!first.probe) return {
3077
3156
  player: null,
3078
3157
  moveAction: move,
@@ -3084,12 +3163,16 @@ async function feelReport(json, opts = {}) {
3084
3163
  turnaroundMs: null,
3085
3164
  jumpApex: null,
3086
3165
  heldJumpApex: null,
3166
+ walkingJumpApex: null,
3167
+ sprintJumpApex: null,
3087
3168
  heldAirtimeMs: null,
3088
3169
  timeToApexMs: null,
3089
3170
  airtimeMs: null,
3090
3171
  coyoteMs: null,
3091
3172
  jumpsInMidair: false,
3092
3173
  declaredCoyoteMs: null,
3174
+ declaredMaxJumps: 1,
3175
+ doubleJump: null,
3093
3176
  jumpBufferMs: null,
3094
3177
  inputToMotionFrames: null,
3095
3178
  moveAxis: null,
@@ -3188,8 +3271,12 @@ async function feelReport(json, opts = {}) {
3188
3271
  };
3189
3272
  let shape = NO_SHAPE;
3190
3273
  let held = NO_SHAPE;
3274
+ let walking = NO_SHAPE;
3275
+ let sprinting = NO_SHAPE;
3191
3276
  let coyote = null;
3192
3277
  let buffer = null;
3278
+ const jumps = declaredMaxJumps(json);
3279
+ let doubleJump = null;
3193
3280
  if (jump) {
3194
3281
  const d = await fresh();
3195
3282
  if (d) {
@@ -3203,8 +3290,33 @@ async function feelReport(json, opts = {}) {
3203
3290
  collect(h);
3204
3291
  h.session.dispose();
3205
3292
  }
3206
- coyote = await measureCoyote(json, opts, jump);
3207
- buffer = await measureBuffer(json, opts, jump);
3293
+ if (action) {
3294
+ const w = await fresh();
3295
+ if (w) {
3296
+ w.engine.input.setActionVector(action, 1, 0);
3297
+ walking = jumpShape(w, jump, true);
3298
+ w.session.dispose();
3299
+ }
3300
+ if (sprintAction) {
3301
+ const sp = await fresh();
3302
+ if (sp) {
3303
+ sp.engine.input.setActionVector(action, 1, 0);
3304
+ sp.engine.input.pressAction(sprintAction);
3305
+ sprinting = jumpShape(sp, jump, true);
3306
+ sp.session.dispose();
3307
+ }
3308
+ }
3309
+ }
3310
+ if (jumps > 1) {
3311
+ const m = await fresh();
3312
+ if (m) {
3313
+ doubleJump = multiJumpShape(m, jump, jumps);
3314
+ collect(m);
3315
+ m.session.dispose();
3316
+ }
3317
+ }
3318
+ coyote = await measureCoyote(json, opts, jump, jumps);
3319
+ buffer = await measureBuffer(json, opts, jump, jumps);
3208
3320
  }
3209
3321
  return {
3210
3322
  player: playerPath,
@@ -3218,6 +3330,8 @@ async function feelReport(json, opts = {}) {
3218
3330
  jumpApex: shape.apex,
3219
3331
  heldJumpApex: held.apex,
3220
3332
  heldAirtimeMs: held.airtimeMs,
3333
+ walkingJumpApex: walking.apex,
3334
+ sprintJumpApex: sprinting.apex,
3221
3335
  timeToApexMs: shape.toApexMs,
3222
3336
  airtimeMs: shape.airtimeMs,
3223
3337
  coyoteMs: coyote,
@@ -3225,6 +3339,8 @@ async function feelReport(json, opts = {}) {
3225
3339
  declaredCoyoteMs: declaredCoyote(json),
3226
3340
  jumpBufferMs: buffer,
3227
3341
  inputToMotionFrames: latency,
3342
+ declaredMaxJumps: jumps,
3343
+ doubleJump,
3228
3344
  moveAxis: axis,
3229
3345
  moveTried: tried,
3230
3346
  errorMessages: [...said.errors],
@@ -3260,7 +3376,12 @@ function feelText(report) {
3260
3376
  lines.push("");
3261
3377
  lines.push(` jump apex ${report.jumpApex === null ? "— (nothing left the ground)" : `${report.jumpApex} u (tapped)`}`);
3262
3378
  const cut = report.heldJumpApex !== null && report.jumpApex !== null && Math.abs(report.heldJumpApex - report.jumpApex) > .02;
3263
- if (cut) lines.push(` held apex ${report.heldJumpApex} u ← size your level against THIS`);
3379
+ if (cut) lines.push(` held apex ${report.heldJumpApex} u (standing)`);
3380
+ if (report.walkingJumpApex !== null || report.sprintJumpApex !== null) {
3381
+ const parts = [report.walkingJumpApex !== null ? `${report.walkingJumpApex} u walking` : null, report.sprintJumpApex !== null ? `${report.sprintJumpApex} u sprinting` : null].filter((x) => x !== null);
3382
+ lines.push(` moving apex ${parts.join(" · ")} ← size your level against the LARGEST`);
3383
+ } else if (cut) lines.push(" (size your level against the held apex — this scene declares no move action)");
3384
+ if (report.doubleJump) lines.push(` ${report.declaredMaxJumps === 2 ? "double jump" : `${report.declaredMaxJumps} jumps`} ${report.doubleJump.apex} u (${report.declaredMaxJumps} TAPS, maxJumps ${report.declaredMaxJumps}${report.doubleJump.airtimeMs !== null ? `, ${ms(report.doubleJump.airtimeMs)} airtime` : ""})`);
3264
3385
  lines.push(` time to apex ${ms(report.timeToApexMs)}`);
3265
3386
  lines.push(` airtime ${ms(report.airtimeMs)}${cut && report.heldAirtimeMs !== null ? ` (${ms(report.heldAirtimeMs)} held)` : ""}`);
3266
3387
  lines.push(` coyote window ${report.jumpsInMidair ? "not measurable — this character jumps in MID-AIR" : report.coyoteMs === null ? "always (no window)" : ms(report.coyoteMs)}`);
@@ -3389,8 +3510,8 @@ async function playMultiplayer(opts) {
3389
3510
  */
3390
3511
  async function joinLate(server, first, opts) {
3391
3512
  try {
3392
- const { Engine } = await import("./register-5IYfooAN.js").then((n) => n.y);
3393
- const { loadScene } = await import("./save-slots-CEuJPUle.js").then((n) => n.g);
3513
+ const { Engine } = await import("./register-CjociOtt.js").then((n) => n.y);
3514
+ const { loadScene } = await import("./save-slots-CEUGeLuB.js").then((n) => n.g);
3394
3515
  const { jsonClone } = await import("./json-CfTjpvW8.js").then((n) => n.i);
3395
3516
  const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + 999 } : {});
3396
3517
  engine.setScene(loadScene(jsonClone(opts.scene), opts.load));
@@ -3988,13 +4109,14 @@ async function runScript(json, opts) {
3988
4109
  if (opts.locale) engine.locale.locale = opts.locale;
3989
4110
  const physics = opts.physics ?? "auto";
3990
4111
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
3991
- const { enablePhysics2D } = await import("./physics-2d-DrIgTlLk.js").then((n) => n.r);
4112
+ const { enablePhysics2D } = await import("./physics-2d-C-SfvjzH.js").then((n) => n.r);
3992
4113
  await enablePhysics2D(engine);
3993
4114
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
3994
- const { enablePhysics3D } = await import("./physics-3d-BzOCw1VS.js").then((n) => n.r);
4115
+ const { enablePhysics3D } = await import("./physics-3d-rOzeg890.js").then((n) => n.r);
3995
4116
  await enablePhysics3D(engine);
3996
4117
  }
3997
4118
  const failures = [];
4119
+ const stalled = [];
3998
4120
  const snapshots = [];
3999
4121
  const live = () => engine.scene ?? scene;
4000
4122
  const ctx = {
@@ -4004,6 +4126,7 @@ async function runScript(json, opts) {
4004
4126
  },
4005
4127
  timeMs: 0,
4006
4128
  getNode: (path) => live().root.getNode(path),
4129
+ getNodeOrNull: (path) => live().root.getNodeOrNull(path),
4007
4130
  capture: () => captureScene(live())
4008
4131
  };
4009
4132
  /** Is `hit` the node we aimed at, or something of its own? */
@@ -4111,6 +4234,7 @@ async function runScript(json, opts) {
4111
4234
  engine.input.handleMouseButton(releaseButton, true);
4112
4235
  releaseAt = tick + 1;
4113
4236
  }
4237
+ const frozenBefore = engine.timeScale === 0;
4114
4238
  step.do?.(ctx);
4115
4239
  if (step.assert) try {
4116
4240
  const verdict = assertVerdict(step.assert(ctx));
@@ -4126,6 +4250,11 @@ async function runScript(json, opts) {
4126
4250
  message: e instanceof Error ? e.message : String(e)
4127
4251
  });
4128
4252
  }
4253
+ if ((step.do || step.assert) && (frozenBefore || engine.timeScale === 0)) stalled.push({
4254
+ atMs: step.atMs,
4255
+ label: step.label,
4256
+ before: frozenBefore
4257
+ });
4129
4258
  };
4130
4259
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
4131
4260
  const totalTicks = Math.round(opts.durationMs / stepMs);
@@ -4178,6 +4307,7 @@ async function runScript(json, opts) {
4178
4307
  return {
4179
4308
  ok,
4180
4309
  failures,
4310
+ stalled,
4181
4311
  snapshots,
4182
4312
  logs,
4183
4313
  finalCapture,
@@ -4186,10 +4316,12 @@ async function runScript(json, opts) {
4186
4316
  const budget = Math.abs(simulatedMs - opts.durationMs) > Math.max(50, opts.durationMs * .02) ? ` (of ${opts.durationMs}ms of steps — the game clock ran slower, or stopped)` : "";
4187
4317
  const head = `run ${ok ? "OK" : "FAILED"} — ${simulatedMs}ms simulated${budget}, ${failures.length} failure(s), ${logs.length} log(s)`;
4188
4318
  const fails = failures.map((f) => ` ✗ at ${f.atMs}ms${f.label ? ` [${f.label}]` : ""}: ${f.message}`);
4319
+ const stopped = stalled.map((s) => ` ! at ${s.atMs}ms${s.label ? ` [${s.label}]` : ""}: the clock was stopped ${s.before ? "when this step ran" : "by the time this step finished"} (timeScale 0 — a terminal GameFlow state or a pause holds it), so engine.step() advanced nothing and every value read here is the last one before it stopped. Restart the flow, or measure before the game ends.`);
4189
4320
  const noisy = engineWarnings.map((l) => ` ! ${l.level}: ${l.parts.map((p) => typeof p === "string" ? p : String(p)).join(" ")}`);
4190
4321
  return [
4191
4322
  head,
4192
4323
  ...fails,
4324
+ ...stopped,
4193
4325
  ...noisy,
4194
4326
  describeCapture(finalCapture)
4195
4327
  ].join("\n");
@@ -4218,10 +4350,10 @@ async function createPlaySession(json, opts = {}) {
4218
4350
  engine.setScene(scene);
4219
4351
  const physics = opts.physics ?? "auto";
4220
4352
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
4221
- const { enablePhysics2D } = await import("./physics-2d-DrIgTlLk.js").then((n) => n.r);
4353
+ const { enablePhysics2D } = await import("./physics-2d-C-SfvjzH.js").then((n) => n.r);
4222
4354
  await enablePhysics2D(engine);
4223
4355
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
4224
- const { enablePhysics3D } = await import("./physics-3d-BzOCw1VS.js").then((n) => n.r);
4356
+ const { enablePhysics3D } = await import("./physics-3d-rOzeg890.js").then((n) => n.r);
4225
4357
  await enablePhysics3D(engine);
4226
4358
  }
4227
4359
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
package/dist/test.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { P as Scene$1, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-Bf9P9oB2.js";
1
+ import { P as Scene$1, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-J1cmVah0.js";
2
2
  import { a as SceneJson, c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
3
- import { t as LoadSceneOptions } from "./loader-CBfQzB6S.js";
4
- import { l as auditScene, o as IncantoError, r as ReplayJson, u as instancedScenes } from "./replay-CBEChqXq.js";
5
- import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-DkqLng6q.js";
3
+ import { t as LoadSceneOptions } from "./loader-BAeWMYfF.js";
4
+ import { l as auditScene, o as IncantoError, r as ReplayJson, u as instancedScenes } from "./replay-wZK5Ok5c.js";
5
+ import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-D6sx8z3Z.js";
6
6
  import { Matrix4, Vector3 } from "three";
7
7
 
8
8
  //#region src/core/asset-urls.d.ts
@@ -453,6 +453,16 @@ interface FeelReport {
453
453
  heldJumpApex: number | null;
454
454
  /** Airtime with the button held. */
455
455
  heldAirtimeMs: number | null;
456
+ /**
457
+ * The held apex while WALKING and while SPRINTING. `sprintJumpMultiplier`
458
+ * scales with the keyboard's intensity (0.6 for any move key, 1 with sprint),
459
+ * so a moving jump is higher than the standing one the other rows measure —
460
+ * +26% walking, +44% sprinting at the defaults — and a ledge sized against
461
+ * the standing apex is cleared by anyone holding a direction. Null when the
462
+ * scene declares no move (or sprint) action.
463
+ */
464
+ walkingJumpApex: number | null;
465
+ sprintJumpApex: number | null;
456
466
  timeToApexMs: number | null;
457
467
  /** Launch → back on the ground. */
458
468
  airtimeMs: number | null;
@@ -492,6 +502,27 @@ interface FeelReport {
492
502
  jumpBufferMs: number | null;
493
503
  /** Frames between the press and the first movement. */
494
504
  inputToMotionFrames: number | null;
505
+ /**
506
+ * `maxJumps` as the scene declares it (1 when it says nothing) — read, like
507
+ * `declaredCoyoteMs`, and for the same reason: the probes below have to know
508
+ * how many presses a character has before they can tell a coyote jump from
509
+ * an air jump.
510
+ */
511
+ declaredMaxJumps: number;
512
+ /**
513
+ * MEASURED: the apex and airtime with EVERY declared jump spent — press,
514
+ * rise, press again. Null when the controller declares one jump.
515
+ *
516
+ * The moment a scene wrote `maxJumps: 2`, this report gave up on two of its
517
+ * numbers ("not measurable — this character jumps in MID-AIR") and called a
518
+ * third suspect, while the scene said exactly why in the line above. A
519
+ * double jump is the 3D character skill's own recommendation; the instrument
520
+ * has to survive it.
521
+ */
522
+ doubleJump: {
523
+ apex: number;
524
+ airtimeMs: number | null;
525
+ } | null;
495
526
  }
496
527
  declare function feelReport(json: unknown, opts?: FeelOptions): Promise<FeelReport>;
497
528
  /** Numbers, not grades. What "good" means here is a genre decision. */
@@ -1144,6 +1175,8 @@ interface RunContext {
1144
1175
  timeMs: number;
1145
1176
  /** Resolve a node path relative to the scene root. */
1146
1177
  getNode(path: string): Node;
1178
+ /** The same lookup, null for a node that is not there — a gem already taken, a foe already dead. */
1179
+ getNodeOrNull(path: string): Node | null;
1147
1180
  /** Snapshot the scene right now. */
1148
1181
  capture(): SceneCapture;
1149
1182
  }
@@ -1225,6 +1258,22 @@ interface RunFailure {
1225
1258
  label?: string;
1226
1259
  message: string;
1227
1260
  }
1261
+ /**
1262
+ * A `do:`/`assert:` step that ran with the game clock STOPPED.
1263
+ *
1264
+ * `GameFlow` sets `timeScale = 0` on a win or a loss (and a pause menu does the
1265
+ * same), after which `engine.step()` advances nothing. A harness that lost its
1266
+ * player three times inside one step then measured `0.00` for everything
1267
+ * after — the position it had just teleported to, no travel, no apex — and
1268
+ * the run was `ok` with nothing said beside the zeros. `before` is whether the
1269
+ * clock was already stopped when the step began (every read in it was stale)
1270
+ * or stopped by the time it finished (the step itself ended the game).
1271
+ */
1272
+ interface RunStalled {
1273
+ atMs: number;
1274
+ label?: string;
1275
+ before: boolean;
1276
+ }
1228
1277
  interface RunScriptOptions {
1229
1278
  /** Total simulated duration in ms. */
1230
1279
  durationMs: number;
@@ -1253,6 +1302,8 @@ interface RunResult {
1253
1302
  /** True when no step assertion failed. */
1254
1303
  ok: boolean;
1255
1304
  failures: RunFailure[];
1305
+ /** Steps that ran with the clock stopped — see `RunStalled`. Not failures. */
1306
+ stalled: RunStalled[];
1256
1307
  snapshots: Array<{
1257
1308
  atMs: number;
1258
1309
  capture: SceneCapture;
@@ -1309,4 +1360,4 @@ interface PlaySession {
1309
1360
  */
1310
1361
  declare function createPlaySession(json: unknown, opts?: PlaySessionOptions): Promise<PlaySession>;
1311
1362
  //#endregion
1312
- 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, feelsRung, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, unmeasuredPlays, validateScene };
1363
+ 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, RunStalled, type RungResult, type RungStatus, type SceneAssetUrl, SceneCapture, ScriptStep, ValidateSceneOptions, ValidationResult, type Where, agreementProblems, auditScene, captureScene, collectionProblems, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, feelsRung, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, unmeasuredPlays, validateScene };
package/dist/test.js CHANGED
@@ -1,3 +1,3 @@
1
- import { i as instancedScenes, r as auditScene } from "./replay-datiAE-b.js";
2
- import { A as geometricPicker, C as facingReport, D as playtest, E as findPlayer, F as sceneAssetUrls, M as pointAtNode, N as describeFraming, O as playtestFailed, P as framingText, S as collectionProblems, T as failingReplays, _ as feelReport, a as registerAllNodes, b as takeCollectionSnapshots, c as feelsRung, d as ladderVerdict, f as saysRung, g as playMultiplayer, h as multiplayText, i as findFloatingProps, j as geometricView, k as playtestText, l as firstLine, m as multiplayProblems, n as createPlaySession, o as runScript, p as unmeasuredPlays, r as describeCapture, s as validateScene, t as captureScene, u as ladderText, v as feelText, w as facingText, x as agreementProblems, y as takeAgreementSnapshots } from "./test-Djuq3Vhq.js";
1
+ import { i as instancedScenes, r as auditScene } from "./replay-B6BbgGM_.js";
2
+ import { A as geometricPicker, C as facingReport, D as playtest, E as findPlayer, F as sceneAssetUrls, M as pointAtNode, N as describeFraming, O as playtestFailed, P as framingText, S as collectionProblems, T as failingReplays, _ as feelReport, a as registerAllNodes, b as takeCollectionSnapshots, c as feelsRung, d as ladderVerdict, f as saysRung, g as playMultiplayer, h as multiplayText, i as findFloatingProps, j as geometricView, k as playtestText, l as firstLine, m as multiplayProblems, n as createPlaySession, o as runScript, p as unmeasuredPlays, r as describeCapture, s as validateScene, t as captureScene, u as ladderText, v as feelText, w as facingText, x as agreementProblems, y as takeAgreementSnapshots } from "./test-CEl0y2Sw.js";
3
3
  export { agreementProblems, auditScene, captureScene, collectionProblems, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, feelsRung, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, unmeasuredPlays, validateScene };
@@ -116,7 +116,7 @@ function validatePropValue(value, def, at, options, kinds) {
116
116
  validOptions: [...options]
117
117
  });
118
118
  const expected = jsonKind(def);
119
- if (expected === "null") return;
119
+ if (expected === "null" && !kinds) return;
120
120
  const actual = jsonKind(value);
121
121
  const accepted = kinds ?? [expected];
122
122
  if (!accepted.includes(actual)) throw new IncantoError("PROP_TYPE_MISMATCH", `Prop '${at}' expects ${listKinds(accepted)} (default ${JSON.stringify(def)}), got ${actual} (${JSON.stringify(value)}).`, { prop: at });
package/dist/vite.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { a as parseJsonText } from "./json-CfTjpvW8.js";
2
- import { k as newUid } from "./save-slots-CEuJPUle.js";
2
+ import { k as newUid } from "./save-slots-CEUGeLuB.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { t as VERSION } from "./src-DRSsRleD.js";
4
+ import { t as VERSION } from "./src-CqtvYtSN.js";
5
5
  import { n as diffSignatures } from "./frame-report-D-_7YF2G.js";
6
- import { s as validateScene } from "./test-Djuq3Vhq.js";
6
+ import { s as validateScene } from "./test-CEl0y2Sw.js";
7
7
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
8
8
  import { basename, dirname, extname, join, normalize, relative, resolve, sep } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
@@ -1 +1 @@
1
- import{i as e,n as t}from"./index-BEKuxYcE.js";async function n(e,n){return new a((await t(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,e,{diagnostics:n})}var r=[500,1e3,2e3,4e3,8e3],i=8,a=class{raw;active=new Map;reconnecting=!1;disposed=!1;diagnostics;wait;constructor(e,t,n){this.diagnostics=n?.diagnostics??null,this.wait=n?.wait??(e=>new Promise(t=>{setTimeout(t,e)})),this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{for(let t=0;t<i;t++){let n=!1,i=null;try{n=await this.rawConnect()!==!1}catch(e){i=e}if(this.disposed)return;if(n){for(let e of this.active.values())e.off(),e.off=e.make();t>0&&e(this.diagnostics,`info`,`[incanto] multiplayer reconnected after ${t+1} attempts.`);return}t===0&&e(this.diagnostics,`warn`,`[incanto] the multiplayer connection dropped and the server would not take it back${i?` (${i instanceof Error?i.message:String(i)})`:``}. Retrying — other players cannot see this one until it comes back.`);let a=r[Math.min(t,r.length-1)]??8e3;if(await this.wait(a),this.disposed)return}e(this.diagnostics,`error`,`[incanto] multiplayer gave up after ${i} reconnect attempts — this client is offline and the others cannot see it. Call connect() again to retry.`)}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{n as createAgent8Server};
1
+ import{i as e,n as t}from"./index-CrUCQoaB.js";async function n(e,n){return new a((await t(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,e,{diagnostics:n})}var r=[500,1e3,2e3,4e3,8e3],i=8,a=class{raw;active=new Map;reconnecting=!1;disposed=!1;diagnostics;wait;constructor(e,t,n){this.diagnostics=n?.diagnostics??null,this.wait=n?.wait??(e=>new Promise(t=>{setTimeout(t,e)})),this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{for(let t=0;t<i;t++){let n=!1,i=null;try{n=await this.rawConnect()!==!1}catch(e){i=e}if(this.disposed)return;if(n){for(let e of this.active.values())e.off(),e.off=e.make();t>0&&e(this.diagnostics,`info`,`[incanto] multiplayer reconnected after ${t+1} attempts.`);return}t===0&&e(this.diagnostics,`warn`,`[incanto] the multiplayer connection dropped and the server would not take it back${i?` (${i instanceof Error?i.message:String(i)})`:``}. Retrying — other players cannot see this one until it comes back.`);let a=r[Math.min(t,r.length-1)]??8e3;if(await this.wait(a),this.disposed)return}e(this.diagnostics,`error`,`[incanto] multiplayer gave up after ${i} reconnect attempts — this client is offline and the others cannot see it. Call connect() again to retry.`)}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{n as createAgent8Server};