incanto 0.68.0 → 0.70.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/assets/catalog.json +9 -5
- package/bin/_behaviors-loader.mjs +22 -0
- package/bin/_read-json.mjs +28 -0
- package/bin/incanto-assets.mjs +19 -5
- package/bin/incanto-check.mjs +92 -15
- package/bin/incanto-editor.mjs +128 -5
- package/bin/incanto-env.mjs +3 -2
- package/bin/incanto-feel.mjs +24 -13
- package/bin/incanto-frame.mjs +8 -1
- package/bin/incanto-multiplay.mjs +11 -9
- package/bin/incanto-new.mjs +128 -5
- package/bin/incanto-play.mjs +158 -16
- package/bin/incanto-playtest.mjs +66 -23
- package/bin/incanto-serve.mjs +160 -0
- package/bin/incanto-skills.mjs +14 -2
- package/bin/incanto-verify.mjs +165 -44
- package/bin/incanto.mjs +4 -2
- package/dist/2d.d.ts +285 -36
- package/dist/2d.js +4 -4
- package/dist/3d.d.ts +158 -10
- package/dist/3d.js +8 -8
- package/dist/{agent8-CvsfVskX.js → agent8-CmNF01gA.js} +61 -8
- package/dist/{audio-player-DOrq7sP-.d.ts → audio-player-DaMxqfNE.d.ts} +33 -14
- package/dist/{behavior-DoFPYrgo.d.ts → behavior-DZExDn9o.d.ts} +809 -44
- package/dist/{create-game-IZIydDwI.js → create-game-Bwvh6q8A.js} +148 -61
- package/dist/{create-game-DbWtVTxD.js → create-game-C7ffQWW7.js} +103 -49
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +2 -3
- package/dist/diagnostics-Cu85N3tL.d.ts +12 -0
- package/dist/{editor-switch-CnIOiyNJ.d.ts → editor-switch-CFU9mCec.d.ts} +22 -13
- package/dist/editor.js +1066 -864
- package/dist/env.d.ts +1 -1
- package/dist/env.js +5 -3
- package/dist/{environment-presets-CybQXNqS.js → environment-presets-D6Q5BxeE.js} +299 -46
- package/dist/{frame-report-DCnHFmto.d.ts → frame-report-DNxDAb1w.d.ts} +8 -0
- package/dist/{frame-report-BSMny7oe.js → frame-report-Dlq13Gyj.js} +1 -0
- package/dist/{gameplay-DM1eu_cV.js → gameplay-BfHkuzVb.js} +825 -221
- package/dist/gameplay.d.ts +187 -7
- package/dist/gameplay.js +1 -1
- package/dist/{heightmap-CRK0M4jT.js → heightmap-BYgD5Edk.js} +1 -1
- package/dist/index.d.ts +156 -13
- package/dist/index.js +10 -12
- package/dist/json-CfTjpvW8.js +67 -0
- package/dist/{loader-CcB533FR.d.ts → loader-Cff09LMm.d.ts} +2 -2
- package/dist/net.d.ts +27 -3
- package/dist/net.js +2 -2
- package/dist/{noise-CGUMx44x.js → noise-D3nPpmFg.js} +1 -1
- package/dist/{physics-2d-B7Y6dPZO.js → physics-2d-CE0Qvy3V.js} +136 -11
- package/dist/{physics-3d-bG3n70Ky.js → physics-3d-CpH-2gn5.js} +104 -23
- package/dist/{teardown-D2NEmxPB.js → picking-CQJ_PJKh.js} +106 -14
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -2
- package/dist/{register-3ta-2Xig.js → register-6DYnKZcy.js} +652 -831
- package/dist/{register-ibCjm-wH.js → register-Bkk0wSDB.js} +348 -30
- package/dist/{replay-CYvhVHHN.js → replay-DjAkAzMq.js} +224 -14
- package/dist/{replay-Dvn8aeBd.d.ts → replay-Dmw-PKQu.d.ts} +20 -3
- package/dist/{schema-B6ugCV1Q.d.ts → rng-Bb-IutXB.d.ts} +38 -21
- package/dist/{rng-DP-SR7eg.js → rng-CDOMybym.js} +22 -0
- package/dist/{loader-BC4PNtJX.js → save-slots-BXVg148r.js} +4558 -2294
- package/dist/{sheet-grid-BT6N_Bjs.js → sheet-grid-Cea343VO.js} +6 -2
- package/dist/{split-screen-DhrSzZIB.d.ts → split-screen--k-XpBjr.d.ts} +36 -4
- package/dist/{split-screen-CYwDkbLF.js → split-screen-PL78oVXP.js} +159 -26
- package/dist/{sprite-animation-CY-mrr1L.js → sprite-animation-CqR2o3SA.js} +39 -8
- package/dist/{src-D7RIqXYF.js → src-Cxfiv1Hg.js} +2 -17
- package/dist/test-iHYVUcDK.js +4036 -0
- package/dist/test.d.ts +542 -30
- package/dist/test.js +3 -3
- package/dist/touch-BnCyPA0G.js +519 -0
- package/dist/vite.d.ts +54 -3
- package/dist/vite.js +349 -17
- package/dist/{webgl-unavailable-N9nQqesw.js → webgl-unavailable-C8aDbGmR.js} +56 -1
- package/editor/assets/agent8-D0MS174y.js +1 -0
- package/editor/assets/{debug-BBhKuBNV.js → debug-BnXkKuYu.js} +2 -2
- package/editor/assets/index-CIu3uc3l.js +11046 -0
- package/editor/index.html +1 -1
- package/package.json +7 -16
- package/schemas/scene.schema.json +30 -3
- package/skills/incanto-3d-character.md +14 -1
- package/skills/incanto-3d-models.md +12 -0
- package/skills/incanto-assets.md +25 -3
- package/skills/incanto-audio.md +27 -5
- package/skills/incanto-behaviors-and-scripts.md +83 -6
- package/skills/incanto-building-2d-games.md +106 -8
- package/skills/incanto-building-3d-games.md +118 -6
- package/skills/incanto-editor.md +46 -7
- package/skills/incanto-environment.md +19 -1
- package/skills/incanto-game-feel.md +70 -0
- package/skills/incanto-gameplay-behaviors.md +121 -14
- package/skills/incanto-hud.md +128 -7
- package/skills/incanto-localization.md +13 -5
- package/skills/incanto-multiplayer.md +83 -3
- package/skills/incanto-node-reference.md +222 -58
- package/skills/incanto-performance.md +52 -0
- package/skills/incanto-physics-and-input.md +123 -24
- package/skills/incanto-playtesting.md +78 -2
- package/skills/incanto-save-slots.md +188 -6
- package/skills/incanto-scene-json-authoring.md +69 -12
- package/skills/incanto-verifying-your-game.md +292 -12
- package/skills/incanto-web-integration.md +28 -0
- package/skills/incanto-your-first-game.md +5 -2
- package/templates-app/beacon-isle-3d/generate-world.ts +77 -9
- package/templates-app/beacon-isle-3d/package.json +2 -2
- package/templates-app/beacon-isle-3d/src/behaviors.ts +22 -0
- package/templates-app/beacon-isle-3d/src/game.scene.json +103 -378
- package/templates-app/beacon-isle-3d/src/main.ts +24 -4
- package/templates-app/beacon-isle-3d/tsconfig.json +1 -1
- package/templates-app/beacon-isle-3d/verify.ts +3 -1
- package/templates-app/molehill-2d/.incanto/playtest/lost-seed1.json +4277 -0
- package/templates-app/molehill-2d/PROJECT/Context.md +58 -0
- package/templates-app/molehill-2d/PROJECT/Requirements.md +39 -0
- package/templates-app/molehill-2d/PROJECT/Status.md +27 -0
- package/templates-app/molehill-2d/PROJECT/Structure.md +48 -0
- package/templates-app/molehill-2d/docs/project-2d-rules.md +44 -0
- package/templates-app/molehill-2d/index.html +73 -0
- package/templates-app/molehill-2d/package.json +23 -0
- package/templates-app/molehill-2d/src/behaviors.ts +198 -0
- package/templates-app/molehill-2d/src/game.scene.json +1255 -0
- package/templates-app/molehill-2d/src/main.ts +41 -0
- package/templates-app/molehill-2d/tsconfig.json +13 -0
- package/templates-app/molehill-2d/verify.ts +247 -0
- package/templates-app/molehill-2d/vite.config.ts +12 -0
- package/templates-app/platformer-2d/index.html +0 -23
- package/templates-app/platformer-2d/package.json +2 -2
- package/templates-app/platformer-2d/src/behaviors.ts +26 -16
- package/templates-app/platformer-2d/src/game.scene.json +143 -625
- package/templates-app/platformer-2d/src/main.ts +35 -13
- package/templates-app/platformer-2d/tsconfig.json +1 -1
- package/templates-app/star-survivor/package.json +2 -2
- package/templates-app/star-survivor/src/game.scene.json +41 -195
- package/templates-app/star-survivor/src/main.ts +28 -7
- package/templates-app/star-survivor/tsconfig.json +1 -1
- package/templates-app/tps-3d/PROJECT/Context.md +1 -1
- package/templates-app/tps-3d/package.json +2 -2
- package/templates-app/tps-3d/src/behaviors.ts +19 -1
- package/templates-app/tps-3d/src/game.scene.json +78 -217
- package/templates-app/tps-3d/src/main.ts +39 -17
- package/templates-app/tps-3d/tsconfig.json +1 -1
- package/templates-app/village-quest-3d/.incanto/playtest/swapped-seed1.json +1735 -0
- package/templates-app/village-quest-3d/package.json +2 -2
- package/templates-app/village-quest-3d/src/behaviors.ts +21 -0
- package/templates-app/village-quest-3d/src/grove.scene.json +54 -221
- package/templates-app/village-quest-3d/src/main.ts +24 -4
- package/templates-app/village-quest-3d/src/village.scene.json +199 -838
- package/templates-app/village-quest-3d/tsconfig.json +1 -1
- package/templates-app/village-quest-3d/verify.ts +14 -1
- package/dist/duplicate-IWIqk0HJ.js +0 -22
- package/dist/json-CwwhxQgb.js +0 -36
- package/dist/registry-CF70EArN.js +0 -212
- package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
- package/dist/test-8hoHeRmo.js +0 -2340
- package/dist/touch-DEAmqGdf.js +0 -225
- package/editor/assets/agent8-BrrHOjMJ.js +0 -1
- package/editor/assets/index-eVd0BToA.js +0 -10958
package/dist/vite.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as parseJsonText } from "./json-CfTjpvW8.js";
|
|
2
|
+
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
|
+
import { t as VERSION } from "./src-Cxfiv1Hg.js";
|
|
4
|
+
import { n as diffSignatures } from "./frame-report-Dlq13Gyj.js";
|
|
5
|
+
import { s as validateScene } from "./test-iHYVUcDK.js";
|
|
4
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { basename, dirname, extname, join, normalize, relative, resolve, sep } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
6
9
|
//#region src/vite/frame-endpoint.ts
|
|
7
10
|
/**
|
|
8
11
|
* The dev server's half of `incanto-frame`.
|
|
@@ -71,7 +74,7 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
71
74
|
c.on("incanto:logs-report", onReport);
|
|
72
75
|
}
|
|
73
76
|
host.middlewares.use("/__incanto/ping", (_req, res) => {
|
|
74
|
-
json$
|
|
77
|
+
json$2(res, 200, {
|
|
75
78
|
incanto: version,
|
|
76
79
|
frame: Boolean(channel),
|
|
77
80
|
root: host.config?.root ?? process.cwd(),
|
|
@@ -81,7 +84,7 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
81
84
|
serveLogs(host, channels, waiting, () => nextId++, timeoutMs);
|
|
82
85
|
host.middlewares.use("/__incanto/frame", (req, res) => {
|
|
83
86
|
if (!channel) {
|
|
84
|
-
json$
|
|
87
|
+
json$2(res, 503, { error: "no-hmr-channel" });
|
|
85
88
|
return;
|
|
86
89
|
}
|
|
87
90
|
const params = query(req);
|
|
@@ -95,13 +98,13 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
95
98
|
const id = nextId++;
|
|
96
99
|
const timer = setTimeout(() => {
|
|
97
100
|
waiting.delete(id);
|
|
98
|
-
json$
|
|
101
|
+
json$2(res, 504, { error: acked.delete(id) ? "page-not-drawing" : "no-page-connected" });
|
|
99
102
|
}, timeoutMs + (driveMs > 0 ? driveMs * 2 + 2e3 : 0));
|
|
100
103
|
waiting.set(id, (value) => {
|
|
101
104
|
clearTimeout(timer);
|
|
102
105
|
acked.delete(id);
|
|
103
106
|
if (!value.ok) {
|
|
104
|
-
json$
|
|
107
|
+
json$2(res, 500, { error: "capture-failed" });
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
107
110
|
const full = value.report;
|
|
@@ -119,7 +122,7 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
119
122
|
store.set(remember || DEFAULT_LABEL, signature);
|
|
120
123
|
body.remembered = remember || DEFAULT_LABEL;
|
|
121
124
|
}
|
|
122
|
-
json$
|
|
125
|
+
json$2(res, 200, body);
|
|
123
126
|
});
|
|
124
127
|
for (const c of channels) c.send({
|
|
125
128
|
type: "custom",
|
|
@@ -142,21 +145,21 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
142
145
|
function serveLogs(host, channels, waiting, nextId, timeoutMs) {
|
|
143
146
|
host.middlewares.use("/__incanto/logs", (_req, res) => {
|
|
144
147
|
if (channels.length === 0) {
|
|
145
|
-
json$
|
|
148
|
+
json$2(res, 503, { error: "no-hmr-channel" });
|
|
146
149
|
return;
|
|
147
150
|
}
|
|
148
151
|
const id = nextId();
|
|
149
152
|
const timer = setTimeout(() => {
|
|
150
153
|
waiting.delete(id);
|
|
151
|
-
json$
|
|
154
|
+
json$2(res, 504, { error: "no-page-connected" });
|
|
152
155
|
}, timeoutMs);
|
|
153
156
|
waiting.set(id, (value) => {
|
|
154
157
|
clearTimeout(timer);
|
|
155
158
|
if (!value.ok) {
|
|
156
|
-
json$
|
|
159
|
+
json$2(res, 500, { error: "report-failed" });
|
|
157
160
|
return;
|
|
158
161
|
}
|
|
159
|
-
json$
|
|
162
|
+
json$2(res, 200, { report: value.report });
|
|
160
163
|
});
|
|
161
164
|
for (const c of channels) c.send({
|
|
162
165
|
type: "custom",
|
|
@@ -178,7 +181,7 @@ function parseGrid(text) {
|
|
|
178
181
|
const rows = Number(m[2]);
|
|
179
182
|
return cols > 0 && rows > 0 ? [cols, rows] : void 0;
|
|
180
183
|
}
|
|
181
|
-
function json$
|
|
184
|
+
function json$2(res, status, body) {
|
|
182
185
|
res.statusCode = status;
|
|
183
186
|
res.setHeader("content-type", "application/json");
|
|
184
187
|
res.end(JSON.stringify(body));
|
|
@@ -255,6 +258,251 @@ async function listeningPorts(deps) {
|
|
|
255
258
|
return out;
|
|
256
259
|
}
|
|
257
260
|
//#endregion
|
|
261
|
+
//#region src/vite/options.ts
|
|
262
|
+
/**
|
|
263
|
+
* A plugin option the plugin does not know is a hard error, like every other
|
|
264
|
+
* key this engine reads.
|
|
265
|
+
*
|
|
266
|
+
* Scene props have had `UNKNOWN_PROP` — "Valid props: […]" — since the first
|
|
267
|
+
* milestone. A vite plugin's options had nothing: `incantoScenes({
|
|
268
|
+
* strictBehavior: true })` type-checked green, built green and did NOTHING,
|
|
269
|
+
* because the scaffolded `tsconfig.json` never pointed the compiler at
|
|
270
|
+
* `vite.config.ts` in the first place.
|
|
271
|
+
*
|
|
272
|
+
* That `include` is fixed too, but it cannot be the whole answer. TypeScript's
|
|
273
|
+
* excess-property check only fires on a FRESH object literal — hoist the
|
|
274
|
+
* options into a `const` and it passes again — and a `vite.config.js` is never
|
|
275
|
+
* type-checked at all. This is the half that holds either way.
|
|
276
|
+
*/
|
|
277
|
+
function onlyKnownOptions(plugin, opts, known) {
|
|
278
|
+
const valid = new Set(known);
|
|
279
|
+
for (const key of Object.keys(opts)) {
|
|
280
|
+
if (valid.has(key)) continue;
|
|
281
|
+
const near = known.find((k) => k.toLowerCase() === key.toLowerCase()) ?? closest(key, known);
|
|
282
|
+
throw new IncantoError("UNKNOWN_PROP", `${plugin}() got an option it does not know: '${key}'.` + (near ? ` Did you mean '${near}'?` : "") + ` Valid options: [${known.join(", ")}].`, { prop: key });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** The one a typo probably meant, or null when nothing is close enough. */
|
|
286
|
+
function closest(written, known) {
|
|
287
|
+
let best = null;
|
|
288
|
+
let bestScore = 3;
|
|
289
|
+
for (const k of known) {
|
|
290
|
+
const d = distance(written.toLowerCase(), k.toLowerCase());
|
|
291
|
+
if (d < bestScore) {
|
|
292
|
+
bestScore = d;
|
|
293
|
+
best = k;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return best;
|
|
297
|
+
}
|
|
298
|
+
function distance(a, b) {
|
|
299
|
+
const rows = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
300
|
+
for (let j = 0; j <= b.length; j++) rows[0][j] = j;
|
|
301
|
+
for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) {
|
|
302
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
303
|
+
rows[i][j] = Math.min(rows[i - 1][j] + 1, rows[i][j - 1] + 1, rows[i - 1][j - 1] + cost);
|
|
304
|
+
}
|
|
305
|
+
return rows[a.length][b.length];
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/vite/static-inspect.ts
|
|
309
|
+
/**
|
|
310
|
+
* Build a `FrameHost` whose channel is a long poll.
|
|
311
|
+
*
|
|
312
|
+
* `use(path, handler)` records middleware the caller dispatches; `hot` is a
|
|
313
|
+
* channel that queues outgoing requests and delivers the page's replies.
|
|
314
|
+
*/
|
|
315
|
+
function createStaticInspectHost(root) {
|
|
316
|
+
const routes = [];
|
|
317
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
318
|
+
const queue = [];
|
|
319
|
+
let waiter = null;
|
|
320
|
+
return {
|
|
321
|
+
config: { root },
|
|
322
|
+
middlewares: { use(path, handler) {
|
|
323
|
+
routes.push([path, handler]);
|
|
324
|
+
} },
|
|
325
|
+
hot: {
|
|
326
|
+
send(payload) {
|
|
327
|
+
const item = {
|
|
328
|
+
event: payload.event,
|
|
329
|
+
data: payload.data
|
|
330
|
+
};
|
|
331
|
+
if (waiter) {
|
|
332
|
+
const resolve = waiter;
|
|
333
|
+
waiter = null;
|
|
334
|
+
resolve(item);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
queue.push(item);
|
|
338
|
+
if (queue.length > 32) queue.shift();
|
|
339
|
+
},
|
|
340
|
+
on(event, cb) {
|
|
341
|
+
const list = listeners.get(event) ?? [];
|
|
342
|
+
list.push(cb);
|
|
343
|
+
listeners.set(event, list);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
handleInspect(req, res) {
|
|
347
|
+
const url = (req.originalUrl ?? req.url ?? "").split("?")[0] ?? "";
|
|
348
|
+
if (url === "/__incanto/pull") {
|
|
349
|
+
const next = queue.shift();
|
|
350
|
+
if (next) {
|
|
351
|
+
json$1(res, next);
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
const timer = setTimeout(() => {
|
|
355
|
+
if (waiter) {
|
|
356
|
+
waiter = null;
|
|
357
|
+
json$1(res, null);
|
|
358
|
+
}
|
|
359
|
+
}, 25e3);
|
|
360
|
+
waiter = (value) => {
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
json$1(res, value);
|
|
363
|
+
};
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
if (url === "/__incanto/push") {
|
|
367
|
+
readBody$1(req, (body) => {
|
|
368
|
+
const message = safeParse(body);
|
|
369
|
+
if (message?.event) for (const cb of listeners.get(message.event) ?? []) cb(message.data);
|
|
370
|
+
json$1(res, { ok: true });
|
|
371
|
+
});
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
for (const [path, handler] of routes) if (url === path || url.startsWith(`${path}/`)) {
|
|
375
|
+
handler(req, res);
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
},
|
|
380
|
+
clientScript: () => CLIENT
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function json$1(res, body) {
|
|
384
|
+
res.statusCode = 200;
|
|
385
|
+
res.setHeader("content-type", "application/json");
|
|
386
|
+
res.end(JSON.stringify(body ?? {}));
|
|
387
|
+
}
|
|
388
|
+
function safeParse(text) {
|
|
389
|
+
try {
|
|
390
|
+
return JSON.parse(text);
|
|
391
|
+
} catch {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function readBody$1(req, done) {
|
|
396
|
+
const stream = req;
|
|
397
|
+
let body = "";
|
|
398
|
+
stream.on("data", (chunk) => {
|
|
399
|
+
body += String(chunk);
|
|
400
|
+
});
|
|
401
|
+
stream.on("end", () => done(body));
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* The page half: installs a channel shaped like vite's HMR, so `wireDevChannel`
|
|
405
|
+
* cannot tell the difference.
|
|
406
|
+
*
|
|
407
|
+
* Injected by `incanto-serve` into the served `index.html`, never bundled — a
|
|
408
|
+
* production build stays exactly what the author deployed, and this only exists
|
|
409
|
+
* while they are looking at it.
|
|
410
|
+
*/
|
|
411
|
+
const CLIENT = `(() => {
|
|
412
|
+
const listeners = new Map();
|
|
413
|
+
const post = (event, data) =>
|
|
414
|
+
fetch('/__incanto/push', {
|
|
415
|
+
method: 'POST',
|
|
416
|
+
headers: { 'content-type': 'application/json' },
|
|
417
|
+
body: JSON.stringify({ event: event, data: data }),
|
|
418
|
+
}).catch(() => {});
|
|
419
|
+
globalThis.__incantoInspect = {
|
|
420
|
+
send: (event, data) => post(event, data),
|
|
421
|
+
on: (event, cb) => {
|
|
422
|
+
const list = listeners.get(event) || [];
|
|
423
|
+
list.push(cb);
|
|
424
|
+
listeners.set(event, list);
|
|
425
|
+
},
|
|
426
|
+
off: (event, cb) => {
|
|
427
|
+
const list = (listeners.get(event) || []).filter((f) => f !== cb);
|
|
428
|
+
listeners.set(event, list);
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
const pull = () => {
|
|
432
|
+
fetch('/__incanto/pull')
|
|
433
|
+
.then((r) => r.json())
|
|
434
|
+
.then((message) => {
|
|
435
|
+
if (message && message.event) {
|
|
436
|
+
for (const cb of listeners.get(message.event) || []) cb(message.data);
|
|
437
|
+
}
|
|
438
|
+
pull();
|
|
439
|
+
})
|
|
440
|
+
.catch(() => setTimeout(pull, 1000));
|
|
441
|
+
};
|
|
442
|
+
pull();
|
|
443
|
+
})();`;
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region src/vite/trim.ts
|
|
446
|
+
/**
|
|
447
|
+
* Leave out of the BUILD what this game can never run.
|
|
448
|
+
*
|
|
449
|
+
* A 2D game's `dist/` contains the 3D adapter and rapier3d. Measured on the
|
|
450
|
+
* shipped `platformer-2d` flagship:
|
|
451
|
+
*
|
|
452
|
+
* ```
|
|
453
|
+
* 2236503 assets/rapier-*.js # rapier3d
|
|
454
|
+
* 1692150 assets/rapier-*.js # rapier2d — the one it uses
|
|
455
|
+
* 471186 assets/3d-*.js # the 3D adapter
|
|
456
|
+
* ─────────
|
|
457
|
+
* 5942650 total → 45.6% of it can never execute
|
|
458
|
+
* ```
|
|
459
|
+
*
|
|
460
|
+
* Nothing is wrong at RUNTIME: the chunks are lazy and never requested. This is
|
|
461
|
+
* deploy weight — a CDN, a jam upload, an agent8 zip — and the only path that
|
|
462
|
+
* drags them in is the live game⇄editor switch: `createGame*` imports
|
|
463
|
+
* `incanto/editor`, and the editor imports `incanto/3d` so it can offer 3D node
|
|
464
|
+
* types.
|
|
465
|
+
*
|
|
466
|
+
* `editor: false` on `createGame*` is a RUNTIME option and cannot remove bytes
|
|
467
|
+
* from a bundle; it changed the artifact by 10 bytes while the docs said it
|
|
468
|
+
* "opts out". A build-time decision needs a build-time lever, which is this.
|
|
469
|
+
*
|
|
470
|
+
* ```ts
|
|
471
|
+
* import { incantoScenes, incantoTrim } from 'incanto/vite';
|
|
472
|
+
* export default defineConfig({
|
|
473
|
+
* plugins: [incantoScenes(), incantoTrim({ editor: false })],
|
|
474
|
+
* });
|
|
475
|
+
* ```
|
|
476
|
+
*
|
|
477
|
+
* Build only — the dev server keeps the editor, so `☰ debug ▸ ✎ edit this
|
|
478
|
+
* scene` works while you are building the game and is simply absent from what
|
|
479
|
+
* you ship.
|
|
480
|
+
*/
|
|
481
|
+
const VIRTUAL = "\0incanto-trimmed-editor";
|
|
482
|
+
function incantoTrim(opts = {}) {
|
|
483
|
+
onlyKnownOptions("incantoTrim", opts, ["editor"]);
|
|
484
|
+
const keepEditor = opts.editor === true;
|
|
485
|
+
return {
|
|
486
|
+
name: "incanto:trim",
|
|
487
|
+
apply: "build",
|
|
488
|
+
enforce: "pre",
|
|
489
|
+
resolveId(id) {
|
|
490
|
+
if (!keepEditor && id === "incanto/editor") return VIRTUAL;
|
|
491
|
+
return null;
|
|
492
|
+
},
|
|
493
|
+
load(id) {
|
|
494
|
+
if (id !== VIRTUAL) return null;
|
|
495
|
+
return `export function mountEditor() {
|
|
496
|
+
throw new Error(
|
|
497
|
+
'incanto: the scene editor was left out of this build by incantoTrim({ editor: false }). ' +
|
|
498
|
+
'Run the dev server to edit scenes, or pass incantoTrim({ editor: true }) to include it.',
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
258
506
|
//#region src/vite/index.ts
|
|
259
507
|
/**
|
|
260
508
|
* incanto/vite — dev-server integration: validate every `*.scene.json` edit
|
|
@@ -275,7 +523,71 @@ async function listeningPorts(deps) {
|
|
|
275
523
|
* Typed structurally (no vite dependency) — the returned objects satisfy
|
|
276
524
|
* vite's Plugin interface.
|
|
277
525
|
*/
|
|
526
|
+
/**
|
|
527
|
+
* The BUILT-IN art, at the path the scenes already name.
|
|
528
|
+
*
|
|
529
|
+
* `incanto-assets` prints `incanto/assets/items/coin.png` as an asset's url and
|
|
530
|
+
* three shipped 2D templates carry exactly that — a BUNDLER specifier, which
|
|
531
|
+
* the game's own `main.ts` resolves with an import and a browser cannot. The
|
|
532
|
+
* editor loads the scene JSON as written, so every sprite in `platformer-2d`,
|
|
533
|
+
* `star-survivor` and `topdown-rpg` was a blank quad in the composer that ships
|
|
534
|
+
* with them, in the standalone editor and in the one you switch into from the
|
|
535
|
+
* running game alike.
|
|
536
|
+
*
|
|
537
|
+
* Serving the package's own `assets/` makes the written url resolve. Dev-only
|
|
538
|
+
* (this is `configureServer`), read-only, and confined to that directory.
|
|
539
|
+
*/
|
|
540
|
+
function serveBuiltInAssets(server) {
|
|
541
|
+
const assets = builtInAssetsDir();
|
|
542
|
+
if (!assets) return;
|
|
543
|
+
server.middlewares.use("/incanto/assets", (req, res) => {
|
|
544
|
+
const asked = (req.url ?? "/").split("?")[0] ?? "/";
|
|
545
|
+
const file = normalize(join(assets, decodeURIComponent(asked)));
|
|
546
|
+
if (!file.startsWith(assets + sep) || !existsSync(file) || !statSync(file).isFile()) {
|
|
547
|
+
res.statusCode = 404;
|
|
548
|
+
res.end(`no built-in asset: ${asked}`);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
res.setHeader("content-type", ASSET_MIME[extname(file)] ?? "application/octet-stream");
|
|
552
|
+
res.end(readFileSync(file));
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* The package's own `assets/`, found by WALKING UP rather than by counting
|
|
557
|
+
* directories.
|
|
558
|
+
*
|
|
559
|
+
* Shipped it is `…/incanto/dist/vite.js` -> `…/incanto/assets`; in this repo
|
|
560
|
+
* the same module is `…/packages/engine/src/vite/index.ts`, one level deeper.
|
|
561
|
+
* A fixed `../..` is right in exactly one of those, and the one it would have
|
|
562
|
+
* been wrong in is the one every test runs from.
|
|
563
|
+
*/
|
|
564
|
+
function builtInAssetsDir() {
|
|
565
|
+
let at = dirname(fileURLToPath(import.meta.url));
|
|
566
|
+
for (let up = 0; up < 5; up++) {
|
|
567
|
+
const candidate = join(at, "assets");
|
|
568
|
+
if (existsSync(join(candidate, "catalog.json"))) return candidate;
|
|
569
|
+
const parent = dirname(at);
|
|
570
|
+
if (parent === at) break;
|
|
571
|
+
at = parent;
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
const ASSET_MIME = {
|
|
576
|
+
".png": "image/png",
|
|
577
|
+
".jpg": "image/jpeg",
|
|
578
|
+
".jpeg": "image/jpeg",
|
|
579
|
+
".webp": "image/webp",
|
|
580
|
+
".gif": "image/gif",
|
|
581
|
+
".svg": "image/svg+xml",
|
|
582
|
+
".json": "application/json; charset=utf-8",
|
|
583
|
+
".mp3": "audio/mpeg",
|
|
584
|
+
".ogg": "audio/ogg",
|
|
585
|
+
".wav": "audio/wav",
|
|
586
|
+
".glb": "model/gltf-binary",
|
|
587
|
+
".gltf": "model/gltf+json"
|
|
588
|
+
};
|
|
278
589
|
function incantoScenes(opts = {}) {
|
|
590
|
+
onlyKnownOptions("incantoScenes", opts, ["strictBehaviors", "root"]);
|
|
279
591
|
return {
|
|
280
592
|
name: "incanto-scenes",
|
|
281
593
|
/**
|
|
@@ -292,19 +604,20 @@ function incantoScenes(opts = {}) {
|
|
|
292
604
|
server.middlewares.use("/api/scene", (req, res) => {
|
|
293
605
|
serveSceneFile(req, res, root);
|
|
294
606
|
});
|
|
607
|
+
serveBuiltInAssets(server);
|
|
295
608
|
},
|
|
296
609
|
async handleHotUpdate(ctx) {
|
|
297
610
|
if (!ctx.file.endsWith(".scene.json")) return void 0;
|
|
298
611
|
let json;
|
|
299
612
|
try {
|
|
300
|
-
json =
|
|
613
|
+
json = parseJsonText(await ctx.read());
|
|
301
614
|
} catch (e) {
|
|
302
615
|
report(ctx, `not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
303
616
|
return [];
|
|
304
617
|
}
|
|
305
618
|
const result = validateScene(json, {
|
|
306
619
|
strictBehaviors: opts.strictBehaviors,
|
|
307
|
-
resolveScene: (path) =>
|
|
620
|
+
resolveScene: (path) => parseJsonText(readFileSync(resolve(dirname(ctx.file), path), "utf-8"))
|
|
308
621
|
});
|
|
309
622
|
if (!result.ok && result.error) {
|
|
310
623
|
const e = result.error;
|
|
@@ -559,6 +872,20 @@ async function serveSceneFile(req, res, root) {
|
|
|
559
872
|
}
|
|
560
873
|
const shape = sceneFacts(parsed);
|
|
561
874
|
if (!shape.ok) return json(res, 422, { error: `not an incanto scene — ${shape.why}. The file was NOT written.` });
|
|
875
|
+
if (existsSync(abs)) {
|
|
876
|
+
let existing = null;
|
|
877
|
+
let readable = true;
|
|
878
|
+
try {
|
|
879
|
+
existing = JSON.parse(readFileSync(abs, "utf-8"));
|
|
880
|
+
} catch {
|
|
881
|
+
readable = false;
|
|
882
|
+
}
|
|
883
|
+
const already = readable ? sceneFacts(existing) : {
|
|
884
|
+
ok: false,
|
|
885
|
+
why: "it is not JSON"
|
|
886
|
+
};
|
|
887
|
+
if (!already.ok) return json(res, 409, { error: `refusing to overwrite ${file} — ${already.why}. The editor only replaces scenes, and only creates new files.` });
|
|
888
|
+
}
|
|
562
889
|
try {
|
|
563
890
|
writeFileSync(abs, raw.endsWith("\n") ? raw : `${raw}\n`);
|
|
564
891
|
} catch (error) {
|
|
@@ -599,6 +926,11 @@ function readEnv() {
|
|
|
599
926
|
* ```
|
|
600
927
|
*/
|
|
601
928
|
function incantoLibrary(opts = {}) {
|
|
929
|
+
onlyKnownOptions("incantoLibrary", opts, [
|
|
930
|
+
"token",
|
|
931
|
+
"url",
|
|
932
|
+
"authUrl"
|
|
933
|
+
]);
|
|
602
934
|
return {
|
|
603
935
|
name: "incanto-library",
|
|
604
936
|
apply: "serve",
|
|
@@ -734,4 +1066,4 @@ function libraryItem(row) {
|
|
|
734
1066
|
};
|
|
735
1067
|
}
|
|
736
1068
|
//#endregion
|
|
737
|
-
export { discoverScenes, incantoLibrary, incantoScenes, listeningPorts, maskToken, parseProcNetTcp, resolveSceneFile, sceneFacts, serveDeviceAuth, serveFrameEndpoints, serveLibrary, serveSceneFile, serveSceneList, serveTokenStatus };
|
|
1069
|
+
export { createStaticInspectHost, discoverScenes, incantoLibrary, incantoScenes, incantoTrim, listeningPorts, maskToken, onlyKnownOptions, parseProcNetTcp, resolveSceneFile, sceneFacts, serveDeviceAuth, serveFrameEndpoints, serveLibrary, serveSceneFile, serveSceneList, serveTokenStatus };
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
2
|
//#region src/core/drive-script.ts
|
|
3
|
+
/** The three buttons a `Clickable` can be bound to, by the words people use. */
|
|
4
|
+
const BUTTONS = {
|
|
5
|
+
left: 0,
|
|
6
|
+
middle: 1,
|
|
7
|
+
right: 2
|
|
8
|
+
};
|
|
3
9
|
const num = (raw) => {
|
|
4
10
|
if (raw === void 0) return null;
|
|
5
11
|
const n = Number(raw);
|
|
@@ -75,6 +81,44 @@ function parseDrive(text) {
|
|
|
75
81
|
});
|
|
76
82
|
break;
|
|
77
83
|
}
|
|
84
|
+
case "at": {
|
|
85
|
+
if (rest.length === 1 && rest[0]) {
|
|
86
|
+
steps.push({
|
|
87
|
+
kind: "atNode",
|
|
88
|
+
path: rest[0]
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
const x = num(rest[0]);
|
|
93
|
+
const y = num(rest[1]);
|
|
94
|
+
if (x === null || y === null || rest.length > 2) return bad("at needs two numbers (canvas px) or one node path");
|
|
95
|
+
steps.push({
|
|
96
|
+
kind: "at",
|
|
97
|
+
x,
|
|
98
|
+
y
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "click": {
|
|
103
|
+
const which = BUTTONS[(rest.length === 0 ? "left" : rest[0]) ?? "left"];
|
|
104
|
+
if (which === void 0 || rest.length > 1) return bad("click takes nothing, or left|middle|right — say where with `at`");
|
|
105
|
+
steps.push({
|
|
106
|
+
kind: "click",
|
|
107
|
+
button: which
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case "mouse": {
|
|
112
|
+
const [which, dir] = rest.length === 1 ? ["left", rest[0]] : [rest[0], rest[1]];
|
|
113
|
+
const button = BUTTONS[which ?? ""] ?? num(which ?? "");
|
|
114
|
+
if (button === null || button === void 0 || dir !== "down" && dir !== "up") return bad("mouse needs a button (left|middle|right, or 0|1|2) and down|up");
|
|
115
|
+
steps.push({
|
|
116
|
+
kind: "mouse",
|
|
117
|
+
button,
|
|
118
|
+
down: dir === "down"
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
78
122
|
case "step": {
|
|
79
123
|
const ms = num(rest[0]);
|
|
80
124
|
if (ms === null || ms < 0) return bad("step needs a duration in ms");
|
|
@@ -103,6 +147,13 @@ const RANK = {
|
|
|
103
147
|
error: 3
|
|
104
148
|
};
|
|
105
149
|
/** Under this, with the loop running, is a frame rate somebody can feel. */
|
|
150
|
+
/**
|
|
151
|
+
* How stale the newest frame may be before the rate stops describing NOW.
|
|
152
|
+
*
|
|
153
|
+
* A live game is always a frame or two behind; a third of a second is about
|
|
154
|
+
* twenty dropped frames, which is not a hiccup.
|
|
155
|
+
*/
|
|
156
|
+
const STALE_FRAME_MS = 350;
|
|
106
157
|
const SLOW_FPS = 50;
|
|
107
158
|
/** How many distinct messages to carry. Beyond this it is a log file, not a report. */
|
|
108
159
|
const MAX_GROUPS = 20;
|
|
@@ -167,7 +218,11 @@ function logText(r) {
|
|
|
167
218
|
if (lines.length === 0) lines.push("nothing wrong — no warnings, no errors, no failed assets");
|
|
168
219
|
if (!r.stats.running) lines.push(`not running — the loop is stopped (${r.stats.nodes} nodes in the scene)`);
|
|
169
220
|
else if (r.hidden) lines.push(`${r.stats.nodes} nodes · frame rate not measurable — the page is hidden (a window covered by another counts), and the browser throttles it. Bring it to the front`);
|
|
170
|
-
else
|
|
221
|
+
else {
|
|
222
|
+
const age = r.stats.lastFrameAgeMs;
|
|
223
|
+
const stale = age !== null && age > STALE_FRAME_MS;
|
|
224
|
+
lines.push(`${Math.round(r.stats.fps)} fps · ${r.stats.frameMs.toFixed(1)}ms per frame · ${r.stats.nodes} nodes${r.slow ? " — slow enough to feel" : ""}` + (stale ? ` — measured ${(age / 1e3).toFixed(1)}s ago; nothing has drawn since, so this is the rate the game HAD, not the rate it has` : ""));
|
|
225
|
+
}
|
|
171
226
|
return lines.join("\n");
|
|
172
227
|
}
|
|
173
228
|
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i as e,n as t}from"./index-CIu3uc3l.js";async function n(e,n){return new a((await t(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,e,{diagnostics:n})}var r=[500,1e3,2e3,4e3,8e3],i=8,a=class{raw;active=new Map;reconnecting=!1;disposed=!1;diagnostics;wait;constructor(e,t,n){this.diagnostics=n?.diagnostics??null,this.wait=n?.wait??(e=>new Promise(t=>{setTimeout(t,e)})),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{for(let t=0;t<i;t++){let n=!1,i=null;try{n=await this.rawConnect()!==!1}catch(e){i=e}if(this.disposed)return;if(n){for(let e of this.active.values())e.off(),e.off=e.make();t>0&&e(this.diagnostics,`info`,`[incanto] multiplayer reconnected after ${t+1} attempts.`);return}t===0&&e(this.diagnostics,`warn`,`[incanto] the multiplayer connection dropped and the server would not take it back${i?` (${i instanceof Error?i.message:String(i)})`:``}. Retrying — other players cannot see this one until it comes back.`);let a=r[Math.min(t,r.length-1)]??8e3;if(await this.wait(a),this.disposed)return}e(this.diagnostics,`error`,`[incanto] multiplayer gave up after ${i} reconnect attempts — this client is offline and the others cannot see it. Call connect() again to retry.`)}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{n as createAgent8Server};
|