incanto 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/2d.d.ts +38 -2
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +4 -3
  4. package/dist/3d.js +4 -4
  5. package/dist/{behavior-62q0HWBO.d.ts → behavior-uPEuZrUB.d.ts} +47 -3
  6. package/dist/{create-game-BLDjy_PW.js → create-game-B_e9hJf7.js} +92 -10
  7. package/dist/{create-game-DqqxEax1.js → create-game-CGnoypjL.js} +6 -6
  8. package/dist/debug.d.ts +1 -1
  9. package/dist/debug.js +1 -1
  10. package/dist/{duplicate-CI9WF_bg.js → duplicate-B-OtSRFL.js} +1 -1
  11. package/dist/editor.js +1917 -1579
  12. package/dist/{environment-presets-XFuqu5jv.js → environment-presets-DSZwsKPs.js} +3 -3
  13. package/dist/{gameplay-DbaI313d.js → gameplay-B6jqvYeM.js} +163 -6
  14. package/dist/gameplay.d.ts +79 -2
  15. package/dist/gameplay.js +2 -2
  16. package/dist/index.d.ts +74 -5
  17. package/dist/index.js +7 -7
  18. package/dist/{loader-DwazzlQb.js → loader-B-Gft32x.js} +372 -23
  19. package/dist/{loader-CeyU_bm1.d.ts → loader-B9iTqs27.d.ts} +1 -1
  20. package/dist/net.d.ts +15 -410
  21. package/dist/net.js +1 -822
  22. package/dist/{pathfinding-C49JSNNq.d.ts → pathfinding-CAR9DjQQ.d.ts} +1 -1
  23. package/dist/{physics-2d-_9VBOHn6.js → physics-2d-Dns-oZlE.js} +7 -2
  24. package/dist/{physics-3d-DrpF5hcG.js → physics-3d-BhI0ehpe.js} +8 -3
  25. package/dist/react.d.ts +1 -1
  26. package/dist/react.js +1 -1
  27. package/dist/{register-uvaZj1KX.js → register-DVwlnZAZ.js} +20 -266
  28. package/dist/{register-BNPZYJmd.js → register-Trx7WHnD.js} +3 -3
  29. package/dist/{registry-IyWCGe4q.js → registry-C7u42TID.js} +23 -1
  30. package/dist/{replay-DYdy1wb0.d.ts → replay-BU1CCM15.d.ts} +1 -1
  31. package/dist/{replay-j-m6lJ4W.js → replay-BicPOMX0.js} +214 -2
  32. package/dist/split-screen-CZ9ccBBQ.js +1267 -0
  33. package/dist/split-screen-paxkQs_q.d.ts +441 -0
  34. package/dist/{src-B3HrKuAi.js → src-5gbZO47I.js} +1 -1
  35. package/dist/{teardown-BKTCzLek.js → teardown-ks3d5W9n.js} +2 -1
  36. package/dist/{test-D16igj4C.js → test-DAojuFdb.js} +119 -22
  37. package/dist/test.d.ts +60 -5
  38. package/dist/test.js +3 -3
  39. package/dist/vite.js +2 -2
  40. package/editor/assets/{agent8-t3kl5q9K.js → agent8-CCvckvbw.js} +1 -1
  41. package/editor/assets/{debug-Bu3eeAlO.js → debug-CLNCOnbc.js} +1 -1
  42. package/editor/assets/{index-Df5g8ofT.js → index-Cb5Brupb.js} +99 -92
  43. package/editor/index.html +1 -1
  44. package/package.json +1 -1
  45. package/skills/incanto-assets.md +4 -1
  46. package/skills/incanto-building-2d-games.md +6 -1
  47. package/skills/incanto-editor.md +48 -11
  48. package/skills/incanto-gameplay-behaviors.md +14 -5
  49. package/skills/incanto-multiplayer.md +39 -3
  50. package/skills/incanto-node-reference.md +13 -0
  51. package/skills/incanto-save-slots.md +134 -12
  52. package/skills/incanto-verifying-your-game.md +56 -0
  53. package/templates/agent8-server.ts +79 -2
  54. package/templates-app/beacon-isle-3d/package.json +1 -1
  55. package/templates-app/tps-3d/package.json +1 -1
  56. package/templates-app/village-quest-3d/package.json +1 -1
  57. package/dist/register-CB11yp21.js +0 -374
@@ -1,4 +1,196 @@
1
- import { M as translationKey } from "./register-uvaZj1KX.js";
1
+ import { E as behaviorSchema, w as parseNodePath } from "./loader-B-Gft32x.js";
2
+ import { D as translationKey } from "./register-DVwlnZAZ.js";
3
+ import { i as getNodeSchema } from "./registry-C7u42TID.js";
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
2
194
  //#region src/core/audit.ts
3
195
  const ANIMATED_TYPES = new Set([
4
196
  "Particles2D",
@@ -31,6 +223,21 @@ const LIGHT_TYPES = new Set([
31
223
  "OmniLight3D",
32
224
  "SpotLight3D"
33
225
  ]);
226
+ /**
227
+ * Built-ins that put something in a save. A save is keyed by node uid, so one
228
+ * of these on a node without one is state that silently goes nowhere: the save
229
+ * comes back `{}` and the load reports a clean bill, because "nothing was
230
+ * missing" is true of an empty save.
231
+ *
232
+ * A JSON walk cannot ask a behavior whether it serializes, so this is the list
233
+ * of the ones that do. Your own behavior with a `serialize()` is caught at
234
+ * runtime instead, by `captureState`.
235
+ */
236
+ const SAVEABLE_SCRIPTS = new Set([
237
+ "Health",
238
+ "ScoreKeeper",
239
+ "Collector"
240
+ ]);
34
241
  /** Does the scene header light anything by itself (sky, hdri, ambient, preset)? */
35
242
  function isLit(environment) {
36
243
  if (!environment) return false;
@@ -47,9 +254,12 @@ function auditScene(scene) {
47
254
  let currentCameras = 0;
48
255
  let cameras = 0;
49
256
  let lights = 0;
257
+ const needUid = [];
50
258
  const walk = (node, path, insideHud, staticRoot) => {
51
259
  const type = node.type ?? "";
52
260
  const props = node.props ?? {};
261
+ const script = node.script?.name;
262
+ if (script && SAVEABLE_SCRIPTS.has(script) && !node.uid) needUid.push(`${path} (${script})`);
53
263
  if (type === "Camera2D" || type === "Camera3D") {
54
264
  cameras += 1;
55
265
  if (props.current === true) currentCameras += 1;
@@ -66,9 +276,11 @@ function auditScene(scene) {
66
276
  for (const child of node.children ?? []) walk(child, `${path}/${child.name ?? "?"}`, nextHud, nextStatic);
67
277
  };
68
278
  walk(root, root.name ?? "root", false, root.props?.static === true ? root.name ?? "root" : null);
279
+ 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().`);
69
280
  if (cameras > 0 && currentCameras === 0) warnings.push("no camera has \"current\": true — the screen renders from a default view, probably not what you framed.");
70
281
  if (currentCameras > 1) warnings.push(`${currentCameras} cameras claim "current": true — only the first found wins.`);
71
282
  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\": \"...\" }.");
283
+ for (const warning of nodeRefWarnings(scene)) warnings.push(warning);
72
284
  for (const warning of auditStrings(scene)) warnings.push(warning);
73
285
  return warnings;
74
286
  }
@@ -198,4 +410,4 @@ function replay(engine, recording, opts) {
198
410
  }
199
411
  }
200
412
  //#endregion
201
- export { startRecording as n, auditScene as r, replay as t };
413
+ export { nodeRefWarnings as a, describeRefProblem as i, startRecording as n, resolveRefInJson as o, auditScene as r, replay as t };