incanto 0.67.0 → 0.68.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 (70) hide show
  1. package/bin/_pick-server.mjs +48 -0
  2. package/bin/incanto-check.mjs +16 -2
  3. package/bin/incanto-frame.mjs +10 -25
  4. package/bin/incanto-logs.mjs +8 -18
  5. package/dist/2d.d.ts +6 -6
  6. package/dist/2d.js +6 -4
  7. package/dist/3d.d.ts +30 -6
  8. package/dist/3d.js +8 -6
  9. package/dist/{audio-player-BNUHo0JF.d.ts → audio-player-DOrq7sP-.d.ts} +62 -4
  10. package/dist/{behavior-ZnfzKDo3.d.ts → behavior-DoFPYrgo.d.ts} +78 -1
  11. package/dist/boot-failure-CKYrEcGF.js +47 -0
  12. package/dist/{create-game-FAXdHta3.js → create-game-DbWtVTxD.js} +21 -25
  13. package/dist/{create-game-DMetd3vR.js → create-game-IZIydDwI.js} +9 -9
  14. package/dist/debug.d.ts +1 -1
  15. package/dist/{duplicate-B8QnX2-r.js → duplicate-IWIqk0HJ.js} +1 -1
  16. package/dist/{editor-switch-DAvWQeld.d.ts → editor-switch-CnIOiyNJ.d.ts} +1 -1
  17. package/dist/editor.js +11 -5
  18. package/dist/env.d.ts +1 -1
  19. package/dist/{environment-presets-C-5g2pMh.js → environment-presets-CybQXNqS.js} +57 -8
  20. package/dist/{gameplay-CGSv-9XR.js → gameplay-DM1eu_cV.js} +20 -2
  21. package/dist/gameplay.d.ts +51 -3
  22. package/dist/gameplay.js +2 -2
  23. package/dist/index.d.ts +6 -6
  24. package/dist/index.js +8 -7
  25. package/dist/{loader-KZESOZmE.js → loader-BC4PNtJX.js} +26 -1
  26. package/dist/{loader-DyT30ArB.d.ts → loader-CcB533FR.d.ts} +2 -2
  27. package/dist/net.d.ts +2 -2
  28. package/dist/net.js +1 -1
  29. package/dist/{physics-2d-_B6zj3zU.js → physics-2d-B7Y6dPZO.js} +2 -2
  30. package/dist/{physics-3d-i1IeAY0F.js → physics-3d-bG3n70Ky.js} +3 -3
  31. package/dist/react.d.ts +2 -2
  32. package/dist/react.js +1 -1
  33. package/dist/{register-Cs53CY7s.js → register-3ta-2Xig.js} +68 -7
  34. package/dist/{register-vIxU3Xbv.js → register-ibCjm-wH.js} +3 -3
  35. package/dist/{replay-BkpDV51O.js → replay-CYvhVHHN.js} +28 -5
  36. package/dist/{replay-D0GcEHYU.d.ts → replay-Dvn8aeBd.d.ts} +38 -4
  37. package/dist/{schema-CFeioQRE.d.ts → schema-B6ugCV1Q.d.ts} +11 -0
  38. package/dist/{split-screen-DS8RaUV_.js → split-screen-CYwDkbLF.js} +2 -2
  39. package/dist/{split-screen-DD5eYIgW.d.ts → split-screen-DhrSzZIB.d.ts} +3 -3
  40. package/dist/{src-BLlsGJW9.js → src-D7RIqXYF.js} +1 -1
  41. package/dist/{teardown-LB9cTnXJ.js → teardown-D2NEmxPB.js} +2 -2
  42. package/dist/{test-81uZ967a.js → test-8hoHeRmo.js} +26 -21
  43. package/dist/test.d.ts +6 -6
  44. package/dist/test.js +3 -3
  45. package/dist/vite.js +2 -2
  46. package/dist/{rendering-options-ByAdi8PF.js → webgl-unavailable-N9nQqesw.js} +45 -1
  47. package/editor/assets/{agent8-C2jtNKbx.js → agent8-BrrHOjMJ.js} +1 -1
  48. package/editor/assets/{debug-DoU3KeqS.js → debug-BBhKuBNV.js} +1 -1
  49. package/editor/assets/{index-CNqHojcC.js → index-eVd0BToA.js} +92 -92
  50. package/editor/index.html +1 -1
  51. package/package.json +1 -1
  52. package/schemas/scene.schema.json +4 -0
  53. package/skills/incanto-3d-character.md +1 -0
  54. package/skills/incanto-audio.md +49 -0
  55. package/skills/incanto-building-2d-games.md +37 -0
  56. package/skills/incanto-building-3d-games.md +37 -0
  57. package/skills/incanto-gameplay-behaviors.md +28 -1
  58. package/skills/incanto-node-reference.md +1 -0
  59. package/skills/incanto-physics-and-input.md +23 -0
  60. package/skills/incanto-scene-json-authoring.md +12 -1
  61. package/templates-app/beacon-isle-3d/package.json +1 -1
  62. package/templates-app/beacon-isle-3d/src/main.ts +8 -1
  63. package/templates-app/platformer-2d/package.json +1 -1
  64. package/templates-app/platformer-2d/src/main.ts +8 -1
  65. package/templates-app/star-survivor/package.json +1 -1
  66. package/templates-app/star-survivor/src/main.ts +8 -1
  67. package/templates-app/tps-3d/package.json +1 -1
  68. package/templates-app/tps-3d/src/main.ts +8 -1
  69. package/templates-app/village-quest-3d/package.json +1 -1
  70. package/templates-app/village-quest-3d/src/main.ts +8 -1
@@ -1,15 +1,15 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { c as parseEnvironment3D, k as diagnose, n as loadScene, s as horizonColorFromSky, u as sunDirectionFromSky, z as registerBehavior } from "./loader-KZESOZmE.js";
3
- import { C as qualityRendering, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-Cs53CY7s.js";
2
+ import { c as parseEnvironment3D, k as diagnose, n as loadScene, s as horizonColorFromSky, u as sunDirectionFromSky, z as registerBehavior } from "./loader-BC4PNtJX.js";
3
+ import { C as qualityRendering, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-3ta-2Xig.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { n as attachTouchControls } from "./touch-DEAmqGdf.js";
6
- import { t as resolveRendering } from "./rendering-options-ByAdi8PF.js";
7
- import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-LB9cTnXJ.js";
6
+ import { n as withWebGLContext, r as resolveRendering } from "./webgl-unavailable-N9nQqesw.js";
7
+ import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-D2NEmxPB.js";
8
8
  import { o as frameStats } from "./frame-report-BSMny7oe.js";
9
- import { J as createCausticsQuad, K as Node3D, U as PhysicsBody3D, n as registerGameplayBehaviors } from "./gameplay-CGSv-9XR.js";
9
+ import { J as createCausticsQuad, K as Node3D, U as PhysicsBody3D, n as registerGameplayBehaviors } from "./gameplay-DM1eu_cV.js";
10
10
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
11
- import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-C-5g2pMh.js";
12
- import { n as enablePhysics3D } from "./physics-3d-i1IeAY0F.js";
11
+ import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-CybQXNqS.js";
12
+ import { n as enablePhysics3D } from "./physics-3d-bG3n70Ky.js";
13
13
  import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
14
14
  import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
15
15
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
@@ -87,22 +87,18 @@ var AssetStore3D = class {
87
87
  url,
88
88
  error
89
89
  });
90
- for (const [ref, url] of this.modelKeys) {
91
- const entry = this.models.get(url);
92
- if (entry?.error) out.push({
93
- ref,
94
- url,
95
- error: String(entry.error)
96
- });
97
- }
98
- for (const [ref, decl] of this.animationKeys) {
99
- const entry = this.animations.get(decl.url);
100
- if (entry?.error) out.push({
101
- ref,
102
- url: decl.url,
103
- error: String(entry.error)
104
- });
105
- }
90
+ const modelRefs = new Map([...this.modelKeys].map(([ref, url]) => [url, ref]));
91
+ for (const [url, entry] of this.models) if (entry.error) out.push({
92
+ ref: modelRefs.get(url) ?? url,
93
+ url,
94
+ error: String(entry.error)
95
+ });
96
+ const clipRefs = new Map([...this.animationKeys].map(([ref, decl]) => [decl.url, ref]));
97
+ for (const [url, entry] of this.animations) if (entry.error) out.push({
98
+ ref: clipRefs.get(url) ?? url,
99
+ url,
100
+ error: String(entry.error)
101
+ });
106
102
  return out;
107
103
  }
108
104
  /** Map a scene's `assets` header. */
@@ -1491,11 +1487,11 @@ var Renderer3D = class {
1491
1487
  pixelRatio: opts.pixelRatio,
1492
1488
  preserveDrawingBuffer: opts.preserveDrawingBuffer
1493
1489
  });
1494
- this.webgl = new WebGLRenderer({
1490
+ this.webgl = withWebGLContext(() => new WebGLRenderer({
1495
1491
  canvas: opts.canvas,
1496
1492
  antialias: rendering.antialias,
1497
1493
  preserveDrawingBuffer: rendering.preserveDrawingBuffer
1498
- });
1494
+ }));
1499
1495
  this.webgl.info.autoReset = false;
1500
1496
  this.engine.picker = (x, y) => this.pick(x, y);
1501
1497
  this.basePixelRatio = rendering.pixelRatio;
@@ -1,15 +1,15 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { d as computeViewport, f as resolveViewport, k as diagnose, n as loadScene, z as registerBehavior } from "./loader-KZESOZmE.js";
3
- import { _ as AudioPlayer, v as Engine } from "./register-Cs53CY7s.js";
2
+ import { d as computeViewport, f as resolveViewport, k as diagnose, n as loadScene, z as registerBehavior } from "./loader-BC4PNtJX.js";
3
+ import { _ as AudioPlayer, v as Engine } from "./register-3ta-2Xig.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { n as attachTouchControls } from "./touch-DEAmqGdf.js";
6
- import { t as resolveRendering } from "./rendering-options-ByAdi8PF.js";
7
- import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-LB9cTnXJ.js";
6
+ import { n as withWebGLContext, r as resolveRendering } from "./webgl-unavailable-N9nQqesw.js";
7
+ import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-D2NEmxPB.js";
8
8
  import { o as frameStats } from "./frame-report-BSMny7oe.js";
9
- import { n as registerGameplayBehaviors } from "./gameplay-CGSv-9XR.js";
10
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-vIxU3Xbv.js";
9
+ import { n as registerGameplayBehaviors } from "./gameplay-DM1eu_cV.js";
10
+ import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-ibCjm-wH.js";
11
11
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
12
- import { n as enablePhysics2D } from "./physics-2d-_B6zj3zU.js";
12
+ import { n as enablePhysics2D } from "./physics-2d-B7Y6dPZO.js";
13
13
  import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
14
14
  //#region src/2d/assets.ts
15
15
  /**
@@ -445,11 +445,11 @@ var Renderer2D = class {
445
445
  pixelRatio: 1,
446
446
  preserveDrawingBuffer: false
447
447
  }, globalThis.devicePixelRatio ?? 1, opts);
448
- this.webgl = new WebGLRenderer({
448
+ this.webgl = withWebGLContext(() => new WebGLRenderer({
449
449
  canvas: opts.canvas,
450
450
  antialias: rendering.antialias,
451
451
  preserveDrawingBuffer: rendering.preserveDrawingBuffer
452
- });
452
+ }));
453
453
  this.webgl.info.autoReset = false;
454
454
  this.engine.picker = (x, y) => this.pick(x, y);
455
455
  this.basePixelRatio = rendering.pixelRatio;
package/dist/debug.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Ft as LogLevel, T as RendererStats, b as Engine } from "./behavior-ZnfzKDo3.js";
1
+ import { Ft as LogLevel, T as RendererStats, b as Engine } from "./behavior-DoFPYrgo.js";
2
2
 
3
3
  //#region src/debug/panel.d.ts
4
4
  /** Minimal document surface the overlay needs (injectable for tests). */
@@ -1,4 +1,4 @@
1
- import { a as serializeNode, t as buildNodeJson } from "./loader-KZESOZmE.js";
1
+ import { a as serializeNode, t as buildNodeJson } from "./loader-BC4PNtJX.js";
2
2
  //#region src/core/scene/duplicate.ts
3
3
  function stripUids(json) {
4
4
  const { uid: _uid, ...rest } = json;
@@ -1,4 +1,4 @@
1
- import { s as JsonObject } from "./schema-CFeioQRE.js";
1
+ import { s as JsonObject } from "./schema-B6ugCV1Q.js";
2
2
 
3
3
  //#region src/core/diagnostics.d.ts
4
4
  /** The minimum an engine has to look like to receive a diagnostic. */
package/dist/editor.js CHANGED
@@ -2398,19 +2398,25 @@ function Hn(e, t, n) {
2398
2398
  t.mutate(() => {
2399
2399
  t.working.dimension = r.value;
2400
2400
  });
2401
- }), e.appendChild(B("dimension", r)), e.appendChild(Tr("physics", "physics"));
2402
- let i = (t.working.physics ?? {}).gravity ?? (t.working.dimension === "3d" ? [
2401
+ }), e.appendChild(B("dimension", r));
2402
+ let i = document.createElement("input");
2403
+ i.type = "checkbox", i.checked = t.working.fragment === !0, i.addEventListener("change", () => {
2404
+ t.mutate(() => {
2405
+ i.checked ? t.working.fragment = !0 : delete t.working.fragment;
2406
+ });
2407
+ }), e.appendChild(B("fragment", i)), e.appendChild(Tr("physics", "physics"));
2408
+ let a = (t.working.physics ?? {}).gravity ?? (t.working.dimension === "3d" ? [
2403
2409
  0,
2404
2410
  -9.81,
2405
2411
  0
2406
- ] : [0, 980]), a = t.working.dimension === "3d" ? [
2412
+ ] : [0, 980]), o = t.working.dimension === "3d" ? [
2407
2413
  0,
2408
2414
  -9.81,
2409
2415
  0
2410
2416
  ] : [0, 980];
2411
- e.appendChild(Mr("gravity", i, (e) => {
2417
+ e.appendChild(Mr("gravity", a, (e) => {
2412
2418
  t.mutate(() => {
2413
- if (JSON.stringify(e) === JSON.stringify(a)) {
2419
+ if (JSON.stringify(e) === JSON.stringify(o)) {
2414
2420
  let e = { ...t.working.physics };
2415
2421
  delete e.gravity, Object.keys(e).length === 0 ? delete t.working.physics : t.working.physics = e;
2416
2422
  } else t.working.physics = {
package/dist/env.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as Rng, i as SceneJson, n as NodeJson, s as JsonObject } from "./schema-CFeioQRE.js";
1
+ import { a as Rng, i as SceneJson, n as NodeJson, s as JsonObject } from "./schema-B6ugCV1Q.js";
2
2
 
3
3
  //#region src/env/arena.d.ts
4
4
  /** Valid `generateArena` themes — drives the catalog options AND validation. */
@@ -1,11 +1,11 @@
1
- import { k as diagnose } from "./loader-KZESOZmE.js";
2
- import { L as translateOn, t as registerCoreNodes } from "./register-Cs53CY7s.js";
1
+ import { k as diagnose } from "./loader-BC4PNtJX.js";
2
+ import { L as translateOn, t as registerCoreNodes } from "./register-3ta-2Xig.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { i as getNodeSchema, l as registerNode } from "./registry-CF70EArN.js";
6
6
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
7
7
  import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
8
- import { B as rejectScale, G as StaticBody3D, H as CharacterBody3D, K as Node3D, L as Water3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, X as colliderFootDrop, w as tolerateUnknownAction, z as WaterCutout3D } from "./gameplay-CGSv-9XR.js";
8
+ import { B as rejectScale, G as StaticBody3D, H as CharacterBody3D, I as worldPosition, K as Node3D, L as Water3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, X as colliderFootDrop, w as tolerateUnknownAction, z as WaterCutout3D } from "./gameplay-DM1eu_cV.js";
9
9
  import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
10
10
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
11
11
  import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, ClampToEdgeWrapping, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NoColorSpace, NormalBlending, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector3, Vector4, VectorKeyframeTrack } from "three";
@@ -2714,6 +2714,7 @@ var CharacterController3D = class extends Node3D {
2714
2714
  pitchMin: { default: -1.3 },
2715
2715
  pitchMax: { default: 1.5 },
2716
2716
  cameraCollision: { default: true },
2717
+ platformCarry: { default: true },
2717
2718
  turnSpeed: { default: 100 },
2718
2719
  camLerp: { default: 25 },
2719
2720
  moveAction: { default: "move" },
@@ -2766,6 +2767,43 @@ var CharacterController3D = class extends Node3D {
2766
2767
  pitchMax = 1.5;
2767
2768
  /** Spring-arm: pull the orbit camera in front of walls/ground instead of clipping. */
2768
2769
  cameraCollision = true;
2770
+ /**
2771
+ * Ride whatever you are standing on.
2772
+ *
2773
+ * `false` restores the old behaviour: the controller steers toward a target
2774
+ * velocity in WORLD space, so standing still on a moving floor brakes you off
2775
+ * it. Turn it off for a conveyor you want to be scenery.
2776
+ */
2777
+ platformCarry = true;
2778
+ /** The node the ground probe hit last step, and where it was. */
2779
+ carriedBy = null;
2780
+ carriedFrom = [0, 0];
2781
+ /**
2782
+ * How fast the floor is moving, in world units per second.
2783
+ *
2784
+ * From the POSITION DELTA, not `linearVelocity`, because the documented way
2785
+ * to author a platform is to move its `position` — a behavior, a `PathFollow`,
2786
+ * an `Oscillate` — and that path calls `body.setTranslation`, which teleports
2787
+ * and carries no momentum at all. The delta sees both kinds.
2788
+ *
2789
+ * Only while grounded on the SAME node two steps running: the first step on a
2790
+ * new platform has nothing to compare against, and inventing a velocity there
2791
+ * would fling the player on every step onto one.
2792
+ */
2793
+ groundCarry(ground, dt) {
2794
+ if (!this.platformCarry || !this.grounded || !ground || dt <= 0) {
2795
+ this.carriedBy = ground;
2796
+ if (ground) this.carriedFrom = groundXZ(ground);
2797
+ return [0, 0];
2798
+ }
2799
+ const at = groundXZ(ground);
2800
+ const same = this.carriedBy === ground;
2801
+ const was = this.carriedFrom;
2802
+ this.carriedBy = ground;
2803
+ this.carriedFrom = at;
2804
+ if (!same) return [0, 0];
2805
+ return [(at[0] - was[0]) / dt, (at[1] - was[1]) / dt];
2806
+ }
2769
2807
  turnSpeed = 100;
2770
2808
  /** Camera smoothing rate (1-exp(-rate·dt)); side view uses 5. */
2771
2809
  camLerp = 25;
@@ -2830,6 +2868,8 @@ var CharacterController3D = class extends Node3D {
2830
2868
  const rayLen = radius + 2;
2831
2869
  const originY = pos[1] - halfHeight;
2832
2870
  let toi = null;
2871
+ /** What the probe landed on — the platform, when there is one. */
2872
+ let ground = null;
2833
2873
  for (const [ox, oz] of [
2834
2874
  [0, 0],
2835
2875
  [radius, 0],
@@ -2848,11 +2888,15 @@ var CharacterController3D = class extends Node3D {
2848
2888
  ], rayLen, body);
2849
2889
  if (hit) {
2850
2890
  const d = hit.distance;
2851
- toi = toi === null ? d : Math.min(toi, d);
2891
+ if (toi === null || d < toi) {
2892
+ toi = d;
2893
+ ground = hit.node;
2894
+ }
2852
2895
  if (ox === 0 && oz === 0) break;
2853
2896
  }
2854
2897
  }
2855
2898
  this.grounded = toi !== null && toi < floatingDis * 2;
2899
+ const carry = this.groundCarry(ground, dt);
2856
2900
  const footed = toi !== null && toi < floatingDis * 1.3 && (vel[1] ?? 0) <= .5;
2857
2901
  let mx = 0;
2858
2902
  let mz = 0;
@@ -2877,17 +2921,17 @@ var CharacterController3D = class extends Node3D {
2877
2921
  const speedMultiplier = 1 + (this.sprintMultiplier - 1) * intensity;
2878
2922
  const targetSpeed = moving ? this.maxSpeed * speedMultiplier : 0;
2879
2923
  const control = this.grounded ? 1 : this.airControl;
2880
- const ax = (mx * targetSpeed - vel[0]) / ACC_DELTA_TIME * control;
2881
- const az = (mz * targetSpeed - vel[2]) / ACC_DELTA_TIME * control;
2924
+ const ax = (mx * targetSpeed + carry[0] - vel[0]) / ACC_DELTA_TIME * control;
2925
+ const az = (mz * targetSpeed + carry[1] - vel[2]) / ACC_DELTA_TIME * control;
2882
2926
  body.applyImpulse([
2883
2927
  ax * mass,
2884
2928
  0,
2885
2929
  az * mass
2886
2930
  ]);
2887
2931
  if (!moving && this.grounded) body.applyImpulse([
2888
- -vel[0] * DRAG_DAMPING * mass,
2932
+ -(vel[0] - carry[0]) * DRAG_DAMPING * mass,
2889
2933
  0,
2890
- -vel[2] * DRAG_DAMPING * mass
2934
+ -(vel[2] - carry[1]) * DRAG_DAMPING * mass
2891
2935
  ]);
2892
2936
  let springToi = toi;
2893
2937
  if (this.stepHeight > 0 && moving && toi !== null && !this.jumping && (vel[1] ?? 0) <= .5) {
@@ -3137,6 +3181,11 @@ function worldOf(node) {
3137
3181
  * coyote time incoherent (a held button would re-fire through the whole
3138
3182
  * window). Undeclared actions are tolerated, and reported once.
3139
3183
  */
3184
+ /** A node's world x/z — the platform may itself be parented to something. */
3185
+ function groundXZ(node) {
3186
+ const world = worldPosition(node);
3187
+ return [world[0] ?? 0, world[2] ?? 0];
3188
+ }
3140
3189
  //#endregion
3141
3190
  //#region src/3d/vegetation/grass-placement.ts
3142
3191
  const NOISE_SCALE = .15;
@@ -1,7 +1,7 @@
1
- import { O as Node, P as Behavior, _ as SaveSlots, k as diagnose, n as loadScene, w as effectiveOrder, z as registerBehavior } from "./loader-KZESOZmE.js";
1
+ import { O as Node, P as Behavior, _ as SaveSlots, k as diagnose, n as loadScene, w as effectiveOrder, z as registerBehavior } from "./loader-BC4PNtJX.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { t as jsonClone } from "./json-CwwhxQgb.js";
4
- import { t as duplicateNode } from "./duplicate-B8QnX2-r.js";
4
+ import { t as duplicateNode } from "./duplicate-IWIqk0HJ.js";
5
5
  import { Color, CubeCamera, DepthTexture, DoubleSide, Euler, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
6
6
  //#region src/3d/frustum.ts
7
7
  const scratchFrustum = new Frustum();
@@ -6675,6 +6675,20 @@ var SavePoint = class extends Behavior {
6675
6675
  this.slots ??= new SaveSlots(this.game);
6676
6676
  return this.slots;
6677
6677
  }
6678
+ /**
6679
+ * Has this run already said the saves are going nowhere?
6680
+ *
6681
+ * `saved(slot)` is the engine's own instrument for "it saved", and it fired
6682
+ * identically in a private window where the write landed in a Map that dies
6683
+ * with the tab. In-session everything looked healthy — the slot read back,
6684
+ * `slotScene()` answered — and across the reload the score went 5 to 0 and
6685
+ * `list()` went `["1"]` to `[]`, with `engine.log.entries().length = 0` and
6686
+ * `stats().errors = 0` in every phase. Byte-identical to a healthy control.
6687
+ *
6688
+ * So it goes through `engine.log`, not just the console: that is what
6689
+ * `incanto-logs` reads and what the `says` rung reports.
6690
+ */
6691
+ warnedVolatile = false;
6678
6692
  pendingRestore = false;
6679
6693
  pendingProbe = false;
6680
6694
  onReady() {
@@ -6734,6 +6748,10 @@ var SavePoint = class extends Behavior {
6734
6748
  savedAt: Date.now(),
6735
6749
  playtime: Math.round(this.elapsed)
6736
6750
  });
6751
+ if (!this.store().persistent && !this.warnedVolatile) {
6752
+ this.warnedVolatile = true;
6753
+ engine.log.warn(`SavePoint wrote slot '${this.slot}' to memory, not to disk — this browser refused localStorage (private window, storage disabled, or quota exhausted), so the run is lost when the page closes. \`SaveSlots.persistent\` is how a game asks before promising the player anything.`);
6754
+ }
6737
6755
  this.emit("saved", this.slot);
6738
6756
  }
6739
6757
  /**
@@ -1,5 +1,5 @@
1
- import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-ZnfzKDo3.js";
2
- import { c as JsonValue, s as JsonObject } from "./schema-CFeioQRE.js";
1
+ import { b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, t as Behavior } from "./behavior-DoFPYrgo.js";
2
+ import { c as JsonValue, s as JsonObject } from "./schema-B6ugCV1Q.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
5
5
  /**
@@ -558,6 +558,20 @@ declare class SavePoint extends Behavior {
558
558
  private elapsed;
559
559
  private slots;
560
560
  private store;
561
+ /**
562
+ * Has this run already said the saves are going nowhere?
563
+ *
564
+ * `saved(slot)` is the engine's own instrument for "it saved", and it fired
565
+ * identically in a private window where the write landed in a Map that dies
566
+ * with the tab. In-session everything looked healthy — the slot read back,
567
+ * `slotScene()` answered — and across the reload the score went 5 to 0 and
568
+ * `list()` went `["1"]` to `[]`, with `engine.log.entries().length = 0` and
569
+ * `stats().errors = 0` in every phase. Byte-identical to a healthy control.
570
+ *
571
+ * So it goes through `engine.log`, not just the console: that is what
572
+ * `incanto-logs` reads and what the `says` rung reports.
573
+ */
574
+ private warnedVolatile;
561
575
  private pendingRestore;
562
576
  private pendingProbe;
563
577
  override onReady(): void;
@@ -1071,6 +1085,40 @@ declare class PathFollow extends Behavior {
1071
1085
  override update(dt: number): void;
1072
1086
  }
1073
1087
  //#endregion
1088
+ //#region src/gameplay/spatial.d.ts
1089
+ /**
1090
+ * A node's position with every spatial ancestor's added in.
1091
+ *
1092
+ * The tree composes transforms for DRAWING — three does it for free — and every
1093
+ * piece of engine logic that reads `node.position` gets the LOCAL number
1094
+ * instead. Physics already knew (`worldPosition2D`/`worldPosition3D` in the
1095
+ * adapters); the camera did not, and neither did anything that chases a target.
1096
+ *
1097
+ * A camera parented to the player is the Godot and Phaser idiom, and the
1098
+ * authoring skill explicitly permits it — *"EVERY node type can hold children …
1099
+ * any node works as a grouping container"*. Measured on exactly that scene:
1100
+ *
1101
+ * ```
1102
+ * player world position : [1400, 900]
1103
+ * renderer view centre : {"x":0,"y":0}
1104
+ * framing : camera /Level/Player/Cam centred [1400, 900]
1105
+ * 1 in view, 0 outside it
1106
+ * ```
1107
+ *
1108
+ * The renderer drew empty world 1600 px from the player while `framing` — the
1109
+ * instrument the skills tell you to trust before handing a game back —
1110
+ * certified the player on screen.
1111
+ *
1112
+ * Arity comes from the node itself: a 2D node has two components, a 3D node
1113
+ * three, and an ancestor that is neither contributes nothing.
1114
+ */
1115
+ declare function worldPosition(node: Node): number[];
1116
+ /**
1117
+ * Turn a world position into the local one `node.position` has to hold for the
1118
+ * node to END UP there — the inverse of `worldPosition` for its parent chain.
1119
+ */
1120
+ declare function localFromWorld(node: Node, world: readonly number[]): number[];
1121
+ //#endregion
1074
1122
  //#region src/gameplay/index.d.ts
1075
1123
  /** Every built-in gameplay behavior, keyed by its registration name. */
1076
1124
  declare const GAMEPLAY_BEHAVIORS: Readonly<Record<string, BehaviorCtor>>;
@@ -1083,4 +1131,4 @@ declare function registerGameplayBehaviors(opts?: {
1083
1131
  replace?: boolean;
1084
1132
  }): void;
1085
1133
  //#endregion
1086
- export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, type DayPhase, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
1134
+ export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, type DayPhase, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as DamageOnContact, C as restartScene, D as FaceTarget, E as FloatAway, M as Currency, N as Collector, O as DayNight, P as Chase, S as goToScene, T as FollowCamera, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as Health, k as phaseOf, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-CGSv-9XR.js";
2
- export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
1
+ import { A as DamageOnContact, C as restartScene, D as FaceTarget, E as FloatAway, F as localFromWorld, I as worldPosition, M as Currency, N as Collector, O as DayNight, P as Chase, S as goToScene, T as FollowCamera, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as Health, k as phaseOf, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-DM1eu_cV.js";
2
+ export { CameraShake, Chase, Collector, Cooldown, Currency, DamageOnContact, DayNight, FaceTarget, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, localFromWorld, phaseOf, registerGameplayBehaviors, restartScene, screenFlash, worldPosition };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { $ as T_PREFIX, A as SettingsValues, At as BusName, B as captureBehaviors, C as EngineStats, Ct as sfxDuration, Dt as MusicTrack, E as DeviceHints, Et as MusicManager, F as BehaviorState, Ft as LogLevel, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as LogManager, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as Signal, M as readDeviceHints, Mt as NodeLifecycle, N as suggestQuality, Nt as SceneTree, O as QualityTier, Ot as PlayMusicOptions, P as Scene, Pt as LogEntry, Q as Localization, R as SaveSlots, Rt as SignalListener, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicBackend, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as spatialPan, a as clearBehaviors, at as EffectLog, b as Engine, bt as SfxParams, c as registeredBehaviors, ct as isAudioContextAvailable, d as PropSchema, dt as Listener, et as suggestLocale, f as clearRegistry, ft as ROLLOFF_MODELS, g as getNodeType, gt as spatialGain, h as getNodeSignals, ht as Vec3, i as behaviorSignals, it as EffectKind, j as qualityEnvironment, jt as Node, k as Settings, kt as AudioBuses, l as NodeCtor, lt as Voice, m as getNodeSchema, mt as SpatialParams, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as SfxEngine, p as createNode, pt as RolloffModel, q as OrderGroupTable, r as behaviorSchema, rt as EffectEvent, s as registerBehavior, st as SfxPlayOptions, t as Behavior, tt as translationKey, u as PropDef, ut as VoicePreset, v as registerNode, vt as SFX_PRESETS, w as GameStats, wt as synthSfx, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-ZnfzKDo3.js";
2
- import { a as Rng, c as JsonValue, d as jsonKind, i as SceneJson, l as jsonClone, n as NodeJson, o as JsonKind, r as SCENE_FORMAT, s as JsonObject, t as ConnectionJson, u as jsonEquals } from "./schema-CFeioQRE.js";
3
- import { n as loadScene, t as LoadSceneOptions } from "./loader-DyT30ArB.js";
1
+ import { a as ParticleSimConfig, c as showBootFailure, i as ParticleSim, n as AudioPlayer, o as ParticleView, s as isWebGLAvailable, t as AudioElementLike } from "./audio-player-DOrq7sP-.js";
2
+ import { $ as T_PREFIX, A as SettingsValues, At as BusName, B as captureBehaviors, C as EngineStats, Ct as sfxDuration, Dt as MusicTrack, E as DeviceHints, Et as MusicManager, F as BehaviorState, Ft as LogLevel, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as LogManager, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as Signal, M as readDeviceHints, Mt as NodeLifecycle, N as suggestQuality, Nt as SceneTree, O as QualityTier, Ot as PlayMusicOptions, P as Scene, Pt as LogEntry, Q as Localization, R as SaveSlots, Rt as SignalListener, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicBackend, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as spatialPan, a as clearBehaviors, at as EffectLog, b as Engine, bt as SfxParams, c as registeredBehaviors, ct as isAudioContextAvailable, d as PropSchema, dt as Listener, et as suggestLocale, f as clearRegistry, ft as ROLLOFF_MODELS, g as getNodeType, gt as spatialGain, h as getNodeSignals, ht as Vec3, i as behaviorSignals, it as EffectKind, j as qualityEnvironment, jt as Node, k as Settings, kt as AudioBuses, l as NodeCtor, lt as Voice, m as getNodeSchema, mt as SpatialParams, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as SfxEngine, p as createNode, pt as RolloffModel, q as OrderGroupTable, r as behaviorSchema, rt as EffectEvent, s as registerBehavior, st as SfxPlayOptions, t as Behavior, tt as translationKey, u as PropDef, ut as VoicePreset, v as registerNode, vt as SFX_PRESETS, w as GameStats, wt as synthSfx, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-DoFPYrgo.js";
3
+ import { a as Rng, c as JsonValue, d as jsonKind, i as SceneJson, l as jsonClone, n as NodeJson, o as JsonKind, r as SCENE_FORMAT, s as JsonObject, t as ConnectionJson, u as jsonEquals } from "./schema-B6ugCV1Q.js";
4
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-CcB533FR.js";
4
5
  import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js";
5
- import { a as ParticleSimConfig, i as ParticleSim, n as AudioPlayer, o as ParticleView, t as AudioElementLike } from "./audio-player-BNUHo0JF.js";
6
6
  import { i as gridFromRows, n as PathGrid, r as findPath, t as FindPathOptions } from "./pathfinding-_fGrCFmH.js";
7
- import { a as startRecording, c as IncantoErrorDetails, i as replay, l as auditScene, n as ReplayEvent, o as IncantoError, r as ReplayJson, s as IncantoErrorCode, t as Recorder } from "./replay-D0GcEHYU.js";
7
+ import { a as startRecording, c as IncantoErrorDetails, i as replay, l as auditScene, n as ReplayEvent, o as IncantoError, r as ReplayJson, s as IncantoErrorCode, t as Recorder } from "./replay-Dvn8aeBd.js";
8
8
 
9
9
  //#region src/core/audio/crossfade.d.ts
10
10
  /**
@@ -1309,4 +1309,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
1309
1309
  /** Engine version. Kept in sync with package.json by the release pipeline. */
1310
1310
  declare const VERSION: string;
1311
1311
  //#endregion
1312
- export { type AnimationDef, type AnimationEntry, type AssetFailure, AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, type DriveScript, type DriveStep, type EffectEvent, type EffectKind, EffectLog, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, type OrderGroupTable, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RefNode, type RefProblem, type RefResult, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, Respawn, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
1312
+ export { type AnimationDef, type AnimationEntry, type AssetFailure, AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, type DriveScript, type DriveStep, type EffectEvent, type EffectKind, EffectLog, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, type OrderGroupTable, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RefNode, type RefProblem, type RefResult, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, Respawn, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, isWebGLAvailable, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, showBootFailure, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
- import { A as describeRefProblem, B as registeredBehaviors, C as ORDER_GROUP_BASE, E as InputMap, F as behaviorSchema, I as behaviorSignals, L as clearBehaviors, M as resolveRefInJson, N as parseNodePath, O as Node, P as Behavior, R as getBehavior, S as createSaveStore, T as resolveOrderGroups, V as Signal, _ as SaveSlots, a as serializeNode, b as restoreBehaviors, d as computeViewport, f as resolveViewport, g as resolveConstants, h as isConstRef, i as Scene, j as nodeRefWarnings, m as CONST_REF_KEY, n as loadScene, o as SCENE_FORMAT, p as SceneTree, v as behaviorsWithoutSave, w as effectiveOrder, x as savesWithoutUid, y as captureBehaviors, z as registerBehavior } from "./loader-KZESOZmE.js";
2
- import { A as UiBanner, B as isAudioContextAvailable, D as EffectLog, E as LogManager, F as T_PREFIX, G as SFX_PRESET_NAMES, H as spatialGain, I as suggestLocale, J as MusicManager, K as sfxDuration, M as UiText, N as BASE_LOCALE, O as HudLayer, P as Localization, Q as AudioBuses, R as translationKey, S as qualityEnvironment, T as suggestQuality, U as spatialPan, V as ROLLOFF_MODELS, W as SFX_PRESETS, X as crossfadeGains, Y as WebAudioMusicBackend, Z as fadeGain, _ as AudioPlayer, a as UiMuteToggle, b as Settings, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as UiDialogue, h as UiButton, i as UiLanguageSelect, j as UiBar, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as synthSfx, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SfxEngine } from "./register-Cs53CY7s.js";
1
+ import { A as describeRefProblem, B as registeredBehaviors, C as ORDER_GROUP_BASE, E as InputMap, F as behaviorSchema, I as behaviorSignals, L as clearBehaviors, M as resolveRefInJson, N as parseNodePath, O as Node, P as Behavior, R as getBehavior, S as createSaveStore, T as resolveOrderGroups, V as Signal, _ as SaveSlots, a as serializeNode, b as restoreBehaviors, d as computeViewport, f as resolveViewport, g as resolveConstants, h as isConstRef, i as Scene, j as nodeRefWarnings, m as CONST_REF_KEY, n as loadScene, o as SCENE_FORMAT, p as SceneTree, v as behaviorsWithoutSave, w as effectiveOrder, x as savesWithoutUid, y as captureBehaviors, z as registerBehavior } from "./loader-BC4PNtJX.js";
2
+ import { A as UiBanner, B as isAudioContextAvailable, D as EffectLog, E as LogManager, F as T_PREFIX, G as SFX_PRESET_NAMES, H as spatialGain, I as suggestLocale, J as MusicManager, K as sfxDuration, M as UiText, N as BASE_LOCALE, O as HudLayer, P as Localization, Q as AudioBuses, R as translationKey, S as qualityEnvironment, T as suggestQuality, U as spatialPan, V as ROLLOFF_MODELS, W as SFX_PRESETS, X as crossfadeGains, Y as WebAudioMusicBackend, Z as fadeGain, _ as AudioPlayer, a as UiMuteToggle, b as Settings, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as UiDialogue, h as UiButton, i as UiLanguageSelect, j as UiBar, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as synthSfx, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SfxEngine } from "./register-3ta-2Xig.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-CwwhxQgb.js";
6
6
  import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-CF70EArN.js";
7
7
  import { i as joystickVector, n as attachTouchControls, t as TouchControls } from "./touch-DEAmqGdf.js";
8
- import { n as startRecording, r as auditScene, t as replay } from "./replay-BkpDV51O.js";
9
- import { i as parseDrive, n as logReport, r as logText, t as resolveRendering } from "./rendering-options-ByAdi8PF.js";
8
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-CYvhVHHN.js";
9
+ import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
10
+ import { a as logText, i as logReport, o as parseDrive, r as resolveRendering, t as isWebGLAvailable } from "./webgl-unavailable-N9nQqesw.js";
10
11
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
11
12
  import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
12
- import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-BLlsGJW9.js";
13
- import { t as duplicateNode } from "./duplicate-B8QnX2-r.js";
14
- export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Respawn, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
13
+ import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-D7RIqXYF.js";
14
+ import { t as duplicateNode } from "./duplicate-IWIqk0HJ.js";
15
+ export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Respawn, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, isWebGLAvailable, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, showBootFailure, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -1570,10 +1570,16 @@ function effectiveOrder(group, renderOrder, table = ORDER_GROUP_BASE) {
1570
1570
  //#endregion
1571
1571
  //#region src/core/save.ts
1572
1572
  const memoryStores = /* @__PURE__ */ new Map();
1573
+ /** Namespaces already warned about, so a per-frame save cannot spam the console. */
1574
+ const warnedVolatile = /* @__PURE__ */ new Set();
1573
1575
  function createSaveStore(namespace) {
1574
1576
  const prefix = `incanto:${namespace}:`;
1575
1577
  const local = storageOrNull();
1576
1578
  if (!local) {
1579
+ if (typeof localStorage !== "undefined" && !warnedVolatile.has(namespace)) {
1580
+ warnedVolatile.add(namespace);
1581
+ console.warn(`[incanto] this browser refused localStorage, so saves for '${namespace}' live in memory and are lost when the page closes (private window, storage disabled, or quota exhausted). Check \`store.persistent\` and tell the player.`);
1582
+ }
1577
1583
  let mem = memoryStores.get(namespace);
1578
1584
  if (!mem) {
1579
1585
  mem = /* @__PURE__ */ new Map();
@@ -1581,6 +1587,7 @@ function createSaveStore(namespace) {
1581
1587
  }
1582
1588
  const backing = mem;
1583
1589
  return {
1590
+ persistent: false,
1584
1591
  get: (key, fallback) => parse(backing.get(key), fallback),
1585
1592
  set: (key, value) => void backing.set(key, JSON.stringify(value)),
1586
1593
  remove: (key) => void backing.delete(key),
@@ -1588,6 +1595,7 @@ function createSaveStore(namespace) {
1588
1595
  };
1589
1596
  }
1590
1597
  return {
1598
+ persistent: true,
1591
1599
  get: (key, fallback) => parse(local.getItem(prefix + key) ?? void 0, fallback),
1592
1600
  set: (key, value) => local.setItem(prefix + key, JSON.stringify(value)),
1593
1601
  remove: (key) => local.removeItem(prefix + key),
@@ -1834,6 +1842,17 @@ var SaveSlots = class {
1834
1842
  constructor(namespace = "saves", store) {
1835
1843
  this.store = store ?? createSaveStore(namespace);
1836
1844
  }
1845
+ /**
1846
+ * Will these slots survive the page closing?
1847
+ *
1848
+ * `false` in a private window, with storage disabled, or headless — every
1849
+ * write still succeeds and every read still answers, for as long as the tab
1850
+ * lives. A load menu should say so instead of offering a slot that will not
1851
+ * be there tomorrow.
1852
+ */
1853
+ get persistent() {
1854
+ return this.store.persistent !== false;
1855
+ }
1837
1856
  /** Slot ids that have a save, newest first. */
1838
1857
  list() {
1839
1858
  const rows = this.store.get(INDEX_KEY, []).map((id) => ({
@@ -2850,6 +2869,8 @@ var Scene = class {
2850
2869
  orderGroups;
2851
2870
  /** Physics config (gravity etc. — interpreted by the physics modules). */
2852
2871
  physics;
2872
+ /** Declared piece of another scene — see `SceneJson.fragment`. */
2873
+ fragment;
2853
2874
  /** Multiplayer config (room etc. — interpreted by incanto/net). */
2854
2875
  multiplayer;
2855
2876
  /** Design-resolution viewport (consumed by renderers via resolveViewport). */
@@ -2869,6 +2890,7 @@ var Scene = class {
2869
2890
  this.orderGroups = source.orderGroups ? { ...source.orderGroups } : void 0;
2870
2891
  this.strings = source.strings ? jsonClone(source.strings) : void 0;
2871
2892
  this.physics = source.physics ? jsonClone(source.physics) : void 0;
2893
+ this.fragment = source.fragment === true ? true : void 0;
2872
2894
  this.multiplayer = source.multiplayer ? jsonClone(source.multiplayer) : void 0;
2873
2895
  this.environment = source.environment ? jsonClone(source.environment) : void 0;
2874
2896
  this.viewport = source.viewport ? jsonClone(source.viewport) : void 0;
@@ -2887,6 +2909,7 @@ var Scene = class {
2887
2909
  ...this.orderGroups ? { orderGroups: { ...this.orderGroups } } : {},
2888
2910
  ...this.strings ? { strings: jsonClone(this.strings) } : {},
2889
2911
  ...this.physics ? { physics: jsonClone(this.physics) } : {},
2912
+ ...this.fragment ? { fragment: true } : {},
2890
2913
  ...this.multiplayer ? { multiplayer: jsonClone(this.multiplayer) } : {},
2891
2914
  ...this.environment ? { environment: jsonClone(this.environment) } : {},
2892
2915
  ...this.viewport ? { viewport: jsonClone(this.viewport) } : {},
@@ -3063,6 +3086,7 @@ function validateHeader(json) {
3063
3086
  if (json.format !== 1) throw new IncantoError("BAD_FORMAT", `Unsupported scene format ${JSON.stringify(json.format)}; this engine reads format 1.`);
3064
3087
  if (json.type !== "scene") throw new IncantoError("BAD_FORMAT", `Expected "type": "scene", got ${JSON.stringify(json.type)}.`);
3065
3088
  if (typeof json.name !== "string" || json.name === "") throw new IncantoError("BAD_FORMAT", "Scene \"name\" must be a non-empty string.");
3089
+ if (json.fragment !== void 0 && typeof json.fragment !== "boolean") throw new IncantoError("BAD_FORMAT", `Scene "fragment" must be true or false, got ${JSON.stringify(json.fragment)}.`);
3066
3090
  if (json.dimension !== void 0 && json.dimension !== "2d" && json.dimension !== "3d") throw new IncantoError("BAD_FORMAT", `Scene "dimension" must be "2d" or "3d", got ${JSON.stringify(json.dimension)}.`);
3067
3091
  if (typeof json.root !== "object" || json.root === null) throw new IncantoError("BAD_FORMAT", "Scene \"root\" must be a node object.");
3068
3092
  resolveViewport(json.viewport);
@@ -3154,10 +3178,11 @@ const SCENE_KEYS = [
3154
3178
  "environment",
3155
3179
  "strings",
3156
3180
  "physics",
3181
+ "fragment",
3157
3182
  "root",
3158
3183
  "connections"
3159
3184
  ];
3160
- /** Closest known key by edit distance — only a genuinely близкая one. */
3185
+ /** Closest known key by edit distance — only a genuinely near one. */
3161
3186
  function nearestKey(key, known) {
3162
3187
  const lower = key.toLowerCase();
3163
3188
  let best = null;
@@ -1,5 +1,5 @@
1
- import { P as Scene, b as Engine } from "./behavior-ZnfzKDo3.js";
2
- import { i as SceneJson } from "./schema-CFeioQRE.js";
1
+ import { P as Scene, b as Engine } from "./behavior-DoFPYrgo.js";
2
+ import { i as SceneJson } from "./schema-B6ugCV1Q.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
5
5
  interface LoadSceneOptions {
package/dist/net.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as PropSchema, jt as Node } from "./behavior-ZnfzKDo3.js";
2
- import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen-DD5eYIgW.js";
1
+ import { d as PropSchema, jt as Node } from "./behavior-DoFPYrgo.js";
2
+ import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen-DhrSzZIB.js";
3
3
 
4
4
  //#region src/net/agent8.d.ts
5
5
  /**
package/dist/net.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
2
- import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-DS8RaUV_.js";
2
+ import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-CYwDkbLF.js";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };