incanto 0.67.0 → 0.69.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/_pick-server.mjs +48 -0
- package/bin/incanto-assets.mjs +14 -1
- package/bin/incanto-check.mjs +69 -5
- package/bin/incanto-frame.mjs +10 -25
- package/bin/incanto-logs.mjs +8 -18
- package/bin/incanto-new.mjs +29 -1
- package/bin/incanto-serve.mjs +160 -0
- package/bin/incanto-verify.mjs +40 -0
- package/bin/incanto.mjs +1 -0
- package/dist/2d.d.ts +10 -8
- package/dist/2d.js +6 -4
- package/dist/3d.d.ts +31 -7
- package/dist/3d.js +9 -7
- package/dist/{audio-player-BNUHo0JF.d.ts → audio-player-C0j2k-yB.d.ts} +80 -3
- package/dist/{behavior-ZnfzKDo3.d.ts → behavior-rm8vVzu_.d.ts} +145 -1
- package/dist/boot-failure-CKYrEcGF.js +47 -0
- package/dist/{create-game-FAXdHta3.js → create-game-CvJ1wSRP.js} +27 -27
- package/dist/{create-game-DMetd3vR.js → create-game-CwxEDlCm.js} +15 -11
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-B8QnX2-r.js → duplicate-Dj4L-5AA.js} +1 -1
- package/dist/{editor-switch-DAvWQeld.d.ts → editor-switch-CnIOiyNJ.d.ts} +1 -1
- package/dist/editor.js +12 -6
- package/dist/env.d.ts +1 -1
- package/dist/{environment-presets-C-5g2pMh.js → environment-presets-BO-7XoFV.js} +58 -9
- 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-CGSv-9XR.js → gameplay-D1KixWK8.js} +175 -151
- package/dist/gameplay.d.ts +51 -3
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.js +8 -7
- package/dist/{loader-KZESOZmE.js → loader-DZE7B07H.js} +48 -7
- package/dist/{loader-DyT30ArB.d.ts → loader-DadV2QCT.d.ts} +2 -2
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{physics-2d-_B6zj3zU.js → physics-2d-CF5yyjbj.js} +2 -2
- package/dist/{physics-3d-i1IeAY0F.js → physics-3d-DbYaEcZy.js} +3 -3
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{register-vIxU3Xbv.js → register-DbVArsns.js} +19 -10
- package/dist/{register-Cs53CY7s.js → register-mmfugpaQ.js} +159 -6
- package/dist/{replay-BkpDV51O.js → replay-BEHLDcQV.js} +28 -5
- package/dist/{replay-D0GcEHYU.d.ts → replay-BQfkX4x7.d.ts} +38 -4
- package/dist/{schema-CFeioQRE.d.ts → schema-B6ugCV1Q.d.ts} +11 -0
- package/dist/{sheet-grid-BT6N_Bjs.js → sheet-grid-Cea343VO.js} +6 -2
- package/dist/{split-screen-DS8RaUV_.js → split-screen-CauMwu6y.js} +2 -2
- package/dist/{split-screen-DD5eYIgW.d.ts → split-screen-Cg76MCV5.d.ts} +3 -3
- package/dist/{src-BLlsGJW9.js → src-CYEu-_S9.js} +1 -1
- package/dist/{teardown-LB9cTnXJ.js → teardown-CMqFVxO9.js} +5 -4
- package/dist/{test-81uZ967a.js → test-D6ISmuFE.js} +73 -25
- package/dist/test.d.ts +17 -6
- package/dist/test.js +3 -3
- package/dist/vite.d.ts +70 -2
- package/dist/vite.js +176 -14
- package/dist/{rendering-options-ByAdi8PF.js → webgl-unavailable-Z5_g8x9H.js} +57 -2
- package/editor/assets/{agent8-C2jtNKbx.js → agent8-BDvaZMQJ.js} +1 -1
- package/editor/assets/{debug-DoU3KeqS.js → debug-CMULinBW.js} +1 -1
- package/editor/assets/{index-CNqHojcC.js → index-CZIRr5EL.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +4 -3
- package/schemas/scene.schema.json +4 -0
- package/skills/incanto-3d-character.md +1 -0
- package/skills/incanto-assets.md +16 -2
- package/skills/incanto-audio.md +65 -0
- package/skills/incanto-building-2d-games.md +66 -0
- package/skills/incanto-building-3d-games.md +97 -2
- package/skills/incanto-gameplay-behaviors.md +46 -2
- package/skills/incanto-node-reference.md +1 -0
- package/skills/incanto-physics-and-input.md +23 -0
- package/skills/incanto-save-slots.md +27 -0
- package/skills/incanto-scene-json-authoring.md +12 -1
- package/skills/incanto-verifying-your-game.md +96 -2
- package/skills/incanto-your-first-game.md +2 -1
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/main.ts +21 -4
- package/templates-app/platformer-2d/index.html +0 -23
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/behaviors.ts +26 -16
- package/templates-app/platformer-2d/src/game.scene.json +17 -27
- package/templates-app/platformer-2d/src/main.ts +32 -13
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/main.ts +25 -7
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/main.ts +34 -16
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/main.ts +21 -4
|
@@ -1087,6 +1087,10 @@ var InputMap = class {
|
|
|
1087
1087
|
injectedDown = /* @__PURE__ */ new Set();
|
|
1088
1088
|
injectedPressed = /* @__PURE__ */ new Set();
|
|
1089
1089
|
injectedReleased = /* @__PURE__ */ new Set();
|
|
1090
|
+
injectedPressedNext = /* @__PURE__ */ new Set();
|
|
1091
|
+
injectedReleasedNext = /* @__PURE__ */ new Set();
|
|
1092
|
+
/** True between the start of a frame's update pass and `endFrame()`. */
|
|
1093
|
+
inFrame = false;
|
|
1090
1094
|
injectedVectors = /* @__PURE__ */ new Map();
|
|
1091
1095
|
/**
|
|
1092
1096
|
* Press a button ACTION directly — no key codes involved. This is how
|
|
@@ -1112,13 +1116,13 @@ var InputMap = class {
|
|
|
1112
1116
|
this.button(action);
|
|
1113
1117
|
if (!this.injectedDown.has(action)) {
|
|
1114
1118
|
this.injectedDown.add(action);
|
|
1115
|
-
this.injectedPressed.add(action);
|
|
1119
|
+
(this.inFrame ? this.injectedPressedNext : this.injectedPressed).add(action);
|
|
1116
1120
|
}
|
|
1117
1121
|
}
|
|
1118
1122
|
/** Release an injected button action (yields one justReleased frame). */
|
|
1119
1123
|
releaseAction(action) {
|
|
1120
1124
|
this.button(action);
|
|
1121
|
-
if (this.injectedDown.delete(action)) this.injectedReleased.add(action);
|
|
1125
|
+
if (this.injectedDown.delete(action)) (this.inFrame ? this.injectedReleasedNext : this.injectedReleased).add(action);
|
|
1122
1126
|
}
|
|
1123
1127
|
/**
|
|
1124
1128
|
* Feed an analog direction for a vector2 ACTION — virtual joysticks and
|
|
@@ -1152,6 +1156,8 @@ var InputMap = class {
|
|
|
1152
1156
|
this.injectedDown.clear();
|
|
1153
1157
|
this.injectedPressed.clear();
|
|
1154
1158
|
this.injectedReleased.clear();
|
|
1159
|
+
this.injectedPressedNext.clear();
|
|
1160
|
+
this.injectedReleasedNext.clear();
|
|
1155
1161
|
this.injectedVectors.clear();
|
|
1156
1162
|
}
|
|
1157
1163
|
dx = 0;
|
|
@@ -1448,6 +1454,16 @@ var InputMap = class {
|
|
|
1448
1454
|
this.releasedEdge.clear();
|
|
1449
1455
|
this.injectedPressed.clear();
|
|
1450
1456
|
this.injectedReleased.clear();
|
|
1457
|
+
for (const a of this.injectedPressedNext) this.injectedPressed.add(a);
|
|
1458
|
+
for (const a of this.injectedReleasedNext) this.injectedReleased.add(a);
|
|
1459
|
+
this.injectedPressedNext.clear();
|
|
1460
|
+
this.injectedReleasedNext.clear();
|
|
1461
|
+
this.inFrame = false;
|
|
1462
|
+
}
|
|
1463
|
+
/** @internal The engine marks the update phase, so an edge injected inside
|
|
1464
|
+
* it can be held for the next frame. */
|
|
1465
|
+
beginFrame() {
|
|
1466
|
+
this.inFrame = true;
|
|
1451
1467
|
}
|
|
1452
1468
|
/**
|
|
1453
1469
|
* Drop all action declarations and injected action state (physical key
|
|
@@ -1570,10 +1586,16 @@ function effectiveOrder(group, renderOrder, table = ORDER_GROUP_BASE) {
|
|
|
1570
1586
|
//#endregion
|
|
1571
1587
|
//#region src/core/save.ts
|
|
1572
1588
|
const memoryStores = /* @__PURE__ */ new Map();
|
|
1589
|
+
/** Namespaces already warned about, so a per-frame save cannot spam the console. */
|
|
1590
|
+
const warnedVolatile = /* @__PURE__ */ new Set();
|
|
1573
1591
|
function createSaveStore(namespace) {
|
|
1574
1592
|
const prefix = `incanto:${namespace}:`;
|
|
1575
1593
|
const local = storageOrNull();
|
|
1576
1594
|
if (!local) {
|
|
1595
|
+
if (typeof localStorage !== "undefined" && !warnedVolatile.has(namespace)) {
|
|
1596
|
+
warnedVolatile.add(namespace);
|
|
1597
|
+
console.warn(`[incanto] this browser refused localStorage, so saves for '${namespace}' live in memory and are lost when the page closes (private window, storage disabled, or quota exhausted). Check \`store.persistent\` and tell the player.`);
|
|
1598
|
+
}
|
|
1577
1599
|
let mem = memoryStores.get(namespace);
|
|
1578
1600
|
if (!mem) {
|
|
1579
1601
|
mem = /* @__PURE__ */ new Map();
|
|
@@ -1581,6 +1603,7 @@ function createSaveStore(namespace) {
|
|
|
1581
1603
|
}
|
|
1582
1604
|
const backing = mem;
|
|
1583
1605
|
return {
|
|
1606
|
+
persistent: false,
|
|
1584
1607
|
get: (key, fallback) => parse(backing.get(key), fallback),
|
|
1585
1608
|
set: (key, value) => void backing.set(key, JSON.stringify(value)),
|
|
1586
1609
|
remove: (key) => void backing.delete(key),
|
|
@@ -1588,6 +1611,7 @@ function createSaveStore(namespace) {
|
|
|
1588
1611
|
};
|
|
1589
1612
|
}
|
|
1590
1613
|
return {
|
|
1614
|
+
persistent: true,
|
|
1591
1615
|
get: (key, fallback) => parse(local.getItem(prefix + key) ?? void 0, fallback),
|
|
1592
1616
|
set: (key, value) => local.setItem(prefix + key, JSON.stringify(value)),
|
|
1593
1617
|
remove: (key) => local.removeItem(prefix + key),
|
|
@@ -1834,6 +1858,17 @@ var SaveSlots = class {
|
|
|
1834
1858
|
constructor(namespace = "saves", store) {
|
|
1835
1859
|
this.store = store ?? createSaveStore(namespace);
|
|
1836
1860
|
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Will these slots survive the page closing?
|
|
1863
|
+
*
|
|
1864
|
+
* `false` in a private window, with storage disabled, or headless — every
|
|
1865
|
+
* write still succeeds and every read still answers, for as long as the tab
|
|
1866
|
+
* lives. A load menu should say so instead of offering a slot that will not
|
|
1867
|
+
* be there tomorrow.
|
|
1868
|
+
*/
|
|
1869
|
+
get persistent() {
|
|
1870
|
+
return this.store.persistent !== false;
|
|
1871
|
+
}
|
|
1837
1872
|
/** Slot ids that have a save, newest first. */
|
|
1838
1873
|
list() {
|
|
1839
1874
|
const rows = this.store.get(INDEX_KEY, []).map((id) => ({
|
|
@@ -2850,6 +2885,8 @@ var Scene = class {
|
|
|
2850
2885
|
orderGroups;
|
|
2851
2886
|
/** Physics config (gravity etc. — interpreted by the physics modules). */
|
|
2852
2887
|
physics;
|
|
2888
|
+
/** Declared piece of another scene — see `SceneJson.fragment`. */
|
|
2889
|
+
fragment;
|
|
2853
2890
|
/** Multiplayer config (room etc. — interpreted by incanto/net). */
|
|
2854
2891
|
multiplayer;
|
|
2855
2892
|
/** Design-resolution viewport (consumed by renderers via resolveViewport). */
|
|
@@ -2869,6 +2906,7 @@ var Scene = class {
|
|
|
2869
2906
|
this.orderGroups = source.orderGroups ? { ...source.orderGroups } : void 0;
|
|
2870
2907
|
this.strings = source.strings ? jsonClone(source.strings) : void 0;
|
|
2871
2908
|
this.physics = source.physics ? jsonClone(source.physics) : void 0;
|
|
2909
|
+
this.fragment = source.fragment === true ? true : void 0;
|
|
2872
2910
|
this.multiplayer = source.multiplayer ? jsonClone(source.multiplayer) : void 0;
|
|
2873
2911
|
this.environment = source.environment ? jsonClone(source.environment) : void 0;
|
|
2874
2912
|
this.viewport = source.viewport ? jsonClone(source.viewport) : void 0;
|
|
@@ -2887,6 +2925,7 @@ var Scene = class {
|
|
|
2887
2925
|
...this.orderGroups ? { orderGroups: { ...this.orderGroups } } : {},
|
|
2888
2926
|
...this.strings ? { strings: jsonClone(this.strings) } : {},
|
|
2889
2927
|
...this.physics ? { physics: jsonClone(this.physics) } : {},
|
|
2928
|
+
...this.fragment ? { fragment: true } : {},
|
|
2890
2929
|
...this.multiplayer ? { multiplayer: jsonClone(this.multiplayer) } : {},
|
|
2891
2930
|
...this.environment ? { environment: jsonClone(this.environment) } : {},
|
|
2892
2931
|
...this.viewport ? { viewport: jsonClone(this.viewport) } : {},
|
|
@@ -3063,6 +3102,7 @@ function validateHeader(json) {
|
|
|
3063
3102
|
if (json.format !== 1) throw new IncantoError("BAD_FORMAT", `Unsupported scene format ${JSON.stringify(json.format)}; this engine reads format 1.`);
|
|
3064
3103
|
if (json.type !== "scene") throw new IncantoError("BAD_FORMAT", `Expected "type": "scene", got ${JSON.stringify(json.type)}.`);
|
|
3065
3104
|
if (typeof json.name !== "string" || json.name === "") throw new IncantoError("BAD_FORMAT", "Scene \"name\" must be a non-empty string.");
|
|
3105
|
+
if (json.fragment !== void 0 && typeof json.fragment !== "boolean") throw new IncantoError("BAD_FORMAT", `Scene "fragment" must be true or false, got ${JSON.stringify(json.fragment)}.`);
|
|
3066
3106
|
if (json.dimension !== void 0 && json.dimension !== "2d" && json.dimension !== "3d") throw new IncantoError("BAD_FORMAT", `Scene "dimension" must be "2d" or "3d", got ${JSON.stringify(json.dimension)}.`);
|
|
3067
3107
|
if (typeof json.root !== "object" || json.root === null) throw new IncantoError("BAD_FORMAT", "Scene \"root\" must be a node object.");
|
|
3068
3108
|
resolveViewport(json.viewport);
|
|
@@ -3154,10 +3194,11 @@ const SCENE_KEYS = [
|
|
|
3154
3194
|
"environment",
|
|
3155
3195
|
"strings",
|
|
3156
3196
|
"physics",
|
|
3197
|
+
"fragment",
|
|
3157
3198
|
"root",
|
|
3158
3199
|
"connections"
|
|
3159
3200
|
];
|
|
3160
|
-
/** Closest known key by edit distance — only a genuinely
|
|
3201
|
+
/** Closest known key by edit distance — only a genuinely near one. */
|
|
3161
3202
|
function nearestKey(key, known) {
|
|
3162
3203
|
const lower = key.toLowerCase();
|
|
3163
3204
|
let best = null;
|
|
@@ -3196,7 +3237,7 @@ function buildNode(nj, opts, stack, parentPath, constants, bands = []) {
|
|
|
3196
3237
|
if (nj.instance !== void 0) return buildInstance(nj, opts, stack, path, constants, bands);
|
|
3197
3238
|
if (nj.type === void 0) throw new IncantoError("BAD_FORMAT", `Node '${nj.name}' must declare either "type" or "instance".`);
|
|
3198
3239
|
const node = createNode(nj.type, resolveConstants(nj.props, constants), { orderGroup: bands });
|
|
3199
|
-
applyCommon(node, nj, opts);
|
|
3240
|
+
applyCommon(node, nj, opts, constants);
|
|
3200
3241
|
node.constructor.validateJson?.(node);
|
|
3201
3242
|
for (const childJson of nj.children ?? []) node.addChild(buildNode(childJson, opts, stack, path, constants, bands));
|
|
3202
3243
|
return node;
|
|
@@ -3222,7 +3263,7 @@ function buildInstance(nj, opts, stack, path, constants, bands) {
|
|
|
3222
3263
|
...nj,
|
|
3223
3264
|
type: void 0,
|
|
3224
3265
|
props: void 0
|
|
3225
|
-
}, opts);
|
|
3266
|
+
}, opts, constants);
|
|
3226
3267
|
for (const childJson of nj.children ?? []) node.addChild(buildNode(childJson, opts, stack, path, constants, bands));
|
|
3227
3268
|
wireConnections(node, sub.connections ?? [], opts?.declareConnectionSignals);
|
|
3228
3269
|
return node;
|
|
@@ -3235,7 +3276,7 @@ function parentOf(path) {
|
|
|
3235
3276
|
function propsFor(node) {
|
|
3236
3277
|
return mergeStaticProps(node.constructor);
|
|
3237
3278
|
}
|
|
3238
|
-
function applyCommon(node, nj, opts) {
|
|
3279
|
+
function applyCommon(node, nj, opts, constants) {
|
|
3239
3280
|
if (typeof nj.uid === "string" && nj.uid !== "") node.uid = nj.uid;
|
|
3240
3281
|
node.name = nj.name;
|
|
3241
3282
|
for (const g of nj.groups ?? []) node.addToGroup(g);
|
|
@@ -3244,7 +3285,7 @@ function applyCommon(node, nj, opts) {
|
|
|
3244
3285
|
node.script = jsonClone(nj.script);
|
|
3245
3286
|
const name = nj.script.name;
|
|
3246
3287
|
if (typeof name !== "string" || name === "") throw new IncantoError("BAD_FORMAT", `Node '${nj.name}': "script" needs a non-empty "name" (a registered behavior).`);
|
|
3247
|
-
const behavior = opts?.stubAllBehaviors || opts?.stubMissingBehaviors && !hasBehavior(name) ? new class extends StubBehavior {}() : createBehavior(name, nj.script.props);
|
|
3288
|
+
const behavior = opts?.stubAllBehaviors || opts?.stubMissingBehaviors && !hasBehavior(name) ? new class extends StubBehavior {}() : createBehavior(name, resolveConstants(nj.script.props, constants));
|
|
3248
3289
|
behavior.node = node;
|
|
3249
3290
|
node.behavior = behavior;
|
|
3250
3291
|
for (const s of mergeStaticSignals(behavior.constructor)) node.declareSignal(s);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as Scene, b as Engine } from "./behavior-
|
|
2
|
-
import { i as SceneJson } from "./schema-
|
|
1
|
+
import { P as Scene, b as Engine } from "./behavior-rm8vVzu_.js";
|
|
2
|
+
import { i as SceneJson } from "./schema-B6ugCV1Q.js";
|
|
3
3
|
|
|
4
4
|
//#region src/core/scene/loader.d.ts
|
|
5
5
|
interface LoadSceneOptions {
|
package/dist/net.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as PropSchema, jt as Node } from "./behavior-
|
|
2
|
-
import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen-
|
|
1
|
+
import { d as PropSchema, jt as Node } from "./behavior-rm8vVzu_.js";
|
|
2
|
+
import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen-Cg76MCV5.js";
|
|
3
3
|
|
|
4
4
|
//#region src/net/agent8.d.ts
|
|
5
5
|
/**
|
package/dist/net.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
|
|
2
|
-
import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-
|
|
2
|
+
import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-CauMwu6y.js";
|
|
3
3
|
export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { k as diagnose } from "./loader-
|
|
2
|
+
import { k as diagnose } from "./loader-DZE7B07H.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-
|
|
4
|
+
import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-DbVArsns.js";
|
|
5
5
|
import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
|
|
6
6
|
import { n as jointContacts, t as withoutRapierInitNoise } from "./quiet-rapier-C6fW4zcW.js";
|
|
7
7
|
//#region src/2d/physics/physics-2d.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { k as diagnose } from "./loader-
|
|
2
|
+
import { k as diagnose } from "./loader-DZE7B07H.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import { H as CharacterBody3D, K as Node3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, q as validateCollider3D } from "./gameplay-
|
|
4
|
+
import { H as CharacterBody3D, K as Node3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, q as validateCollider3D } from "./gameplay-D1KixWK8.js";
|
|
5
5
|
import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
|
|
6
6
|
import { n as jointContacts, t as withoutRapierInitNoise } from "./quiet-rapier-C6fW4zcW.js";
|
|
7
|
-
import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-
|
|
7
|
+
import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-BO-7XoFV.js";
|
|
8
8
|
import { Euler, Matrix4, Quaternion, Vector3 } from "three";
|
|
9
9
|
//#region src/3d/physics/collider-lines.ts
|
|
10
10
|
/**
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-
|
|
2
|
-
import { c as JsonValue } from "./schema-
|
|
1
|
+
import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-rm8vVzu_.js";
|
|
2
|
+
import { c as JsonValue } from "./schema-B6ugCV1Q.js";
|
|
3
3
|
import { CSSProperties, ReactNode } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/react/index.d.ts
|
package/dist/react.js
CHANGED
|
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
|
|
|
156
156
|
pointer: latest.pointer,
|
|
157
157
|
...keyboard !== void 0 ? { keyboard } : {}
|
|
158
158
|
};
|
|
159
|
-
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-
|
|
159
|
+
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-CvJ1wSRP.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CwxEDlCm.js").then((n) => n.n)).createGame2D(o)))(opts);
|
|
160
160
|
if (disposed) {
|
|
161
161
|
next.dispose();
|
|
162
162
|
return;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-
|
|
2
|
-
import { L as translateOn, t as registerCoreNodes } from "./register-
|
|
1
|
+
import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-DZE7B07H.js";
|
|
2
|
+
import { L as translateOn, t as registerCoreNodes } from "./register-mmfugpaQ.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
5
|
import { i as getNodeSchema, l as registerNode } from "./registry-CF70EArN.js";
|
|
6
6
|
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
|
|
7
|
-
import { F as localFromWorld, I as worldPosition } from "./gameplay-
|
|
8
|
-
import { t as checkSheetGrid } from "./sheet-grid-
|
|
7
|
+
import { F as localFromWorld, I as worldPosition } from "./gameplay-D1KixWK8.js";
|
|
8
|
+
import { t as checkSheetGrid } from "./sheet-grid-Cea343VO.js";
|
|
9
9
|
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
|
|
10
10
|
//#region src/2d/physics/collider-validate.ts
|
|
11
11
|
/**
|
|
@@ -1443,9 +1443,11 @@ var TileMap2D = class extends Node2D {
|
|
|
1443
1443
|
_solid = [];
|
|
1444
1444
|
/** Rebuild flags — raised when the map-shaping props are REPLACED. */
|
|
1445
1445
|
geometryDirty = true;
|
|
1446
|
-
/**
|
|
1446
|
+
/** `texture|highestTile` already reported, so a bad grid says so once. */
|
|
1447
1447
|
gridChecked = "";
|
|
1448
1448
|
collidersDirty = true;
|
|
1449
|
+
/** The `scale` the colliders were last built for — see `rebuildColliders`. */
|
|
1450
|
+
builtScale = [1, 1];
|
|
1449
1451
|
/** Replace the whole array to change the map (mutations are not watched). */
|
|
1450
1452
|
get cells() {
|
|
1451
1453
|
return this._cells;
|
|
@@ -1487,19 +1489,23 @@ var TileMap2D = class extends Node2D {
|
|
|
1487
1489
|
this.rebuildColliders();
|
|
1488
1490
|
}
|
|
1489
1491
|
update(_dt) {
|
|
1492
|
+
const [sx = 1, sy = 1] = this.scale;
|
|
1493
|
+
if (sx !== this.builtScale[0] || sy !== this.builtScale[1]) this.collidersDirty = true;
|
|
1490
1494
|
if (this.collidersDirty) this.rebuildColliders();
|
|
1491
1495
|
}
|
|
1492
1496
|
rebuildColliders() {
|
|
1493
1497
|
this.collidersDirty = false;
|
|
1494
1498
|
for (const child of [...this.children]) if (child.name.startsWith("__tileSolid")) child.free();
|
|
1499
|
+
this.builtScale = [this.scale[0] ?? 1, this.scale[1] ?? 1];
|
|
1495
1500
|
if (this._solid.length === 0) return;
|
|
1496
1501
|
const ts = this.tileSize;
|
|
1502
|
+
const [sx, sy] = this.builtScale;
|
|
1497
1503
|
mergeSolidRects(this.grid(), new Set(this._solid)).forEach((r, i) => {
|
|
1498
1504
|
const body = new StaticBody2D(`__tileSolid${i}`);
|
|
1499
|
-
body.position = [(r.x + r.w / 2) * ts, (r.y + r.h / 2) * ts];
|
|
1505
|
+
body.position = [(r.x + r.w / 2) * ts * sx, (r.y + r.h / 2) * ts * sy];
|
|
1500
1506
|
body.collider = {
|
|
1501
1507
|
shape: "rect",
|
|
1502
|
-
size: [r.w * ts, r.h * ts]
|
|
1508
|
+
size: [r.w * ts * sx, r.h * ts * sy]
|
|
1503
1509
|
};
|
|
1504
1510
|
this.addChild(body);
|
|
1505
1511
|
});
|
|
@@ -1543,9 +1549,12 @@ var TileMap2D = class extends Node2D {
|
|
|
1543
1549
|
mat.needsUpdate = true;
|
|
1544
1550
|
this.geometryDirty = true;
|
|
1545
1551
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1552
|
+
let highestAsked = -1;
|
|
1553
|
+
for (const row of this.grid()) for (const tile of row) if (tile > highestAsked) highestAsked = tile;
|
|
1554
|
+
const checkKey = `${this.texture}|${highestAsked}`;
|
|
1555
|
+
if (this.gridChecked !== checkKey) {
|
|
1556
|
+
this.gridChecked = checkKey;
|
|
1557
|
+
const problem = checkSheetGrid(this.texture, this.tileSize, this.tileSize, image.width, image.height, highestAsked >= 0 ? highestAsked : void 0, "tile");
|
|
1549
1558
|
if (problem) diagnose(this.tree?.engine ?? null, "error", problem);
|
|
1550
1559
|
}
|
|
1551
1560
|
const columns = this.columns > 0 ? this.columns : Math.max(1, Math.floor(image.width / this.tileSize));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-
|
|
2
|
+
import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-DZE7B07H.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
5
|
import { l as registerNode } from "./registry-CF70EArN.js";
|
|
@@ -179,6 +179,11 @@ var WebAudioTrack = class {
|
|
|
179
179
|
setLoop(loop) {
|
|
180
180
|
this.el.loop = loop;
|
|
181
181
|
}
|
|
182
|
+
/** The element's own playhead — the clock the sound is actually on. */
|
|
183
|
+
playhead() {
|
|
184
|
+
const at = this.el.currentTime;
|
|
185
|
+
return typeof at === "number" && Number.isFinite(at) ? at : null;
|
|
186
|
+
}
|
|
182
187
|
play() {
|
|
183
188
|
this.el.play().catch((error) => {
|
|
184
189
|
const name = error?.name ?? "";
|
|
@@ -243,7 +248,8 @@ const SILENT_BACKEND = {
|
|
|
243
248
|
setGain() {},
|
|
244
249
|
setLoop() {},
|
|
245
250
|
play() {},
|
|
246
|
-
stop() {}
|
|
251
|
+
stop() {},
|
|
252
|
+
playhead: () => null
|
|
247
253
|
}),
|
|
248
254
|
unlock() {}
|
|
249
255
|
};
|
|
@@ -283,6 +289,24 @@ var MusicManager = class {
|
|
|
283
289
|
get current() {
|
|
284
290
|
return this.active?.track.src ?? null;
|
|
285
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Seconds into the current track — the playhead of the thing you can HEAR.
|
|
294
|
+
*
|
|
295
|
+
* `null` when nothing is playing, or when the backend has no clock (headless,
|
|
296
|
+
* or a custom backend that does not implement it).
|
|
297
|
+
*
|
|
298
|
+
* A game charted to a soundtrack needs this and there was no way to get it:
|
|
299
|
+
* `current` gave the src and the backend was private, so the only option was
|
|
300
|
+
* an `engine.time` counter started next to `music.play()` — a different clock
|
|
301
|
+
* from the one the audio is on, and one that keeps counting through a
|
|
302
|
+
* gesture-block, a stall or a seek. This is the audio's own answer.
|
|
303
|
+
*
|
|
304
|
+
* It is the RAW element time, so a looping track wraps to 0 at each pass:
|
|
305
|
+
* that is a loop boundary you can chart against, not a fault.
|
|
306
|
+
*/
|
|
307
|
+
get playhead() {
|
|
308
|
+
return this.active?.track.playhead?.() ?? null;
|
|
309
|
+
}
|
|
286
310
|
/** Per-manager volume (0..1), on top of the bus gain. */
|
|
287
311
|
get volume() {
|
|
288
312
|
return this._volume;
|
|
@@ -1036,6 +1060,16 @@ var SfxEngine = class {
|
|
|
1036
1060
|
if (ctx && ctx.state === "suspended") ctx.resume();
|
|
1037
1061
|
}
|
|
1038
1062
|
/**
|
|
1063
|
+
* The AUDIO clock, in seconds — the one a sound is actually scheduled
|
|
1064
|
+
* against, and the only clock that does not drift relative to what you hear.
|
|
1065
|
+
*
|
|
1066
|
+
* `0` headless and before the first sound, since there is no context yet.
|
|
1067
|
+
* Schedule with a lead: `sfx.play(params, 1, { when: sfx.now + 0.08 })`.
|
|
1068
|
+
*/
|
|
1069
|
+
get now() {
|
|
1070
|
+
return this.ctx?.currentTime ?? 0;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1039
1073
|
* Play a preset at `gain` (0..1, already bus-multiplied by the caller). Each
|
|
1040
1074
|
* call spawns its own source so rapid repeats overlap instead of cutting off.
|
|
1041
1075
|
* Headless → no-op.
|
|
@@ -1077,7 +1111,7 @@ var SfxEngine = class {
|
|
|
1077
1111
|
const source = ctx.createBufferSource();
|
|
1078
1112
|
source.buffer = buffer;
|
|
1079
1113
|
source.connect(gainNode);
|
|
1080
|
-
source.start();
|
|
1114
|
+
source.start(opts.when === void 0 ? 0 : Math.max(0, opts.when));
|
|
1081
1115
|
}
|
|
1082
1116
|
};
|
|
1083
1117
|
//#endregion
|
|
@@ -1997,6 +2031,25 @@ var FrameStatsRing = class {
|
|
|
1997
2031
|
const span = this.spanMs();
|
|
1998
2032
|
return span > 0 ? span / (this.count - 1) : 0;
|
|
1999
2033
|
}
|
|
2034
|
+
/**
|
|
2035
|
+
* How long ago the newest sample arrived, in ms. `null` with no samples.
|
|
2036
|
+
*
|
|
2037
|
+
* `fps()` is a rate over a WINDOW, so it keeps answering after the frames
|
|
2038
|
+
* stop: a hidden tab, a descheduled loop, a page the browser froze. Measured
|
|
2039
|
+
* on an engine that ticked 40 times and then stopped being ticked:
|
|
2040
|
+
*
|
|
2041
|
+
* while ticking : fps 59.9
|
|
2042
|
+
* after 0 more ticks : fps 59.9 <- the last sample is 668 ms old
|
|
2043
|
+
*
|
|
2044
|
+
* Both numbers are true about the window they describe. What was missing was
|
|
2045
|
+
* any way to ask WHEN that window was, so "60 fps" read the same whether it
|
|
2046
|
+
* was measured 8 ms ago or four seconds ago.
|
|
2047
|
+
*/
|
|
2048
|
+
ageMs(nowMs) {
|
|
2049
|
+
if (this.count === 0) return null;
|
|
2050
|
+
const newest = this.times[(this.head + this.count - 1) % this.capacity];
|
|
2051
|
+
return Math.max(0, nowMs - newest);
|
|
2052
|
+
}
|
|
2000
2053
|
/** Forget all samples (engine.stop() — a paused game has no frame rate). */
|
|
2001
2054
|
clear() {
|
|
2002
2055
|
this.head = 0;
|
|
@@ -2399,6 +2452,49 @@ var Engine = class {
|
|
|
2399
2452
|
* learns about Rapier or three. From a behavior, prefer `this.physics`.
|
|
2400
2453
|
*/
|
|
2401
2454
|
physics = null;
|
|
2455
|
+
/**
|
|
2456
|
+
* How to build a physics world for a scene that needs one.
|
|
2457
|
+
*
|
|
2458
|
+
* `createGame2D`/`createGame3D` install this; core never learns what Rapier
|
|
2459
|
+
* is. It exists because `physics: 'auto'` can only look at the scene the game
|
|
2460
|
+
* BOOTED with, and the most universal act of finishing a game is putting a
|
|
2461
|
+
* title screen in front of it:
|
|
2462
|
+
*
|
|
2463
|
+
* ```
|
|
2464
|
+
* after boot on the title : engine.physics = null
|
|
2465
|
+
* after goToScene into game: engine.physics = null
|
|
2466
|
+
* player y after 2 s : 0 -> 0
|
|
2467
|
+
* ```
|
|
2468
|
+
*
|
|
2469
|
+
* The game rendered perfectly and was inert — nothing fell, nothing collided,
|
|
2470
|
+
* nothing could be collected or hurt. One scene swap turned a working game
|
|
2471
|
+
* into a diorama, and nothing was logged.
|
|
2472
|
+
*/
|
|
2473
|
+
physicsProvider = null;
|
|
2474
|
+
ensuring = null;
|
|
2475
|
+
/**
|
|
2476
|
+
* Give the current scene a physics world if it needs one and has none.
|
|
2477
|
+
*
|
|
2478
|
+
* Idempotent and safe to call on every swap: the provider decides whether the
|
|
2479
|
+
* incoming tree actually wants physics, and an explicit `physics: false` at
|
|
2480
|
+
* boot installs no provider at all, so opting out stays opted out.
|
|
2481
|
+
*
|
|
2482
|
+
* Asynchronous because the Rapier WASM has to load. The first frames after a
|
|
2483
|
+
* swap therefore run unsimulated — which every AI behaviour already tolerates
|
|
2484
|
+
* (`moveBody` falls back to a direct write when `_physics` is null) and which
|
|
2485
|
+
* is a hitch, where the alternative was a game that never simulated at all.
|
|
2486
|
+
*/
|
|
2487
|
+
ensurePhysics() {
|
|
2488
|
+
if (this.physics || !this.physicsProvider || !this._scene) return Promise.resolve();
|
|
2489
|
+
if (this.ensuring) return this.ensuring;
|
|
2490
|
+
const root = this._scene.root;
|
|
2491
|
+
this.ensuring = this.physicsProvider(root).catch((e) => {
|
|
2492
|
+
this.log.error(`[incanto] could not enable physics for this scene: ${String(e?.message ?? e)}`);
|
|
2493
|
+
}).finally(() => {
|
|
2494
|
+
this.ensuring = null;
|
|
2495
|
+
});
|
|
2496
|
+
return this.ensuring;
|
|
2497
|
+
}
|
|
2402
2498
|
/** Seeded randomness for game logic (deterministic when `seed` is set). */
|
|
2403
2499
|
rng;
|
|
2404
2500
|
/** The engine log channel (debug overlay + headless harness tail this). */
|
|
@@ -2722,6 +2818,7 @@ var Engine = class {
|
|
|
2722
2818
|
return {
|
|
2723
2819
|
fps: this.frameStats.fps(),
|
|
2724
2820
|
frameMs: this.frameStats.frameMs(),
|
|
2821
|
+
lastFrameAgeMs: this.frameStats.ageMs(performance.now()),
|
|
2725
2822
|
nodes: this._scene?.tree.root ? countNodes(this._scene.tree.root) : 0,
|
|
2726
2823
|
running: this.disposeScheduler !== null,
|
|
2727
2824
|
errors: this.errorCount,
|
|
@@ -2774,6 +2871,7 @@ var Engine = class {
|
|
|
2774
2871
|
this._time += dt;
|
|
2775
2872
|
this._unscaledTime += this.fixedStep;
|
|
2776
2873
|
const startedFixed = performance.now();
|
|
2874
|
+
this.input.beginFrame();
|
|
2777
2875
|
scene.tree.fixedUpdate(dt);
|
|
2778
2876
|
this.fixedUpdated.emit(dt);
|
|
2779
2877
|
const startedUpdate = performance.now();
|
|
@@ -2801,6 +2899,7 @@ var Engine = class {
|
|
|
2801
2899
|
this.lastMs = nowMs;
|
|
2802
2900
|
const scene = this._scene;
|
|
2803
2901
|
if (!scene) return;
|
|
2902
|
+
this.input.beginFrame();
|
|
2804
2903
|
this.accumulator += dt;
|
|
2805
2904
|
let steps = 0;
|
|
2806
2905
|
const startedFixed = performance.now();
|
|
@@ -2973,7 +3072,8 @@ var AudioPlayer = class extends Node {
|
|
|
2973
3072
|
name,
|
|
2974
3073
|
from: this.getPath(),
|
|
2975
3074
|
bus: this.bus === "music" ? "music" : "sfx",
|
|
2976
|
-
at: engine.time
|
|
3075
|
+
at: engine.time,
|
|
3076
|
+
...this.loop ? { loop: true } : {}
|
|
2977
3077
|
});
|
|
2978
3078
|
}
|
|
2979
3079
|
/** Final gain = engine buses × this volume (1 when not in a tree). */
|
|
@@ -3005,6 +3105,54 @@ var AudioPlayer = class extends Node {
|
|
|
3005
3105
|
rolloff: this.rolloff
|
|
3006
3106
|
};
|
|
3007
3107
|
}
|
|
3108
|
+
/**
|
|
3109
|
+
* Start the sound, now.
|
|
3110
|
+
*
|
|
3111
|
+
* **Takes no arguments, on purpose.** This is the method scenes wire signals
|
|
3112
|
+
* to — `incanto-audio.md` teaches
|
|
3113
|
+
* `{"signal": "collected", "from": "Player/Collector", "to": "Coin",
|
|
3114
|
+
* "handler": "play"}` — and a signal hands its handler whatever it carries.
|
|
3115
|
+
* `collected` leads with a NUMBER. When this briefly took an optional
|
|
3116
|
+
* scheduling time, that wiring became `play(10)`, which scheduled the pickup
|
|
3117
|
+
* sound at ABSOLUTE audio-clock second 10: inaudible for the first ten
|
|
3118
|
+
* seconds of the game and fine thereafter. The same optional parameter also
|
|
3119
|
+
* made `Function.length` 1, so the engine's own arity checker cried wolf on
|
|
3120
|
+
* the shipped `platformer-2d` template's own harness.
|
|
3121
|
+
*
|
|
3122
|
+
* Scheduling has its own name: {@link playAt}.
|
|
3123
|
+
*/
|
|
3124
|
+
/**
|
|
3125
|
+
* Start the sound at `when` on the AUDIO clock (`engine.sfx.now` + a lead).
|
|
3126
|
+
*
|
|
3127
|
+
* A frame is a 16.67 ms grid at 60 Hz and 33.33 ms at 30, so a sound fired
|
|
3128
|
+
* from `update()` cannot land closer than one frame to where a chart wants
|
|
3129
|
+
* it — measured 0 of 64 notes given a scheduled start. Queue them a lead
|
|
3130
|
+
* ahead and the same notes land at |mean| 0.000000 ms:
|
|
3131
|
+
*
|
|
3132
|
+
* ```ts
|
|
3133
|
+
* const LEAD = 0.08;
|
|
3134
|
+
* for (const note of dueSoon(engine.sfx.now + LEAD)) {
|
|
3135
|
+
* hit.playAt(startedAt + note.atSec);
|
|
3136
|
+
* }
|
|
3137
|
+
* ```
|
|
3138
|
+
*
|
|
3139
|
+
* PRESETS only — a `src` clip goes through an `<audio>` element, which has no
|
|
3140
|
+
* scheduling clock, so `playAt` on one plays immediately rather than being
|
|
3141
|
+
* silently approximated. A time already past plays now (Web Audio's own
|
|
3142
|
+
* rule), so a late scheduler is late, not silent. The bus gain, the
|
|
3143
|
+
* `engine.audio.recent()` entry and `muted` all still apply.
|
|
3144
|
+
*
|
|
3145
|
+
* Separate from {@link play} because `play` is what scenes wire signals to,
|
|
3146
|
+
* and a signal hands its handler whatever it carries.
|
|
3147
|
+
*/
|
|
3148
|
+
playAt(when) {
|
|
3149
|
+
if (this.preset !== "custom") {
|
|
3150
|
+
this.note("preset", this.preset);
|
|
3151
|
+
this.playPreset(Number.isFinite(when) ? when : void 0);
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
this.play();
|
|
3155
|
+
}
|
|
3008
3156
|
play() {
|
|
3009
3157
|
if (this.preset !== "custom") {
|
|
3010
3158
|
this.note("preset", this.preset);
|
|
@@ -3052,7 +3200,7 @@ var AudioPlayer = class extends Node {
|
|
|
3052
3200
|
* the length is arithmetic, not a device. WebAudio needs a gesture; a
|
|
3053
3201
|
* suspended context is resumed by the same unlock listener as `src` players.
|
|
3054
3202
|
*/
|
|
3055
|
-
playPreset() {
|
|
3203
|
+
playPreset(at) {
|
|
3056
3204
|
const params = SFX_PRESETS[this.preset];
|
|
3057
3205
|
if (!params) return;
|
|
3058
3206
|
const engine = this.tree?.engine;
|
|
@@ -3063,7 +3211,8 @@ var AudioPlayer = class extends Node {
|
|
|
3063
3211
|
sfx.play(params, this.gain(), {
|
|
3064
3212
|
pitch: this.pitch,
|
|
3065
3213
|
seed: this.seed,
|
|
3066
|
-
spatial: this.spatialPlay()
|
|
3214
|
+
spatial: this.spatialPlay(),
|
|
3215
|
+
when: at
|
|
3067
3216
|
});
|
|
3068
3217
|
}
|
|
3069
3218
|
/**
|
|
@@ -3099,6 +3248,10 @@ var AudioPlayer = class extends Node {
|
|
|
3099
3248
|
}
|
|
3100
3249
|
if (this.presetEndsAt !== null) {
|
|
3101
3250
|
if ((this.tree?.engine?.unscaledTime ?? 0) >= this.presetEndsAt) {
|
|
3251
|
+
if (this.loop) {
|
|
3252
|
+
this.playPreset();
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3102
3255
|
this.presetEndsAt = null;
|
|
3103
3256
|
this._playing = false;
|
|
3104
3257
|
this.emit("finished");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-
|
|
2
|
-
import { R as translationKey, k as HudWidgetBase } from "./register-
|
|
1
|
+
import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-DZE7B07H.js";
|
|
2
|
+
import { R as translationKey, k as HudWidgetBase } from "./register-mmfugpaQ.js";
|
|
3
3
|
import { o as getNodeType } from "./registry-CF70EArN.js";
|
|
4
4
|
//#region src/core/audit.ts
|
|
5
5
|
const ANIMATED_TYPES = new Set([
|
|
@@ -104,6 +104,28 @@ function transformOf(props) {
|
|
|
104
104
|
if (scaled) return "scale";
|
|
105
105
|
return null;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Which files in a corpus some OTHER scene embeds with `instance`.
|
|
109
|
+
*
|
|
110
|
+
* A fragment cannot be recognised by looking at itself — `enemy.scene.json` and
|
|
111
|
+
* a broken first level are the same file shape. It is recognised by the project
|
|
112
|
+
* around it, which every tool that audits more than one scene already walks.
|
|
113
|
+
*
|
|
114
|
+
* `resolveRef(fromFile, ref)` turns a scene-relative `instance` path into the
|
|
115
|
+
* same spelling the corpus uses for that file, so the caller owns all path
|
|
116
|
+
* logic and this stays runnable in a browser.
|
|
117
|
+
*/
|
|
118
|
+
function instancedScenes(corpus, resolveRef) {
|
|
119
|
+
const instanced = /* @__PURE__ */ new Set();
|
|
120
|
+
const walk = (node, from) => {
|
|
121
|
+
if (!node || typeof node !== "object") return;
|
|
122
|
+
const ref = node.instance;
|
|
123
|
+
if (typeof ref === "string" && ref) instanced.add(resolveRef(from, ref));
|
|
124
|
+
for (const child of node.children ?? []) walk(child, from);
|
|
125
|
+
};
|
|
126
|
+
for (const { path, json } of corpus) walk(json.root, path);
|
|
127
|
+
return instanced;
|
|
128
|
+
}
|
|
107
129
|
function auditScene(scene, opts = {}) {
|
|
108
130
|
const warnings = [];
|
|
109
131
|
const root = scene.root;
|
|
@@ -166,8 +188,9 @@ function auditScene(scene, opts = {}) {
|
|
|
166
188
|
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.`);
|
|
167
189
|
if (cameras > 0 && currentCameras === 0) warnings.push("no camera has \"current\": true — the screen renders from a default view, probably not what you framed.");
|
|
168
190
|
if (currentCameras > 1) warnings.push(`${currentCameras} cameras claim "current": true — only the first found wins.`);
|
|
169
|
-
const isFragment =
|
|
170
|
-
if (scene.dimension === "3d" && !isFragment &&
|
|
191
|
+
const isFragment = opts.instanced === true || scene.fragment === true;
|
|
192
|
+
if (scene.dimension === "3d" && !isFragment && cameras === 0) warnings.push("this 3D scene has no camera — the screen renders from a default view, which is almost never the shot you want. Add a Camera3D with \"current\": true, or set \"fragment\": true in the scene header if this file is a prefab another scene drops in.");
|
|
193
|
+
if (scene.dimension === "3d" && !isFragment && 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\": \"...\" } — or set \"fragment\": true if another scene lights this one.");
|
|
171
194
|
if (damageGroups.length > 0) {
|
|
172
195
|
const hurtable = hurtableGroups(root);
|
|
173
196
|
for (const { path, group } of damageGroups) {
|
|
@@ -359,4 +382,4 @@ function replay(engine, recording, opts) {
|
|
|
359
382
|
}
|
|
360
383
|
}
|
|
361
384
|
//#endregion
|
|
362
|
-
export { startRecording as n, auditScene as r, replay as t };
|
|
385
|
+
export { instancedScenes as i, startRecording as n, auditScene as r, replay as t };
|