incanto 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/incanto-play.mjs +7 -2
- package/dist/2d.d.ts +12 -5
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +67 -6
- package/dist/3d.js +4 -4
- package/dist/{audit-C6rMyict.js → audit-C4kmDK0o.js} +16 -0
- package/dist/{behavior-BAc0erXF.d.ts → behavior-D_jMpFh8.d.ts} +117 -55
- package/dist/{create-game-5z_QVtLx.js → create-game-bKHgHcsZ.js} +19 -10
- package/dist/{create-game-DuBTv2zI.js → create-game-gXI7PYl0.js} +61 -46
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +7 -2
- package/dist/{duplicate-BgtFrFo4.js → duplicate-KRPtUtzl.js} +1 -1
- package/dist/{editor-switch-B0wB_DSr.d.ts → editor-switch-BJb-CWfA.d.ts} +12 -1
- package/dist/{register-DJ0SByQg.js → environment-presets--DigHNg4.js} +42 -11
- package/dist/{gameplay-Cfr6aFZ1.js → gameplay-BftxM_It.js} +2 -2
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.js +7 -7
- package/dist/{loader-B242FF6N.d.ts → loader-BYBrqTxP.d.ts} +1 -1
- package/dist/{loader-BZqOKfI2.js → loader-BlaRQGaA.js} +482 -6
- package/dist/net.d.ts +1 -1
- package/dist/net.js +3 -3
- package/dist/{pathfinding-BwD974Ss.d.ts → pathfinding-BwhqPD3i.d.ts} +1 -1
- package/dist/{physics-2d-3kOQCtgd.js → physics-2d-D9wquBvK.js} +4 -3
- package/dist/{physics-3d-CeRH-Ff_.js → physics-3d-CnPygVGo.js} +5 -4
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-MelqEdza.js → register-CUY284Is.js} +3 -3
- package/dist/{register-BTg0EM7s.js → register-Dzkd6-os.js} +56 -380
- package/dist/{register-DWcWq4QG.js → register-tkR_8tWg.js} +4 -4
- package/dist/{registry-BVJ2HbCn.js → registry-CJdGpT2V.js} +6 -2
- package/dist/test-WwRIlXsK.js +642 -0
- package/dist/test.d.ts +55 -8
- package/dist/test.js +3 -364
- package/dist/{touch-031PxtCR.js → touch-BoNg_MnF.js} +2 -1
- package/dist/vite.js +1 -1
- package/editor/assets/{agent8-CAp0i5qn.js → agent8-C5k1nTCH.js} +1 -1
- package/editor/assets/{debug-BoEYfbqK.js → debug-BT_0mjk-.js} +3 -2
- package/editor/assets/{index-BO6WU8by.js → index-gfyrByWw.js} +90 -90
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/skills/README.md +14 -2
- package/skills/incanto-building-2d-games.md +16 -0
- package/skills/incanto-building-3d-games.md +16 -0
- package/skills/incanto-verifying-your-game.md +118 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { f as Node,
|
|
1
|
+
import { f as Node, h as InputMap, x as Signal } from "./loader-BlaRQGaA.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
3
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
4
|
-
import { l as registerNode } from "./registry-
|
|
4
|
+
import { l as registerNode } from "./registry-CJdGpT2V.js";
|
|
5
5
|
//#region src/core/audio/buses.ts
|
|
6
6
|
/**
|
|
7
7
|
* Global volume state — `master` × `sfx`/`music` gain (0..1) plus a `muted`
|
|
@@ -960,381 +960,6 @@ var FrameStatsRing = class {
|
|
|
960
960
|
}
|
|
961
961
|
};
|
|
962
962
|
//#endregion
|
|
963
|
-
//#region src/core/input.ts
|
|
964
|
-
/** Keys typed into editable elements belong to the page UI, not the game. */
|
|
965
|
-
function isEditableTarget(target) {
|
|
966
|
-
const el = target;
|
|
967
|
-
return !!el && (el.isContentEditable === true || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT");
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* Declarative input from scene JSON `input{}` — DOM-free logic (feed key state
|
|
971
|
-
* via `handleKey`, or wire a browser with `attachKeyboard(window)`).
|
|
972
|
-
*
|
|
973
|
-
* Convention: vector2 follows the 2D y-down space (up = -y). `justPressed` /
|
|
974
|
-
* `justReleased` are one-frame edges, settled by the Engine each tick.
|
|
975
|
-
*/
|
|
976
|
-
var InputMap = class {
|
|
977
|
-
actions = /* @__PURE__ */ new Map();
|
|
978
|
-
down = /* @__PURE__ */ new Set();
|
|
979
|
-
pressedEdge = /* @__PURE__ */ new Set();
|
|
980
|
-
releasedEdge = /* @__PURE__ */ new Set();
|
|
981
|
-
detach = null;
|
|
982
|
-
/** Load (or extend with) scene-JSON action declarations. Hard-validates shape. */
|
|
983
|
-
declare(decls) {
|
|
984
|
-
for (const [name, raw] of Object.entries(decls)) {
|
|
985
|
-
const decl = raw;
|
|
986
|
-
if (decl.type === "button") {
|
|
987
|
-
const keys = decl.keys;
|
|
988
|
-
if (!Array.isArray(keys) || keys.some((k) => typeof k !== "string")) throw new IncantoError("BAD_FORMAT", `Input action '${name}': button "keys" must be an array of KeyboardEvent.code strings.`);
|
|
989
|
-
if (decl.touch !== void 0 && decl.touch !== "button") throw new IncantoError("BAD_FORMAT", `Input action '${name}': button "touch" must be 'button', got ${JSON.stringify(decl.touch)}.`);
|
|
990
|
-
this.actions.set(name, {
|
|
991
|
-
type: "button",
|
|
992
|
-
keys: [...keys],
|
|
993
|
-
...decl.touch === "button" ? { touch: "button" } : {}
|
|
994
|
-
});
|
|
995
|
-
} else if (decl.type === "vector2") {
|
|
996
|
-
const dirs = decl.keys;
|
|
997
|
-
for (const dir of [
|
|
998
|
-
"up",
|
|
999
|
-
"down",
|
|
1000
|
-
"left",
|
|
1001
|
-
"right"
|
|
1002
|
-
]) if (!Array.isArray(dirs?.[dir])) throw new IncantoError("BAD_FORMAT", `Input action '${name}': vector2 "keys" needs arrays for up/down/left/right.`);
|
|
1003
|
-
if (decl.touch !== void 0 && decl.touch !== "joystick") throw new IncantoError("BAD_FORMAT", `Input action '${name}': vector2 "touch" must be 'joystick', got ${JSON.stringify(decl.touch)}.`);
|
|
1004
|
-
const d = dirs;
|
|
1005
|
-
this.actions.set(name, {
|
|
1006
|
-
type: "vector2",
|
|
1007
|
-
keys: {
|
|
1008
|
-
up: [...d.up],
|
|
1009
|
-
down: [...d.down],
|
|
1010
|
-
left: [...d.left],
|
|
1011
|
-
right: [...d.right]
|
|
1012
|
-
},
|
|
1013
|
-
...decl.touch === "joystick" ? { touch: "joystick" } : {}
|
|
1014
|
-
});
|
|
1015
|
-
} else throw new IncantoError("BAD_FORMAT", `Input action '${name}': "type" must be 'button' or 'vector2', got ${JSON.stringify(decl.type)}.`);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
/** Feed a key state change (code = KeyboardEvent.code, e.g. 'Space', 'KeyW'). */
|
|
1019
|
-
handleKey(code, isDown) {
|
|
1020
|
-
if (isDown) {
|
|
1021
|
-
if (!this.down.has(code)) {
|
|
1022
|
-
this.down.add(code);
|
|
1023
|
-
this.pressedEdge.add(code);
|
|
1024
|
-
}
|
|
1025
|
-
} else if (this.down.has(code)) {
|
|
1026
|
-
this.down.delete(code);
|
|
1027
|
-
this.releasedEdge.add(code);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
injectedDown = /* @__PURE__ */ new Set();
|
|
1031
|
-
injectedPressed = /* @__PURE__ */ new Set();
|
|
1032
|
-
injectedReleased = /* @__PURE__ */ new Set();
|
|
1033
|
-
injectedVectors = /* @__PURE__ */ new Map();
|
|
1034
|
-
/**
|
|
1035
|
-
* Press a button ACTION directly — no key codes involved. This is how
|
|
1036
|
-
* scripted gameplay tests and touch buttons drive the game by intent
|
|
1037
|
-
* (`press('jump')`) instead of reverse-engineering keybinds.
|
|
1038
|
-
*/
|
|
1039
|
-
pressAction(action) {
|
|
1040
|
-
this.button(action);
|
|
1041
|
-
if (!this.injectedDown.has(action)) {
|
|
1042
|
-
this.injectedDown.add(action);
|
|
1043
|
-
this.injectedPressed.add(action);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
/** Release an injected button action (yields one justReleased frame). */
|
|
1047
|
-
releaseAction(action) {
|
|
1048
|
-
this.button(action);
|
|
1049
|
-
if (this.injectedDown.delete(action)) this.injectedReleased.add(action);
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Feed an analog direction for a vector2 ACTION — virtual joysticks and
|
|
1053
|
-
* scripted runs. Persists until replaced; (0, 0) clears the injection.
|
|
1054
|
-
* Combines with key state in getVector (clamped to unit length).
|
|
1055
|
-
*/
|
|
1056
|
-
setActionVector(action, x, y) {
|
|
1057
|
-
if (this.get(action).type !== "vector2") throw new IncantoError("BAD_FORMAT", `Input action '${action}' is a button — use pressAction/releaseAction, not setActionVector.`);
|
|
1058
|
-
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new IncantoError("BAD_FORMAT", `setActionVector('${action}') needs finite numbers, got (${x}, ${y}).`);
|
|
1059
|
-
if (x === 0 && y === 0) this.injectedVectors.delete(action);
|
|
1060
|
-
else this.injectedVectors.set(action, {
|
|
1061
|
-
x,
|
|
1062
|
-
y
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Actions the scene wants on-screen touch controls for, in declaration
|
|
1067
|
-
* order (`"touch": "joystick"` on vector2, `"touch": "button"` on buttons).
|
|
1068
|
-
* The touch overlay (core/touch.ts) renders these.
|
|
1069
|
-
*/
|
|
1070
|
-
touchControls() {
|
|
1071
|
-
const out = [];
|
|
1072
|
-
for (const [name, def] of this.actions) if (def.touch) out.push({
|
|
1073
|
-
action: name,
|
|
1074
|
-
kind: def.touch
|
|
1075
|
-
});
|
|
1076
|
-
return out;
|
|
1077
|
-
}
|
|
1078
|
-
/** Drop all injected action state (held buttons, vectors, pending edges). */
|
|
1079
|
-
clearInjected() {
|
|
1080
|
-
this.injectedDown.clear();
|
|
1081
|
-
this.injectedPressed.clear();
|
|
1082
|
-
this.injectedReleased.clear();
|
|
1083
|
-
this.injectedVectors.clear();
|
|
1084
|
-
}
|
|
1085
|
-
dx = 0;
|
|
1086
|
-
dy = 0;
|
|
1087
|
-
wheel = 0;
|
|
1088
|
-
/** Mouse buttons feed the same code space as keys: Mouse0/Mouse1/Mouse2. */
|
|
1089
|
-
handleMouseButton(button, isDown) {
|
|
1090
|
-
this.handleKey(`Mouse${button}`, isDown);
|
|
1091
|
-
}
|
|
1092
|
-
/** Accumulate look deltas (movementX/Y under pointer lock, else move deltas). */
|
|
1093
|
-
handlePointerMove(dx, dy) {
|
|
1094
|
-
this.dx += dx;
|
|
1095
|
-
this.dy += dy;
|
|
1096
|
-
}
|
|
1097
|
-
handleWheel(deltaY) {
|
|
1098
|
-
this.wheel += deltaY;
|
|
1099
|
-
}
|
|
1100
|
-
/** Drain the accumulated pointer delta (read once per frame). */
|
|
1101
|
-
pointerDelta() {
|
|
1102
|
-
const d = {
|
|
1103
|
-
x: this.dx,
|
|
1104
|
-
y: this.dy
|
|
1105
|
-
};
|
|
1106
|
-
this.dx = 0;
|
|
1107
|
-
this.dy = 0;
|
|
1108
|
-
return d;
|
|
1109
|
-
}
|
|
1110
|
-
/** Drain the accumulated wheel delta. */
|
|
1111
|
-
wheelDelta() {
|
|
1112
|
-
const w = this.wheel;
|
|
1113
|
-
this.wheel = 0;
|
|
1114
|
-
return w;
|
|
1115
|
-
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Wire browser pointer events on a canvas: buttons → Mouse0/1/2 codes,
|
|
1118
|
-
* movement → pointerDelta (movementX/Y so pointer lock just works),
|
|
1119
|
-
* wheel → wheelDelta. `lockOnClick` requests pointer lock on mousedown
|
|
1120
|
-
* (the FPS pattern).
|
|
1121
|
-
*/
|
|
1122
|
-
padAxesState = {
|
|
1123
|
-
lx: 0,
|
|
1124
|
-
ly: 0,
|
|
1125
|
-
rx: 0,
|
|
1126
|
-
ry: 0
|
|
1127
|
-
};
|
|
1128
|
-
padButtonsDown = 0;
|
|
1129
|
-
/**
|
|
1130
|
-
* Feed one polled gamepad snapshot (standard mapping). Buttons become
|
|
1131
|
-
* codes `Pad0`..`Pad16` in the same space as keys — declare them in input
|
|
1132
|
-
* actions like any key ("jump": ["Space", "Pad0"]). Axes land in
|
|
1133
|
-
* `padAxes()` with a deadzone. Pass null when no pad is connected.
|
|
1134
|
-
*/
|
|
1135
|
-
pollGamepad(pad) {
|
|
1136
|
-
const dead = .15;
|
|
1137
|
-
const shape = (v) => Math.abs(v) < dead ? 0 : v;
|
|
1138
|
-
if (!pad) {
|
|
1139
|
-
for (let i = 0; i < 17; i++) if (this.padButtonsDown & 1 << i) this.handleKey(`Pad${i}`, false);
|
|
1140
|
-
this.padButtonsDown = 0;
|
|
1141
|
-
this.padAxesState.lx = 0;
|
|
1142
|
-
this.padAxesState.ly = 0;
|
|
1143
|
-
this.padAxesState.rx = 0;
|
|
1144
|
-
this.padAxesState.ry = 0;
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
for (let i = 0; i < Math.min(17, pad.buttons.length); i++) {
|
|
1148
|
-
const down = pad.buttons[i]?.pressed === true;
|
|
1149
|
-
if (down !== ((this.padButtonsDown & 1 << i) !== 0)) {
|
|
1150
|
-
this.handleKey(`Pad${i}`, down);
|
|
1151
|
-
this.padButtonsDown = down ? this.padButtonsDown | 1 << i : this.padButtonsDown & ~(1 << i);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
this.padAxesState.lx = shape(pad.axes[0] ?? 0);
|
|
1155
|
-
this.padAxesState.ly = shape(pad.axes[1] ?? 0);
|
|
1156
|
-
this.padAxesState.rx = shape(pad.axes[2] ?? 0);
|
|
1157
|
-
this.padAxesState.ry = shape(pad.axes[3] ?? 0);
|
|
1158
|
-
}
|
|
1159
|
-
/** Deadzoned analog sticks: 0 = left {x,y}, 1 = right. */
|
|
1160
|
-
padAxes(stick = 0) {
|
|
1161
|
-
return stick === 0 ? {
|
|
1162
|
-
x: this.padAxesState.lx,
|
|
1163
|
-
y: this.padAxesState.ly
|
|
1164
|
-
} : {
|
|
1165
|
-
x: this.padAxesState.rx,
|
|
1166
|
-
y: this.padAxesState.ry
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Poll the browser Gamepad API every frame (the first connected pad).
|
|
1171
|
-
* Wire once at boot: `engine.input.attachGamepad(engine)`. Headless no-op.
|
|
1172
|
-
*/
|
|
1173
|
-
attachGamepad(engine) {
|
|
1174
|
-
if (typeof navigator === "undefined" || typeof navigator.getGamepads !== "function") return () => {};
|
|
1175
|
-
const disconnect = engine.updated.connect(() => {
|
|
1176
|
-
const pads = navigator.getGamepads();
|
|
1177
|
-
let pad = null;
|
|
1178
|
-
for (const p of pads) if (p?.connected) {
|
|
1179
|
-
pad = p;
|
|
1180
|
-
break;
|
|
1181
|
-
}
|
|
1182
|
-
this.pollGamepad(pad);
|
|
1183
|
-
});
|
|
1184
|
-
const prev = this.detach;
|
|
1185
|
-
this.detach = () => {
|
|
1186
|
-
prev?.();
|
|
1187
|
-
disconnect();
|
|
1188
|
-
};
|
|
1189
|
-
return disconnect;
|
|
1190
|
-
}
|
|
1191
|
-
attachPointer(target, opts) {
|
|
1192
|
-
let buttonsHeld = 0;
|
|
1193
|
-
const onDown = (e) => {
|
|
1194
|
-
buttonsHeld++;
|
|
1195
|
-
this.handleMouseButton(e.button, true);
|
|
1196
|
-
if (opts?.lockOnClick && document.pointerLockElement !== target) target.requestPointerLock?.();
|
|
1197
|
-
};
|
|
1198
|
-
const onUp = (e) => {
|
|
1199
|
-
buttonsHeld = Math.max(0, buttonsHeld - 1);
|
|
1200
|
-
this.handleMouseButton(e.button, false);
|
|
1201
|
-
};
|
|
1202
|
-
const onMove = (e) => {
|
|
1203
|
-
if (document.pointerLockElement === target || buttonsHeld > 0) this.handlePointerMove(e.movementX, e.movementY);
|
|
1204
|
-
};
|
|
1205
|
-
const onWheel = (e) => {
|
|
1206
|
-
e.preventDefault();
|
|
1207
|
-
this.handleWheel(e.deltaY);
|
|
1208
|
-
};
|
|
1209
|
-
const onContext = (e) => e.preventDefault();
|
|
1210
|
-
target.addEventListener("mousedown", onDown);
|
|
1211
|
-
window.addEventListener("mouseup", onUp);
|
|
1212
|
-
window.addEventListener("mousemove", onMove);
|
|
1213
|
-
target.addEventListener("wheel", onWheel, { passive: false });
|
|
1214
|
-
target.addEventListener("contextmenu", onContext);
|
|
1215
|
-
const detach = () => {
|
|
1216
|
-
target.removeEventListener("mousedown", onDown);
|
|
1217
|
-
window.removeEventListener("mouseup", onUp);
|
|
1218
|
-
window.removeEventListener("mousemove", onMove);
|
|
1219
|
-
target.removeEventListener("wheel", onWheel);
|
|
1220
|
-
target.removeEventListener("contextmenu", onContext);
|
|
1221
|
-
};
|
|
1222
|
-
const prev = this.detach;
|
|
1223
|
-
this.detach = () => {
|
|
1224
|
-
prev?.();
|
|
1225
|
-
detach();
|
|
1226
|
-
};
|
|
1227
|
-
return detach;
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Wire browser keyboard events. Returns (and chains into dispose()) a detach
|
|
1231
|
-
* function.
|
|
1232
|
-
*
|
|
1233
|
-
* By default the browser default is prevented for keys BOUND to a declared
|
|
1234
|
-
* action — an embedded game must not scroll its host page on Space/arrows.
|
|
1235
|
-
* Keys typed into editable elements (inputs, textareas, contenteditable)
|
|
1236
|
-
* are ignored entirely so DOM UI overlays keep working.
|
|
1237
|
-
*/
|
|
1238
|
-
attachKeyboard(target, opts) {
|
|
1239
|
-
const mode = opts?.preventDefault ?? "bound";
|
|
1240
|
-
const onDown = (e) => {
|
|
1241
|
-
if (isEditableTarget(e.target)) return;
|
|
1242
|
-
if ((e.ctrlKey || e.metaKey) && !e.code.startsWith("Control") && !e.code.startsWith("Meta")) return;
|
|
1243
|
-
if (mode === "bound" && this.codeIsBound(e.code)) e.preventDefault?.();
|
|
1244
|
-
if (!e.repeat) this.handleKey(e.code, true);
|
|
1245
|
-
};
|
|
1246
|
-
const onUp = (e) => {
|
|
1247
|
-
if (isEditableTarget(e.target)) return;
|
|
1248
|
-
this.handleKey(e.code, false);
|
|
1249
|
-
};
|
|
1250
|
-
target.addEventListener("keydown", onDown);
|
|
1251
|
-
target.addEventListener("keyup", onUp);
|
|
1252
|
-
const detach = () => {
|
|
1253
|
-
target.removeEventListener("keydown", onDown);
|
|
1254
|
-
target.removeEventListener("keyup", onUp);
|
|
1255
|
-
};
|
|
1256
|
-
const prev = this.detach;
|
|
1257
|
-
this.detach = () => {
|
|
1258
|
-
prev?.();
|
|
1259
|
-
detach();
|
|
1260
|
-
};
|
|
1261
|
-
return detach;
|
|
1262
|
-
}
|
|
1263
|
-
/** Whether any declared action binds this key code. */
|
|
1264
|
-
codeIsBound(code) {
|
|
1265
|
-
for (const action of this.actions.values()) if (action.type === "button") {
|
|
1266
|
-
if (action.keys.includes(code)) return true;
|
|
1267
|
-
} else {
|
|
1268
|
-
const k = action.keys;
|
|
1269
|
-
if (k.up.includes(code) || k.down.includes(code) || k.left.includes(code) || k.right.includes(code)) return true;
|
|
1270
|
-
}
|
|
1271
|
-
return false;
|
|
1272
|
-
}
|
|
1273
|
-
dispose() {
|
|
1274
|
-
this.detach?.();
|
|
1275
|
-
this.detach = null;
|
|
1276
|
-
}
|
|
1277
|
-
isPressed(action) {
|
|
1278
|
-
return this.button(action).keys.some((k) => this.down.has(k)) || this.injectedDown.has(action);
|
|
1279
|
-
}
|
|
1280
|
-
justPressed(action) {
|
|
1281
|
-
return this.button(action).keys.some((k) => this.pressedEdge.has(k)) || this.injectedPressed.has(action);
|
|
1282
|
-
}
|
|
1283
|
-
justReleased(action) {
|
|
1284
|
-
return this.button(action).keys.some((k) => this.releasedEdge.has(k)) || this.injectedReleased.has(action);
|
|
1285
|
-
}
|
|
1286
|
-
/** Normalized direction for a vector2 action (y-down: up = -y). */
|
|
1287
|
-
getVector(action) {
|
|
1288
|
-
const def = this.get(action);
|
|
1289
|
-
if (def.type !== "vector2") throw new IncantoError("BAD_FORMAT", `Input action '${action}' is a button — use isPressed/justPressed, not getVector.`);
|
|
1290
|
-
const active = (codes) => codes.some((k) => this.down.has(k)) ? 1 : 0;
|
|
1291
|
-
let x = active(def.keys.right) - active(def.keys.left);
|
|
1292
|
-
let y = active(def.keys.down) - active(def.keys.up);
|
|
1293
|
-
const injected = this.injectedVectors.get(action);
|
|
1294
|
-
if (injected) {
|
|
1295
|
-
x += injected.x;
|
|
1296
|
-
y += injected.y;
|
|
1297
|
-
}
|
|
1298
|
-
const len = Math.hypot(x, y);
|
|
1299
|
-
if (len > 1) {
|
|
1300
|
-
x /= len;
|
|
1301
|
-
y /= len;
|
|
1302
|
-
}
|
|
1303
|
-
return {
|
|
1304
|
-
x,
|
|
1305
|
-
y
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
/** Consume one-frame edges. The Engine calls this at the end of every tick. */
|
|
1309
|
-
endFrame() {
|
|
1310
|
-
this.pressedEdge.clear();
|
|
1311
|
-
this.releasedEdge.clear();
|
|
1312
|
-
this.injectedPressed.clear();
|
|
1313
|
-
this.injectedReleased.clear();
|
|
1314
|
-
}
|
|
1315
|
-
/**
|
|
1316
|
-
* Drop all action declarations and injected action state (physical key
|
|
1317
|
-
* state is kept). The Engine calls this on setScene so keybinds never
|
|
1318
|
-
* bleed between scenes.
|
|
1319
|
-
*/
|
|
1320
|
-
clear() {
|
|
1321
|
-
this.actions.clear();
|
|
1322
|
-
this.pressedEdge.clear();
|
|
1323
|
-
this.releasedEdge.clear();
|
|
1324
|
-
this.clearInjected();
|
|
1325
|
-
}
|
|
1326
|
-
get(action) {
|
|
1327
|
-
const def = this.actions.get(action);
|
|
1328
|
-
if (!def) throw new IncantoError("BAD_FORMAT", `Unknown input action '${action}'. Declared actions: [${[...this.actions.keys()].join(", ")}].`);
|
|
1329
|
-
return def;
|
|
1330
|
-
}
|
|
1331
|
-
button(action) {
|
|
1332
|
-
const def = this.get(action);
|
|
1333
|
-
if (def.type !== "button") throw new IncantoError("BAD_FORMAT", `Input action '${action}' is a vector2 — use getVector, not button queries.`);
|
|
1334
|
-
return def;
|
|
1335
|
-
}
|
|
1336
|
-
};
|
|
1337
|
-
//#endregion
|
|
1338
963
|
//#region src/core/log.ts
|
|
1339
964
|
/**
|
|
1340
965
|
* The engine's log channel (`engine.log`, `this.log` in a Behavior): a ring
|
|
@@ -1502,7 +1127,57 @@ var Engine = class {
|
|
|
1502
1127
|
}
|
|
1503
1128
|
start() {
|
|
1504
1129
|
if (this.disposeScheduler) return;
|
|
1505
|
-
this.disposeScheduler = this.scheduler((now) =>
|
|
1130
|
+
this.disposeScheduler = this.scheduler((now) => {
|
|
1131
|
+
try {
|
|
1132
|
+
this.tick(now);
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
this.reportFrameError(error);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* A frame threw somewhere outside a single node (a renderer, physics, a
|
|
1140
|
+
* signal handler). Report the first one and count the rest: a broken frame
|
|
1141
|
+
* repeats sixty times a second, and sixty identical traces a second is how
|
|
1142
|
+
* you lose the first one.
|
|
1143
|
+
*/
|
|
1144
|
+
reportFrameError(error) {
|
|
1145
|
+
this.errorCount += 1;
|
|
1146
|
+
const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
1147
|
+
if (text === this.lastFrameError) return;
|
|
1148
|
+
this.lastFrameError = text;
|
|
1149
|
+
this.log.error("[incanto] a frame threw — the loop keeps running:", error);
|
|
1150
|
+
console.error("[incanto] a frame threw — the loop keeps running:", error);
|
|
1151
|
+
}
|
|
1152
|
+
/** @internal A node quarantined itself; count it for `stats().errors`. */
|
|
1153
|
+
_nodeErrored(_node) {
|
|
1154
|
+
this.errorCount += 1;
|
|
1155
|
+
}
|
|
1156
|
+
errorCount = 0;
|
|
1157
|
+
lastFrameError = null;
|
|
1158
|
+
/**
|
|
1159
|
+
* Every node with something quarantined — its own update, its script, or
|
|
1160
|
+
* both — so a tool can list them and put them back. Walks on demand; there is
|
|
1161
|
+
* no per-frame bookkeeping for this.
|
|
1162
|
+
*/
|
|
1163
|
+
erroredNodes() {
|
|
1164
|
+
const out = [];
|
|
1165
|
+
const walk = (node) => {
|
|
1166
|
+
if (node.errored || node.behaviorErrored) out.push(node);
|
|
1167
|
+
for (const child of node.children) walk(child);
|
|
1168
|
+
};
|
|
1169
|
+
if (this._scene?.root) walk(this._scene.root);
|
|
1170
|
+
return out;
|
|
1171
|
+
}
|
|
1172
|
+
/** Un-skip every quarantined node — the "try again" after you fix the code. */
|
|
1173
|
+
resumeErroredNodes() {
|
|
1174
|
+
const nodes = this.erroredNodes();
|
|
1175
|
+
for (const node of nodes) {
|
|
1176
|
+
node.errored = false;
|
|
1177
|
+
node.behaviorErrored = false;
|
|
1178
|
+
}
|
|
1179
|
+
this.lastFrameError = null;
|
|
1180
|
+
return nodes.length;
|
|
1506
1181
|
}
|
|
1507
1182
|
stop() {
|
|
1508
1183
|
this.disposeScheduler?.();
|
|
@@ -1522,7 +1197,8 @@ var Engine = class {
|
|
|
1522
1197
|
fps: this.frameStats.fps(),
|
|
1523
1198
|
frameMs: this.frameStats.frameMs(),
|
|
1524
1199
|
nodes: this._scene?.tree.root ? countNodes(this._scene.tree.root) : 0,
|
|
1525
|
-
running: this.disposeScheduler !== null
|
|
1200
|
+
running: this.disposeScheduler !== null,
|
|
1201
|
+
errors: this.errorCount
|
|
1526
1202
|
};
|
|
1527
1203
|
}
|
|
1528
1204
|
/**
|
|
@@ -2370,4 +2046,4 @@ function registerCoreNodes() {
|
|
|
2370
2046
|
registerNode(UiDialogue);
|
|
2371
2047
|
}
|
|
2372
2048
|
//#endregion
|
|
2373
|
-
export {
|
|
2049
|
+
export { fadeGain as C, crossfadeGains as S, SFX_PRESETS as _, HudLayer as a, MusicManager as b, UiText as c, LogManager as d, SfxEngine as f, spatialPan as g, spatialGain as h, UiDialogue as i, AudioPlayer as l, ROLLOFF_MODELS as m, Timer as n, UiBanner as o, isAudioContextAvailable as p, UiButton as r, UiBar as s, registerCoreNodes as t, Engine as u, SFX_PRESET_NAMES as v, AudioBuses as w, WebAudioMusicBackend as x, synthSfx as y };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { f as Node } from "./loader-
|
|
2
|
-
import { t as registerCoreNodes } from "./register-
|
|
1
|
+
import { f as Node } from "./loader-BlaRQGaA.js";
|
|
2
|
+
import { t as registerCoreNodes } from "./register-Dzkd6-os.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
|
-
import { i as getNodeSchema, l as registerNode } from "./registry-
|
|
6
|
-
import { r as effectiveOrder } from "./duplicate-
|
|
5
|
+
import { i as getNodeSchema, l as registerNode } from "./registry-CJdGpT2V.js";
|
|
6
|
+
import { r as effectiveOrder } from "./duplicate-KRPtUtzl.js";
|
|
7
7
|
import { i as applyParticlePreset, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
|
|
8
8
|
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry } from "three";
|
|
9
9
|
//#region src/2d/physics/collider-validate.ts
|
|
@@ -8,7 +8,11 @@ import { r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
|
8
8
|
* - non-empty default arrays are FIXED templates: same length, element-wise match
|
|
9
9
|
* (empty default arrays are unconstrained — variable-length lists)
|
|
10
10
|
*/
|
|
11
|
-
function validatePropValue(value, def, at) {
|
|
11
|
+
function validatePropValue(value, def, at, options) {
|
|
12
|
+
if (options && typeof value === "string" && !options.includes(value)) throw new IncantoError("PROP_TYPE_MISMATCH", `Prop '${at}' is ${JSON.stringify(value)}, which is not one of: ${options.map((o) => JSON.stringify(o)).join(", ")}.`, {
|
|
13
|
+
prop: at,
|
|
14
|
+
validOptions: [...options]
|
|
15
|
+
});
|
|
12
16
|
const expected = jsonKind(def);
|
|
13
17
|
if (expected === "null") return;
|
|
14
18
|
const actual = jsonKind(value);
|
|
@@ -114,7 +118,7 @@ function applySchemaProps(target, schema, props, label, aliases = {}) {
|
|
|
114
118
|
validOptions: valid
|
|
115
119
|
});
|
|
116
120
|
}
|
|
117
|
-
validatePropValue(value, def.default, `${label}.${key}
|
|
121
|
+
validatePropValue(value, def.default, `${label}.${key}`, def.options);
|
|
118
122
|
target[key] = jsonClone(value);
|
|
119
123
|
}
|
|
120
124
|
}
|