incanto 0.30.0 → 0.31.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/2d.d.ts +12 -5
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +47 -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-DuBTv2zI.js → create-game-DqOjMBUS.js} +50 -41
- package/dist/{create-game-5z_QVtLx.js → create-game-z5XaB1p5.js} +16 -8
- 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 +3 -3
- package/dist/index.js +6 -6
- 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.d.ts +2 -7
- package/dist/test.js +43 -14
- package/editor/assets/{agent8-CAp0i5qn.js → agent8-o27_Y1xN.js} +1 -1
- package/editor/assets/{debug-BoEYfbqK.js → debug-DfcWX3uW.js} +3 -2
- package/editor/assets/{index-BO6WU8by.js → index-C9fb5QcT.js} +90 -90
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/skills/incanto-verifying-your-game.md +62 -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
|
}
|
package/dist/test.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { $ as LogEntry, X as Node, n as BehaviorCtor, v as Engine, w as Scene } from "./behavior-D_jMpFh8.js";
|
|
1
2
|
import { c as JsonValue, i as SceneJson } from "./schema-CcoWb32N.js";
|
|
2
|
-
import {
|
|
3
|
-
import { t as LoadSceneOptions } from "./loader-B242FF6N.js";
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-BYBrqTxP.js";
|
|
4
4
|
import { i as auditScene, t as IncantoError } from "./errors-1dXlIwoR.js";
|
|
5
5
|
|
|
6
6
|
//#region src/test/index.d.ts
|
|
@@ -74,11 +74,6 @@ interface ValidationResult {
|
|
|
74
74
|
/** The hard load error, when ok is false — `details` is machine-readable. */
|
|
75
75
|
error?: IncantoError;
|
|
76
76
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Run every hard load-time check headlessly. Unregistered behaviors are
|
|
79
|
-
* stubbed by default (structure-only validation, no TypeScript needed) —
|
|
80
|
-
* pass the real classes via `behaviors` to validate script props too.
|
|
81
|
-
*/
|
|
82
77
|
declare function validateScene(json: unknown, opts?: ValidateSceneOptions): ValidationResult;
|
|
83
78
|
interface RunContext {
|
|
84
79
|
engine: Engine;
|
package/dist/test.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { u as Engine } from "./register-
|
|
1
|
+
import { n as loadScene, y as registerBehavior } from "./loader-BlaRQGaA.js";
|
|
2
|
+
import { u as Engine } from "./register-Dzkd6-os.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import { t as auditScene } from "./audit-
|
|
4
|
+
import { t as auditScene } from "./audit-C4kmDK0o.js";
|
|
5
5
|
import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
6
|
-
import { i as getNodeSchema, s as mergeStaticProps } from "./registry-
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
-
import { t as registerNodes2D } from "./register-
|
|
9
|
-
import {
|
|
10
|
-
import { t as registerNodesNet } from "./register-
|
|
6
|
+
import { i as getNodeSchema, s as mergeStaticProps } from "./registry-CJdGpT2V.js";
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-BftxM_It.js";
|
|
8
|
+
import { t as registerNodes2D } from "./register-tkR_8tWg.js";
|
|
9
|
+
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets--DigHNg4.js";
|
|
10
|
+
import { t as registerNodesNet } from "./register-CUY284Is.js";
|
|
11
11
|
//#region src/test/index.ts
|
|
12
12
|
/**
|
|
13
13
|
* incanto/test — the browserless verification harness.
|
|
@@ -184,10 +184,28 @@ function defaultsFor(typeName) {
|
|
|
184
184
|
* stubbed by default (structure-only validation, no TypeScript needed) —
|
|
185
185
|
* pass the real classes via `behaviors` to validate script props too.
|
|
186
186
|
*/
|
|
187
|
+
/**
|
|
188
|
+
* `environment.preset` is resolved deep inside Renderer3D, which no headless
|
|
189
|
+
* tool reaches — so a typo there passed `incanto-check` and hard-failed at boot.
|
|
190
|
+
* It cannot live in the core loader (core must never import 3d), but it belongs
|
|
191
|
+
* exactly here: the test entry already registers the 3D nodes.
|
|
192
|
+
*/
|
|
193
|
+
function checkEnvironment(json) {
|
|
194
|
+
const env = json?.environment;
|
|
195
|
+
if (!env || typeof env !== "object") return;
|
|
196
|
+
if (env.preset === void 0) return;
|
|
197
|
+
if (typeof env.preset !== "string") throw new IncantoError("BAD_FORMAT", `Scene "environment.preset" must be a string, got ${JSON.stringify(env.preset)}.`);
|
|
198
|
+
try {
|
|
199
|
+
resolveEnvironmentHdri(env);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw new IncantoError("BAD_FORMAT", error instanceof Error ? error.message : String(error));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
187
204
|
function validateScene(json, opts = {}) {
|
|
188
205
|
registerAllNodes();
|
|
189
206
|
for (const [name, ctor] of Object.entries(opts.behaviors ?? {})) registerBehavior(name, ctor, { replace: true });
|
|
190
207
|
try {
|
|
208
|
+
checkEnvironment(json);
|
|
191
209
|
loadScene(json, {
|
|
192
210
|
resolveScene: opts.resolveScene,
|
|
193
211
|
stubMissingBehaviors: !opts.strictBehaviors
|
|
@@ -218,10 +236,10 @@ async function runScript(json, opts) {
|
|
|
218
236
|
engine.setScene(scene);
|
|
219
237
|
const physics = opts.physics ?? "auto";
|
|
220
238
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
221
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
239
|
+
const { enablePhysics2D } = await import("./physics-2d-D9wquBvK.js").then((n) => n.r);
|
|
222
240
|
await enablePhysics2D(engine);
|
|
223
241
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
224
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
242
|
+
const { enablePhysics3D } = await import("./physics-3d-CnPygVGo.js").then((n) => n.r);
|
|
225
243
|
await enablePhysics3D(engine);
|
|
226
244
|
}
|
|
227
245
|
const failures = [];
|
|
@@ -284,6 +302,12 @@ async function runScript(json, opts) {
|
|
|
284
302
|
}
|
|
285
303
|
const finalCapture = captureScene(scene);
|
|
286
304
|
const logs = [...engine.log.entries()];
|
|
305
|
+
const swallowed = engine.stats().errors;
|
|
306
|
+
if (swallowed > 0) failures.push({
|
|
307
|
+
atMs: opts.durationMs,
|
|
308
|
+
label: "engine",
|
|
309
|
+
message: `${swallowed} error(s) were swallowed to keep the loop alive — the logs above name the node, the behavior and the phase`
|
|
310
|
+
});
|
|
287
311
|
engine.dispose();
|
|
288
312
|
const ok = failures.length === 0;
|
|
289
313
|
return {
|
|
@@ -293,9 +317,14 @@ async function runScript(json, opts) {
|
|
|
293
317
|
logs,
|
|
294
318
|
finalCapture,
|
|
295
319
|
describe: () => {
|
|
320
|
+
const engineWarnings = logs.filter((l) => l.level === "warn" || l.level === "error");
|
|
321
|
+
const head = `run ${ok ? "OK" : "FAILED"} — ${opts.durationMs}ms simulated, ${failures.length} failure(s), ${logs.length} log(s)`;
|
|
322
|
+
const fails = failures.map((f) => ` ✗ at ${f.atMs}ms${f.label ? ` [${f.label}]` : ""}: ${f.message}`);
|
|
323
|
+
const noisy = engineWarnings.map((l) => ` ! ${l.level}: ${l.parts.map((p) => typeof p === "string" ? p : String(p)).join(" ")}`);
|
|
296
324
|
return [
|
|
297
|
-
|
|
298
|
-
...
|
|
325
|
+
head,
|
|
326
|
+
...fails,
|
|
327
|
+
...noisy,
|
|
299
328
|
describeCapture(finalCapture)
|
|
300
329
|
].join("\n");
|
|
301
330
|
}
|
|
@@ -323,10 +352,10 @@ async function createPlaySession(json, opts = {}) {
|
|
|
323
352
|
engine.setScene(scene);
|
|
324
353
|
const physics = opts.physics ?? "auto";
|
|
325
354
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
326
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
355
|
+
const { enablePhysics2D } = await import("./physics-2d-D9wquBvK.js").then((n) => n.r);
|
|
327
356
|
await enablePhysics2D(engine);
|
|
328
357
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
329
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
358
|
+
const { enablePhysics3D } = await import("./physics-3d-CnPygVGo.js").then((n) => n.r);
|
|
330
359
|
await enablePhysics3D(engine);
|
|
331
360
|
}
|
|
332
361
|
const stepMs = 1e3 / (opts.fixedHz ?? 60);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./index-
|
|
1
|
+
import{n as e}from"./index-C9fb5QcT.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){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{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}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{t as createAgent8Server};
|