incanto 0.60.0 → 0.62.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-check.mjs +13 -0
- package/bin/incanto-verify.mjs +134 -55
- package/dist/2d.d.ts +64 -3
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +5 -5
- package/dist/3d.js +5 -4
- package/dist/{pathfinding-BqWBb0kh.d.ts → audio-player-D5GJgb_x.d.ts} +103 -38
- package/dist/{behavior-DWKTUzKI.d.ts → behavior-DsgayMsH.d.ts} +79 -1
- package/dist/{create-game-ClnIb_M5.js → create-game-BpunnGPX.js} +78 -13
- package/dist/{create-game-BCm38FJV.js → create-game-Caut3bqN.js} +21 -294
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/{duplicate-DJQd44CD.js → duplicate-E4FUs5Bn.js} +1 -1
- package/dist/editor.js +41 -19
- package/dist/{environment-presets-8cjF3t6w.js → environment-presets-BAWeOeqf.js} +50 -13
- package/dist/{gameplay-BBEjPFsR.js → gameplay-CaHqDiQD.js} +177 -24
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +97 -6
- package/dist/index.js +7 -7
- package/dist/{loader-D8n7TU8W.js → loader-DEe272nY.js} +900 -2
- package/dist/{loader-TvkRFbyL.d.ts → loader-DolLJWJn.d.ts} +13 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/pathfinding-_fGrCFmH.d.ts +28 -0
- package/dist/{physics-2d-DqdVp1bt.js → physics-2d-BXmu2i7W.js} +11 -3
- package/dist/{physics-3d-BP0DZb_1.js → physics-3d-ClxP6Uv7.js} +121 -17
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-BSu2dWGC.js → register-CNh4FlbD.js} +104 -16
- package/dist/{register-Da3hXh2H.js → register-D3yx8D4r.js} +228 -16
- package/dist/{registry-WWcQcfMr.js → registry-CF70EArN.js} +55 -3
- package/dist/{replay-BCMK_VRP.d.ts → replay-C5x2vPF5.d.ts} +2 -2
- package/dist/{replay-BlNuIDdg.js → replay-DlgHItNv.js} +57 -197
- package/dist/{split-screen-CL5Yvxse.js → split-screen-CBM9wcMX.js} +3 -3
- package/dist/{split-screen-BQ3tAsf-.d.ts → split-screen-D7OopelJ.d.ts} +2 -2
- package/dist/{src-DFpXBMJN.js → src-CH00_JsR.js} +1 -1
- package/dist/{teardown-CTTwhWSe.js → teardown-C7qP-dcC.js} +1 -1
- package/dist/{test-BeZ95pqw.js → test-BMkg8zMV.js} +29 -16
- package/dist/test.d.ts +45 -4
- package/dist/test.js +2 -2
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-DSJries_.js → agent8-m5mtAO_A.js} +1 -1
- package/editor/assets/{debug-D15Wi5TO.js → debug-CPhzCT8f.js} +1 -1
- package/editor/assets/{index-BjC88k97.js → index-D422P4kW.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +91 -0
- package/skills/incanto-3d-models.md +1 -1
- package/skills/incanto-assets.md +10 -1
- package/skills/incanto-audio.md +21 -13
- package/skills/incanto-behaviors-and-scripts.md +44 -3
- package/skills/incanto-building-2d-games.md +32 -4
- package/skills/incanto-building-3d-games.md +1 -1
- package/skills/incanto-editor.md +1 -1
- package/skills/incanto-gameplay-behaviors.md +39 -2
- package/skills/incanto-hud.md +5 -3
- package/skills/incanto-localization.md +7 -0
- package/skills/incanto-node-reference.md +12 -0
- package/skills/incanto-physics-and-input.md +23 -2
- package/skills/incanto-scene-json-authoring.md +6 -1
- package/skills/incanto-verifying-your-game.md +31 -1
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/dist/particle-sim-C5OfBbmU.d.ts +0 -77
|
@@ -1,196 +1,6 @@
|
|
|
1
|
-
import { D as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
//#region src/core/node-refs.ts
|
|
5
|
-
/**
|
|
6
|
-
* Node-path PROPS, resolved against scene JSON.
|
|
7
|
-
*
|
|
8
|
-
* `Chase.target`, `Camera2D.follow`, `Spawner.prefab`, `Joint3D.target`,
|
|
9
|
-
* `CharacterController3D.skinPath`, `Foliage3D.terrain` … each holds a path to
|
|
10
|
-
* another node, and nothing checked any of them. A connection whose end does not
|
|
11
|
-
* resolve is a hard `DANGLING_CONNECTION` at load; a PROP that does not resolve
|
|
12
|
-
* is nothing at all — `getNodeOrNull` returns null, the scene loads, the enemy
|
|
13
|
-
* never chases, and no error is ever printed.
|
|
14
|
-
*
|
|
15
|
-
* That makes it exactly the class `auditScene` exists for: loads fine, plays
|
|
16
|
-
* wrong. The editor uses the same resolver so the two cannot drift.
|
|
17
|
-
*
|
|
18
|
-
* The grammar comes from `parseNodePath` — the loader's own parser — so a path
|
|
19
|
-
* this module accepts is one the engine accepts.
|
|
20
|
-
*/
|
|
21
|
-
function nodeAt(root, path) {
|
|
22
|
-
let node = root;
|
|
23
|
-
for (const index of path) {
|
|
24
|
-
node = node?.children?.[index];
|
|
25
|
-
if (!node) return null;
|
|
26
|
-
}
|
|
27
|
-
return node ?? null;
|
|
28
|
-
}
|
|
29
|
-
function byName(root, name) {
|
|
30
|
-
const found = [];
|
|
31
|
-
const walk = (node, at) => {
|
|
32
|
-
if (node.name === name) found.push(at);
|
|
33
|
-
(node.children ?? []).forEach((child, i) => {
|
|
34
|
-
walk(child, [...at, i]);
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
walk(root, []);
|
|
38
|
-
return found;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Resolve `value` from the node at `owner`, the way the engine would.
|
|
42
|
-
*
|
|
43
|
-
* Note the asymmetry with connections, whose paths resolve from the SCENE ROOT:
|
|
44
|
-
* a prop's path resolves from the node holding it, which is why `'../Skin'` is
|
|
45
|
-
* the documented default of `CharacterController3D.skinPath` and would mean
|
|
46
|
-
* nothing in a connection.
|
|
47
|
-
*/
|
|
48
|
-
function resolveRefInJson(root, owner, value) {
|
|
49
|
-
if (!root) return {
|
|
50
|
-
ok: false,
|
|
51
|
-
problem: {
|
|
52
|
-
kind: "noMatch",
|
|
53
|
-
name: value
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
let parsed;
|
|
57
|
-
try {
|
|
58
|
-
parsed = parseNodePath(value);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
return {
|
|
61
|
-
ok: false,
|
|
62
|
-
problem: {
|
|
63
|
-
kind: "grammar",
|
|
64
|
-
message: error instanceof Error ? error.message : String(error)
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
if (parsed.kind === "unique") {
|
|
69
|
-
const found = byName(root, parsed.name);
|
|
70
|
-
if (found.length === 1) return {
|
|
71
|
-
ok: true,
|
|
72
|
-
path: found[0]
|
|
73
|
-
};
|
|
74
|
-
if (found.length === 0) return {
|
|
75
|
-
ok: false,
|
|
76
|
-
problem: {
|
|
77
|
-
kind: "noMatch",
|
|
78
|
-
name: parsed.name
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
return {
|
|
82
|
-
ok: false,
|
|
83
|
-
problem: {
|
|
84
|
-
kind: "ambiguous",
|
|
85
|
-
name: parsed.name,
|
|
86
|
-
count: found.length
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
let at;
|
|
91
|
-
let segments = parsed.segments;
|
|
92
|
-
if (parsed.kind === "absolute") {
|
|
93
|
-
const first = segments[0] ?? "";
|
|
94
|
-
if (first !== root.name && first !== "root") return {
|
|
95
|
-
ok: false,
|
|
96
|
-
problem: {
|
|
97
|
-
kind: "wrongRoot",
|
|
98
|
-
first,
|
|
99
|
-
root: String(root.name ?? "")
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
at = [];
|
|
103
|
-
segments = segments.slice(1);
|
|
104
|
-
} else at = [...owner];
|
|
105
|
-
for (const segment of segments) {
|
|
106
|
-
if (segment === "..") {
|
|
107
|
-
if (at.length === 0) return {
|
|
108
|
-
ok: false,
|
|
109
|
-
problem: { kind: "aboveRoot" }
|
|
110
|
-
};
|
|
111
|
-
at.pop();
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
const here = nodeAt(root, at);
|
|
115
|
-
const index = (here?.children ?? []).findIndex((child) => child.name === segment);
|
|
116
|
-
if (index < 0) return {
|
|
117
|
-
ok: false,
|
|
118
|
-
problem: {
|
|
119
|
-
kind: "noChild",
|
|
120
|
-
parent: String(here?.name ?? "?"),
|
|
121
|
-
segment
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
at.push(index);
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
ok: true,
|
|
128
|
-
path: at
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
/** The problem in English, for `incanto-check` and as the editor's base text. */
|
|
132
|
-
function describeRefProblem(problem, value) {
|
|
133
|
-
switch (problem.kind) {
|
|
134
|
-
case "grammar": return problem.message;
|
|
135
|
-
case "noMatch": return `'%${problem.name}' matches no node in this scene.`;
|
|
136
|
-
case "ambiguous": return `'%${problem.name}' matches ${problem.count} nodes — a %name must be unique.`;
|
|
137
|
-
case "wrongRoot": return `'${value}' starts at '${problem.first}', but the scene root is '${problem.root}' (or write '/root/…').`;
|
|
138
|
-
case "noChild": return `'${value}' leads nowhere — '${problem.parent}' has no child named '${problem.segment}'.`;
|
|
139
|
-
case "aboveRoot": return `'${value}' walks above the scene root.`;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/** Every prop on this node declared `nodePath: true`, per bag. */
|
|
143
|
-
function refProps(node) {
|
|
144
|
-
const collect = (schema) => Object.entries(schema).filter(([, def]) => def?.nodePath).map(([key]) => key);
|
|
145
|
-
let props = [];
|
|
146
|
-
if (node.type) try {
|
|
147
|
-
props = collect(getNodeSchema(node.type));
|
|
148
|
-
} catch {}
|
|
149
|
-
const name = node.script?.name;
|
|
150
|
-
return {
|
|
151
|
-
props,
|
|
152
|
-
script: name ? collect(behaviorSchema(name)) : []
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Scene lints for node-path props that point at nothing.
|
|
157
|
-
*
|
|
158
|
-
* An EMPTY value is not reported: `''` is the default of most of these props and
|
|
159
|
-
* means "not set", which is a choice rather than a typo.
|
|
160
|
-
*/
|
|
161
|
-
function nodeRefWarnings(scene) {
|
|
162
|
-
const root = scene.root;
|
|
163
|
-
if (!root) return [];
|
|
164
|
-
const warnings = [];
|
|
165
|
-
const walk = (node, at, path) => {
|
|
166
|
-
const keys = refProps(node);
|
|
167
|
-
const bags = [[
|
|
168
|
-
node.props,
|
|
169
|
-
keys.props,
|
|
170
|
-
""
|
|
171
|
-
], [
|
|
172
|
-
node.script?.props,
|
|
173
|
-
keys.script,
|
|
174
|
-
`${node.script?.name ?? "script"}.`
|
|
175
|
-
]];
|
|
176
|
-
for (const [bag, propKeys, prefix] of bags) {
|
|
177
|
-
if (!bag) continue;
|
|
178
|
-
for (const key of propKeys) {
|
|
179
|
-
const value = bag[key];
|
|
180
|
-
if (typeof value !== "string" || value === "") continue;
|
|
181
|
-
const result = resolveRefInJson(root, at, value);
|
|
182
|
-
if (result.ok) continue;
|
|
183
|
-
warnings.push(`${path}: ${prefix}${key} — ${describeRefProblem(result.problem, value)} The scene loads and the prop does nothing.`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
(node.children ?? []).forEach((child, i) => {
|
|
187
|
-
walk(child, [...at, i], `${path}/${child.name ?? "?"}`);
|
|
188
|
-
});
|
|
189
|
-
};
|
|
190
|
-
walk(root, [], String(root.name ?? "root"));
|
|
191
|
-
return warnings;
|
|
192
|
-
}
|
|
193
|
-
//#endregion
|
|
1
|
+
import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-DEe272nY.js";
|
|
2
|
+
import { R as translationKey, k as HudWidgetBase } from "./register-D3yx8D4r.js";
|
|
3
|
+
import { o as getNodeType } from "./registry-CF70EArN.js";
|
|
194
4
|
//#region src/core/audit.ts
|
|
195
5
|
const ANIMATED_TYPES = new Set([
|
|
196
6
|
"Particles2D",
|
|
@@ -244,6 +54,18 @@ const LIGHT_TYPES = new Set([
|
|
|
244
54
|
"SpotLight3D"
|
|
245
55
|
]);
|
|
246
56
|
/**
|
|
57
|
+
* Nodes whose gameplay answer comes from a CPU twin built out of `size`.
|
|
58
|
+
*
|
|
59
|
+
* `Terrain3D.heightAt`, the heightfield collider and `Water3D._waterYAt` all
|
|
60
|
+
* read `size` and compose ancestor POSITIONS only, so an ancestor that scales
|
|
61
|
+
* or rotates them moves the picture and not the answer.
|
|
62
|
+
*/
|
|
63
|
+
const WORLD_QUERY_TYPES = new Set([
|
|
64
|
+
"Terrain3D",
|
|
65
|
+
"Water3D",
|
|
66
|
+
"River3D"
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
247
69
|
* Built-ins that put something in a save. A save is keyed by node uid, so one
|
|
248
70
|
* of these on a node without one is state that silently goes nowhere: the save
|
|
249
71
|
* comes back `{}` and the load reports a clean bill, because "nothing was
|
|
@@ -266,6 +88,22 @@ function isLit(environment) {
|
|
|
266
88
|
const ambient = environment.ambient;
|
|
267
89
|
return typeof ambient?.intensity === "number" && ambient.intensity > 0;
|
|
268
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Which of this node's own transforms physics cannot compose, if any.
|
|
93
|
+
*
|
|
94
|
+
* Offsets compose; rotation and scale do not — the body is placed at its WORLD
|
|
95
|
+
* position and given its OWN rotation, so an ancestor's is simply lost.
|
|
96
|
+
*/
|
|
97
|
+
function transformOf(props) {
|
|
98
|
+
const rot = props.rotation;
|
|
99
|
+
const scale = props.scale;
|
|
100
|
+
const turned = typeof rot === "number" ? rot !== 0 : Array.isArray(rot) && rot.some((v) => typeof v === "number" && v !== 0);
|
|
101
|
+
const scaled = Array.isArray(scale) && scale.some((v) => typeof v === "number" && v !== 1);
|
|
102
|
+
if (turned && scaled) return "rotation and scale";
|
|
103
|
+
if (turned) return "rotation";
|
|
104
|
+
if (scaled) return "scale";
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
269
107
|
function auditScene(scene, opts = {}) {
|
|
270
108
|
const warnings = [];
|
|
271
109
|
const root = scene.root;
|
|
@@ -273,17 +111,29 @@ function auditScene(scene, opts = {}) {
|
|
|
273
111
|
let currentCameras = 0;
|
|
274
112
|
let cameras = 0;
|
|
275
113
|
let lights = 0;
|
|
114
|
+
/** Nodes that drive the current camera themselves — see the warning below. */
|
|
115
|
+
const cameraDrivers = [];
|
|
116
|
+
const followedCameras = [];
|
|
276
117
|
const needUid = [];
|
|
277
|
-
const walk = (node, path, insideHud, staticRoot) => {
|
|
118
|
+
const walk = (node, path, insideHud, staticRoot, transformed) => {
|
|
278
119
|
const type = node.type ?? "";
|
|
279
120
|
const props = node.props ?? {};
|
|
280
121
|
const script = node.script?.name;
|
|
281
122
|
if (script && SAVEABLE_SCRIPTS.has(script) && !node.uid) needUid.push(`${path} (${script})`);
|
|
123
|
+
if (type === "AudioPlayer" && props.spatial === true && scene.dimension === "2d") {
|
|
124
|
+
const ref = props.refDistance;
|
|
125
|
+
const max = props.maxDistance;
|
|
126
|
+
if (ref === void 0 && max === void 0) warnings.push(`${path}: spatial audio in a 2D scene measures distance in PIXELS, and "refDistance"/"maxDistance" default to 1 and 50 — about one sprite, so the sound is at its quietest almost immediately. Set them to your level's scale (e.g. 100 and 900).`);
|
|
127
|
+
}
|
|
282
128
|
if (type === "Camera2D" || type === "Camera3D") {
|
|
283
129
|
cameras += 1;
|
|
284
130
|
if (props.current === true) currentCameras += 1;
|
|
131
|
+
if (script === "FollowCamera") followedCameras.push(path);
|
|
285
132
|
}
|
|
133
|
+
if (type === "CharacterController3D") cameraDrivers.push(path);
|
|
286
134
|
if (LIGHT_TYPES.has(type)) lights += 1;
|
|
135
|
+
if (BODY_TYPES.has(type) && transformed) warnings.push(`${path}: ${type} sits under '${transformed.path}', whose ${transformed.what} physics cannot compose — the collider will not line up with what is drawn. Keep body ancestors untransformed or translation-only, or put the body outside them.`);
|
|
136
|
+
if (WORLD_QUERY_TYPES.has(type) && transformed) warnings.push(`${path}: ${type} sits under '${transformed.path}', whose ${transformed.what} it cannot compose — the ground (or water) you can see would not be the ground you can stand on. Keep its ancestors untransformed or translation-only.`);
|
|
287
137
|
if (BODY_TYPES.has(type)) {
|
|
288
138
|
const collider = props.collider;
|
|
289
139
|
if (!collider || collider.shape === void 0) warnings.push(`${path}: ${type} has no collider — physics will skip it (add "collider": { "shape": ... }).`);
|
|
@@ -292,14 +142,24 @@ function auditScene(scene, opts = {}) {
|
|
|
292
142
|
if (staticRoot && ANIMATED_TYPES.has(type)) warnings.push(`${path}: ${type} is frozen inside static subtree '${staticRoot}' — it will not animate/update on screen.`);
|
|
293
143
|
const nextStatic = staticRoot ?? (props.static === true ? node.name ?? path : null);
|
|
294
144
|
const nextHud = insideHud || type === "HudLayer";
|
|
295
|
-
|
|
145
|
+
const own = transformOf(props);
|
|
146
|
+
const nextTransformed = own ? {
|
|
147
|
+
path,
|
|
148
|
+
what: own
|
|
149
|
+
} : transformed;
|
|
150
|
+
for (const child of node.children ?? []) walk(child, `${path}/${child.name ?? "?"}`, nextHud, nextStatic, nextTransformed);
|
|
296
151
|
};
|
|
297
|
-
walk(root, root.name ?? "root", false, root.props?.static === true ? root.name ?? "root" : null)
|
|
152
|
+
walk(root, root.name ?? "root", false, root.props?.static === true ? root.name ?? "root" : null, transformOf(root.props ?? {}) ? {
|
|
153
|
+
path: root.name ?? "root",
|
|
154
|
+
what: transformOf(root.props ?? {})
|
|
155
|
+
} : null);
|
|
298
156
|
if (needUid.length > 0) warnings.push(`these carry state a save keeps and have no uid to key it under, so the save comes back EMPTY and the load reports no problem: ${needUid.join(", ")}. Give each one a "uid" from newUid().`);
|
|
157
|
+
if (cameraDrivers.length > 0 && followedCameras.length > 0) warnings.push(`${followedCameras.join(", ")}: a FollowCamera writes this camera's position every frame, and so does the CharacterController3D at ${cameraDrivers.join(", ")} — the controller owns the camera in every view it has. Drop one of them: use the controller's own \`view\`/\`camDistance\`, or take the controller off the camera duty.`);
|
|
299
158
|
if (cameras > 0 && currentCameras === 0) warnings.push("no camera has \"current\": true — the screen renders from a default view, probably not what you framed.");
|
|
300
159
|
if (currentCameras > 1) warnings.push(`${currentCameras} cameras claim "current": true — only the first found wins.`);
|
|
301
160
|
if (scene.dimension === "3d" && lights === 0 && !isLit(scene.environment)) warnings.push("nothing lights this 3D scene — no light node, no environment sky/hdri, and no ambient intensity. It will render black. Add a DirectionalLight3D, or an \"environment\": { \"sky\": { \"type\": \"atmosphere\" } } / { \"preset\": \"...\" }.");
|
|
302
161
|
for (const warning of nodeRefWarnings(scene)) warnings.push(warning);
|
|
162
|
+
for (const warning of handlerShadowWarnings(scene)) warnings.push(warning);
|
|
303
163
|
for (const warning of auditStrings(scene, opts.declaredElsewhere)) warnings.push(warning);
|
|
304
164
|
return warnings;
|
|
305
165
|
}
|
|
@@ -458,4 +318,4 @@ function replay(engine, recording, opts) {
|
|
|
458
318
|
}
|
|
459
319
|
}
|
|
460
320
|
//#endregion
|
|
461
|
-
export {
|
|
321
|
+
export { startRecording as n, auditScene as r, replay as t };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-DEe272nY.js";
|
|
2
|
+
import { t as registerCoreNodes, v as Engine } from "./register-D3yx8D4r.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
|
|
5
|
-
import { l as registerNode } from "./registry-
|
|
5
|
+
import { l as registerNode } from "./registry-CF70EArN.js";
|
|
6
6
|
//#region src/net/loopback.ts
|
|
7
7
|
/**
|
|
8
8
|
* In-memory multiplayer hub implementing the SAME kernel contract as
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Lt as Signal, b as Engine, jt as Node } from "./behavior-DsgayMsH.js";
|
|
2
2
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
3
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
|
|
4
4
|
|
|
5
5
|
//#region src/net/types.d.ts
|
|
6
6
|
type Unsubscribe = () => void;
|
|
@@ -214,6 +214,6 @@ function newUid() {
|
|
|
214
214
|
//#endregion
|
|
215
215
|
//#region src/index.ts
|
|
216
216
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
217
|
-
const VERSION = "0.
|
|
217
|
+
const VERSION = "0.62.0";
|
|
218
218
|
//#endregion
|
|
219
219
|
export { preloadUrls as a, preloadSceneAssets as i, newUid as n, findPath as o, assetUrls as r, gridFromRows as s, VERSION as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as AudioPlayer } from "./register-D3yx8D4r.js";
|
|
2
2
|
import { a as logReport, s as parseDrive } from "./touch-DESwnpOc.js";
|
|
3
3
|
import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
4
4
|
//#region src/core/audio-errors.ts
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { f as resolveViewport, n as loadScene, z as registerBehavior } from "./loader-DEe272nY.js";
|
|
2
|
+
import { v as Engine } from "./register-D3yx8D4r.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
|
|
5
|
-
import { i as getNodeSchema, s as mergeStaticProps } from "./registry-
|
|
6
|
-
import { n as startRecording } from "./replay-
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
-
import { t as registerNodes2D } from "./register-
|
|
9
|
-
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-
|
|
10
|
-
import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-
|
|
5
|
+
import { i as getNodeSchema, s as mergeStaticProps } from "./registry-CF70EArN.js";
|
|
6
|
+
import { n as startRecording } from "./replay-DlgHItNv.js";
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-CaHqDiQD.js";
|
|
8
|
+
import { t as registerNodes2D } from "./register-CNh4FlbD.js";
|
|
9
|
+
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-BAWeOeqf.js";
|
|
10
|
+
import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-CBM9wcMX.js";
|
|
11
11
|
import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from "three";
|
|
12
12
|
//#region src/test/framing.ts
|
|
13
13
|
/**
|
|
@@ -799,7 +799,8 @@ async function runOnce(json, seed, opts) {
|
|
|
799
799
|
run,
|
|
800
800
|
targets,
|
|
801
801
|
signals,
|
|
802
|
-
declaresEnd: oracle.declaresEnd()
|
|
802
|
+
declaresEnd: oracle.declaresEnd(),
|
|
803
|
+
hasPlayer: player !== null
|
|
803
804
|
};
|
|
804
805
|
}
|
|
805
806
|
/** Play the scene `runs` times and report what happened. */
|
|
@@ -810,12 +811,14 @@ async function playtest(json, opts = {}) {
|
|
|
810
811
|
let targets = [];
|
|
811
812
|
let signals = [];
|
|
812
813
|
let declaresWin = false;
|
|
814
|
+
let hasPlayer = false;
|
|
813
815
|
for (let i = 0; i < count; i++) {
|
|
814
816
|
const one = await runOnce(json, base + i, opts);
|
|
815
817
|
runs.push(one.run);
|
|
816
818
|
targets = one.targets;
|
|
817
819
|
signals = one.signals;
|
|
818
820
|
declaresWin = declaresWin || one.declaresEnd;
|
|
821
|
+
hasPlayer = hasPlayer || one.hasPlayer;
|
|
819
822
|
}
|
|
820
823
|
const probe = await createPlaySession(json, {
|
|
821
824
|
seed: base,
|
|
@@ -844,6 +847,7 @@ async function playtest(json, opts = {}) {
|
|
|
844
847
|
actions,
|
|
845
848
|
inertActions: [],
|
|
846
849
|
declaresWin,
|
|
850
|
+
hasPlayer,
|
|
847
851
|
seconds: opts.seconds ?? 60,
|
|
848
852
|
feedback: {
|
|
849
853
|
declaredAudio: feedbackNodes.audio,
|
|
@@ -876,6 +880,10 @@ function playtestText(report) {
|
|
|
876
880
|
const won = runs.filter((r) => r.outcome === "won");
|
|
877
881
|
if (won.length > 0) lines.push(` ✓ reached "won" in ${pct(won.length, total)} median ${Math.round(median(won.map((r) => r.timeMs)) / 1e3)}s`);
|
|
878
882
|
else lines.push(report.declaresWin ? ` ✗ never reached "won" in ${total} runs — a win IS declared, and no run got to it` : ` · no win declared in this scene — nothing emits won/lost, so there is nothing to reach`);
|
|
883
|
+
if (!report.hasPlayer) {
|
|
884
|
+
lines.push(" · nothing here is drivable — no character controller, no node in the `player` group, no node named Player. The runs went nowhere because there was nobody to move, not because anything is wedged.");
|
|
885
|
+
return lines.join("\n");
|
|
886
|
+
}
|
|
879
887
|
for (const bad of [
|
|
880
888
|
"error",
|
|
881
889
|
"fell",
|
|
@@ -1669,8 +1677,8 @@ async function playMultiplayer(opts) {
|
|
|
1669
1677
|
*/
|
|
1670
1678
|
async function joinLate(server, first, opts) {
|
|
1671
1679
|
try {
|
|
1672
|
-
const { Engine } = await import("./register-
|
|
1673
|
-
const { loadScene } = await import("./loader-
|
|
1680
|
+
const { Engine } = await import("./register-D3yx8D4r.js").then((n) => n.y);
|
|
1681
|
+
const { loadScene } = await import("./loader-DEe272nY.js").then((n) => n.r);
|
|
1674
1682
|
const { jsonClone } = await import("./json-CwwhxQgb.js").then((n) => n.i);
|
|
1675
1683
|
const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + 999 } : {});
|
|
1676
1684
|
engine.setScene(loadScene(jsonClone(opts.scene), opts.load));
|
|
@@ -1786,7 +1794,11 @@ function ladderText(v) {
|
|
|
1786
1794
|
].join("\n");
|
|
1787
1795
|
return "nothing to verify — no scene was given and none was found";
|
|
1788
1796
|
}
|
|
1789
|
-
const lines =
|
|
1797
|
+
const lines = [];
|
|
1798
|
+
for (const r of v.rungs) {
|
|
1799
|
+
lines.push(`${MARK[r.status]} ${r.name} — ${r.summary}`);
|
|
1800
|
+
for (const note of r.notes ?? []) lines.push(` ! ${note}`);
|
|
1801
|
+
}
|
|
1790
1802
|
lines.push("");
|
|
1791
1803
|
if (!v.ok) lines.push("NOT verified.");
|
|
1792
1804
|
else if (v.unmeasured.length > 0) lines.push(`passes what was measured — ${v.unmeasured.join(", ")} not measured.`);
|
|
@@ -1998,6 +2010,7 @@ function validateScene(json, opts = {}) {
|
|
|
1998
2010
|
checkEnvironment(json);
|
|
1999
2011
|
loadScene(json, {
|
|
2000
2012
|
resolveScene: opts.resolveScene,
|
|
2013
|
+
...opts.declaredAssets ? { declaredAssets: opts.declaredAssets } : {},
|
|
2001
2014
|
stubMissingBehaviors: !opts.strictBehaviors
|
|
2002
2015
|
}).root.free();
|
|
2003
2016
|
return { ok: true };
|
|
@@ -2027,10 +2040,10 @@ async function runScript(json, opts) {
|
|
|
2027
2040
|
if (opts.locale) engine.locale.locale = opts.locale;
|
|
2028
2041
|
const physics = opts.physics ?? "auto";
|
|
2029
2042
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
2030
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
2043
|
+
const { enablePhysics2D } = await import("./physics-2d-BXmu2i7W.js").then((n) => n.r);
|
|
2031
2044
|
await enablePhysics2D(engine);
|
|
2032
2045
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
2033
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
2046
|
+
const { enablePhysics3D } = await import("./physics-3d-ClxP6Uv7.js").then((n) => n.r);
|
|
2034
2047
|
await enablePhysics3D(engine);
|
|
2035
2048
|
}
|
|
2036
2049
|
const failures = [];
|
|
@@ -2143,10 +2156,10 @@ async function createPlaySession(json, opts = {}) {
|
|
|
2143
2156
|
engine.setScene(scene);
|
|
2144
2157
|
const physics = opts.physics ?? "auto";
|
|
2145
2158
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
2146
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
2159
|
+
const { enablePhysics2D } = await import("./physics-2d-BXmu2i7W.js").then((n) => n.r);
|
|
2147
2160
|
await enablePhysics2D(engine);
|
|
2148
2161
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
2149
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
2162
|
+
const { enablePhysics3D } = await import("./physics-3d-ClxP6Uv7.js").then((n) => n.r);
|
|
2150
2163
|
await enablePhysics3D(engine);
|
|
2151
2164
|
}
|
|
2152
2165
|
const stepMs = 1e3 / (opts.fixedHz ?? 60);
|
package/dist/test.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { P as Scene, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-DsgayMsH.js";
|
|
2
2
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
3
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
4
|
-
import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-
|
|
5
|
-
import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
|
|
4
|
+
import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-C5x2vPF5.js";
|
|
5
|
+
import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-D7OopelJ.js";
|
|
6
6
|
|
|
7
7
|
//#region src/test/facing.d.ts
|
|
8
8
|
interface FacingOptions {
|
|
@@ -355,6 +355,25 @@ interface PlaytestReport {
|
|
|
355
355
|
*/
|
|
356
356
|
declaresWin: boolean;
|
|
357
357
|
/**
|
|
358
|
+
* Did `findPlayer` find anyone to drive?
|
|
359
|
+
*
|
|
360
|
+
* `findPlayer`'s own comment says a scene it cannot drive "says so rather
|
|
361
|
+
* than guessing" — and nothing said so. With no player the driver moves
|
|
362
|
+
* nothing, `wentAsFarAs` stays 0, and every run is reported `✗ stuck in
|
|
363
|
+
* 8/8`: the word for a character wedged in geometry, used for a scene that
|
|
364
|
+
* has no character. Measured on the shipped `examples/2d-phaser-basic`,
|
|
365
|
+
* whose whole tree is a ground, a sprite and a camera:
|
|
366
|
+
*
|
|
367
|
+
* ```
|
|
368
|
+
* ✗ stuck in 4/4
|
|
369
|
+
* danger: the player took damage 0 times in 4 runs — nothing here can hurt you
|
|
370
|
+
* ```
|
|
371
|
+
*
|
|
372
|
+
* Both sentences are about a player that does not exist. The `plays` rung
|
|
373
|
+
* needs to tell the two apart before it can call `stuck` a defect.
|
|
374
|
+
*/
|
|
375
|
+
hasPlayer: boolean;
|
|
376
|
+
/**
|
|
358
377
|
* The per-run clock the caller asked for, in simulated seconds.
|
|
359
378
|
*
|
|
360
379
|
* The header used to read this off the FIRST run's length, which is the one
|
|
@@ -426,6 +445,24 @@ interface RungResult {
|
|
|
426
445
|
summary: string;
|
|
427
446
|
/** What to do about it, when there is something to do. */
|
|
428
447
|
fix?: string;
|
|
448
|
+
/**
|
|
449
|
+
* Things the rung's own tool said that are not failures — printed under it.
|
|
450
|
+
*
|
|
451
|
+
* The `loads` rung runs `incanto-check`, which returns
|
|
452
|
+
* `{ok, results:[{file, ok, warnings}]}`, and the rung read `ok` and threw
|
|
453
|
+
* the warnings away. So a scene `incanto-check` describes as *"nothing lights
|
|
454
|
+
* this 3D scene … It will render black"* came back from the headline command
|
|
455
|
+
* as:
|
|
456
|
+
*
|
|
457
|
+
* ```
|
|
458
|
+
* ✓ loads — the scene is legal and its assets resolve
|
|
459
|
+
* ```
|
|
460
|
+
*
|
|
461
|
+
* The ladder's own header table names `incanto-check` as the tool for "the
|
|
462
|
+
* scene will not load, OR RENDERS BLACK". It ran it, was told, and did not
|
|
463
|
+
* pass it on.
|
|
464
|
+
*/
|
|
465
|
+
notes?: string[];
|
|
429
466
|
}
|
|
430
467
|
interface LadderVerdict {
|
|
431
468
|
ok: boolean;
|
|
@@ -513,6 +550,10 @@ declare function captureScene(scene: Scene): SceneCapture;
|
|
|
513
550
|
*/
|
|
514
551
|
declare function describeCapture(capture: SceneCapture): string;
|
|
515
552
|
interface ValidateSceneOptions {
|
|
553
|
+
/** Assets the rest of the PROJECT declares — see `LoadSceneOptions`. */
|
|
554
|
+
declaredAssets?: Record<string, {
|
|
555
|
+
type?: unknown;
|
|
556
|
+
}>;
|
|
516
557
|
/** Real behavior classes to register (hot-replacing) before loading. */
|
|
517
558
|
behaviors?: Record<string, BehaviorCtor>;
|
|
518
559
|
/** Hard-fail on unregistered behaviors instead of stubbing them. */
|
package/dist/test.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { r as auditScene } from "./replay-
|
|
2
|
-
import { C as describeFraming, S as playtestText, _ as facingReport, a as registerAllNodes, b as findPlayer, c as ladderText, d as multiplayText, f as playMultiplayer, g as agreementProblems, h as takeAgreementSnapshots, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as facingText, w as framingText, x as playtest, y as failingReplays } from "./test-
|
|
1
|
+
import { r as auditScene } from "./replay-DlgHItNv.js";
|
|
2
|
+
import { C as describeFraming, S as playtestText, _ as facingReport, a as registerAllNodes, b as findPlayer, c as ladderText, d as multiplayText, f as playMultiplayer, g as agreementProblems, h as takeAgreementSnapshots, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as facingText, w as framingText, x as playtest, y as failingReplays } from "./test-BMkg8zMV.js";
|
|
3
3
|
export { agreementProblems, auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestText, registerAllNodes, runScript, takeAgreementSnapshots, validateScene };
|
package/dist/vite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as VERSION } from "./src-
|
|
1
|
+
import { t as VERSION } from "./src-CH00_JsR.js";
|
|
2
2
|
import { n as diffSignatures } from "./frame-report-BSMny7oe.js";
|
|
3
|
-
import { s as validateScene } from "./test-
|
|
3
|
+
import { s as validateScene } from "./test-BMkg8zMV.js";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
6
6
|
//#region src/vite/frame-endpoint.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./index-
|
|
1
|
+
import{n as e}from"./index-D422P4kW.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
|