incanto 0.67.0 → 0.69.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 (87) hide show
  1. package/assets/catalog.json +9 -5
  2. package/bin/_pick-server.mjs +48 -0
  3. package/bin/incanto-assets.mjs +14 -1
  4. package/bin/incanto-check.mjs +69 -5
  5. package/bin/incanto-frame.mjs +10 -25
  6. package/bin/incanto-logs.mjs +8 -18
  7. package/bin/incanto-new.mjs +29 -1
  8. package/bin/incanto-serve.mjs +160 -0
  9. package/bin/incanto-verify.mjs +40 -0
  10. package/bin/incanto.mjs +1 -0
  11. package/dist/2d.d.ts +10 -8
  12. package/dist/2d.js +6 -4
  13. package/dist/3d.d.ts +31 -7
  14. package/dist/3d.js +9 -7
  15. package/dist/{audio-player-BNUHo0JF.d.ts → audio-player-C0j2k-yB.d.ts} +80 -3
  16. package/dist/{behavior-ZnfzKDo3.d.ts → behavior-rm8vVzu_.d.ts} +145 -1
  17. package/dist/boot-failure-CKYrEcGF.js +47 -0
  18. package/dist/{create-game-FAXdHta3.js → create-game-CvJ1wSRP.js} +27 -27
  19. package/dist/{create-game-DMetd3vR.js → create-game-CwxEDlCm.js} +15 -11
  20. package/dist/debug.d.ts +1 -1
  21. package/dist/{duplicate-B8QnX2-r.js → duplicate-Dj4L-5AA.js} +1 -1
  22. package/dist/{editor-switch-DAvWQeld.d.ts → editor-switch-CnIOiyNJ.d.ts} +1 -1
  23. package/dist/editor.js +12 -6
  24. package/dist/env.d.ts +1 -1
  25. package/dist/{environment-presets-C-5g2pMh.js → environment-presets-BO-7XoFV.js} +58 -9
  26. package/dist/{frame-report-DCnHFmto.d.ts → frame-report-DNxDAb1w.d.ts} +8 -0
  27. package/dist/{frame-report-BSMny7oe.js → frame-report-Dlq13Gyj.js} +1 -0
  28. package/dist/{gameplay-CGSv-9XR.js → gameplay-D1KixWK8.js} +175 -151
  29. package/dist/gameplay.d.ts +51 -3
  30. package/dist/gameplay.js +2 -2
  31. package/dist/index.d.ts +6 -6
  32. package/dist/index.js +8 -7
  33. package/dist/{loader-KZESOZmE.js → loader-DZE7B07H.js} +48 -7
  34. package/dist/{loader-DyT30ArB.d.ts → loader-DadV2QCT.d.ts} +2 -2
  35. package/dist/net.d.ts +2 -2
  36. package/dist/net.js +1 -1
  37. package/dist/{physics-2d-_B6zj3zU.js → physics-2d-CF5yyjbj.js} +2 -2
  38. package/dist/{physics-3d-i1IeAY0F.js → physics-3d-DbYaEcZy.js} +3 -3
  39. package/dist/react.d.ts +2 -2
  40. package/dist/react.js +1 -1
  41. package/dist/{register-vIxU3Xbv.js → register-DbVArsns.js} +19 -10
  42. package/dist/{register-Cs53CY7s.js → register-mmfugpaQ.js} +159 -6
  43. package/dist/{replay-BkpDV51O.js → replay-BEHLDcQV.js} +28 -5
  44. package/dist/{replay-D0GcEHYU.d.ts → replay-BQfkX4x7.d.ts} +38 -4
  45. package/dist/{schema-CFeioQRE.d.ts → schema-B6ugCV1Q.d.ts} +11 -0
  46. package/dist/{sheet-grid-BT6N_Bjs.js → sheet-grid-Cea343VO.js} +6 -2
  47. package/dist/{split-screen-DS8RaUV_.js → split-screen-CauMwu6y.js} +2 -2
  48. package/dist/{split-screen-DD5eYIgW.d.ts → split-screen-Cg76MCV5.d.ts} +3 -3
  49. package/dist/{src-BLlsGJW9.js → src-CYEu-_S9.js} +1 -1
  50. package/dist/{teardown-LB9cTnXJ.js → teardown-CMqFVxO9.js} +5 -4
  51. package/dist/{test-81uZ967a.js → test-D6ISmuFE.js} +73 -25
  52. package/dist/test.d.ts +17 -6
  53. package/dist/test.js +3 -3
  54. package/dist/vite.d.ts +70 -2
  55. package/dist/vite.js +176 -14
  56. package/dist/{rendering-options-ByAdi8PF.js → webgl-unavailable-Z5_g8x9H.js} +57 -2
  57. package/editor/assets/{agent8-C2jtNKbx.js → agent8-BDvaZMQJ.js} +1 -1
  58. package/editor/assets/{debug-DoU3KeqS.js → debug-CMULinBW.js} +1 -1
  59. package/editor/assets/{index-CNqHojcC.js → index-CZIRr5EL.js} +92 -92
  60. package/editor/index.html +1 -1
  61. package/package.json +4 -3
  62. package/schemas/scene.schema.json +4 -0
  63. package/skills/incanto-3d-character.md +1 -0
  64. package/skills/incanto-assets.md +16 -2
  65. package/skills/incanto-audio.md +65 -0
  66. package/skills/incanto-building-2d-games.md +66 -0
  67. package/skills/incanto-building-3d-games.md +97 -2
  68. package/skills/incanto-gameplay-behaviors.md +46 -2
  69. package/skills/incanto-node-reference.md +1 -0
  70. package/skills/incanto-physics-and-input.md +23 -0
  71. package/skills/incanto-save-slots.md +27 -0
  72. package/skills/incanto-scene-json-authoring.md +12 -1
  73. package/skills/incanto-verifying-your-game.md +96 -2
  74. package/skills/incanto-your-first-game.md +2 -1
  75. package/templates-app/beacon-isle-3d/package.json +1 -1
  76. package/templates-app/beacon-isle-3d/src/main.ts +21 -4
  77. package/templates-app/platformer-2d/index.html +0 -23
  78. package/templates-app/platformer-2d/package.json +1 -1
  79. package/templates-app/platformer-2d/src/behaviors.ts +26 -16
  80. package/templates-app/platformer-2d/src/game.scene.json +17 -27
  81. package/templates-app/platformer-2d/src/main.ts +32 -13
  82. package/templates-app/star-survivor/package.json +1 -1
  83. package/templates-app/star-survivor/src/main.ts +25 -7
  84. package/templates-app/tps-3d/package.json +1 -1
  85. package/templates-app/tps-3d/src/main.ts +34 -16
  86. package/templates-app/village-quest-3d/package.json +1 -1
  87. package/templates-app/village-quest-3d/src/main.ts +21 -4
package/dist/2d.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-ZnfzKDo3.js";
2
- import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
3
- import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
4
- import { r as FrameStats } from "./frame-report-DCnHFmto.js";
5
- import { t as LoadSceneOptions } from "./loader-DyT30ArB.js";
1
+ import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-C0j2k-yB.js";
2
+ import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-rm8vVzu_.js";
3
+ import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-CnIOiyNJ.js";
4
+ import { i as SceneJson, s as JsonObject } from "./schema-B6ugCV1Q.js";
5
+ import { r as FrameStats } from "./frame-report-DNxDAb1w.js";
6
+ import { t as LoadSceneOptions } from "./loader-DadV2QCT.js";
6
7
  import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
7
- import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-BNUHo0JF.js";
8
8
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
9
9
  import * as RapierNs from "@dimforge/rapier2d-compat";
10
10
 
@@ -972,9 +972,11 @@ declare class TileMap2D extends Node2D {
972
972
  private _solid;
973
973
  /** Rebuild flags — raised when the map-shaping props are REPLACED. */
974
974
  private geometryDirty;
975
- /** Reported once per atlas, so a bad grid says so on the first build only. */
975
+ /** `texture|highestTile` already reported, so a bad grid says so once. */
976
976
  private gridChecked;
977
977
  private collidersDirty;
978
+ /** The `scale` the colliders were last built for — see `rebuildColliders`. */
979
+ private builtScale;
978
980
  /** Replace the whole array to change the map (mutations are not watched). */
979
981
  get cells(): (string | number[])[];
980
982
  set cells(value: (string | number[])[]);
@@ -1247,4 +1249,4 @@ declare function parseCells(cells: readonly (string | readonly number[])[], lege
1247
1249
  */
1248
1250
  declare function mergeSolidRects(grid: readonly (readonly number[])[], solid: Set<number>): TileRect[];
1249
1251
  //#endregion
1250
- export { AnimatedSprite2D, Area2D, type AssetStatus, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, type CreateGame2DOptions, type Game2D, Joint2D, type JointType2D, Label, Node2D, Particles2D, Physics2D, type Physics2DOptions, PhysicsBody2D, Renderer2D, type Renderer2DOptions, type ResolvedSpriteTexture, RigidBody2D, type SheetInfo, Sprite2D, type SpriteFromLibraryResult, StaticBody2D, type Sync2DResult, type TextureLoadCallbacks, TileMap2D, type TileRect, type UIAnchor, UILayer, createGame2D, enablePhysics2D, mergeSolidRects, parseCells, registerNodes2D, spriteFromLibraryMeta, syncTree2D };
1252
+ export { AnimatedSprite2D, Area2D, type AssetStatus, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, type CreateGame2DOptions, type Game2D, Joint2D, type JointType2D, Label, Node2D, Particles2D, Physics2D, type Physics2DOptions, PhysicsBody2D, Renderer2D, type Renderer2DOptions, type ResolvedSpriteTexture, RigidBody2D, type SheetInfo, Sprite2D, type SpriteFromLibraryResult, StaticBody2D, type Sync2DResult, type TextureLoadCallbacks, TileMap2D, type TileRect, type UIAnchor, UILayer, createGame2D, enablePhysics2D, isWebGLAvailable, mergeSolidRects, parseCells, registerNodes2D, showBootFailure, spriteFromLibraryMeta, syncTree2D };
package/dist/2d.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-DMetd3vR.js";
3
- import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-vIxU3Xbv.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-_B6zj3zU.js";
2
+ import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
3
+ import { t as isWebGLAvailable } from "./webgl-unavailable-Z5_g8x9H.js";
4
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-CwxEDlCm.js";
5
+ import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-DbVArsns.js";
6
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-CF5yyjbj.js";
5
7
  //#region src/2d/library-sprite.ts
6
8
  /**
7
9
  * What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in
@@ -95,4 +97,4 @@ function spriteFromLibraryMeta(meta, opts) {
95
97
  };
96
98
  }
97
99
  //#endregion
98
- export { AnimatedSprite2D, Area2D, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, Joint2D, Label, Node2D, Particles2D, Physics2D, PhysicsBody2D, Renderer2D, RigidBody2D, Sprite2D, StaticBody2D, TileMap2D, UILayer, createGame2D, enablePhysics2D, mergeSolidRects, parseCells, registerNodes2D, spriteFromLibraryMeta, syncTree2D };
100
+ export { AnimatedSprite2D, Area2D, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, Joint2D, Label, Node2D, Particles2D, Physics2D, PhysicsBody2D, Renderer2D, RigidBody2D, Sprite2D, StaticBody2D, TileMap2D, UILayer, createGame2D, enablePhysics2D, isWebGLAvailable, mergeSolidRects, parseCells, registerNodes2D, showBootFailure, spriteFromLibraryMeta, syncTree2D };
package/dist/3d.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-ZnfzKDo3.js";
2
- import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
3
- import { i as SceneJson$1, s as JsonObject } from "./schema-CFeioQRE.js";
4
- import { a as GridCell, c as diffText, d as frameText, i as FrameStatsOptions, l as frameSignature, n as FrameSignature, o as SIGNATURE_GRID, r as FrameStats, s as diffSignatures, t as FrameDiff, u as frameStats } from "./frame-report-DCnHFmto.js";
5
- import { t as LoadSceneOptions } from "./loader-DyT30ArB.js";
6
- import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-BNUHo0JF.js";
1
+ import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-C0j2k-yB.js";
2
+ import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-rm8vVzu_.js";
3
+ import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-CnIOiyNJ.js";
4
+ import { i as SceneJson$1, s as JsonObject } from "./schema-B6ugCV1Q.js";
5
+ import { a as GridCell, c as diffText, d as frameText, i as FrameStatsOptions, l as frameSignature, n as FrameSignature, o as SIGNATURE_GRID, r as FrameStats, s as diffSignatures, t as FrameDiff, u as frameStats } from "./frame-report-DNxDAb1w.js";
6
+ import { t as LoadSceneOptions } from "./loader-DadV2QCT.js";
7
7
  import { n as PathGrid } from "./pathfinding-_fGrCFmH.js";
8
8
  import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
9
9
  import { VRM } from "@pixiv/three-vrm";
@@ -1404,6 +1404,30 @@ declare class CharacterController3D extends Node3D {
1404
1404
  pitchMax: number;
1405
1405
  /** Spring-arm: pull the orbit camera in front of walls/ground instead of clipping. */
1406
1406
  cameraCollision: boolean;
1407
+ /**
1408
+ * Ride whatever you are standing on.
1409
+ *
1410
+ * `false` restores the old behaviour: the controller steers toward a target
1411
+ * velocity in WORLD space, so standing still on a moving floor brakes you off
1412
+ * it. Turn it off for a conveyor you want to be scenery.
1413
+ */
1414
+ platformCarry: boolean;
1415
+ /** The node the ground probe hit last step, and where it was. */
1416
+ private carriedBy;
1417
+ private carriedFrom;
1418
+ /**
1419
+ * How fast the floor is moving, in world units per second.
1420
+ *
1421
+ * From the POSITION DELTA, not `linearVelocity`, because the documented way
1422
+ * to author a platform is to move its `position` — a behavior, a `PathFollow`,
1423
+ * an `Oscillate` — and that path calls `body.setTranslation`, which teleports
1424
+ * and carries no momentum at all. The delta sees both kinds.
1425
+ *
1426
+ * Only while grounded on the SAME node two steps running: the first step on a
1427
+ * new platform has nothing to compare against, and inventing a velocity there
1428
+ * would fling the player on every step onto one.
1429
+ */
1430
+ private groundCarry;
1407
1431
  turnSpeed: number;
1408
1432
  /** Camera smoothing rate (1-exp(-rate·dt)); side view uses 5. */
1409
1433
  camLerp: number;
@@ -4106,4 +4130,4 @@ declare function createNavDebugNode(nav: TerrainNav, opts?: {
4106
4130
  name?: string;
4107
4131
  }): InstancedMesh3D;
4108
4132
  //#endregion
4109
- export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type FrameDiff, type FrameSignature, type FrameStats, type FrameStatsOptions, type Game3D, type GridCell, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, type ModelFacts, ModelInstance3D, type ModelVerdict, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type Rig, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, SIGNATURE_GRID, type SceneJson, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
4133
+ export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type FrameDiff, type FrameSignature, type FrameStats, type FrameStatsOptions, type Game3D, type GridCell, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, type ModelFacts, ModelInstance3D, type ModelVerdict, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type Rig, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, SIGNATURE_GRID, type SceneJson, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
package/dist/3d.js CHANGED
@@ -1,10 +1,12 @@
1
- import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-KZESOZmE.js";
2
- import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-BSMny7oe.js";
3
- import { G as StaticBody3D, H as CharacterBody3D, K as Node3D, L as Water3D, R as WATER_CUTOUT_MAX, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, Y as WATER_MAX_RIPPLES, z as WaterCutout3D } from "./gameplay-CGSv-9XR.js";
4
- import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-C-5g2pMh.js";
5
- 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-FAXdHta3.js";
1
+ import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-DZE7B07H.js";
2
+ import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
3
+ import { t as isWebGLAvailable } from "./webgl-unavailable-Z5_g8x9H.js";
4
+ import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-Dlq13Gyj.js";
5
+ import { G as StaticBody3D, H as CharacterBody3D, K as Node3D, L as Water3D, R as WATER_CUTOUT_MAX, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, Y as WATER_MAX_RIPPLES, z as WaterCutout3D } from "./gameplay-D1KixWK8.js";
6
+ import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-BO-7XoFV.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-CvJ1wSRP.js";
6
8
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
7
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-i1IeAY0F.js";
9
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-DbYaEcZy.js";
8
10
  //#region src/3d/model-verdict.ts
9
11
  /** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
10
12
  const MIXAMO = /^mixamorig[:_]?/i;
@@ -317,4 +319,4 @@ function createNavDebugNode(nav, opts) {
317
319
  return node;
318
320
  }
319
321
  //#endregion
320
- export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
322
+ export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, SIGNATURE_GRID, StaticBody3D, TERRAIN_THEMES, Terrain3D, TextureCache3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
@@ -1,6 +1,42 @@
1
- import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-ZnfzKDo3.js";
2
- import { a as Rng } from "./schema-CFeioQRE.js";
1
+ import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-rm8vVzu_.js";
2
+ import { a as Rng } from "./schema-B6ugCV1Q.js";
3
3
 
4
+ //#region src/core/boot-failure.d.ts
5
+ /**
6
+ * Put a boot failure on the SCREEN, where the player is looking.
7
+ *
8
+ * Every shipped starter preloads its assets behind a `#loading` overlay and
9
+ * removes that overlay on the line after `await createGame*`. So anything that
10
+ * rejects — no WebGL context, a scene that will not load, a 404 the preload
11
+ * insisted on — leaves the overlay standing at "100%" forever, with the reason
12
+ * in a console the player will never open. Measured on a pristine scaffold with
13
+ * WebGL denied:
14
+ *
15
+ * ```
16
+ * control #loading gone "Talk to the Elder [E] …"
17
+ * no webgl #loading present "Emberwood 100%" … and still there 8.6 s later
18
+ * ```
19
+ *
20
+ * A bar that reaches 100% and stops reads as a hang, which is the one failure
21
+ * mode a player cannot report usefully. This turns it into a sentence.
22
+ *
23
+ * Headless it does nothing but rethrow-safe logging, so a test that boots a
24
+ * game without a document is unaffected.
25
+ */
26
+ declare function showBootFailure(error: unknown, opts?: {
27
+ into?: string;
28
+ }): void;
29
+ //#endregion
30
+ //#region src/core/webgl-unavailable.d.ts
31
+ /**
32
+ * Can this document make a WebGL context at all?
33
+ *
34
+ * The question a game wants BEFORE it boots, so the answer can be a screen
35
+ * instead of an exception. Uses a throwaway canvas and never touches yours;
36
+ * `false` outside a browser, where the question is not meaningful.
37
+ */
38
+ declare function isWebGLAvailable(): boolean;
39
+ //#endregion
4
40
  //#region src/core/particle-sim.d.ts
5
41
  /**
6
42
  * Deterministic particle pool — pure math, renderer-agnostic, headless-
@@ -197,6 +233,47 @@ declare class AudioPlayer extends Node {
197
233
  private spatialElementGain;
198
234
  /** The `spatial` option for the SFX path, or undefined when not spatial. */
199
235
  private spatialPlay;
236
+ /**
237
+ * Start the sound, now.
238
+ *
239
+ * **Takes no arguments, on purpose.** This is the method scenes wire signals
240
+ * to — `incanto-audio.md` teaches
241
+ * `{"signal": "collected", "from": "Player/Collector", "to": "Coin",
242
+ * "handler": "play"}` — and a signal hands its handler whatever it carries.
243
+ * `collected` leads with a NUMBER. When this briefly took an optional
244
+ * scheduling time, that wiring became `play(10)`, which scheduled the pickup
245
+ * sound at ABSOLUTE audio-clock second 10: inaudible for the first ten
246
+ * seconds of the game and fine thereafter. The same optional parameter also
247
+ * made `Function.length` 1, so the engine's own arity checker cried wolf on
248
+ * the shipped `platformer-2d` template's own harness.
249
+ *
250
+ * Scheduling has its own name: {@link playAt}.
251
+ */
252
+ /**
253
+ * Start the sound at `when` on the AUDIO clock (`engine.sfx.now` + a lead).
254
+ *
255
+ * A frame is a 16.67 ms grid at 60 Hz and 33.33 ms at 30, so a sound fired
256
+ * from `update()` cannot land closer than one frame to where a chart wants
257
+ * it — measured 0 of 64 notes given a scheduled start. Queue them a lead
258
+ * ahead and the same notes land at |mean| 0.000000 ms:
259
+ *
260
+ * ```ts
261
+ * const LEAD = 0.08;
262
+ * for (const note of dueSoon(engine.sfx.now + LEAD)) {
263
+ * hit.playAt(startedAt + note.atSec);
264
+ * }
265
+ * ```
266
+ *
267
+ * PRESETS only — a `src` clip goes through an `<audio>` element, which has no
268
+ * scheduling clock, so `playAt` on one plays immediately rather than being
269
+ * silently approximated. A time already past plays now (Web Audio's own
270
+ * rule), so a late scheduler is late, not silent. The bus gain, the
271
+ * `engine.audio.recent()` entry and `muted` all still apply.
272
+ *
273
+ * Separate from {@link play} because `play` is what scenes wire signals to,
274
+ * and a signal hands its handler whatever it carries.
275
+ */
276
+ playAt(when: number): void;
200
277
  play(): void;
201
278
  /**
202
279
  * Procedural-SFX path: synthesize the preset and fire it through WebAudio
@@ -222,4 +299,4 @@ declare class AudioPlayer extends Node {
222
299
  override update(_dt: number): void;
223
300
  }
224
301
  //#endregion
225
- export { ParticleSimConfig as a, ParticleSim as i, AudioPlayer as n, ParticleView as o, SpatialPose as r, AudioElementLike as t };
302
+ export { ParticleSimConfig as a, showBootFailure as c, ParticleSim as i, AudioPlayer as n, ParticleView as o, SpatialPose as r, isWebGLAvailable as s, AudioElementLike as t };
@@ -1,4 +1,4 @@
1
- import { a as Rng, c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
1
+ import { a as Rng, c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-B6ugCV1Q.js";
2
2
 
3
3
  //#region src/core/signal.d.ts
4
4
  /**
@@ -353,6 +353,15 @@ interface AudioEvent {
353
353
  bus: BusName;
354
354
  /** `engine.time` when it fired, in seconds. */
355
355
  at: number;
356
+ /**
357
+ * It repeats until stopped, rather than ending on its own.
358
+ *
359
+ * A looping sound is recorded ONCE — re-recording every pass would evict the
360
+ * rest of the log within seconds for a 0.2 s preset — so without this a
361
+ * headless check could not tell "the alarm loops" from "the alarm fired once
362
+ * and stopped". Both read `countOf('alarm') === 1`.
363
+ */
364
+ loop?: boolean;
356
365
  }
357
366
  declare class AudioBuses {
358
367
  /** Fires whenever any value changes (drives live re-gain of playing sounds). */
@@ -419,6 +428,12 @@ interface MusicTrack {
419
428
  play(): void;
420
429
  /** Stop and release the underlying element/source. */
421
430
  stop(): void;
431
+ /**
432
+ * Seconds into the track, or `null` when the backend cannot say.
433
+ *
434
+ * Optional so an existing custom backend keeps compiling.
435
+ */
436
+ playhead?(): number | null;
422
437
  }
423
438
  /** Backend that mints {@link MusicTrack}s — WebAudio-backed in the browser. */
424
439
  interface MusicBackend {
@@ -470,6 +485,22 @@ declare class MusicManager {
470
485
  constructor(buses: AudioBuses);
471
486
  /** Source of the logically-current track, or null when nothing is playing. */
472
487
  get current(): string | null;
488
+ /**
489
+ * Seconds into the current track — the playhead of the thing you can HEAR.
490
+ *
491
+ * `null` when nothing is playing, or when the backend has no clock (headless,
492
+ * or a custom backend that does not implement it).
493
+ *
494
+ * A game charted to a soundtrack needs this and there was no way to get it:
495
+ * `current` gave the src and the backend was private, so the only option was
496
+ * an `engine.time` counter started next to `music.play()` — a different clock
497
+ * from the one the audio is on, and one that keeps counting through a
498
+ * gesture-block, a stall or a seek. This is the audio's own answer.
499
+ *
500
+ * It is the RAW element time, so a looping track wraps to 0 at each pass:
501
+ * that is a loop boundary you can chart against, not a fault.
502
+ */
503
+ get playhead(): number | null;
473
504
  /** Per-manager volume (0..1), on top of the bus gain. */
474
505
  get volume(): number;
475
506
  /**
@@ -694,6 +725,23 @@ interface SfxPlayOptions {
694
725
  seed?: number;
695
726
  /** When set, the sound is spatialized through a PannerNode at this emitter. */
696
727
  spatial?: SpatialPlay;
728
+ /**
729
+ * Start at this point on the AUDIO clock (`engine.sfx.now` + lead), instead
730
+ * of "as soon as this call returns".
731
+ *
732
+ * Nothing in the audio API took a time, so a sound could only be fired from a
733
+ * frame — and a frame is a 16.67 ms grid at 60 Hz, 33.33 ms at 30. Measured
734
+ * on a metronome: firing from `update()` gave 0 of 64 notes a scheduled
735
+ * start; the same notes queued one frame ahead with `when` land at
736
+ * |mean| 0.000000 ms. That difference is the whole of a rhythm game, and
737
+ * anything charted to a soundtrack.
738
+ *
739
+ * A time already past plays immediately (Web Audio's own rule), so a late
740
+ * scheduler degrades instead of going silent. The bus gain and the
741
+ * `engine.audio.recent()` record apply either way — which is what a
742
+ * hand-rolled `AudioContext` scheduler loses.
743
+ */
744
+ when?: number;
697
745
  }
698
746
  /**
699
747
  * One AudioContext + a PCM cache. Instance-scoped (held by the renderer's game
@@ -741,6 +789,14 @@ declare class SfxEngine {
741
789
  /** Resume a gesture-suspended context (wired to the first user gesture). */
742
790
  unlock(): void;
743
791
  /**
792
+ * The AUDIO clock, in seconds — the one a sound is actually scheduled
793
+ * against, and the only clock that does not drift relative to what you hear.
794
+ *
795
+ * `0` headless and before the first sound, since there is no context yet.
796
+ * Schedule with a lead: `sfx.play(params, 1, { when: sfx.now + 0.08 })`.
797
+ */
798
+ get now(): number;
799
+ /**
744
800
  * Play a preset at `gain` (0..1, already bus-multiplied by the caller). Each
745
801
  * call spawns its own source so rapid repeats overlap instead of cutting off.
746
802
  * Headless → no-op.
@@ -839,6 +895,10 @@ declare class InputMap {
839
895
  private readonly injectedDown;
840
896
  private readonly injectedPressed;
841
897
  private readonly injectedReleased;
898
+ private readonly injectedPressedNext;
899
+ private readonly injectedReleasedNext;
900
+ /** True between the start of a frame's update pass and `endFrame()`. */
901
+ private inFrame;
842
902
  private readonly injectedVectors;
843
903
  /**
844
904
  * Press a button ACTION directly — no key codes involved. This is how
@@ -990,6 +1050,9 @@ declare class InputMap {
990
1050
  };
991
1051
  /** Consume one-frame edges. The Engine calls this at the end of every tick. */
992
1052
  endFrame(): void;
1053
+ /** @internal The engine marks the update phase, so an edge injected inside
1054
+ * it can be held for the next frame. */
1055
+ beginFrame(): void;
993
1056
  /**
994
1057
  * Drop all action declarations and injected action state (physical key
995
1058
  * state is kept). The Engine calls this on setScene so keybinds never
@@ -1182,6 +1245,25 @@ interface SaveStore {
1182
1245
  remove(key: string): void;
1183
1246
  /** Wipe THIS namespace only. */
1184
1247
  clear(): void;
1248
+ /**
1249
+ * Does a write here survive the page closing?
1250
+ *
1251
+ * `false` means this store is a Map that lives as long as the tab does — a
1252
+ * private window, storage disabled, quota exhausted, or headless. Everything
1253
+ * still works IN the session, which is exactly what made it undetectable:
1254
+ * measured in Safari-private conditions, `set('highScore', 4200)` then
1255
+ * `get('highScore', 0)` returned 4200, zero warnings, and the interface had
1256
+ * no way to ask. On reload the score was 0, the slot list empty, and
1257
+ * `SavePoint` had emitted `saved(1)` for a write that went nowhere.
1258
+ *
1259
+ * Read it once and tell the player their progress will not be kept.
1260
+ *
1261
+ * Optional so a game that injects its OWN store (a cloud backend, a test
1262
+ * double) keeps compiling; absent counts as persistent, which is what a store
1263
+ * somebody wrote on purpose almost always is. Every store the engine makes
1264
+ * says so explicitly.
1265
+ */
1266
+ readonly persistent?: boolean;
1185
1267
  }
1186
1268
  declare function createSaveStore(namespace: string): SaveStore;
1187
1269
  //#endregion
@@ -1261,6 +1343,15 @@ declare function restoreBehaviors(root: Node, state: BehaviorState): RestoreRepo
1261
1343
  declare class SaveSlots {
1262
1344
  private readonly store;
1263
1345
  constructor(namespace?: string, store?: SaveStore);
1346
+ /**
1347
+ * Will these slots survive the page closing?
1348
+ *
1349
+ * `false` in a private window, with storage disabled, or headless — every
1350
+ * write still succeeds and every read still answers, for as long as the tab
1351
+ * lives. A load menu should say so instead of offering a slot that will not
1352
+ * be there tomorrow.
1353
+ */
1354
+ get persistent(): boolean;
1264
1355
  /** Slot ids that have a save, newest first. */
1265
1356
  list(): string[];
1266
1357
  /** Every slot with its contents — what a load menu actually renders. */
@@ -1331,6 +1422,8 @@ declare class Scene {
1331
1422
  readonly orderGroups: Record<string, number> | undefined;
1332
1423
  /** Physics config (gravity etc. — interpreted by the physics modules). */
1333
1424
  readonly physics: JsonObject | undefined;
1425
+ /** Declared piece of another scene — see `SceneJson.fragment`. */
1426
+ readonly fragment: boolean | undefined;
1334
1427
  /** Multiplayer config (room etc. — interpreted by incanto/net). */
1335
1428
  readonly multiplayer: JsonObject | undefined;
1336
1429
  /** Design-resolution viewport (consumed by renderers via resolveViewport). */
@@ -1577,6 +1670,24 @@ interface EngineStats {
1577
1670
  fps: number;
1578
1671
  /** Mean frame delta in ms over the same window (headless runs: 0). */
1579
1672
  frameMs: number;
1673
+ /**
1674
+ * How long ago the newest frame arrived, in ms. `null` when none has.
1675
+ *
1676
+ * `fps` is a rate over a WINDOW, so it keeps answering after the frames stop
1677
+ * — a hidden tab, a descheduled loop, a page the browser froze. Measured on
1678
+ * an engine that ticked 40 times and then stopped being ticked:
1679
+ *
1680
+ * ```
1681
+ * while ticking : fps 59.9
1682
+ * after 0 more ticks : fps 59.9 <- the last sample is 668 ms old
1683
+ * ```
1684
+ *
1685
+ * Both numbers are true about the window they describe; nothing said WHEN
1686
+ * that window was, so "60 fps" read the same whether it was measured 8 ms ago
1687
+ * or four seconds ago. A reader that cares should compare this against a
1688
+ * frame budget before trusting `fps`.
1689
+ */
1690
+ lastFrameAgeMs: number | null;
1580
1691
  /** Current node count of the active scene tree (no scene: 0). */
1581
1692
  nodes: number;
1582
1693
  /** Whether the loop is scheduled (start() without stop()/dispose()). */
@@ -1673,6 +1784,39 @@ declare class Engine {
1673
1784
  * learns about Rapier or three. From a behavior, prefer `this.physics`.
1674
1785
  */
1675
1786
  physics: PhysicsQuery | null;
1787
+ /**
1788
+ * How to build a physics world for a scene that needs one.
1789
+ *
1790
+ * `createGame2D`/`createGame3D` install this; core never learns what Rapier
1791
+ * is. It exists because `physics: 'auto'` can only look at the scene the game
1792
+ * BOOTED with, and the most universal act of finishing a game is putting a
1793
+ * title screen in front of it:
1794
+ *
1795
+ * ```
1796
+ * after boot on the title : engine.physics = null
1797
+ * after goToScene into game: engine.physics = null
1798
+ * player y after 2 s : 0 -> 0
1799
+ * ```
1800
+ *
1801
+ * The game rendered perfectly and was inert — nothing fell, nothing collided,
1802
+ * nothing could be collected or hurt. One scene swap turned a working game
1803
+ * into a diorama, and nothing was logged.
1804
+ */
1805
+ physicsProvider: ((root: Node) => Promise<void>) | null;
1806
+ private ensuring;
1807
+ /**
1808
+ * Give the current scene a physics world if it needs one and has none.
1809
+ *
1810
+ * Idempotent and safe to call on every swap: the provider decides whether the
1811
+ * incoming tree actually wants physics, and an explicit `physics: false` at
1812
+ * boot installs no provider at all, so opting out stays opted out.
1813
+ *
1814
+ * Asynchronous because the Rapier WASM has to load. The first frames after a
1815
+ * swap therefore run unsimulated — which every AI behaviour already tolerates
1816
+ * (`moveBody` falls back to a direct write when `_physics` is null) and which
1817
+ * is a hitch, where the alternative was a game that never simulated at all.
1818
+ */
1819
+ ensurePhysics(): Promise<void>;
1676
1820
  /** Seeded randomness for game logic (deterministic when `seed` is set). */
1677
1821
  readonly rng: Rng;
1678
1822
  /** The engine log channel (debug overlay + headless harness tail this). */
@@ -0,0 +1,47 @@
1
+ import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
+ //#region src/core/boot-failure.ts
3
+ /**
4
+ * Put a boot failure on the SCREEN, where the player is looking.
5
+ *
6
+ * Every shipped starter preloads its assets behind a `#loading` overlay and
7
+ * removes that overlay on the line after `await createGame*`. So anything that
8
+ * rejects — no WebGL context, a scene that will not load, a 404 the preload
9
+ * insisted on — leaves the overlay standing at "100%" forever, with the reason
10
+ * in a console the player will never open. Measured on a pristine scaffold with
11
+ * WebGL denied:
12
+ *
13
+ * ```
14
+ * control #loading gone "Talk to the Elder [E] …"
15
+ * no webgl #loading present "Emberwood 100%" … and still there 8.6 s later
16
+ * ```
17
+ *
18
+ * A bar that reaches 100% and stops reads as a hang, which is the one failure
19
+ * mode a player cannot report usefully. This turns it into a sentence.
20
+ *
21
+ * Headless it does nothing but rethrow-safe logging, so a test that boots a
22
+ * game without a document is unaffected.
23
+ */
24
+ function showBootFailure(error, opts = {}) {
25
+ const message = error instanceof IncantoError ? error.message : String(error?.message ?? error);
26
+ const code = error instanceof IncantoError ? error.code : null;
27
+ console.error(`[incanto] the game could not start${code ? ` (${code})` : ""}: ${message}`);
28
+ const doc = globalThis.document;
29
+ if (!doc?.body) return;
30
+ const host = opts.into ? doc.querySelector(opts.into) : doc.querySelector("#loading");
31
+ const panel = host ?? doc.createElement("div");
32
+ if (!host) {
33
+ panel.style.cssText = "position:fixed;inset:0;display:grid;place-content:center;gap:12px;padding:32px;background:#10131a;color:#e6e9ef;z-index:2147483647;font:14px/1.6 system-ui,sans-serif;text-align:center";
34
+ doc.body.appendChild(panel);
35
+ }
36
+ panel.textContent = "";
37
+ const title = doc.createElement("div");
38
+ title.textContent = "This game could not start";
39
+ title.style.cssText = "font-size:18px;font-weight:600;margin-bottom:8px";
40
+ const detail = doc.createElement("div");
41
+ detail.textContent = message;
42
+ detail.style.cssText = "opacity:.85;max-width:52ch;margin:0 auto";
43
+ panel.appendChild(title);
44
+ panel.appendChild(detail);
45
+ }
46
+ //#endregion
47
+ export { showBootFailure as t };
@@ -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-DZE7B07H.js";
3
+ import { C as qualityRendering, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-mmfugpaQ.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";
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";
6
+ import { n as withWebGLContext, r as resolveRendering } from "./webgl-unavailable-Z5_g8x9H.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-CMqFVxO9.js";
8
+ import { o as frameStats } from "./frame-report-Dlq13Gyj.js";
9
+ import { J as createCausticsQuad, K as Node3D, U as PhysicsBody3D, n as registerGameplayBehaviors } from "./gameplay-D1KixWK8.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-BO-7XoFV.js";
12
+ import { n as enablePhysics3D } from "./physics-3d-DbYaEcZy.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;
@@ -2253,6 +2249,10 @@ async function createGame3D(opts) {
2253
2249
  await report(.06, "scene");
2254
2250
  let physics = null;
2255
2251
  if (opts.physics === true || (opts.physics ?? "auto") === "auto" && hasBody(scene.root)) physics = await enablePhysics3D(engine);
2252
+ if (opts.physics !== false) engine.physicsProvider = async (root) => {
2253
+ if (engine.physics || !hasBody(root)) return;
2254
+ physics = await enablePhysics3D(engine);
2255
+ };
2256
2256
  await report(.1, "physics");
2257
2257
  const cleanups = [];
2258
2258
  const keyboard = opts.keyboard ?? (typeof window !== "undefined" ? window : false);
@@ -2420,7 +2420,7 @@ async function createGame3D(opts) {
2420
2420
  await report(1, "ready");
2421
2421
  function disposeGame() {
2422
2422
  teardown([
2423
- physics ? ["physics", () => physics?.dispose()] : null,
2423
+ ["physics", () => physics?.dispose()],
2424
2424
  ["app cleanups", () => {
2425
2425
  for (const cleanup of cleanups) cleanup();
2426
2426
  }],