incanto 0.74.0 → 0.75.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 (92) hide show
  1. package/bin/incanto-multiplay.mjs +52 -4
  2. package/dist/2d.d.ts +83 -7
  3. package/dist/2d.js +4 -3
  4. package/dist/3d.d.ts +743 -167
  5. package/dist/3d.js +6 -6
  6. package/dist/{agent8-CHTT4unP.js → agent8-B-bh3n0X.js} +1 -1
  7. package/dist/{audio-player-BOrk4eQx.d.ts → audio-player-BRo2uvG6.d.ts} +23 -2
  8. package/dist/{behavior-J1cmVah0.d.ts → behavior-B_245qRy.d.ts} +15 -1
  9. package/dist/{create-game-BAiA-FjP.js → create-game-DVUhHs2B.js} +79 -10
  10. package/dist/{create-game-ssvGvP7W.js → create-game-DnwhJ5nW.js} +83 -7
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-O8mxpgRX.d.ts} +1 -1
  13. package/dist/editor.js +1600 -1529
  14. package/dist/env.d.ts +1 -1
  15. package/dist/{environment-presets-BkqlWewf.js → environment-presets-BP77Dl0K.js} +2005 -60
  16. package/dist/{gameplay-Bxe1sMVT.js → gameplay-D-JuGura.js} +9309 -6595
  17. package/dist/gameplay.d.ts +573 -31
  18. package/dist/gameplay.js +3 -2
  19. package/dist/index.d.ts +150 -6
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-BAeWMYfF.d.ts → loader-CbkVdXL8.d.ts} +2 -2
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +2 -2
  24. package/dist/{physics-2d-C-SfvjzH.js → physics-2d-x3bCrQf0.js} +250 -31
  25. package/dist/{physics-3d-rOzeg890.js → physics-3d-Bz2VPQ6D.js} +673 -51
  26. package/dist/{picking-BMV34Pjl.js → picking-Buita5T9.js} +2 -2
  27. package/dist/{save-slots-CEUGeLuB.js → pose-ByFB_J3O.js} +217 -11
  28. package/dist/react.d.ts +2 -2
  29. package/dist/react.js +1 -1
  30. package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
  31. package/dist/{register-DRQyZKGU.js → register-DxAbTO2T.js} +191 -430
  32. package/dist/{replay-B6BbgGM_.js → replay-DrqQ6dZj.js} +133 -18
  33. package/dist/{replay-wZK5Ok5c.d.ts → replay-W5nCw_cU.d.ts} +21 -2
  34. package/dist/{rng-Bb-IutXB.d.ts → rng-BsXZg3D6.d.ts} +7 -0
  35. package/dist/{split-screen-BxZ5rBe7.js → split-screen-B-dIcQsQ.js} +10 -5
  36. package/dist/{split-screen-D6sx8z3Z.d.ts → split-screen-CiLJf2-C.d.ts} +3 -3
  37. package/dist/{sprite-animation-Bz3DHD_4.js → sprite-animation-SQa5gIu2.js} +8 -6
  38. package/dist/{src-CqtvYtSN.js → src-DLKVc6Hj.js} +1 -1
  39. package/dist/{test-CEl0y2Sw.js → test-HlVUeCSn.js} +487 -82
  40. package/dist/test.d.ts +90 -12
  41. package/dist/test.js +2 -2
  42. package/dist/vite.js +3 -3
  43. package/editor/assets/{agent8-BlzturGi.js → agent8-BXlYM9rv.js} +1 -1
  44. package/editor/assets/{debug-BMUi8usj.js → debug-S42XGP4Y.js} +1 -1
  45. package/editor/assets/index-DgrgUKno.js +11046 -0
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +1630 -299
  49. package/skills/incanto-3d-character.md +358 -20
  50. package/skills/incanto-audio.md +24 -2
  51. package/skills/incanto-building-2d-games.md +21 -2
  52. package/skills/incanto-building-3d-games.md +38 -3
  53. package/skills/incanto-editor.md +17 -6
  54. package/skills/incanto-environment.md +227 -2
  55. package/skills/incanto-game-feel.md +60 -1
  56. package/skills/incanto-gameplay-behaviors.md +668 -20
  57. package/skills/incanto-hud.md +116 -0
  58. package/skills/incanto-multiplayer.md +141 -2
  59. package/skills/incanto-node-reference.md +393 -16
  60. package/skills/incanto-performance.md +32 -0
  61. package/skills/incanto-physics-and-input.md +241 -13
  62. package/skills/incanto-playtesting.md +2 -2
  63. package/skills/incanto-save-slots.md +61 -2
  64. package/skills/incanto-scene-json-authoring.md +13 -0
  65. package/skills/incanto-verifying-your-game.md +86 -3
  66. package/templates-app/beacon-isle-3d/PROJECT/Status.md +12 -0
  67. package/templates-app/beacon-isle-3d/package.json +1 -1
  68. package/templates-app/beacon-isle-3d/src/game.scene.json +151 -2
  69. package/templates-app/molehill-2d/PROJECT/Status.md +7 -0
  70. package/templates-app/molehill-2d/package.json +1 -1
  71. package/templates-app/molehill-2d/src/game.scene.json +129 -2
  72. package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
  73. package/templates-app/platformer-2d/docs/project-2d-rules.md +3 -2
  74. package/templates-app/platformer-2d/package.json +1 -1
  75. package/templates-app/platformer-2d/src/behaviors.ts +40 -26
  76. package/templates-app/platformer-2d/src/game.scene.json +144 -2
  77. package/templates-app/platformer-2d/verify.ts +18 -1
  78. package/templates-app/star-survivor/PROJECT/Status.md +7 -0
  79. package/templates-app/star-survivor/package.json +1 -1
  80. package/templates-app/star-survivor/src/behaviors.ts +9 -8
  81. package/templates-app/star-survivor/src/game.scene.json +142 -4
  82. package/templates-app/star-survivor/verify.ts +13 -0
  83. package/templates-app/tps-3d/PROJECT/Status.md +6 -0
  84. package/templates-app/tps-3d/package.json +1 -1
  85. package/templates-app/tps-3d/src/game.scene.json +71 -10
  86. package/templates-app/village-quest-3d/PROJECT/Status.md +7 -0
  87. package/templates-app/village-quest-3d/package.json +1 -1
  88. package/templates-app/village-quest-3d/src/behaviors.ts +8 -16
  89. package/templates-app/village-quest-3d/src/grove.scene.json +121 -5
  90. package/templates-app/village-quest-3d/src/village.scene.json +126 -2
  91. package/templates-app/village-quest-3d/verify.ts +52 -2
  92. package/editor/assets/index-CrUCQoaB.js +0 -11046
package/dist/3d.js CHANGED
@@ -1,12 +1,12 @@
1
- import { C as sunDirectionFromSky, S as sunDirectionFromElevationAzimuth, b as horizonColorFromSky, x as parseEnvironment3D } from "./save-slots-CEUGeLuB.js";
1
+ import { D as sunDirectionFromElevationAzimuth, E as parseEnvironment3D, O as sunDirectionFromSky, T as horizonColorFromSky } from "./pose-ByFB_J3O.js";
2
2
  import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
3
3
  import { t as isWebGLAvailable } from "./webgl-unavailable-C8aDbGmR.js";
4
4
  import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-D-_7YF2G.js";
5
- import { B as WATER_CUTOUT_MAX, G as PhysicsBody3D, J as Node3D, K as RigidBody3D, U as Area3D, V as WaterCutout3D, W as CharacterBody3D, Z as WATER_MAX_RIPPLES, q as StaticBody3D, z as Water3D } from "./gameplay-Bxe1sMVT.js";
6
- import { A as Billboard3D, B as cameraRelative, C as Flowers3D, D as Camera3D, E as CharacterController3D, F as Joint3D, G as acquireOwnTexture, H as movementState, I as InstancedMesh3D, K as acquireTexture, L as MeshInstance3D, M as DEFAULT_TERRAIN_TEXTURE_BASE, N as TERRAIN_THEMES, O as BoneLookAt3D, P as terrainThemeLayers, S as DENSITY_PRESETS, T as FLOWER_VARIETIES, U as rigPose, V as keyboardIntensity, W as TextureCache3D, _ as LoftMesh3D, a as Tree3D, b as SpotLight3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as Terrain3D, k as BoneAttachment3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as resolveFlowerDensity, x as Foliage3D, y as OmniLight3D, z as QUARTER_PITCH } from "./environment-presets-BkqlWewf.js";
7
- import { a as Environment3D, c as AssetStore3D, i as syncTree, o as applyQualityTier3D, r as Renderer3D, s as setEnvironment3D, t as createGame3D } from "./create-game-BAiA-FjP.js";
5
+ import { $ as Water3D, at as PhysicsBody3D, ct as Node3D, dt as WATER_MAX_RIPPLES, et as WATER_CUTOUT_MAX, it as CharacterBody3D, ot as RigidBody3D, rt as Area3D, st as StaticBody3D, tt as WaterCutout3D } from "./gameplay-D-JuGura.js";
6
+ import { A as CharacterController3D, B as InstancedMesh3D, C as SpotLight3D, D as resolveFlowerDensity, E as Flowers3D, F as Terrain3D, G as keyboardIntensity, I as DEFAULT_TERRAIN_TEXTURE_BASE, J as TextureCache3D, K as movementState, L as TERRAIN_THEMES, M as BoneLookAt3D, N as BoneAttachment3D, O as FLOWER_VARIETIES, P as Billboard3D, R as terrainThemeLayers, S as OmniLight3D, T as DENSITY_PRESETS, U as QUARTER_PITCH, V as MeshInstance3D, W as cameraRelative, X as acquireTexture, Y as acquireOwnTexture, _ as Ragdoll3D, a as VoxelGrid3D, b as LoftMesh3D, c as Trail3D, d as findRiverCoverageGaps, f as projectToRiver, g as traceDownhillPath, h as smoothCourse, i as VOXEL_PALETTE, j as Camera3D, k as Flock3D, l as River3D, m as riverStepFor, n as registerNodes3D, o as Vehicle3D, p as riverCarveChannels, q as rigPose, r as Weather3D, s as Tree3D, u as buildRiverRings, v as Particles3D, w as Foliage3D, x as DirectionalLight3D, y as ModelInstance3D, z as Joint3D } from "./environment-presets-BP77Dl0K.js";
7
+ import { a as Environment3D, c as AssetStore3D, i as syncTree, o as applyQualityTier3D, r as Renderer3D, s as setEnvironment3D, t as createGame3D } from "./create-game-DVUhHs2B.js";
8
8
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-BYgD5Edk.js";
9
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-rOzeg890.js";
9
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-Bz2VPQ6D.js";
10
10
  //#region src/3d/model-verdict.ts
11
11
  /** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
12
12
  const MIXAMO = /^mixamorig[:_]?/i;
@@ -319,4 +319,4 @@ function createNavDebugNode(nav, opts) {
319
319
  return node;
320
320
  }
321
321
  //#endregion
322
- export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, SpotLight3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
322
+ export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flock3D, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Ragdoll3D, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, SpotLight3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, Vehicle3D, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, Weather3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
@@ -1,5 +1,5 @@
1
1
  import { o as __exportAll } from "./json-CfTjpvW8.js";
2
- import { Y as diagnose } from "./save-slots-CEUGeLuB.js";
2
+ import { et as diagnose } from "./pose-ByFB_J3O.js";
3
3
  //#region src/net/agent8.ts
4
4
  var agent8_exports = /* @__PURE__ */ __exportAll({
5
5
  Agent8TransportForTest: () => Agent8Transport,
@@ -1,5 +1,5 @@
1
- import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-J1cmVah0.js";
2
- import { t as Rng } from "./rng-Bb-IutXB.js";
1
+ import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-B_245qRy.js";
2
+ import { t as Rng } from "./rng-BsXZg3D6.js";
3
3
 
4
4
  //#region src/core/boot-failure.d.ts
5
5
  /**
@@ -61,6 +61,10 @@ interface ParticleSimConfig {
61
61
  maxParticles: number;
62
62
  /** Spread emission into the z axis too (3D). */
63
63
  spreadZ?: boolean;
64
+ /** Born anywhere inside a box of these HALF-extents around the emitter (rain from a cloud, dust over a field); absent = a point. */
65
+ emitBox?: [number, number, number];
66
+ /** A constant velocity every particle is born with on top of its own — wind on smoke, a current in water. */
67
+ drift?: [number, number, number];
64
68
  }
65
69
  interface ParticleView {
66
70
  x: number;
@@ -220,6 +224,23 @@ declare class AudioPlayer extends Node {
220
224
  */
221
225
  _setSpatialPose(pose: SpatialPose): void;
222
226
  /**
227
+ * Where this sound arrives from, for the record: gain, side and distance.
228
+ *
229
+ * The renderer feeds a pose every frame it draws; headless there is none, and
230
+ * that is exactly where the question matters — a harness asking "was the bell
231
+ * on my left" had nothing to read. So when no pose has been pushed, one is
232
+ * computed HERE, from the scene's own current camera: the same listener the
233
+ * panner would use, without a renderer to ask.
234
+ *
235
+ * Null when the sound is not spatial, or when the scene has no camera to hear
236
+ * it — the record then says nothing rather than inventing a centred sound.
237
+ */
238
+ private spatialAnswer;
239
+ /** The listener the scene implies: its current camera, posed from the tree. */
240
+ private poseFromScene;
241
+ /** Where this player SOUNDS from: its nearest spatial ancestor (or itself). */
242
+ private emitterPosition;
243
+ /**
223
244
  * Write this sound into the engine's audio record.
224
245
  *
225
246
  * The CALL happens even where the backend does not (headless, the verify VM),
@@ -1,4 +1,4 @@
1
- import { a as SceneJson, c as JsonValue, o as JsonKind, s as JsonObject, t as Rng } from "./rng-Bb-IutXB.js";
1
+ import { a as SceneJson, c as JsonValue, o as JsonKind, s as JsonObject, t as Rng } from "./rng-BsXZg3D6.js";
2
2
 
3
3
  //#region src/core/signal.d.ts
4
4
  /**
@@ -546,6 +546,20 @@ interface AudioEvent {
546
546
  * and stopped". Both read `countOf('alarm') === 1`.
547
547
  */
548
548
  loop?: boolean;
549
+ /**
550
+ * WHERE it came from, for a spatial sound: the gain it arrived at (0..1
551
+ * after distance rolloff), which side (`pan`, −1 left … +1 right) and how
552
+ * far the listener was. Absent for a sound that is not spatial, and for one
553
+ * played in a scene with no camera to hear it.
554
+ *
555
+ * Thirty shipped scenes set `spatial: true` and nothing had ever checked
556
+ * that a sound is louder on the side it comes from, because the record said
557
+ * only that it fired. A game whose mechanic is "walk toward the noise" could
558
+ * not be verified at all.
559
+ */
560
+ gain?: number;
561
+ pan?: number;
562
+ distance?: number;
549
563
  }
550
564
  declare class AudioBuses {
551
565
  /** Fires whenever any value changes (drives live re-gain of playing sounds). */
@@ -1,15 +1,15 @@
1
1
  import { o as __exportAll } from "./json-CfTjpvW8.js";
2
- import { C as sunDirectionFromSky, R as sceneDimension, Y as diagnose, b as horizonColorFromSky, h as loadScene, ot as registerBehavior, x as parseEnvironment3D } from "./save-slots-CEUGeLuB.js";
3
- import { C as qualityRendering, S as qualityEnvironment, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-CjociOtt.js";
2
+ import { E as parseEnvironment3D, O as sunDirectionFromSky, T as horizonColorFromSky, U as sceneDimension, b as loadScene, dt as registerBehavior, et as diagnose } from "./pose-ByFB_J3O.js";
3
+ import { C as qualityCaps, T as qualityRendering, b as Engine, w as qualityEnvironment, y as AudioPlayer } from "./register-B768Qedj.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { n as attachTouchControls } from "./touch-CioTZB-y.js";
6
6
  import { n as withWebGLContext, r as resolveRendering } from "./webgl-unavailable-C8aDbGmR.js";
7
- import { a as devServerLibrary, c as applyDriveStep, d as audioErrors, i as crossFade, l as wireDevChannel, n as teardown, o as openBundledEditor, r as pauseWhenHidden, s as poseFromRenderer, t as nodeFromHits, u as claimCanvasGestures } from "./picking-BMV34Pjl.js";
7
+ import { a as devServerLibrary, c as applyDriveStep, d as audioErrors, i as crossFade, l as wireDevChannel, n as teardown, o as openBundledEditor, r as pauseWhenHidden, s as poseFromRenderer, t as nodeFromHits, u as claimCanvasGestures } from "./picking-Buita5T9.js";
8
8
  import { o as frameStats } from "./frame-report-D-_7YF2G.js";
9
- import { G as PhysicsBody3D, J as Node3D, X as createCausticsQuad, n as registerGameplayBehaviors } from "./gameplay-Bxe1sMVT.js";
9
+ import { at as PhysicsBody3D, ct as Node3D, n as registerGameplayBehaviors, ut as createCausticsQuad } from "./gameplay-D-JuGura.js";
10
10
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
11
- import { D as Camera3D, W as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-BkqlWewf.js";
12
- import { n as enablePhysics3D } from "./physics-3d-rOzeg890.js";
11
+ import { J as TextureCache3D, j as Camera3D, n as registerNodes3D, t as resolveEnvironmentHdri, x as DirectionalLight3D, y as ModelInstance3D } from "./environment-presets-BP77Dl0K.js";
12
+ import { n as enablePhysics3D } from "./physics-3d-Bz2VPQ6D.js";
13
13
  import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, LoadingManager, 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";
@@ -1232,9 +1232,24 @@ function createSyncScratch() {
1232
1232
  alpha: 1,
1233
1233
  ignoreStatic: false,
1234
1234
  quality: null
1235
+ },
1236
+ blend: {
1237
+ cam: new PerspectiveCamera(),
1238
+ active: false,
1239
+ t: 0,
1240
+ seconds: 0,
1241
+ fromPos: new Vector3(),
1242
+ fromQuat: new Quaternion(),
1243
+ fromFov: 60,
1244
+ lastNode: null,
1245
+ lastPos: new Vector3(),
1246
+ lastQuat: new Quaternion(),
1247
+ lastFov: 60
1235
1248
  }
1236
1249
  };
1237
1250
  }
1251
+ const blendToPos = new Vector3();
1252
+ const blendToQuat = new Quaternion();
1238
1253
  /**
1239
1254
  * Mirror an Incanto node tree onto a three.js scene (dirty-push, once per frame):
1240
1255
  *
@@ -1270,7 +1285,8 @@ function syncTree(root, threeScene, assets, opts, scratch) {
1270
1285
  }
1271
1286
  }
1272
1287
  if (!current) current = s.cameras[0] ?? null;
1273
- const activeCamera = current ? current._ensureObject3D() : null;
1288
+ let activeCamera = current ? current._ensureObject3D() : null;
1289
+ if (current && activeCamera) activeCamera = blendCamera(s.blend, current, activeCamera, opts?.dt ?? 0);
1274
1290
  feedSpatialEmitters(s.emitters, activeCamera);
1275
1291
  return {
1276
1292
  activeCamera,
@@ -1278,6 +1294,55 @@ function syncTree(root, threeScene, assets, opts, scratch) {
1278
1294
  sunLight: state.sunLight
1279
1295
  };
1280
1296
  }
1297
+ /**
1298
+ * A camera switch used to be a CUT: `makeCurrent()` handed the renderer the
1299
+ * new camera on the next frame and the picture jumped. When the camera being
1300
+ * switched TO declares `blendSeconds` (or `makeCurrent(seconds)` asked once),
1301
+ * the picture flies from the last drawn pose to the new camera's LIVE pose over
1302
+ * that long — position lerped, orientation slerped, fov lerped, eased in and
1303
+ * out — on real time. The blend camera is the sync's own object outside the
1304
+ * scene graph; the nodes never move.
1305
+ */
1306
+ function blendCamera(b, node, live, dt) {
1307
+ const request = node._blendRequest;
1308
+ node._blendRequest = void 0;
1309
+ if (b.lastNode !== node) {
1310
+ const seconds = request ?? node.blendSeconds;
1311
+ if (b.lastNode && seconds > 0) {
1312
+ b.fromPos.copy(b.lastPos);
1313
+ b.fromQuat.copy(b.lastQuat);
1314
+ b.fromFov = b.lastFov;
1315
+ b.t = 0;
1316
+ b.seconds = seconds;
1317
+ b.active = true;
1318
+ } else b.active = false;
1319
+ }
1320
+ let out = live;
1321
+ if (b.active) {
1322
+ b.t += dt;
1323
+ const k = Math.min(1, b.t / b.seconds);
1324
+ const e = k < .5 ? 2 * k * k : 1 - (-2 * k + 2) ** 2 / 2;
1325
+ live.updateWorldMatrix(true, false);
1326
+ live.getWorldPosition(blendToPos);
1327
+ live.getWorldQuaternion(blendToQuat);
1328
+ b.cam.position.copy(b.fromPos).lerp(blendToPos, e);
1329
+ b.cam.quaternion.copy(b.fromQuat).slerp(blendToQuat, e);
1330
+ b.cam.fov = b.fromFov + (live.fov - b.fromFov) * e;
1331
+ b.cam.near = live.near;
1332
+ b.cam.far = live.far;
1333
+ b.cam.aspect = live.aspect;
1334
+ b.cam.updateProjectionMatrix();
1335
+ b.cam.updateMatrixWorld(true);
1336
+ if (k >= 1) b.active = false;
1337
+ else out = b.cam;
1338
+ }
1339
+ out.updateWorldMatrix(true, false);
1340
+ out.getWorldPosition(b.lastPos);
1341
+ out.getWorldQuaternion(b.lastQuat);
1342
+ b.lastFov = out.fov;
1343
+ b.lastNode = node;
1344
+ return out;
1345
+ }
1281
1346
  const emitterScratch = new Vector3();
1282
1347
  const listenerScratch = new Vector3();
1283
1348
  const forwardScratch = new Vector3();
@@ -1677,6 +1742,7 @@ var Renderer3D = class {
1677
1742
  const { activeCamera: sceneCamera, renderHooks, sunLight } = syncTree(scene.root, this.threeScene, this.assets, {
1678
1743
  sunDirection: this.environment.sunDirection,
1679
1744
  alpha: this.engine.interpolationAlpha,
1745
+ dt: this.engine.unscaledDelta,
1680
1746
  ignoreStatic: this.ignoreStatic,
1681
1747
  quality: qualityCaps(this.engine.settings.get("quality"))
1682
1748
  }, this.syncScratch);
@@ -2364,13 +2430,16 @@ async function createGame3D(opts) {
2364
2430
  if (overlay) cleanups.push(() => overlay.dispose());
2365
2431
  }
2366
2432
  }
2367
- engine.start();
2433
+ const autoStart = opts.autoStart !== false;
2434
+ if (autoStart) engine.start();
2368
2435
  await report(.16, "shaders");
2369
2436
  try {
2370
2437
  renderer.deferFirstCompile?.();
2371
2438
  const nowMs = globalThis.performance?.now?.() ?? 0;
2372
- engine.tick(nowMs);
2373
- engine.tick(nowMs + 16);
2439
+ if (autoStart) {
2440
+ engine.tick(nowMs);
2441
+ engine.tick(nowMs + 16);
2442
+ }
2374
2443
  await renderer.warmUp?.((f) => {
2375
2444
  opts.onProgress?.(.16 + f * .82, "shaders");
2376
2445
  });
@@ -1,15 +1,15 @@
1
1
  import { o as __exportAll } from "./json-CfTjpvW8.js";
2
- import { D as computeViewport, O as resolveViewport, R as sceneDimension, Y as diagnose, h as loadScene, ot as registerBehavior } from "./save-slots-CEUGeLuB.js";
3
- import { C as qualityRendering, _ as AudioPlayer, v as Engine } from "./register-CjociOtt.js";
2
+ import { M as computeViewport, N as resolveViewport, U as sceneDimension, b as loadScene, dt as registerBehavior, et as diagnose } from "./pose-ByFB_J3O.js";
3
+ import { T as qualityRendering, b as Engine, y as AudioPlayer } from "./register-B768Qedj.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { n as attachTouchControls } from "./touch-CioTZB-y.js";
6
6
  import { n as withWebGLContext, r as resolveRendering } from "./webgl-unavailable-C8aDbGmR.js";
7
- import { a as devServerLibrary, c as applyDriveStep, d as audioErrors, i as crossFade, l as wireDevChannel, n as teardown, o as openBundledEditor, r as pauseWhenHidden, t as nodeFromHits, u as claimCanvasGestures } from "./picking-BMV34Pjl.js";
7
+ import { a as devServerLibrary, c as applyDriveStep, d as audioErrors, i as crossFade, l as wireDevChannel, n as teardown, o as openBundledEditor, r as pauseWhenHidden, t as nodeFromHits, u as claimCanvasGestures } from "./picking-Buita5T9.js";
8
8
  import { o as frameStats } from "./frame-report-D-_7YF2G.js";
9
- import { n as registerGameplayBehaviors } from "./gameplay-Bxe1sMVT.js";
10
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-DRQyZKGU.js";
9
+ import { _ as Node2D, m as PhysicsBody2D, n as registerGameplayBehaviors } from "./gameplay-D-JuGura.js";
11
10
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
12
- import { n as enablePhysics2D } from "./physics-2d-C-SfvjzH.js";
11
+ import { n as UILayer, t as registerNodes2D, u as Camera2D } from "./register-DxAbTO2T.js";
12
+ import { n as enablePhysics2D } from "./physics-2d-x3bCrQf0.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
  /**
@@ -378,6 +378,73 @@ function prune(obj, visited) {
378
378
  else if (child.userData.incantoStatic !== true) prune(child, visited);
379
379
  }
380
380
  }
381
+ function createViewBlend() {
382
+ return {
383
+ lastNode: null,
384
+ lastCx: 0,
385
+ lastCy: 0,
386
+ lastZoom: 1,
387
+ active: false,
388
+ t: 0,
389
+ seconds: 0,
390
+ fromCx: 0,
391
+ fromCy: 0,
392
+ fromZoom: 1
393
+ };
394
+ }
395
+ /**
396
+ * A camera switch in 2D was a cut too: the renderer read the new camera's
397
+ * centre and zoom on the next frame. When the camera being switched TO
398
+ * declares `blendSeconds` (or `makeCurrent(seconds)` asked once) the view
399
+ * flies from the last drawn centre and zoom to the new camera's live ones over
400
+ * that long, eased in and out, on real time. Pure: the renderer hands in what
401
+ * the camera says and draws what comes back.
402
+ */
403
+ function blendView2D(b, node, cx, cy, zoom, dt) {
404
+ if (!node) {
405
+ b.lastNode = null;
406
+ b.active = false;
407
+ return {
408
+ cx,
409
+ cy,
410
+ zoom
411
+ };
412
+ }
413
+ const request = node._blendRequest;
414
+ node._blendRequest = void 0;
415
+ if (b.lastNode !== node) {
416
+ const seconds = request ?? node.blendSeconds;
417
+ if (b.lastNode && seconds > 0) {
418
+ b.fromCx = b.lastCx;
419
+ b.fromCy = b.lastCy;
420
+ b.fromZoom = b.lastZoom;
421
+ b.t = 0;
422
+ b.seconds = seconds;
423
+ b.active = true;
424
+ } else b.active = false;
425
+ }
426
+ let out = {
427
+ cx,
428
+ cy,
429
+ zoom
430
+ };
431
+ if (b.active) {
432
+ b.t += dt;
433
+ const k = Math.min(1, b.t / b.seconds);
434
+ const e = k < .5 ? 2 * k * k : 1 - (-2 * k + 2) ** 2 / 2;
435
+ out = {
436
+ cx: b.fromCx + (cx - b.fromCx) * e,
437
+ cy: b.fromCy + (cy - b.fromCy) * e,
438
+ zoom: b.fromZoom + (zoom - b.fromZoom) * e
439
+ };
440
+ if (k >= 1) b.active = false;
441
+ }
442
+ b.lastCx = out.cx;
443
+ b.lastCy = out.cy;
444
+ b.lastZoom = out.zoom;
445
+ b.lastNode = node;
446
+ return out;
447
+ }
381
448
  //#endregion
382
449
  //#region src/2d/renderer.ts
383
450
  /**
@@ -394,6 +461,7 @@ var Renderer2D = class {
394
461
  * scene data. `null` restores normal camera behavior.
395
462
  */
396
463
  viewOverride = null;
464
+ viewBlend = createViewBlend();
397
465
  webgl;
398
466
  /**
399
467
  * The pixels of the next drawn frame — how `incanto-frame` sees a 2D game.
@@ -624,6 +692,14 @@ var Renderer2D = class {
624
692
  center = activeCamera.clampedCenter(uiW, uiH);
625
693
  zoom = activeCamera.effectiveZoom;
626
694
  }
695
+ if (!this.viewOverride) {
696
+ const eased = blendView2D(this.viewBlend, activeCamera ?? null, center.x, center.y, zoom, this.engine.unscaledDelta);
697
+ center = {
698
+ x: eased.cx,
699
+ y: eased.cy
700
+ };
701
+ zoom = eased.zoom;
702
+ }
627
703
  const scale = view?.scale ?? 1;
628
704
  this.lastView = {
629
705
  cx: center.x,
@@ -982,7 +1058,7 @@ async function createGame2D(opts) {
982
1058
  if (overlay) cleanups.push(() => overlay.dispose());
983
1059
  }
984
1060
  }
985
- engine.start();
1061
+ if (opts.autoStart !== false) engine.start();
986
1062
  /**
987
1063
  * Has this handle already been torn down?
988
1064
  *
package/dist/debug.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Ft as LogLevel, T as RendererStats, b as Engine } from "./behavior-J1cmVah0.js";
1
+ import { Ft as LogLevel, T as RendererStats, b as Engine } from "./behavior-B_245qRy.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 { s as JsonObject } from "./rng-Bb-IutXB.js";
1
+ import { s as JsonObject } from "./rng-BsXZg3D6.js";
2
2
 
3
3
  //#region src/core/editor-switch.d.ts
4
4
  /** Where a camera is and what it looks at, in world space. */