incanto 0.66.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.
- package/bin/_behaviors-loader.mjs +87 -0
- package/bin/_pick-server.mjs +48 -0
- package/bin/incanto-check.mjs +16 -2
- package/bin/incanto-editor.mjs +39 -2
- package/bin/incanto-feel.mjs +4 -1
- package/bin/incanto-frame.mjs +37 -3
- package/bin/incanto-logs.mjs +23 -3
- package/bin/incanto-multiplay.mjs +50 -1
- package/bin/incanto-play.mjs +3 -1
- package/bin/incanto-playtest.mjs +3 -25
- package/bin/incanto-verify.mjs +15 -2
- package/dist/2d.d.ts +44 -7
- package/dist/2d.js +6 -4
- package/dist/3d.d.ts +112 -16
- package/dist/3d.js +8 -6
- package/dist/{audio-player-_UAcHxnC.d.ts → audio-player-DOrq7sP-.d.ts} +62 -4
- package/dist/{behavior-BXNLfIJk.d.ts → behavior-DoFPYrgo.d.ts} +117 -2
- package/dist/boot-failure-CKYrEcGF.js +47 -0
- package/dist/{create-game-DYJCIzO0.js → create-game-DbWtVTxD.js} +91 -29
- package/dist/{create-game-Dd7H4bJV.js → create-game-IZIydDwI.js} +16 -10
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +3 -2
- package/dist/{duplicate-CqSAtdrh.js → duplicate-IWIqk0HJ.js} +1 -1
- package/dist/{editor-switch-DAvWQeld.d.ts → editor-switch-CnIOiyNJ.d.ts} +1 -1
- package/dist/editor.js +18 -7
- package/dist/env.d.ts +1 -1
- package/dist/{environment-presets-BlPsEmq6.js → environment-presets-CybQXNqS.js} +100 -11
- package/dist/{gameplay-D1RADWu3.js → gameplay-DM1eu_cV.js} +46 -5
- package/dist/gameplay.d.ts +75 -6
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +52 -8
- package/dist/index.js +9 -7
- package/dist/{loader-lQDCwNag.js → loader-BC4PNtJX.js} +173 -1
- package/dist/{loader-Cga7FVP4.d.ts → loader-CcB533FR.d.ts} +2 -2
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{physics-2d-Cji5A6sX.js → physics-2d-B7Y6dPZO.js} +29 -2
- package/dist/{physics-3d-BkHjwJgI.js → physics-3d-bG3n70Ky.js} +30 -3
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{register-BmuqYTiY.js → register-3ta-2Xig.js} +148 -22
- package/dist/{register-D0CxCveZ.js → register-ibCjm-wH.js} +3 -3
- package/dist/{replay-IsZbNu6d.js → replay-CYvhVHHN.js} +28 -4
- package/dist/{replay-BHoB6fCU.d.ts → replay-Dvn8aeBd.d.ts} +38 -4
- package/dist/{schema-CFeioQRE.d.ts → schema-B6ugCV1Q.d.ts} +11 -0
- package/dist/{split-screen-D_i7GRcY.js → split-screen-CYwDkbLF.js} +2 -2
- package/dist/{split-screen-eJUFjhi1.d.ts → split-screen-DhrSzZIB.d.ts} +3 -3
- package/dist/{src-BTLbXFPZ.js → src-D7RIqXYF.js} +1 -1
- package/dist/{teardown-byR9USax.js → teardown-D2NEmxPB.js} +2 -2
- package/dist/{test-DgrD0jHD.js → test-8hoHeRmo.js} +140 -32
- package/dist/test.d.ts +9 -7
- package/dist/test.js +3 -3
- package/dist/{touch-DESwnpOc.js → touch-DEAmqGdf.js} +30 -186
- package/dist/vite.d.ts +0 -7
- package/dist/vite.js +13 -3
- package/dist/webgl-unavailable-N9nQqesw.js +228 -0
- package/editor/assets/{agent8-9N-Pd_YS.js → agent8-BrrHOjMJ.js} +1 -1
- package/editor/assets/debug-BBhKuBNV.js +3 -0
- package/editor/assets/{index-CeDhIPTC.js → index-eVd0BToA.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +5 -1
- package/skills/incanto-3d-character.md +1 -0
- package/skills/incanto-audio.md +49 -0
- package/skills/incanto-building-2d-games.md +37 -0
- package/skills/incanto-building-3d-games.md +56 -0
- package/skills/incanto-editor.md +5 -1
- package/skills/incanto-environment.md +2 -2
- package/skills/incanto-gameplay-behaviors.md +29 -2
- package/skills/incanto-hud.md +14 -1
- package/skills/incanto-multiplayer.md +14 -0
- package/skills/incanto-node-reference.md +2 -1
- package/skills/incanto-performance.md +35 -0
- package/skills/incanto-physics-and-input.md +23 -0
- package/skills/incanto-playtesting.md +15 -1
- package/skills/incanto-save-slots.md +21 -1
- package/skills/incanto-scene-json-authoring.md +12 -1
- package/skills/incanto-verifying-your-game.md +19 -0
- package/skills/incanto-your-first-game.md +4 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +38 -19
- package/templates-app/beacon-isle-3d/src/main.ts +8 -1
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +99 -53
- package/templates-app/platformer-2d/src/main.ts +8 -1
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/game.scene.json +31 -17
- package/templates-app/star-survivor/src/main.ts +8 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +14 -8
- package/templates-app/tps-3d/src/main.ts +8 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +20 -11
- package/templates-app/village-quest-3d/src/main.ts +8 -1
- package/templates-app/village-quest-3d/src/village.scene.json +10 -7
- package/editor/assets/debug-CkbJICYp.js +0 -3
package/dist/2d.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
|
|
3
|
+
import { t as isWebGLAvailable } from "./webgl-unavailable-N9nQqesw.js";
|
|
4
|
+
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-IZIydDwI.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-ibCjm-wH.js";
|
|
6
|
+
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-B7Y6dPZO.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 {
|
|
2
|
-
import { n as
|
|
3
|
-
import {
|
|
1
|
+
import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-DOrq7sP-.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-DoFPYrgo.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";
|
|
4
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-DCnHFmto.js";
|
|
5
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
6
|
-
import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-_UAcHxnC.js";
|
|
6
|
+
import { t as LoadSceneOptions } from "./loader-CcB533FR.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";
|
|
@@ -133,6 +133,31 @@ interface AnimationEntry {
|
|
|
133
133
|
*/
|
|
134
134
|
declare class AssetStore3D {
|
|
135
135
|
private readonly engine;
|
|
136
|
+
/**
|
|
137
|
+
* One geometry and one material per distinct SHAPE and per distinct look.
|
|
138
|
+
*
|
|
139
|
+
* `MeshInstance3D._createObject3D` used to `new BoxGeometry()` and
|
|
140
|
+
* `new MeshPhysicalMaterial()` for every node, even when byte-identical.
|
|
141
|
+
* Three identical nodes reported `geometry shared? false false / material
|
|
142
|
+
* shared? false false`, with distinct uuids. Measured at 3,200 units, all
|
|
143
|
+
* three runs at identical 3,209 draw calls / 1,741,932 triangles and an
|
|
144
|
+
* identical screenshot:
|
|
145
|
+
*
|
|
146
|
+
* ```
|
|
147
|
+
* per-node geometry + material 31.0 ms wall (renderMs 27.3)
|
|
148
|
+
* share one geometry 27.0 ms
|
|
149
|
+
* + share two materials 15.4 ms <- 2x the headroom
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* 13.1 ms of that 26.5 ms cliff was GPU state churn from per-node objects,
|
|
153
|
+
* and none of it was visible headlessly: with no renderer `_syncObject3D`
|
|
154
|
+
* never runs, so the bench measured 55 ns/node instead of ~2,000.
|
|
155
|
+
*
|
|
156
|
+
* Renderer-scoped, deliberately — module-level state is the predecessor's
|
|
157
|
+
* fatal flaw and two games in one page must not share a cache.
|
|
158
|
+
*/
|
|
159
|
+
readonly meshGeometries: Map<string, unknown>;
|
|
160
|
+
readonly meshMaterials: Map<string, unknown>;
|
|
136
161
|
private readonly models;
|
|
137
162
|
private readonly animations;
|
|
138
163
|
private readonly modelKeys;
|
|
@@ -578,6 +603,16 @@ declare class Physics3D {
|
|
|
578
603
|
private readonly overlaps;
|
|
579
604
|
private trackOverlap;
|
|
580
605
|
/**
|
|
606
|
+
* Forget every overlap whose node has left the tree.
|
|
607
|
+
*
|
|
608
|
+
* The lazy drop inside `overlapping()` only cleans the SETS of a key someone
|
|
609
|
+
* asks about, and only when they ask. This clears the KEYS, which is where the
|
|
610
|
+
* dead nodes were pinned.
|
|
611
|
+
*/
|
|
612
|
+
/** Drop one node from the overlap map, both as a key and as a member. */
|
|
613
|
+
private forgetOverlaps;
|
|
614
|
+
private pruneOverlaps;
|
|
615
|
+
/**
|
|
581
616
|
* The bodies currently overlapping `node` — the standing answer behind
|
|
582
617
|
* `triggerEnter`/`triggerExit`.
|
|
583
618
|
*
|
|
@@ -683,6 +718,18 @@ interface CreateGame3DOptions {
|
|
|
683
718
|
*/
|
|
684
719
|
editor?: boolean | EditorSwitchOptions;
|
|
685
720
|
seed?: number;
|
|
721
|
+
/**
|
|
722
|
+
* Namespace this game's persisted settings — volume, language, quality tier.
|
|
723
|
+
*
|
|
724
|
+
* They are keyed `incanto:<namespace>:<key>` in the browser's storage and
|
|
725
|
+
* defaulted to `settings` for everyone, so two Incanto games on one domain
|
|
726
|
+
* shared one options screen. Measured: game A's player sets `renderScale
|
|
727
|
+
* 0.25, muted, quality low`; game B, whose source never mentions settings,
|
|
728
|
+
* boots at a 480x232 drawing buffer and silent.
|
|
729
|
+
*
|
|
730
|
+
* `SaveSlots(namespace)` already takes one for exactly this reason.
|
|
731
|
+
*/
|
|
732
|
+
settings?: EngineOptions["settings"];
|
|
686
733
|
fixedHz?: number;
|
|
687
734
|
pixelRatio?: number;
|
|
688
735
|
/** Keep the frame rate by rendering fewer pixels while frames run slow
|
|
@@ -732,7 +779,22 @@ interface GameRenderer {
|
|
|
732
779
|
}
|
|
733
780
|
interface Game3D {
|
|
734
781
|
engine: Engine;
|
|
735
|
-
|
|
782
|
+
/**
|
|
783
|
+
* The LIVE scene — a getter over `engine.scene`, not a snapshot.
|
|
784
|
+
*
|
|
785
|
+
* This used to be the scene captured at boot. `Engine.setScene` calls
|
|
786
|
+
* `this._scene?.root.free()`, so after any swap the old handle was not merely
|
|
787
|
+
* stale, it was EMPTIED — and a single-scene game reaches that through
|
|
788
|
+
* `flow.restart()`, which `incanto-hud.md` wires to a Start button. Measured:
|
|
789
|
+
*
|
|
790
|
+
* before swap game.scene.root.children = 2 engine.scene = 2
|
|
791
|
+
* after swap game.scene.root.children = 0 engine.scene = 2
|
|
792
|
+
* game.scene.root.getNode('Flow') -> No node at 'Flow'. Children here: [].
|
|
793
|
+
*
|
|
794
|
+
* `game.engine.scene` always worked and three skills use it, so there were
|
|
795
|
+
* two handles with one name and no note saying which was which.
|
|
796
|
+
*/
|
|
797
|
+
get scene(): Scene$1;
|
|
736
798
|
/**
|
|
737
799
|
* The AUTHORED scene this game booted from — the JSON, not the live tree.
|
|
738
800
|
*
|
|
@@ -1028,19 +1090,25 @@ declare class Environment3D {
|
|
|
1028
1090
|
}
|
|
1029
1091
|
//#endregion
|
|
1030
1092
|
//#region src/3d/environment-runtime.d.ts
|
|
1093
|
+
declare function setEnvironment3D(engine: Engine, patch: JsonObject, opts?: {
|
|
1094
|
+
rebaseTierKeys?: boolean;
|
|
1095
|
+
}): void;
|
|
1031
1096
|
/**
|
|
1032
|
-
*
|
|
1033
|
-
* every frame (cheap when unchanged), so changing the LOOK at runtime is
|
|
1034
|
-
* just writing that object. This helper does it safely:
|
|
1097
|
+
* Apply a quality tier — the only caller that may RESET as well as patch.
|
|
1035
1098
|
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1099
|
+
* A tier is re-applied every time the player moves the slider, so it cannot
|
|
1100
|
+
* compose with what the last one left behind: `low` strips the author's bloom,
|
|
1101
|
+
* and `high` then has nothing to put back. This re-derives the four tier-owned
|
|
1102
|
+
* stages from the scene FILE first, so every tier is reversible, and so a stage
|
|
1103
|
+
* the author never wrote can never appear — the contract `settings.ts` states
|
|
1104
|
+
* and `incanto-performance.md` promises: *the tier can only take things away.*
|
|
1038
1105
|
*
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1106
|
+
* Measured before this, on a scene with no `bloom` and no `post` in its JSON:
|
|
1107
|
+
* `high` and `medium` both rendered five GL passes at 3.3 ms where `low`
|
|
1108
|
+
* rendered one at 1.24 ms — 62% of the frame on a chain the game never asked
|
|
1109
|
+
* for, and `environment.bloom` read back `{strength: 0.8, threshold: 1}`.
|
|
1042
1110
|
*/
|
|
1043
|
-
declare function
|
|
1111
|
+
declare function applyQualityTier3D(engine: Engine, patch: JsonObject): void;
|
|
1044
1112
|
//#endregion
|
|
1045
1113
|
//#region src/3d/model-verdict.d.ts
|
|
1046
1114
|
/**
|
|
@@ -1336,6 +1404,30 @@ declare class CharacterController3D extends Node3D {
|
|
|
1336
1404
|
pitchMax: number;
|
|
1337
1405
|
/** Spring-arm: pull the orbit camera in front of walls/ground instead of clipping. */
|
|
1338
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;
|
|
1339
1431
|
turnSpeed: number;
|
|
1340
1432
|
/** Camera smoothing rate (1-exp(-rate·dt)); side view uses 5. */
|
|
1341
1433
|
camLerp: number;
|
|
@@ -1792,6 +1884,10 @@ declare class MeshInstance3D extends Node3D {
|
|
|
1792
1884
|
/** Loader hook: malformed materials fail at LOAD time, not at render. */
|
|
1793
1885
|
static validateJson(node: Node): void;
|
|
1794
1886
|
private geometryKey;
|
|
1887
|
+
private materialKey;
|
|
1888
|
+
/** True while this node is using a cache entry it must not dispose. */
|
|
1889
|
+
private sharedGeometry;
|
|
1890
|
+
private sharedMaterial;
|
|
1795
1891
|
private textureKey;
|
|
1796
1892
|
private loadedTextures;
|
|
1797
1893
|
protected override _createObject3D(): Object3D;
|
|
@@ -4034,4 +4130,4 @@ declare function createNavDebugNode(nav: TerrainNav, opts?: {
|
|
|
4034
4130
|
name?: string;
|
|
4035
4131
|
}): InstancedMesh3D;
|
|
4036
4132
|
//#endregion
|
|
4037
|
-
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, 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-
|
|
1
|
+
import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-BC4PNtJX.js";
|
|
2
|
+
import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
|
|
3
|
+
import { t as isWebGLAvailable } from "./webgl-unavailable-N9nQqesw.js";
|
|
2
4
|
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-
|
|
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-
|
|
5
|
-
import { a as Environment3D, i as syncTree, o as
|
|
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-DM1eu_cV.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-CybQXNqS.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-DbWtVTxD.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-
|
|
9
|
+
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-bG3n70Ky.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, 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-
|
|
2
|
-
import { a as Rng } from "./schema-
|
|
1
|
+
import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-DoFPYrgo.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,7 +233,29 @@ 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;
|
|
200
|
-
|
|
236
|
+
/**
|
|
237
|
+
* Start the sound. `at` schedules it on the AUDIO clock instead of now.
|
|
238
|
+
*
|
|
239
|
+
* A frame is a 16.67 ms grid at 60 Hz and 33.33 ms at 30, so a sound fired
|
|
240
|
+
* from `update()` cannot land closer than one frame to where a chart wants
|
|
241
|
+
* it — measured 0 of 64 notes scheduled on the documented path. Queue it a
|
|
242
|
+
* lead ahead of `engine.sfx.now` and the same notes land at |mean| 0.000000
|
|
243
|
+
* ms:
|
|
244
|
+
*
|
|
245
|
+
* ```ts
|
|
246
|
+
* const lead = 0.08; // schedule this far ahead
|
|
247
|
+
* for (const note of dueSoon(engine.sfx.now + lead)) {
|
|
248
|
+
* hit.play(engine.sfx.now + (note.atSec - songSeconds));
|
|
249
|
+
* }
|
|
250
|
+
* ```
|
|
251
|
+
*
|
|
252
|
+
* PRESETS only — a `src` clip goes through an `<audio>` element, which has no
|
|
253
|
+
* scheduling clock, and passing `at` for one is ignored rather than silently
|
|
254
|
+
* approximated. Everything else is unchanged: the bus gain applies, the sound
|
|
255
|
+
* lands in `engine.audio.recent()`, and `muted` still silences it, none of
|
|
256
|
+
* which a hand-rolled `AudioContext` scheduler keeps.
|
|
257
|
+
*/
|
|
258
|
+
play(at?: number): void;
|
|
201
259
|
/**
|
|
202
260
|
* Procedural-SFX path: synthesize the preset and fire it through WebAudio
|
|
203
261
|
* (low-latency, overlap-friendly). Headless (no AudioContext) plays nothing —
|
|
@@ -222,4 +280,4 @@ declare class AudioPlayer extends Node {
|
|
|
222
280
|
override update(_dt: number): void;
|
|
223
281
|
}
|
|
224
282
|
//#endregion
|
|
225
|
-
export { ParticleSimConfig as a, ParticleSim as i, AudioPlayer as n, ParticleView as o, SpatialPose as r, AudioElementLike as t };
|
|
283
|
+
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-
|
|
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
|
/**
|
|
@@ -16,6 +16,15 @@ declare class Signal<Args extends unknown[] = unknown[]> {
|
|
|
16
16
|
}): () => void;
|
|
17
17
|
disconnect(fn: SignalListener<Args>): void;
|
|
18
18
|
disconnectAll(): void;
|
|
19
|
+
/**
|
|
20
|
+
* How many listeners are attached.
|
|
21
|
+
*
|
|
22
|
+
* A leak is a SLOPE, and a game had no way to see one: every check in this
|
|
23
|
+
* engine is a snapshot. A handler connected per frame and never disconnected,
|
|
24
|
+
* or a `once` that armed and never fired, accumulates invisibly and is
|
|
25
|
+
* perfectly healthy for the first thirty seconds of every playthrough.
|
|
26
|
+
*/
|
|
27
|
+
get count(): number;
|
|
19
28
|
emit(...args: Args): void;
|
|
20
29
|
get connectionCount(): number;
|
|
21
30
|
}
|
|
@@ -247,6 +256,14 @@ declare class Node implements NodeLifecycle {
|
|
|
247
256
|
once?: boolean;
|
|
248
257
|
}): () => void;
|
|
249
258
|
off(signal: string, fn: SignalListener<unknown[]>): void;
|
|
259
|
+
/**
|
|
260
|
+
* How many listeners this node has on `signal` — 0 for one nobody watches.
|
|
261
|
+
*
|
|
262
|
+
* The engine's own leak test reads it, and so can a game: a handler connected
|
|
263
|
+
* every frame and never disconnected is invisible to every other check here,
|
|
264
|
+
* because every other check is a snapshot and a leak is a slope.
|
|
265
|
+
*/
|
|
266
|
+
listenerCount(signal: string): number;
|
|
250
267
|
emit(signal: string, ...args: unknown[]): void;
|
|
251
268
|
/**
|
|
252
269
|
* Defer destruction to the end of the current update pass (flushed by the
|
|
@@ -402,6 +419,12 @@ interface MusicTrack {
|
|
|
402
419
|
play(): void;
|
|
403
420
|
/** Stop and release the underlying element/source. */
|
|
404
421
|
stop(): void;
|
|
422
|
+
/**
|
|
423
|
+
* Seconds into the track, or `null` when the backend cannot say.
|
|
424
|
+
*
|
|
425
|
+
* Optional so an existing custom backend keeps compiling.
|
|
426
|
+
*/
|
|
427
|
+
playhead?(): number | null;
|
|
405
428
|
}
|
|
406
429
|
/** Backend that mints {@link MusicTrack}s — WebAudio-backed in the browser. */
|
|
407
430
|
interface MusicBackend {
|
|
@@ -453,6 +476,22 @@ declare class MusicManager {
|
|
|
453
476
|
constructor(buses: AudioBuses);
|
|
454
477
|
/** Source of the logically-current track, or null when nothing is playing. */
|
|
455
478
|
get current(): string | null;
|
|
479
|
+
/**
|
|
480
|
+
* Seconds into the current track — the playhead of the thing you can HEAR.
|
|
481
|
+
*
|
|
482
|
+
* `null` when nothing is playing, or when the backend has no clock (headless,
|
|
483
|
+
* or a custom backend that does not implement it).
|
|
484
|
+
*
|
|
485
|
+
* A game charted to a soundtrack needs this and there was no way to get it:
|
|
486
|
+
* `current` gave the src and the backend was private, so the only option was
|
|
487
|
+
* an `engine.time` counter started next to `music.play()` — a different clock
|
|
488
|
+
* from the one the audio is on, and one that keeps counting through a
|
|
489
|
+
* gesture-block, a stall or a seek. This is the audio's own answer.
|
|
490
|
+
*
|
|
491
|
+
* It is the RAW element time, so a looping track wraps to 0 at each pass:
|
|
492
|
+
* that is a loop boundary you can chart against, not a fault.
|
|
493
|
+
*/
|
|
494
|
+
get playhead(): number | null;
|
|
456
495
|
/** Per-manager volume (0..1), on top of the bus gain. */
|
|
457
496
|
get volume(): number;
|
|
458
497
|
/**
|
|
@@ -677,6 +716,23 @@ interface SfxPlayOptions {
|
|
|
677
716
|
seed?: number;
|
|
678
717
|
/** When set, the sound is spatialized through a PannerNode at this emitter. */
|
|
679
718
|
spatial?: SpatialPlay;
|
|
719
|
+
/**
|
|
720
|
+
* Start at this point on the AUDIO clock (`engine.sfx.now` + lead), instead
|
|
721
|
+
* of "as soon as this call returns".
|
|
722
|
+
*
|
|
723
|
+
* Nothing in the audio API took a time, so a sound could only be fired from a
|
|
724
|
+
* frame — and a frame is a 16.67 ms grid at 60 Hz, 33.33 ms at 30. Measured
|
|
725
|
+
* on a metronome: firing from `update()` gave 0 of 64 notes a scheduled
|
|
726
|
+
* start; the same notes queued one frame ahead with `when` land at
|
|
727
|
+
* |mean| 0.000000 ms. That difference is the whole of a rhythm game, and
|
|
728
|
+
* anything charted to a soundtrack.
|
|
729
|
+
*
|
|
730
|
+
* A time already past plays immediately (Web Audio's own rule), so a late
|
|
731
|
+
* scheduler degrades instead of going silent. The bus gain and the
|
|
732
|
+
* `engine.audio.recent()` record apply either way — which is what a
|
|
733
|
+
* hand-rolled `AudioContext` scheduler loses.
|
|
734
|
+
*/
|
|
735
|
+
when?: number;
|
|
680
736
|
}
|
|
681
737
|
/**
|
|
682
738
|
* One AudioContext + a PCM cache. Instance-scoped (held by the renderer's game
|
|
@@ -724,6 +780,14 @@ declare class SfxEngine {
|
|
|
724
780
|
/** Resume a gesture-suspended context (wired to the first user gesture). */
|
|
725
781
|
unlock(): void;
|
|
726
782
|
/**
|
|
783
|
+
* The AUDIO clock, in seconds — the one a sound is actually scheduled
|
|
784
|
+
* against, and the only clock that does not drift relative to what you hear.
|
|
785
|
+
*
|
|
786
|
+
* `0` headless and before the first sound, since there is no context yet.
|
|
787
|
+
* Schedule with a lead: `sfx.play(params, 1, { when: sfx.now + 0.08 })`.
|
|
788
|
+
*/
|
|
789
|
+
get now(): number;
|
|
790
|
+
/**
|
|
727
791
|
* Play a preset at `gain` (0..1, already bus-multiplied by the caller). Each
|
|
728
792
|
* call spawns its own source so rapid repeats overlap instead of cutting off.
|
|
729
793
|
* Headless → no-op.
|
|
@@ -1165,6 +1229,25 @@ interface SaveStore {
|
|
|
1165
1229
|
remove(key: string): void;
|
|
1166
1230
|
/** Wipe THIS namespace only. */
|
|
1167
1231
|
clear(): void;
|
|
1232
|
+
/**
|
|
1233
|
+
* Does a write here survive the page closing?
|
|
1234
|
+
*
|
|
1235
|
+
* `false` means this store is a Map that lives as long as the tab does — a
|
|
1236
|
+
* private window, storage disabled, quota exhausted, or headless. Everything
|
|
1237
|
+
* still works IN the session, which is exactly what made it undetectable:
|
|
1238
|
+
* measured in Safari-private conditions, `set('highScore', 4200)` then
|
|
1239
|
+
* `get('highScore', 0)` returned 4200, zero warnings, and the interface had
|
|
1240
|
+
* no way to ask. On reload the score was 0, the slot list empty, and
|
|
1241
|
+
* `SavePoint` had emitted `saved(1)` for a write that went nowhere.
|
|
1242
|
+
*
|
|
1243
|
+
* Read it once and tell the player their progress will not be kept.
|
|
1244
|
+
*
|
|
1245
|
+
* Optional so a game that injects its OWN store (a cloud backend, a test
|
|
1246
|
+
* double) keeps compiling; absent counts as persistent, which is what a store
|
|
1247
|
+
* somebody wrote on purpose almost always is. Every store the engine makes
|
|
1248
|
+
* says so explicitly.
|
|
1249
|
+
*/
|
|
1250
|
+
readonly persistent?: boolean;
|
|
1168
1251
|
}
|
|
1169
1252
|
declare function createSaveStore(namespace: string): SaveStore;
|
|
1170
1253
|
//#endregion
|
|
@@ -1220,7 +1303,8 @@ interface RestoreReport {
|
|
|
1220
1303
|
declare function captureBehaviors(root: Node, source?: JsonValue): {
|
|
1221
1304
|
state: BehaviorState; /** Paths of nodes that had state to save and no uid to save it under. */
|
|
1222
1305
|
unaddressable: string[]; /** Behaviors in this tree that have something to save — addressable or not. */
|
|
1223
|
-
saveable: number;
|
|
1306
|
+
saveable: number; /** Authored nodes this run consumed that the `#freed` ledger cannot record. */
|
|
1307
|
+
unrecordable: string[];
|
|
1224
1308
|
};
|
|
1225
1309
|
/**
|
|
1226
1310
|
* Hand each behavior its state back.
|
|
@@ -1243,6 +1327,15 @@ declare function restoreBehaviors(root: Node, state: BehaviorState): RestoreRepo
|
|
|
1243
1327
|
declare class SaveSlots {
|
|
1244
1328
|
private readonly store;
|
|
1245
1329
|
constructor(namespace?: string, store?: SaveStore);
|
|
1330
|
+
/**
|
|
1331
|
+
* Will these slots survive the page closing?
|
|
1332
|
+
*
|
|
1333
|
+
* `false` in a private window, with storage disabled, or headless — every
|
|
1334
|
+
* write still succeeds and every read still answers, for as long as the tab
|
|
1335
|
+
* lives. A load menu should say so instead of offering a slot that will not
|
|
1336
|
+
* be there tomorrow.
|
|
1337
|
+
*/
|
|
1338
|
+
get persistent(): boolean;
|
|
1246
1339
|
/** Slot ids that have a save, newest first. */
|
|
1247
1340
|
list(): string[];
|
|
1248
1341
|
/** Every slot with its contents — what a load menu actually renders. */
|
|
@@ -1313,6 +1406,8 @@ declare class Scene {
|
|
|
1313
1406
|
readonly orderGroups: Record<string, number> | undefined;
|
|
1314
1407
|
/** Physics config (gravity etc. — interpreted by the physics modules). */
|
|
1315
1408
|
readonly physics: JsonObject | undefined;
|
|
1409
|
+
/** Declared piece of another scene — see `SceneJson.fragment`. */
|
|
1410
|
+
readonly fragment: boolean | undefined;
|
|
1316
1411
|
/** Multiplayer config (room etc. — interpreted by incanto/net). */
|
|
1317
1412
|
readonly multiplayer: JsonObject | undefined;
|
|
1318
1413
|
/** Design-resolution viewport (consumed by renderers via resolveViewport). */
|
|
@@ -1600,6 +1695,26 @@ interface EngineOptions {
|
|
|
1600
1695
|
seed?: number;
|
|
1601
1696
|
/** Frame cap in fps; 0 (default) runs at whatever the display offers. */
|
|
1602
1697
|
maxFps?: number;
|
|
1698
|
+
/**
|
|
1699
|
+
* Which SETTINGS this game owns — its own, or the origin's.
|
|
1700
|
+
*
|
|
1701
|
+
* Persisted settings are keyed `incanto:<namespace>:<key>` in the browser's
|
|
1702
|
+
* storage, and this defaulted to `settings` with no way to change it. So two
|
|
1703
|
+
* Incanto games served from one domain shared one volume slider, one language
|
|
1704
|
+
* and one quality tier. Measured: game A's player sets
|
|
1705
|
+
* `renderScale 0.25, muted, quality low`; game B — whose source never mentions
|
|
1706
|
+
* settings — boots at a 480×232 drawing buffer, silent.
|
|
1707
|
+
*
|
|
1708
|
+
* `SaveSlots(namespace)` and `createSaveStore(namespace)` have always taken
|
|
1709
|
+
* one and document it as "keeps two games on one origin apart". This is the
|
|
1710
|
+
* third sibling finally saying the same thing.
|
|
1711
|
+
*
|
|
1712
|
+
* Pass a `Settings` instance instead when a game wants to share deliberately
|
|
1713
|
+
* (a launcher and its titles), or drive them from its own store.
|
|
1714
|
+
*/
|
|
1715
|
+
settings?: Settings | {
|
|
1716
|
+
namespace: string;
|
|
1717
|
+
};
|
|
1603
1718
|
}
|
|
1604
1719
|
/**
|
|
1605
1720
|
* The game loop: drives a Scene's tree with fixed-timestep `fixedUpdate`
|
|
@@ -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 };
|