incanto 0.29.0 → 0.30.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/incanto-editor.mjs +101 -28
- package/dist/2d.d.ts +16 -1
- package/dist/2d.js +2 -2
- package/dist/3d.d.ts +34 -1
- package/dist/3d.js +4 -4
- package/dist/{create-game-CbuLWorm.js → create-game-5z_QVtLx.js} +13 -9
- package/dist/{create-game-CNKXGfpr.js → create-game-DuBTv2zI.js} +14 -10
- package/dist/debug-draw-BM3DsvtT.js +18 -0
- package/dist/debug.js +1 -1
- package/dist/editor.js +1346 -1231
- package/dist/{gameplay-L05WgWd1.js → gameplay-Cfr6aFZ1.js} +12 -3
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1 -1
- package/dist/{physics-2d-CCVTrKOd.js → physics-2d-3kOQCtgd.js} +26 -1
- package/dist/{physics-3d-BZZLtwJu.js → physics-3d-CeRH-Ff_.js} +38 -3
- package/dist/react.js +1 -1
- package/dist/{register-C44aSduO.js → register-DJ0SByQg.js} +1 -1
- package/dist/{editor-switch-CAKlJMIY.js → teardown-ByzfDPyu.js} +83 -4
- package/dist/test.js +6 -6
- package/dist/vite.d.ts +87 -1
- package/dist/vite.js +262 -3
- package/editor/assets/{agent8-DEVkEa3d.js → agent8-CAp0i5qn.js} +1 -1
- package/editor/assets/{debug-zGAtpDF0.js → debug-BoEYfbqK.js} +1 -1
- package/editor/assets/index-BO6WU8by.js +10696 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/skills/incanto-editor.md +61 -8
- package/skills/incanto-physics-and-input.md +15 -0
- package/skills/incanto-verifying-your-game.md +6 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/vite.config.ts +7 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/vite.config.ts +7 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/vite.config.ts +7 -0
- package/dist/debug-draw-CZmOYjL2.js +0 -13
- package/editor/assets/index-Bx4UtWYY.js +0 -10586
|
@@ -2367,6 +2367,15 @@ var Node3D = class Node3D extends Node {
|
|
|
2367
2367
|
this.position[2] ?? 0
|
|
2368
2368
|
];
|
|
2369
2369
|
}
|
|
2370
|
+
/**
|
|
2371
|
+
* @internal The backing object IF it has been built — never builds one.
|
|
2372
|
+
*
|
|
2373
|
+
* `_ensureObject3D()` is the wrong call during teardown: it allocates a mesh
|
|
2374
|
+
* for a node that never rendered, purely so the next line can dispose it.
|
|
2375
|
+
*/
|
|
2376
|
+
get _builtObject3D() {
|
|
2377
|
+
return this._object3D;
|
|
2378
|
+
}
|
|
2370
2379
|
/** @internal The backing three object (lazily created). */
|
|
2371
2380
|
_ensureObject3D() {
|
|
2372
2381
|
if (!this._object3D) {
|
|
@@ -3341,9 +3350,9 @@ var Water3D = class Water3D extends Node3D {
|
|
|
3341
3350
|
} : null;
|
|
3342
3351
|
}
|
|
3343
3352
|
free() {
|
|
3344
|
-
const mesh = this.
|
|
3345
|
-
mesh
|
|
3346
|
-
mesh
|
|
3353
|
+
const mesh = this._builtObject3D;
|
|
3354
|
+
mesh?.geometry?.dispose();
|
|
3355
|
+
(mesh?.material)?.dispose?.();
|
|
3347
3356
|
this.cubeTarget?.dispose();
|
|
3348
3357
|
this.cubeTarget = null;
|
|
3349
3358
|
this.cubeCamera = null;
|
package/dist/gameplay.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-
|
|
1
|
+
import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-Cfr6aFZ1.js";
|
|
2
2
|
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,15 @@ declare class WebAudioMusicBackend implements MusicBackend {
|
|
|
47
47
|
*/
|
|
48
48
|
interface DebugLineSource {
|
|
49
49
|
readonly dimension: "2d" | "3d";
|
|
50
|
+
/**
|
|
51
|
+
* The engine whose world these lines describe.
|
|
52
|
+
*
|
|
53
|
+
* A page can hold more than one at a time — the editor runs its edit engine
|
|
54
|
+
* and a preview engine side by side — and a renderer must draw ITS world, not
|
|
55
|
+
* whichever source happens to be first in a module-level Set. Without this,
|
|
56
|
+
* one leaked physics instance paints its frozen colliders over everybody.
|
|
57
|
+
*/
|
|
58
|
+
readonly engine: object;
|
|
50
59
|
/** Toggle at runtime: `physics.debugDraw = true`. */
|
|
51
60
|
debugDraw: boolean;
|
|
52
61
|
/**
|
package/dist/index.js
CHANGED
|
@@ -295,6 +295,6 @@ function newUid() {
|
|
|
295
295
|
//#endregion
|
|
296
296
|
//#region src/index.ts
|
|
297
297
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
298
|
-
const VERSION = "0.
|
|
298
|
+
const VERSION = "0.30.0";
|
|
299
299
|
//#endregion
|
|
300
300
|
export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
3
|
import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-DWcWq4QG.js";
|
|
4
|
-
import { n as registerDebugSource } from "./debug-draw-
|
|
4
|
+
import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
|
|
5
5
|
//#region src/2d/physics/physics-2d.ts
|
|
6
6
|
var physics_2d_exports = /* @__PURE__ */ __exportAll({
|
|
7
7
|
Physics2D: () => Physics2D,
|
|
@@ -46,6 +46,7 @@ var Physics2D = class {
|
|
|
46
46
|
this.R = R;
|
|
47
47
|
this.engine = engine;
|
|
48
48
|
this.optsGravity = opts?.gravity;
|
|
49
|
+
this.simulate = opts?.simulate ?? true;
|
|
49
50
|
this.lastScene = engine.scene;
|
|
50
51
|
const declared = engine.scene?.physics?.gravity;
|
|
51
52
|
const g = opts?.gravity ?? declared ?? [0, 980];
|
|
@@ -64,8 +65,15 @@ var Physics2D = class {
|
|
|
64
65
|
this.syncBodies();
|
|
65
66
|
}
|
|
66
67
|
/** @internal Driven by engine.fixedUpdated. */
|
|
68
|
+
/** Whether the solver runs at all. False = collider outlines only. */
|
|
69
|
+
simulate;
|
|
67
70
|
step(dt) {
|
|
68
71
|
this.lastDt = dt;
|
|
72
|
+
if (!this.simulate) {
|
|
73
|
+
this.syncBodies();
|
|
74
|
+
this.poseFromTree();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
69
77
|
if (this.engine.scene !== this.lastScene) {
|
|
70
78
|
this.lastScene = this.engine.scene;
|
|
71
79
|
if (!this.optsGravity) {
|
|
@@ -294,6 +302,23 @@ var Physics2D = class {
|
|
|
294
302
|
jointSet = /* @__PURE__ */ new Set();
|
|
295
303
|
joints = /* @__PURE__ */ new Map();
|
|
296
304
|
bodySet = /* @__PURE__ */ new Set();
|
|
305
|
+
/**
|
|
306
|
+
* Move every body to its node's current transform — outline mode only.
|
|
307
|
+
*
|
|
308
|
+
* Computed exactly as `ensureEntry` computes a body's first pose, so an
|
|
309
|
+
* outline drawn in the editor is the outline the running game draws.
|
|
310
|
+
*/
|
|
311
|
+
poseFromTree() {
|
|
312
|
+
for (const [node, e] of this.entries) {
|
|
313
|
+
const [wx, wy] = worldPosition2D(node);
|
|
314
|
+
e.body.setTranslation({
|
|
315
|
+
x: wx / SCALE,
|
|
316
|
+
y: wy / SCALE
|
|
317
|
+
}, false);
|
|
318
|
+
e.body.setRotation(node.rotation * DEG, false);
|
|
319
|
+
}
|
|
320
|
+
this.world.propagateModifiedBodyPositionsToColliders();
|
|
321
|
+
}
|
|
297
322
|
syncBodies() {
|
|
298
323
|
const scene = this.engine.scene;
|
|
299
324
|
if (!scene) return;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
|
-
import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-
|
|
4
|
-
import { n as registerDebugSource } from "./debug-draw-
|
|
5
|
-
import { I as buildMeshGeometry, N as Joint3D, P as InstancedMesh3D, k as Terrain3D } from "./register-
|
|
3
|
+
import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-Cfr6aFZ1.js";
|
|
4
|
+
import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
|
|
5
|
+
import { I as buildMeshGeometry, N as Joint3D, P as InstancedMesh3D, k as Terrain3D } from "./register-DJ0SByQg.js";
|
|
6
6
|
import { Euler, Matrix4, Quaternion, Vector3 } from "three";
|
|
7
7
|
//#region src/3d/physics/collider-lines.ts
|
|
8
8
|
/**
|
|
@@ -241,6 +241,7 @@ var Physics3D = class {
|
|
|
241
241
|
this.R = R;
|
|
242
242
|
this.engine = engine;
|
|
243
243
|
this.optsGravity = opts?.gravity;
|
|
244
|
+
this.simulate = opts?.simulate ?? true;
|
|
244
245
|
this.lastScene = engine.scene;
|
|
245
246
|
const declared = engine.scene?.physics?.gravity;
|
|
246
247
|
const g = opts?.gravity ?? declared ?? [
|
|
@@ -259,9 +260,16 @@ var Physics3D = class {
|
|
|
259
260
|
this.disconnect = engine.fixedUpdated.connect((dt) => this.step(dt));
|
|
260
261
|
this.syncBodies();
|
|
261
262
|
}
|
|
263
|
+
/** Whether the solver runs at all. False = collider outlines only. */
|
|
264
|
+
simulate;
|
|
262
265
|
/** @internal Driven by engine.fixedUpdated. */
|
|
263
266
|
step(dt) {
|
|
264
267
|
this.lastDt = dt;
|
|
268
|
+
if (!this.simulate) {
|
|
269
|
+
this.syncBodies();
|
|
270
|
+
this.poseFromTree();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
265
273
|
if (this.engine.scene !== this.lastScene) {
|
|
266
274
|
this.lastScene = this.engine.scene;
|
|
267
275
|
if (!this.optsGravity) {
|
|
@@ -332,6 +340,33 @@ var Physics3D = class {
|
|
|
332
340
|
}
|
|
333
341
|
});
|
|
334
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Move every body to its node's current transform.
|
|
345
|
+
*
|
|
346
|
+
* Only used in outline mode: normally physics owns the pose and writes it
|
|
347
|
+
* INTO the node, so nothing re-reads the tree. Deliberately computed the same
|
|
348
|
+
* way `ensureEntry` computes a body's initial pose — world position, node-local
|
|
349
|
+
* euler — so an outline in the editor is the outline the game will draw, right
|
|
350
|
+
* down to the places where that pair disagrees with a rotated parent.
|
|
351
|
+
*/
|
|
352
|
+
poseFromTree() {
|
|
353
|
+
for (const [node, e] of this.entries) {
|
|
354
|
+
const [wx, wy, wz] = worldPosition3D(node);
|
|
355
|
+
e.body.setTranslation({
|
|
356
|
+
x: wx,
|
|
357
|
+
y: wy,
|
|
358
|
+
z: wz
|
|
359
|
+
}, false);
|
|
360
|
+
quatScratch.setFromEuler(eulerScratch.set((node.rotation[0] ?? 0) * DEG, (node.rotation[1] ?? 0) * DEG, (node.rotation[2] ?? 0) * DEG, "XYZ"));
|
|
361
|
+
e.body.setRotation({
|
|
362
|
+
x: quatScratch.x,
|
|
363
|
+
y: quatScratch.y,
|
|
364
|
+
z: quatScratch.z,
|
|
365
|
+
w: quatScratch.w
|
|
366
|
+
}, false);
|
|
367
|
+
}
|
|
368
|
+
this.world.propagateModifiedBodyPositionsToColliders();
|
|
369
|
+
}
|
|
335
370
|
/** @internal Called by CharacterBody3D.moveAndSlide (during tree fixedUpdate). */
|
|
336
371
|
moveAndSlide(node) {
|
|
337
372
|
const e = this.ensureEntry(node);
|
package/dist/react.js
CHANGED
|
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
|
|
|
156
156
|
pointer: latest.pointer,
|
|
157
157
|
...keyboard !== void 0 ? { keyboard } : {}
|
|
158
158
|
};
|
|
159
|
-
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-
|
|
159
|
+
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-DuBTv2zI.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-5z_QVtLx.js").then((n) => n.n)).createGame2D(o)))(opts);
|
|
160
160
|
if (disposed) {
|
|
161
161
|
next.dispose();
|
|
162
162
|
return;
|
|
@@ -4,7 +4,7 @@ import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
|
4
4
|
import { i as getNodeSchema, l as registerNode } from "./registry-BVJ2HbCn.js";
|
|
5
5
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
6
6
|
import { i as applyParticlePreset, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
|
|
7
|
-
import { A as Water3D, F as PhysicsBody3D, H as colliderFootDrop, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D } from "./gameplay-
|
|
7
|
+
import { A as Water3D, F as PhysicsBody3D, H as colliderFootDrop, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D } from "./gameplay-Cfr6aFZ1.js";
|
|
8
8
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
9
9
|
import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NormalBlending, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector3, Vector4, VectorKeyframeTrack } from "three";
|
|
10
10
|
import { clone } from "three/addons/utils/SkeletonUtils.js";
|
|
@@ -69,6 +69,35 @@ function devServerLibrary(token) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
+
* Does the page's own dev server serve the project's scenes?
|
|
73
|
+
*
|
|
74
|
+
* `incantoScenes()` from `incanto/vite` (the plugin that already validates
|
|
75
|
+
* scenes on save; `incanto-new` puts it in every scaffolded project) answers
|
|
76
|
+
* `GET /api/scenes`. When it does, the
|
|
77
|
+
* editor a game switches into is a PROJECT editor: it can list, open, create
|
|
78
|
+
* and save every scene in the project, and play whichever one you loaded.
|
|
79
|
+
* When it does not (a built game, someone else's server), the editor still
|
|
80
|
+
* edits the scene the game is running, exactly as before.
|
|
81
|
+
*/
|
|
82
|
+
async function projectScenes() {
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch("/api/scenes");
|
|
85
|
+
if (!res.ok) return null;
|
|
86
|
+
const body = await res.json();
|
|
87
|
+
return Array.isArray(body) ? body : null;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** What to say when the dev server has no idea what scenes this project has. */
|
|
93
|
+
const NO_SCENE_ROUTES = "this dev server does not serve the project's scenes yet. Add incantoScenes() to vite.config.ts and restart:\n\n import { incantoScenes } from 'incanto/vite';\n export default defineConfig({ plugins: [incantoScenes()] });\n\nIt also validates every scene you save, before the page reloads into a broken one.";
|
|
94
|
+
async function readProjectScene(file) {
|
|
95
|
+
const res = await fetch(`/api/scene?file=${encodeURIComponent(file)}`);
|
|
96
|
+
if (res.ok) return await res.json();
|
|
97
|
+
const body = await res.json().catch(() => null);
|
|
98
|
+
throw new Error(body?.error ?? `could not open ${file}: HTTP ${res.status}`);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
72
101
|
* The default opener: pull `incanto/editor` in on demand and mount it.
|
|
73
102
|
*
|
|
74
103
|
* It is a dynamic import so the editor — an IDE's worth of UI — never enters a
|
|
@@ -83,18 +112,44 @@ async function openBundledEditor(opts) {
|
|
|
83
112
|
throw new Error(`createGame3D editor: could not load 'incanto/editor'. If this app aliases incanto to the engine SOURCE (the examples and playground do), add an alias for 'incanto/editor' → packages/editor/src/main.ts — or pass editor: { open } and load it yourself. (${error instanceof Error ? error.message : String(error)})`);
|
|
84
113
|
}
|
|
85
114
|
let handle = null;
|
|
115
|
+
const project = await projectScenes();
|
|
116
|
+
const format = mod.formatSceneJson ?? ((json) => JSON.stringify(json, null, 2));
|
|
117
|
+
const writeProjectScene = async (file, json) => {
|
|
118
|
+
const res = await fetch(`/api/scene?file=${encodeURIComponent(file)}`, {
|
|
119
|
+
method: "PUT",
|
|
120
|
+
body: format(json)
|
|
121
|
+
});
|
|
122
|
+
if (!res.ok) throw new Error(`could not save ${file}: HTTP ${res.status} ${await res.text()}`);
|
|
123
|
+
};
|
|
86
124
|
handle = await mod.mountEditor({
|
|
87
125
|
...opts.camera ? { camera: opts.camera } : {},
|
|
88
126
|
api: {
|
|
89
127
|
meta: async () => ({
|
|
90
128
|
version: "live",
|
|
91
|
-
mode: "
|
|
129
|
+
mode: "project",
|
|
92
130
|
root: "",
|
|
93
131
|
input: null,
|
|
94
132
|
output: null
|
|
95
133
|
}),
|
|
96
|
-
loadScene: async () => opts.scene,
|
|
97
|
-
...opts.save ? { saveScene: async (json) =>
|
|
134
|
+
loadScene: async (file) => file ? await readProjectScene(file) : opts.scene,
|
|
135
|
+
...opts.save || project ? { saveScene: async (json, file) => {
|
|
136
|
+
if (file) return writeProjectScene(file, json);
|
|
137
|
+
await opts.save?.(json);
|
|
138
|
+
} } : {},
|
|
139
|
+
scenes: async () => {
|
|
140
|
+
const found = await projectScenes();
|
|
141
|
+
if (!found) throw new Error(NO_SCENE_ROUTES);
|
|
142
|
+
return found;
|
|
143
|
+
},
|
|
144
|
+
createScene: async (path) => {
|
|
145
|
+
const res = await fetch("/api/scenes", {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: JSON.stringify({ path })
|
|
148
|
+
});
|
|
149
|
+
if (res.status === 404) throw new Error(NO_SCENE_ROUTES);
|
|
150
|
+
if (!res.ok) throw new Error(`could not create ${path}: ${await res.text()}`);
|
|
151
|
+
return await res.json();
|
|
152
|
+
},
|
|
98
153
|
...opts.library ? { library: opts.library } : {}
|
|
99
154
|
},
|
|
100
155
|
onPlay: (choice, working) => {
|
|
@@ -158,4 +213,28 @@ function poseFromRenderer(renderer, fallbackDistance = 8) {
|
|
|
158
213
|
};
|
|
159
214
|
}
|
|
160
215
|
//#endregion
|
|
161
|
-
|
|
216
|
+
//#region src/core/teardown.ts
|
|
217
|
+
function teardown(steps, onError = defaultReport) {
|
|
218
|
+
const failures = [];
|
|
219
|
+
for (const step of steps) {
|
|
220
|
+
if (!step) continue;
|
|
221
|
+
const [what, run] = step;
|
|
222
|
+
try {
|
|
223
|
+
run();
|
|
224
|
+
} catch (error) {
|
|
225
|
+
failures.push({
|
|
226
|
+
what,
|
|
227
|
+
error
|
|
228
|
+
});
|
|
229
|
+
try {
|
|
230
|
+
onError(what, error);
|
|
231
|
+
} catch {}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { failures };
|
|
235
|
+
}
|
|
236
|
+
function defaultReport(what, error) {
|
|
237
|
+
console.error(`[incanto] teardown: ${what} failed (continuing)`, error);
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
export { poseFromRenderer as a, openBundledEditor as i, crossFade as n, devServerLibrary as r, teardown as t };
|
package/dist/test.js
CHANGED
|
@@ -4,9 +4,9 @@ import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
|
4
4
|
import { t as auditScene } from "./audit-C6rMyict.js";
|
|
5
5
|
import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
6
6
|
import { i as getNodeSchema, s as mergeStaticProps } from "./registry-BVJ2HbCn.js";
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-Cfr6aFZ1.js";
|
|
8
8
|
import { t as registerNodes2D } from "./register-DWcWq4QG.js";
|
|
9
|
-
import { t as registerNodes3D } from "./register-
|
|
9
|
+
import { t as registerNodes3D } from "./register-DJ0SByQg.js";
|
|
10
10
|
import { t as registerNodesNet } from "./register-MelqEdza.js";
|
|
11
11
|
//#region src/test/index.ts
|
|
12
12
|
/**
|
|
@@ -218,10 +218,10 @@ async function runScript(json, opts) {
|
|
|
218
218
|
engine.setScene(scene);
|
|
219
219
|
const physics = opts.physics ?? "auto";
|
|
220
220
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
221
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
221
|
+
const { enablePhysics2D } = await import("./physics-2d-3kOQCtgd.js").then((n) => n.r);
|
|
222
222
|
await enablePhysics2D(engine);
|
|
223
223
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
224
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
224
|
+
const { enablePhysics3D } = await import("./physics-3d-CeRH-Ff_.js").then((n) => n.r);
|
|
225
225
|
await enablePhysics3D(engine);
|
|
226
226
|
}
|
|
227
227
|
const failures = [];
|
|
@@ -323,10 +323,10 @@ async function createPlaySession(json, opts = {}) {
|
|
|
323
323
|
engine.setScene(scene);
|
|
324
324
|
const physics = opts.physics ?? "auto";
|
|
325
325
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
326
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
326
|
+
const { enablePhysics2D } = await import("./physics-2d-3kOQCtgd.js").then((n) => n.r);
|
|
327
327
|
await enablePhysics2D(engine);
|
|
328
328
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
329
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
329
|
+
const { enablePhysics3D } = await import("./physics-3d-CeRH-Ff_.js").then((n) => n.r);
|
|
330
330
|
await enablePhysics3D(engine);
|
|
331
331
|
}
|
|
332
332
|
const stepMs = 1e3 / (opts.fixedHz ?? 60);
|
package/dist/vite.d.ts
CHANGED
|
@@ -17,11 +17,97 @@ interface HotUpdateContext {
|
|
|
17
17
|
interface IncantoScenesOptions {
|
|
18
18
|
/** Hard-fail on unregistered behaviors too (default: structure-only). */
|
|
19
19
|
strictBehaviors?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Where scenes are found and written. Default: vite's `root` (your project).
|
|
22
|
+
* Nothing outside it is readable or writable, whatever a request asks for.
|
|
23
|
+
*/
|
|
24
|
+
root?: string;
|
|
20
25
|
}
|
|
21
26
|
declare function incantoScenes(opts?: IncantoScenesOptions): {
|
|
22
27
|
name: string;
|
|
23
28
|
handleHotUpdate(ctx: HotUpdateContext): Promise<undefined | never[]>;
|
|
29
|
+
configureServer(server: SceneDevServer): void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* `GET /api/scenes` → every `*.scene.json` under the project root
|
|
33
|
+
* `POST /api/scenes` `{path}` → create a minimal scene there
|
|
34
|
+
* `GET /api/scene?file=<rel>` → that scene's JSON
|
|
35
|
+
* `PUT /api/scene?file=<rel>` → write it back (bytes verbatim)
|
|
36
|
+
*
|
|
37
|
+
* These are the routes `incanto-editor` serves, on YOUR dev server, so the
|
|
38
|
+
* editor a game switches into is a project editor rather than a single-file
|
|
39
|
+
* one: it can list, open, create and save any scene in the project, and play
|
|
40
|
+
* whichever one you loaded.
|
|
41
|
+
*
|
|
42
|
+
* Everything is bounded to the root and to `*.scene.json`. A dev server is
|
|
43
|
+
* still a server: `?file=../../.ssh/id_rsa` has to be a 400, not a read.
|
|
44
|
+
*/
|
|
45
|
+
interface SceneDevServer {
|
|
46
|
+
config?: {
|
|
47
|
+
root?: string;
|
|
48
|
+
};
|
|
49
|
+
middlewares: {
|
|
50
|
+
use(path: string, handler: (req: ApiReq, res: LibraryRes) => void): void;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
interface ApiReq extends LibraryReq {
|
|
54
|
+
on(event: "data" | "end" | "error", cb: (chunk?: unknown) => void): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* One JSON file in the project, and whether it is a scene.
|
|
58
|
+
*
|
|
59
|
+
* A file browser that only shows `*.scene.json` cannot show you the scene you
|
|
60
|
+
* saved as `level.json` — and, worse, gives no reason for its absence. So the
|
|
61
|
+
* list is every `.json` under the project (a scene is JSON by definition, so
|
|
62
|
+
* nothing else can be one), and `scene` says which of them actually are.
|
|
63
|
+
*/
|
|
64
|
+
interface ProjectFileEntry {
|
|
65
|
+
rel: string;
|
|
66
|
+
abs: string;
|
|
67
|
+
size: number;
|
|
68
|
+
modified: number;
|
|
69
|
+
/** Present only when the file IS a scene — read from the file, not the name. */
|
|
70
|
+
scene?: {
|
|
71
|
+
name: string;
|
|
72
|
+
dimension?: "2d" | "3d";
|
|
73
|
+
nodes: number;
|
|
74
|
+
};
|
|
75
|
+
/** Why it is not one, in a form a person can act on. */
|
|
76
|
+
notScene?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Is this JSON an incanto scene? The file itself answers.
|
|
80
|
+
*
|
|
81
|
+
* A scene declares what it is — `{"format": 1, "type": "scene", "name", "root"}`
|
|
82
|
+
* — and those are exactly the four keys the loader demands before it will build
|
|
83
|
+
* anything (`validateHeader`). So "is this a scene" is not a guess from the file
|
|
84
|
+
* name; it is a fact readable from the first object in the file. That is what
|
|
85
|
+
* lets the browser mark a row ✓ instead of hoping.
|
|
86
|
+
*/
|
|
87
|
+
declare function sceneFacts(json: unknown): {
|
|
88
|
+
ok: true;
|
|
89
|
+
name: string;
|
|
90
|
+
dimension?: "2d" | "3d";
|
|
91
|
+
nodes: number;
|
|
92
|
+
} | {
|
|
93
|
+
ok: false;
|
|
94
|
+
why: string;
|
|
24
95
|
};
|
|
96
|
+
/** @internal Exported for tests. Root-bounded walk, deepest 6 levels. */
|
|
97
|
+
declare function discoverScenes(root: string): ProjectFileEntry[];
|
|
98
|
+
/**
|
|
99
|
+
* @internal The one place a request's path becomes a file path.
|
|
100
|
+
*
|
|
101
|
+
* `.json` under the project, nothing else — the file browser offers every JSON
|
|
102
|
+
* and the loader decides, but a dev server still must not be a way to read
|
|
103
|
+
* `.env` or somebody's key file. Content is gated separately: a JSON that is
|
|
104
|
+
* not a scene is never sent to the page, only the reason it is not one.
|
|
105
|
+
*/
|
|
106
|
+
declare function resolveSceneFile(root: string, rel: unknown): string | null;
|
|
107
|
+
/** @internal Exported for tests — `GET`/`POST /api/scenes`. */
|
|
108
|
+
declare function serveSceneList(req: ApiReq, res: LibraryRes, root: string): Promise<void>;
|
|
109
|
+
/** @internal Exported for tests — `GET`/`PUT /api/scene?file=`. */
|
|
110
|
+
declare function serveSceneFile(req: ApiReq, res: LibraryRes, root: string): Promise<void>;
|
|
25
111
|
interface DevServer {
|
|
26
112
|
middlewares: {
|
|
27
113
|
use(path: string, handler: (req: LibraryReq, res: LibraryRes) => void): void;
|
|
@@ -75,4 +161,4 @@ declare function incantoLibrary(opts?: IncantoLibraryOptions): {
|
|
|
75
161
|
/** @internal Exported for tests — the whole request/response behaviour. */
|
|
76
162
|
declare function serveLibrary(req: LibraryReq, res: LibraryRes, opts?: IncantoLibraryOptions, doFetch?: typeof fetch): Promise<void>;
|
|
77
163
|
//#endregion
|
|
78
|
-
export { IncantoLibraryOptions, IncantoScenesOptions, incantoLibrary, incantoScenes, serveLibrary };
|
|
164
|
+
export { IncantoLibraryOptions, IncantoScenesOptions, ProjectFileEntry, discoverScenes, incantoLibrary, incantoScenes, resolveSceneFile, sceneFacts, serveLibrary, serveSceneFile, serveSceneList };
|