incanto 0.76.0 → 0.77.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/dist/index.js +1 -1
- package/dist/{src-D823V07k.js → src-DygJfhOb.js} +1 -1
- package/dist/vite.d.ts +47 -3
- package/dist/vite.js +49 -17
- package/package.json +1 -1
- package/skills/incanto-editor.md +44 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/molehill-2d/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/index.js
CHANGED
|
@@ -9,5 +9,5 @@ import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
|
|
|
9
9
|
import { a as logText, i as logReport, o as parseDrive, r as resolveRendering, t as isWebGLAvailable } from "./webgl-unavailable-C8aDbGmR.js";
|
|
10
10
|
import { t as createNoise2D } from "./noise-D3nPpmFg.js";
|
|
11
11
|
import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-SQa5gIu2.js";
|
|
12
|
-
import { a as findPath, i as preloadUrls, n as assetUrls, o as gridFromRows, r as preloadSceneAssets, t as VERSION } from "./src-
|
|
12
|
+
import { a as findPath, i as preloadUrls, n as assetUrls, o as gridFromRows, r as preloadSceneAssets, t as VERSION } from "./src-DygJfhOb.js";
|
|
13
13
|
export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Respawn, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMinimap, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, UiWaypoint, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, isWebGLAvailable, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, showBootFailure, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
|
|
@@ -199,6 +199,6 @@ async function preloadSceneAssets(assets, title) {
|
|
|
199
199
|
//#endregion
|
|
200
200
|
//#region src/index.ts
|
|
201
201
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
202
|
-
const VERSION = "0.
|
|
202
|
+
const VERSION = "0.77.0";
|
|
203
203
|
//#endregion
|
|
204
204
|
export { findPath as a, preloadUrls as i, assetUrls as n, gridFromRows as o, preloadSceneAssets as r, VERSION as t };
|
package/dist/vite.d.ts
CHANGED
|
@@ -151,6 +151,50 @@ interface IncantoScenesOptions {
|
|
|
151
151
|
* Nothing outside it is readable or writable, whatever a request asks for.
|
|
152
152
|
*/
|
|
153
153
|
root?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Called after a scene is written through this server — the editor's save,
|
|
156
|
+
* and the editor's create. In-process, with the scene itself; the same
|
|
157
|
+
* event goes to stdout for anything watching the dev server from outside
|
|
158
|
+
* (see {@link SceneEvent}).
|
|
159
|
+
*/
|
|
160
|
+
onSave?(event: SceneEvent): void;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* What this server announces when a scene lands, on stdout AND to `onSave`.
|
|
164
|
+
*
|
|
165
|
+
* ONE line per event, `[incanto] ` and a JSON object — readable by a person in
|
|
166
|
+
* the terminal they are already watching, unambiguous to a machine, and
|
|
167
|
+
* extensible without breaking a parser that only reads the fields it knows:
|
|
168
|
+
*
|
|
169
|
+
* [incanto] {"event":"scene:saved","file":"src/game.scene.json","bytes":13284,"at":"2026-09-08T10:19:57.412Z"}
|
|
170
|
+
*
|
|
171
|
+
* It exists because a container had no way to hear a save that happened inside
|
|
172
|
+
* it. The editor a running game hosts writes through `PUT /api/scene`, and the
|
|
173
|
+
* only signals available to the process supervising that container were
|
|
174
|
+
* accidents: watching the filesystem, or scraping vite's `page reload` line —
|
|
175
|
+
* which existed only because saving used to reload the page, and went away
|
|
176
|
+
* when that was fixed.
|
|
177
|
+
*
|
|
178
|
+
* `abs` and `json` are for `onSave` only; the printed line carries neither (an
|
|
179
|
+
* absolute container path means nothing outside it, and a scene does not
|
|
180
|
+
* belong on one log line).
|
|
181
|
+
*/
|
|
182
|
+
interface SceneEvent {
|
|
183
|
+
event: "scene:saved" | "scene:created";
|
|
184
|
+
/** Project-relative, forward slashes on every OS. */
|
|
185
|
+
file: string;
|
|
186
|
+
/** Bytes written. */
|
|
187
|
+
bytes: number;
|
|
188
|
+
/** ISO-8601, when it landed. */
|
|
189
|
+
at: string;
|
|
190
|
+
/** Absolute path — `onSave` only, never printed. */
|
|
191
|
+
abs?: string;
|
|
192
|
+
/** The scene as written — `onSave` only, never printed. */
|
|
193
|
+
json?: unknown;
|
|
194
|
+
}
|
|
195
|
+
/** Hooks the middlewares carry (exported for tests, not for vite). */
|
|
196
|
+
interface SceneHooks {
|
|
197
|
+
onSave?(event: SceneEvent): void;
|
|
154
198
|
}
|
|
155
199
|
declare function incantoScenes(opts?: IncantoScenesOptions): {
|
|
156
200
|
name: string;
|
|
@@ -241,9 +285,9 @@ declare function discoverScenes(root: string): ProjectFileEntry[];
|
|
|
241
285
|
*/
|
|
242
286
|
declare function resolveSceneFile(root: string, rel: unknown): string | null;
|
|
243
287
|
/** @internal Exported for tests — `GET`/`POST /api/scenes`. */
|
|
244
|
-
declare function serveSceneList(req: ApiReq, res: LibraryRes, root: string): Promise<void>;
|
|
288
|
+
declare function serveSceneList(req: ApiReq, res: LibraryRes, root: string, hooks?: SceneHooks): Promise<void>;
|
|
245
289
|
/** @internal Exported for tests — `GET`/`PUT /api/scene?file=`. */
|
|
246
|
-
declare function serveSceneFile(req: ApiReq, res: LibraryRes, root: string): Promise<void>;
|
|
290
|
+
declare function serveSceneFile(req: ApiReq, res: LibraryRes, root: string, hooks?: SceneHooks): Promise<void>;
|
|
247
291
|
interface DevServer {
|
|
248
292
|
middlewares: {
|
|
249
293
|
use(path: string, handler: (req: LibraryReq, res: LibraryRes) => void): void;
|
|
@@ -317,4 +361,4 @@ declare function serveDeviceAuth(req: LibraryReq, res: LibraryRes, opts?: Incant
|
|
|
317
361
|
/** @internal Exported for tests — the whole request/response behaviour. */
|
|
318
362
|
declare function serveLibrary(req: LibraryReq, res: LibraryRes, opts?: IncantoLibraryOptions, doFetch?: typeof fetch): Promise<void>;
|
|
319
363
|
//#endregion
|
|
320
|
-
export { type FrameHost, type FrameReq, type FrameRes, IncantoLibraryOptions, IncantoScenesOptions, type IncantoTrimOptions, ProjectFileEntry, type StaticInspectHost, createStaticInspectHost, discoverScenes, incantoLibrary, incantoScenes, incantoTrim, listeningPorts, maskToken, onlyKnownOptions, parseProcNetTcp, projectDimension, resolveSceneFile, sceneFacts, serveDeviceAuth, serveFrameEndpoints, serveLibrary, serveSceneFile, serveSceneList, serveTokenStatus };
|
|
364
|
+
export { type FrameHost, type FrameReq, type FrameRes, IncantoLibraryOptions, IncantoScenesOptions, type IncantoTrimOptions, ProjectFileEntry, SceneEvent, SceneHooks, type StaticInspectHost, createStaticInspectHost, discoverScenes, incantoLibrary, incantoScenes, incantoTrim, listeningPorts, maskToken, onlyKnownOptions, parseProcNetTcp, projectDimension, resolveSceneFile, sceneFacts, serveDeviceAuth, serveFrameEndpoints, serveLibrary, serveSceneFile, serveSceneList, serveTokenStatus };
|
package/dist/vite.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as parseJsonText } from "./json-CfTjpvW8.js";
|
|
2
2
|
import { P as newUid } from "./pose-ByFB_J3O.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import { t as VERSION } from "./src-
|
|
4
|
+
import { t as VERSION } from "./src-DygJfhOb.js";
|
|
5
5
|
import { n as diffSignatures } from "./frame-report-D-_7YF2G.js";
|
|
6
6
|
import { s as validateScene } from "./test-CVbxnXlv.js";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -525,6 +525,29 @@ function incantoTrim(opts = {}) {
|
|
|
525
525
|
* vite's Plugin interface.
|
|
526
526
|
*/
|
|
527
527
|
/**
|
|
528
|
+
* Announce a scene that LANDED. Never called for a refused write: the line
|
|
529
|
+
* means the bytes are on disk, which is the only reading that is any use to a
|
|
530
|
+
* process deciding whether to rebuild.
|
|
531
|
+
*/
|
|
532
|
+
function announce(kind, base, abs, raw, json, hooks) {
|
|
533
|
+
const event = {
|
|
534
|
+
event: kind,
|
|
535
|
+
file: relative(base, abs).split(sep).join("/"),
|
|
536
|
+
bytes: Buffer.byteLength(raw),
|
|
537
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
538
|
+
};
|
|
539
|
+
console.log(`[incanto] ${JSON.stringify(event)}`);
|
|
540
|
+
try {
|
|
541
|
+
hooks?.onSave?.({
|
|
542
|
+
...event,
|
|
543
|
+
abs,
|
|
544
|
+
json
|
|
545
|
+
});
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error(`[incanto] onSave threw: ${message(error)}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
528
551
|
* The BUILT-IN art, at the path the scenes already name.
|
|
529
552
|
*
|
|
530
553
|
* `incanto-assets` prints `incanto/assets/items/coin.png` as an asset's url and
|
|
@@ -604,7 +627,11 @@ const ASSET_MIME = {
|
|
|
604
627
|
*/
|
|
605
628
|
const OWN_WRITES = /* @__PURE__ */ new Map();
|
|
606
629
|
function incantoScenes(opts = {}) {
|
|
607
|
-
onlyKnownOptions("incantoScenes", opts, [
|
|
630
|
+
onlyKnownOptions("incantoScenes", opts, [
|
|
631
|
+
"strictBehaviors",
|
|
632
|
+
"root",
|
|
633
|
+
"onSave"
|
|
634
|
+
]);
|
|
608
635
|
return {
|
|
609
636
|
name: "incanto-scenes",
|
|
610
637
|
/**
|
|
@@ -615,11 +642,12 @@ function incantoScenes(opts = {}) {
|
|
|
615
642
|
configureServer(server) {
|
|
616
643
|
serveFrameEndpoints(server, VERSION);
|
|
617
644
|
const root = opts.root ?? server.config?.root ?? process.cwd();
|
|
645
|
+
const hooks = opts.onSave ? { onSave: opts.onSave } : {};
|
|
618
646
|
server.middlewares.use("/api/scenes", (req, res) => {
|
|
619
|
-
serveSceneList(req, res, root);
|
|
647
|
+
serveSceneList(req, res, root, hooks);
|
|
620
648
|
});
|
|
621
649
|
server.middlewares.use("/api/scene", (req, res) => {
|
|
622
|
-
serveSceneFile(req, res, root);
|
|
650
|
+
serveSceneFile(req, res, root, hooks);
|
|
623
651
|
});
|
|
624
652
|
serveBuiltInAssets(server);
|
|
625
653
|
},
|
|
@@ -838,7 +866,7 @@ function readBody(req) {
|
|
|
838
866
|
});
|
|
839
867
|
}
|
|
840
868
|
/** @internal Exported for tests — `GET`/`POST /api/scenes`. */
|
|
841
|
-
async function serveSceneList(req, res, root) {
|
|
869
|
+
async function serveSceneList(req, res, root, hooks) {
|
|
842
870
|
const base = normalize(resolve(root));
|
|
843
871
|
if (req.method === void 0 || req.method === "GET") return json(res, 200, discoverScenes(base));
|
|
844
872
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
@@ -855,22 +883,25 @@ async function serveSceneList(req, res, root) {
|
|
|
855
883
|
if (existsSync(abs)) return json(res, 409, { error: `already exists: ${String(wanted)}` });
|
|
856
884
|
const name = basename(abs).replace(/\.scene\.json$|\.json$/, "") || "Scene";
|
|
857
885
|
const dimension = (body.dimension === "3d" || body.dimension === "2d" ? body.dimension : null) ?? projectDimension(base) ?? "2d";
|
|
886
|
+
const created = {
|
|
887
|
+
format: 1,
|
|
888
|
+
type: "scene",
|
|
889
|
+
dimension,
|
|
890
|
+
name,
|
|
891
|
+
root: {
|
|
892
|
+
name,
|
|
893
|
+
type: dimension === "3d" ? "Node3D" : "Node2D",
|
|
894
|
+
uid: newUid()
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
const raw = `${JSON.stringify(created, null, 2)}\n`;
|
|
858
898
|
try {
|
|
859
899
|
mkdirSync(dirname(abs), { recursive: true });
|
|
860
|
-
writeFileSync(abs,
|
|
861
|
-
format: 1,
|
|
862
|
-
type: "scene",
|
|
863
|
-
dimension,
|
|
864
|
-
name,
|
|
865
|
-
root: {
|
|
866
|
-
name,
|
|
867
|
-
type: dimension === "3d" ? "Node3D" : "Node2D",
|
|
868
|
-
uid: newUid()
|
|
869
|
-
}
|
|
870
|
-
}, null, 2)}\n`);
|
|
900
|
+
writeFileSync(abs, raw);
|
|
871
901
|
} catch (error) {
|
|
872
902
|
return json(res, 400, { error: message(error) });
|
|
873
903
|
}
|
|
904
|
+
announce("scene:created", base, abs, raw, created, hooks);
|
|
874
905
|
return json(res, 201, {
|
|
875
906
|
rel: relative(base, abs).split(sep).join("/"),
|
|
876
907
|
abs,
|
|
@@ -879,7 +910,7 @@ async function serveSceneList(req, res, root) {
|
|
|
879
910
|
});
|
|
880
911
|
}
|
|
881
912
|
/** @internal Exported for tests — `GET`/`PUT /api/scene?file=`. */
|
|
882
|
-
async function serveSceneFile(req, res, root) {
|
|
913
|
+
async function serveSceneFile(req, res, root, hooks) {
|
|
883
914
|
const base = normalize(resolve(root));
|
|
884
915
|
const file = new URL(req.url ?? "/", "http://localhost").searchParams.get("file");
|
|
885
916
|
const abs = resolveSceneFile(base, file);
|
|
@@ -937,6 +968,7 @@ async function serveSceneFile(req, res, root) {
|
|
|
937
968
|
return json(res, 400, { error: message(error) });
|
|
938
969
|
}
|
|
939
970
|
OWN_WRITES.set(normalize(abs), written);
|
|
971
|
+
announce("scene:saved", base, abs, written, parsed, hooks);
|
|
940
972
|
return json(res, 200, {
|
|
941
973
|
ok: true,
|
|
942
974
|
output: abs
|
package/package.json
CHANGED
package/skills/incanto-editor.md
CHANGED
|
@@ -493,6 +493,50 @@ embedded editor posts nothing. The page posts to its parent window:
|
|
|
493
493
|
{ type: 'incanto-editor:error', message } // loader/save errors
|
|
494
494
|
```
|
|
495
495
|
|
|
496
|
+
### Hearing a save from OUTSIDE the browser
|
|
497
|
+
|
|
498
|
+
A preview running in a container saves into that container, and the process
|
|
499
|
+
supervising it — the one that rebuilds, commits or syncs — is not in the page.
|
|
500
|
+
It has two ways to hear about it, and neither needs the iframe:
|
|
501
|
+
|
|
502
|
+
**1. stdout, zero configuration.** Every scene this dev server writes announces
|
|
503
|
+
itself on the terminal you are already tailing, as ONE line: a fixed `[incanto] `
|
|
504
|
+
prefix and a JSON object.
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
[incanto] {"event":"scene:saved","file":"src/game.scene.json","bytes":13284,"at":"2026-09-08T10:19:57.412Z"}
|
|
508
|
+
[incanto] {"event":"scene:created","file":"src/levels/arena.scene.json","bytes":214,"at":"…"}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
`file` is project-relative with forward slashes on every OS, `bytes` is what was
|
|
512
|
+
written, `at` is ISO-8601. The line is printed only when the bytes LANDED — a
|
|
513
|
+
refused write (not a scene, would overwrite a non-scene) says nothing, so the
|
|
514
|
+
line means "there is something new on disk". Read the fields you know and ignore
|
|
515
|
+
the rest: more may be added, these will not change. Nothing to configure; a
|
|
516
|
+
project with `incantoScenes()` in its vite config already prints it.
|
|
517
|
+
|
|
518
|
+
Do not scrape vite's own `page reload <file>` line for this. It appears for a
|
|
519
|
+
HAND edit and deliberately does not for the editor's own save (that reload used
|
|
520
|
+
to throw you out of the editor mid-edit).
|
|
521
|
+
|
|
522
|
+
**2. `onSave`, in-process, with the scene itself.** When the host wants the JSON
|
|
523
|
+
rather than the path:
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
incantoScenes({
|
|
527
|
+
onSave: ({ event, file, abs, json }) => {
|
|
528
|
+
// event: 'scene:saved' | 'scene:created'
|
|
529
|
+
void fetch('http://127.0.0.1:9000/scene-saved', {
|
|
530
|
+
method: 'POST',
|
|
531
|
+
body: JSON.stringify({ file, json }),
|
|
532
|
+
});
|
|
533
|
+
},
|
|
534
|
+
})
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
It runs after the write, and a throw from it is reported and swallowed — a
|
|
538
|
+
broken hook must not take the dev server down or silence the stdout line.
|
|
539
|
+
|
|
496
540
|
The page can never choose filesystem paths — `input`/`output` are fixed at launch;
|
|
497
541
|
the API only reads the input and writes the output. Saves are rejected (422, file
|
|
498
542
|
untouched) unless the body is scene-shaped (`format: 1`, `type: "scene"`, `name`,
|