incanto 0.7.0 → 0.8.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.
Files changed (48) hide show
  1. package/bin/incanto-check.mjs +6 -4
  2. package/dist/2d.d.ts +93 -3
  3. package/dist/2d.js +4 -4
  4. package/dist/3d.d.ts +45 -4
  5. package/dist/3d.js +4 -4
  6. package/dist/{audio-player-VSjk_vG8.d.ts → audio-player-Dyjg5k92.d.ts} +1 -1
  7. package/dist/audit-C6rMyict.js +58 -0
  8. package/dist/{behavior-e3kAmPGC.d.ts → behavior-Bod1AyJS.d.ts} +211 -174
  9. package/dist/{create-game-CEFoFN8d.js → create-game-66c4iYJE.js} +76 -6
  10. package/dist/{create-game-X5iRu6pf.js → create-game-BNDSbhy-.js} +88 -6
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/debug.js +3 -0
  13. package/dist/{errors-BMFaY68Q.d.ts → errors-1dXlIwoR.d.ts} +7 -1
  14. package/dist/{gameplay-CDFgSG6z.js → gameplay-DPMgZk9W.js} +108 -1
  15. package/dist/gameplay.d.ts +44 -3
  16. package/dist/gameplay.js +2 -2
  17. package/dist/index.d.ts +151 -9
  18. package/dist/index.js +189 -4
  19. package/dist/{loader-y_X4zYO0.d.ts → loader-BcUDfNHn.d.ts} +1 -1
  20. package/dist/net.d.ts +46 -2
  21. package/dist/net.js +54 -2
  22. package/dist/particle-sim-CyUU7HVU.js +281 -0
  23. package/dist/{physics-2d-DmQ540uR.js → physics-2d-q2N362ph.js} +45 -4
  24. package/dist/{physics-3d-CDjuhtt_.js → physics-3d-CvHr31d4.js} +1 -1
  25. package/dist/react.d.ts +1 -1
  26. package/dist/react.js +1 -1
  27. package/dist/{register-CscQqB7V.js → register-Cbs7QxoV.js} +139 -4
  28. package/dist/{register-ClKnoILk.js → register-Cqjxrfq0.js} +254 -4
  29. package/dist/{register-C35HDZpm.js → register-CxmuI9FL.js} +1 -1
  30. package/dist/{particle-sim-CrTE7c02.js → register-DRPIjrKG.js} +1185 -278
  31. package/dist/test.d.ts +4 -10
  32. package/dist/test.js +11 -16
  33. package/editor/assets/{agent8-1WJU-yXr.js → agent8-B8QnWCeP.js} +1 -1
  34. package/editor/assets/{index-DzYFgZbl.js → index-CVhnNkb3.js} +81 -81
  35. package/editor/index.html +1 -1
  36. package/package.json +1 -1
  37. package/schemas/scene.schema.json +427 -0
  38. package/skills/incanto-3d-character.md +14 -0
  39. package/skills/incanto-audio.md +23 -0
  40. package/skills/incanto-building-2d-games.md +31 -0
  41. package/skills/incanto-building-3d-games.md +21 -0
  42. package/skills/incanto-gameplay-behaviors.md +28 -0
  43. package/skills/incanto-hud.md +24 -0
  44. package/skills/incanto-multiplayer.md +27 -0
  45. package/skills/incanto-node-reference.md +73 -0
  46. package/skills/incanto-physics-and-input.md +9 -0
  47. package/skills/incanto-verifying-your-game.md +40 -0
  48. package/dist/register-Dl_ixIJe.js +0 -834
package/dist/index.js CHANGED
@@ -1,13 +1,128 @@
1
1
  import { _ as registerBehavior, a as SCENE_FORMAT, c as SceneTree, d as resolveConstants, f as Node, g as getBehavior, h as clearBehaviors, i as serializeNode, l as CONST_REF_KEY, m as Behavior, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as registeredBehaviors, y as Signal } from "./loader-B4OEXDZ8.js";
2
- import { a as ORDER_GROUP_BASE, c as LogManager, d as isAudioContextAvailable, f as MusicManager, g as AudioBuses, h as fadeGain, i as applyParticlePreset, l as InputMap, m as crossfadeGains, n as PARTICLE_PRESETS, o as effectiveOrder, p as WebAudioMusicBackend, r as PARTICLE_PRESET_NAMES, s as Engine, t as ParticleSim, u as SfxEngine } from "./particle-sim-CrTE7c02.js";
2
+ import { C as crossfadeGains, S as WebAudioMusicBackend, T as AudioBuses, _ as spatialPan, a as HudLayer, b as synthSfx, c as UiText, d as LogManager, f as InputMap, g as spatialGain, h as ROLLOFF_MODELS, i as UiDialogue, l as AudioPlayer, m as isAudioContextAvailable, n as Timer, o as UiBanner, p as SfxEngine, r as UiButton, s as UiBar, t as registerCoreNodes, u as Engine, v as SFX_PRESETS, w as fadeGain, x as MusicManager, y as SFX_PRESET_NAMES } from "./register-DRPIjrKG.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
- import { a as UiBar, c as ROLLOFF_MODELS, d as SFX_PRESETS, f as SFX_PRESET_NAMES, i as UiBanner, l as spatialGain, n as Timer, o as UiText, p as synthSfx, r as HudLayer, s as AudioPlayer, t as registerCoreNodes, u as spatialPan } from "./register-Dl_ixIJe.js";
5
+ import { t as auditScene } from "./audit-C6rMyict.js";
6
6
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
7
7
  import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-BVJ2HbCn.js";
8
8
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
9
+ import { a as ORDER_GROUP_BASE, i as applyParticlePreset, n as PARTICLE_PRESETS, o as effectiveOrder, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-CyUU7HVU.js";
9
10
  import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-031PxtCR.js";
10
11
  import { t as duplicateNode } from "./duplicate-BEvGBtb_.js";
12
+ //#region src/core/pathfinding.ts
13
+ /**
14
+ * A* from `from` to `to` (inclusive cell coords). Returns the cell path
15
+ * INCLUDING both endpoints, or null when unreachable. Straight steps cost
16
+ * 1, diagonals √2; the heuristic is octile (admissible for both modes).
17
+ */
18
+ function findPath(grid, from, to, opts) {
19
+ const [sx, sy] = from;
20
+ const [tx, ty] = to;
21
+ const { width, height } = grid;
22
+ const inBounds = (x, y) => x >= 0 && y >= 0 && x < width && y < height;
23
+ if (!inBounds(sx, sy) || !inBounds(tx, ty)) return null;
24
+ if (grid.solid(sx, sy) || grid.solid(tx, ty)) return null;
25
+ if (sx === tx && sy === ty) return [[sx, sy]];
26
+ const diagonal = opts?.diagonal ?? true;
27
+ const maxExpansions = opts?.maxExpansions ?? 2e4;
28
+ const idx = (x, y) => y * width + x;
29
+ const gScore = new Float64Array(width * height).fill(Number.POSITIVE_INFINITY);
30
+ const cameFrom = new Int32Array(width * height).fill(-1);
31
+ const closed = new Uint8Array(width * height);
32
+ const heap = [];
33
+ const fScore = new Float64Array(width * height).fill(Number.POSITIVE_INFINITY);
34
+ const push = (n) => {
35
+ heap.push(n);
36
+ let i = heap.length - 1;
37
+ while (i > 0) {
38
+ const p = i - 1 >> 1;
39
+ if (fScore[heap[p]] <= fScore[heap[i]]) break;
40
+ const t = heap[p];
41
+ heap[p] = heap[i];
42
+ heap[i] = t;
43
+ i = p;
44
+ }
45
+ };
46
+ const pop = () => {
47
+ const top = heap[0];
48
+ const last = heap.pop();
49
+ if (heap.length > 0) {
50
+ heap[0] = last;
51
+ let i = 0;
52
+ for (;;) {
53
+ const l = i * 2 + 1;
54
+ const r = l + 1;
55
+ let m = i;
56
+ if (l < heap.length && fScore[heap[l]] < fScore[heap[m]]) m = l;
57
+ if (r < heap.length && fScore[heap[r]] < fScore[heap[m]]) m = r;
58
+ if (m === i) break;
59
+ const t = heap[m];
60
+ heap[m] = heap[i];
61
+ heap[i] = t;
62
+ i = m;
63
+ }
64
+ }
65
+ return top;
66
+ };
67
+ const SQRT2 = Math.SQRT2;
68
+ const octile = (x, y) => {
69
+ const dx = Math.abs(x - tx);
70
+ const dy = Math.abs(y - ty);
71
+ return dx > dy ? dx - dy + SQRT2 * dy : dy - dx + SQRT2 * dx;
72
+ };
73
+ const start = idx(sx, sy);
74
+ gScore[start] = 0;
75
+ fScore[start] = octile(sx, sy);
76
+ push(start);
77
+ let expansions = 0;
78
+ while (heap.length > 0) {
79
+ if (++expansions > maxExpansions) return null;
80
+ const current = pop();
81
+ if (closed[current]) continue;
82
+ closed[current] = 1;
83
+ const cx = current % width;
84
+ const cy = current / width | 0;
85
+ if (cx === tx && cy === ty) {
86
+ const path = [];
87
+ let n = current;
88
+ while (n !== -1) {
89
+ path.push([n % width, n / width | 0]);
90
+ n = cameFrom[n];
91
+ }
92
+ path.reverse();
93
+ return path;
94
+ }
95
+ for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
96
+ if (dx === 0 && dy === 0) continue;
97
+ const isDiag = dx !== 0 && dy !== 0;
98
+ if (isDiag && !diagonal) continue;
99
+ const nx = cx + dx;
100
+ const ny = cy + dy;
101
+ if (!inBounds(nx, ny) || grid.solid(nx, ny)) continue;
102
+ if (isDiag && (grid.solid(cx + dx, cy) || grid.solid(cx, cy + dy))) continue;
103
+ const n = idx(nx, ny);
104
+ if (closed[n]) continue;
105
+ const tentative = gScore[current] + (isDiag ? SQRT2 : 1);
106
+ if (tentative < gScore[n]) {
107
+ gScore[n] = tentative;
108
+ fScore[n] = tentative + octile(nx, ny);
109
+ cameFrom[n] = current;
110
+ push(n);
111
+ }
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+ /** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
117
+ function gridFromRows(rows) {
118
+ const height = rows.length;
119
+ return {
120
+ width: rows[0]?.length ?? 0,
121
+ height,
122
+ solid: (x, y) => (rows[y]?.[x] ?? "#") === "#"
123
+ };
124
+ }
125
+ //#endregion
11
126
  //#region src/core/preload.ts
12
127
  /**
13
128
  * Warm the HTTP cache for a list of asset urls with a progress callback —
@@ -40,6 +155,76 @@ function assetUrls(assets) {
40
155
  return Object.values(assets ?? {}).map((a) => a.url).filter((u) => typeof u === "string" && !u.startsWith("data:"));
41
156
  }
42
157
  //#endregion
158
+ //#region src/core/replay.ts
159
+ const RECORDED_METHODS = [
160
+ "handleKey",
161
+ "handleMouseButton",
162
+ "handlePointerMove",
163
+ "handleWheel",
164
+ "pressAction",
165
+ "releaseAction"
166
+ ];
167
+ /**
168
+ * Tap the engine's input entry points and tick clock. Recording starts at
169
+ * the CURRENT moment — start it before the first tick for full-run replays.
170
+ */
171
+ function startRecording(engine) {
172
+ const ticks = [];
173
+ const events = [];
174
+ const input = engine.input;
175
+ const originals = /* @__PURE__ */ new Map();
176
+ for (const name of RECORDED_METHODS) {
177
+ const original = input[name];
178
+ originals.set(name, original);
179
+ input[name] = (...args) => {
180
+ events.push([
181
+ ticks.length,
182
+ name,
183
+ ...args
184
+ ]);
185
+ return original.apply(engine.input, args);
186
+ };
187
+ }
188
+ const originalTick = engine.tick.bind(engine);
189
+ const engineAny = engine;
190
+ engineAny.tick = (nowMs) => {
191
+ ticks.push(nowMs);
192
+ originalTick(nowMs);
193
+ };
194
+ let stopped = false;
195
+ return { stop() {
196
+ if (!stopped) {
197
+ stopped = true;
198
+ for (const [name, fn] of originals) input[name] = fn;
199
+ engineAny.tick = originalTick;
200
+ }
201
+ return {
202
+ replay: 1,
203
+ ticks,
204
+ events
205
+ };
206
+ } };
207
+ }
208
+ /**
209
+ * Drive a fresh engine through a recording: for each recorded tick, feed
210
+ * that tick's input events first, then tick with the recorded timestamp.
211
+ * `onTick(index)` lets tests assert mid-run state.
212
+ */
213
+ function replay(engine, recording, opts) {
214
+ const input = engine.input;
215
+ let cursor = 0;
216
+ for (let i = 0; i < recording.ticks.length; i++) {
217
+ while (cursor < recording.events.length && recording.events[cursor][0] === i) {
218
+ const [, method, ...args] = recording.events[cursor];
219
+ const fn = input[method];
220
+ if (typeof fn === "function") fn.apply(engine.input, args);
221
+ cursor += 1;
222
+ }
223
+ engine.tick(recording.ticks[i]);
224
+ opts?.onTick?.(i);
225
+ }
226
+ }
227
+ //#endregion
43
228
  //#region src/core/save.ts
44
229
  const memoryStores = /* @__PURE__ */ new Map();
45
230
  function createSaveStore(namespace) {
@@ -110,6 +295,6 @@ function newUid() {
110
295
  //#endregion
111
296
  //#region src/index.ts
112
297
  /** Engine version. Kept in sync with package.json by the release pipeline. */
113
- const VERSION = "0.7.0";
298
+ const VERSION = "0.8.0";
114
299
  //#endregion
115
- export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
300
+ export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
@@ -1,5 +1,5 @@
1
1
  import { i as SceneJson } from "./schema-CcoWb32N.js";
2
- import { v as Engine, w as Scene } from "./behavior-e3kAmPGC.js";
2
+ import { v as Engine, w as Scene } from "./behavior-Bod1AyJS.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
1
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
2
- import { T as Node, et as Signal, l as PropSchema, v as Engine } from "./behavior-e3kAmPGC.js";
2
+ import { $ as Node, l as PropSchema, tt as Signal, v as Engine } from "./behavior-Bod1AyJS.js";
3
3
 
4
4
  //#region src/net/types.d.ts
5
5
  type Unsubscribe = () => void;
@@ -424,4 +424,48 @@ declare class NetworkSpawner extends Node {
424
424
  */
425
425
  declare function registerNodesNet(): void;
426
426
  //#endregion
427
- export { type CollectionOptions, LocalGameServer, type LocalGameServerOptions, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, type NetworkManagerOptions, NetworkSpawner, type NetworkTransport, type ServerClass, type Unsubscribe, applySyncPatch, createAgent8Server, createLocalGameServer, registerNodesNet };
427
+ //#region src/net/split-screen.d.ts
428
+ interface SplitScreenPlayer {
429
+ account: string;
430
+ engine: Engine;
431
+ manager: NetworkManager;
432
+ }
433
+ interface SplitScreenOptions extends LocalGameServerOptions {
434
+ /** The shared scene JSON every panel loads. */
435
+ scene: SceneJson;
436
+ /** Player accounts, one panel each (default ['p1', 'p2']). */
437
+ accounts?: string[];
438
+ /** Called per player to finish wiring (renderer, input, HUD). */
439
+ setup: (player: SplitScreenPlayer, index: number) => void | Promise<void>;
440
+ /** name → scene JSON registered on every panel's NetworkManager. */
441
+ scenes?: Record<string, SceneJson>;
442
+ /** Engine seed base (each panel gets seed + index). */
443
+ seed?: number;
444
+ }
445
+ /**
446
+ * N local clients on ONE page sharing ONE in-memory game server — the
447
+ * split-screen preview harness three examples were hand-wiring (~40 lines
448
+ * each). Wires: LocalGameServer + per-player Engine/scene/NetworkManager +
449
+ * the server tick driven from the first panel's clock.
450
+ *
451
+ * const { players, server } = await createSplitScreen({
452
+ * scene: gameJson,
453
+ * server: MyServerClass,
454
+ * scenes: { 'remote-player': remoteJson },
455
+ * setup: ({ engine }, i) => {
456
+ * new Renderer2D({ canvas: canvases[i], engine });
457
+ * engine.input.attachKeyboard(window);
458
+ * engine.start();
459
+ * },
460
+ * });
461
+ *
462
+ * Going live stays the one-line story: replace the transport with
463
+ * `createAgent8Server()` and boot ONE client per browser.
464
+ */
465
+ declare function createSplitScreen(opts: SplitScreenOptions): Promise<{
466
+ server: ReturnType<typeof createLocalGameServer>;
467
+ players: SplitScreenPlayer[];
468
+ dispose(): void;
469
+ }>;
470
+ //#endregion
471
+ export { type CollectionOptions, LocalGameServer, type LocalGameServerOptions, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, type NetworkManagerOptions, NetworkSpawner, type NetworkTransport, type ServerClass, type SplitScreenOptions, type SplitScreenPlayer, type Unsubscribe, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
package/dist/net.js CHANGED
@@ -1,7 +1,9 @@
1
+ import { n as loadScene } from "./loader-B4OEXDZ8.js";
2
+ import { u as Engine } from "./register-DRPIjrKG.js";
1
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
4
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
3
5
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
4
- import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-C35HDZpm.js";
6
+ import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-CxmuI9FL.js";
5
7
  //#region src/net/loopback.ts
6
8
  /**
7
9
  * In-memory multiplayer hub implementing the SAME kernel contract as
@@ -769,4 +771,54 @@ var LocalGameServerTransport = class {
769
771
  }
770
772
  };
771
773
  //#endregion
772
- export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, registerNodesNet };
774
+ //#region src/net/split-screen.ts
775
+ /**
776
+ * N local clients on ONE page sharing ONE in-memory game server — the
777
+ * split-screen preview harness three examples were hand-wiring (~40 lines
778
+ * each). Wires: LocalGameServer + per-player Engine/scene/NetworkManager +
779
+ * the server tick driven from the first panel's clock.
780
+ *
781
+ * const { players, server } = await createSplitScreen({
782
+ * scene: gameJson,
783
+ * server: MyServerClass,
784
+ * scenes: { 'remote-player': remoteJson },
785
+ * setup: ({ engine }, i) => {
786
+ * new Renderer2D({ canvas: canvases[i], engine });
787
+ * engine.input.attachKeyboard(window);
788
+ * engine.start();
789
+ * },
790
+ * });
791
+ *
792
+ * Going live stays the one-line story: replace the transport with
793
+ * `createAgent8Server()` and boot ONE client per browser.
794
+ */
795
+ async function createSplitScreen(opts) {
796
+ const server = createLocalGameServer(opts);
797
+ const accounts = opts.accounts ?? ["p1", "p2"];
798
+ const players = [];
799
+ for (let i = 0; i < accounts.length; i++) {
800
+ const account = accounts[i];
801
+ const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + i } : {});
802
+ engine.setScene(loadScene(jsonClone(opts.scene)));
803
+ const manager = await NetworkManager.create(engine, { transport: server.createClient(account) });
804
+ for (const [name, json] of Object.entries(opts.scenes ?? {})) manager.registerScene(name, json);
805
+ const player = {
806
+ account,
807
+ engine,
808
+ manager
809
+ };
810
+ players.push(player);
811
+ await opts.setup(player, i);
812
+ }
813
+ const disconnect = (players[0]?.engine)?.updated.connect((dt) => void server.tick(Math.min(dt, .05) * 1e3));
814
+ return {
815
+ server,
816
+ players,
817
+ dispose() {
818
+ disconnect?.();
819
+ for (const p of players) p.engine.dispose?.();
820
+ }
821
+ };
822
+ }
823
+ //#endregion
824
+ export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -0,0 +1,281 @@
1
+ import { n as jsonEquals } from "./json-BLk7H2Qa.js";
2
+ //#region src/core/order-groups.ts
3
+ const ORDER_GROUP_BASE = {
4
+ background: -2e3,
5
+ terrain: -1e3,
6
+ default: 0,
7
+ characters: 1e3,
8
+ effects: 2e3,
9
+ overlay: 3e3
10
+ };
11
+ /** base + fine offset (unknown group names fall back to `default`). */
12
+ function effectiveOrder(group, renderOrder) {
13
+ return (ORDER_GROUP_BASE[group] ?? 0) + renderOrder;
14
+ }
15
+ //#endregion
16
+ //#region src/core/particle-presets.ts
17
+ const PARTICLE_PRESETS = {
18
+ fire: {
19
+ rate: 60,
20
+ lifetime: [.4, .9],
21
+ speed: [30, 70],
22
+ directionDeg: -90,
23
+ spreadDeg: 35,
24
+ gravity: [0, -30],
25
+ sizeStart: 14,
26
+ sizeEnd: 3,
27
+ colorStart: "#ffcf5a",
28
+ colorEnd: "#e8401f",
29
+ alphaStart: .9,
30
+ alphaEnd: 0,
31
+ blend: "add"
32
+ },
33
+ smoke: {
34
+ rate: 20,
35
+ lifetime: [1.5, 2.5],
36
+ speed: [10, 25],
37
+ directionDeg: -90,
38
+ spreadDeg: 40,
39
+ gravity: [0, -12],
40
+ sizeStart: 10,
41
+ sizeEnd: 26,
42
+ colorStart: "#8a8a8a",
43
+ colorEnd: "#3a3a3a",
44
+ alphaStart: .35,
45
+ alphaEnd: 0,
46
+ blend: "normal"
47
+ },
48
+ sparks: {
49
+ rate: 80,
50
+ lifetime: [.3, .7],
51
+ speed: [120, 260],
52
+ spreadDeg: 360,
53
+ gravity: [0, 240],
54
+ drag: 1,
55
+ sizeStart: 4,
56
+ sizeEnd: 1,
57
+ colorStart: "#fff3b0",
58
+ colorEnd: "#ff9d2e",
59
+ blend: "add"
60
+ },
61
+ fireworks: {
62
+ rate: 0,
63
+ burst: 120,
64
+ lifetime: [.8, 1.6],
65
+ speed: [80, 240],
66
+ spreadDeg: 360,
67
+ gravity: [0, 90],
68
+ drag: .6,
69
+ sizeStart: 5,
70
+ sizeEnd: 1,
71
+ colorStart: "#9ad8ff",
72
+ colorEnd: "#ff5ad8",
73
+ blend: "add"
74
+ },
75
+ explosion: {
76
+ rate: 0,
77
+ burst: 60,
78
+ lifetime: [.25, .6],
79
+ speed: [150, 420],
80
+ spreadDeg: 360,
81
+ drag: 3,
82
+ sizeStart: 16,
83
+ sizeEnd: 2,
84
+ colorStart: "#ffe08a",
85
+ colorEnd: "#ff3b1f",
86
+ blend: "add"
87
+ },
88
+ flash: {
89
+ rate: 0,
90
+ burst: 8,
91
+ lifetime: [.08, .16],
92
+ speed: [0, 30],
93
+ spreadDeg: 360,
94
+ sizeStart: 64,
95
+ sizeEnd: 96,
96
+ colorStart: "#ffffff",
97
+ colorEnd: "#ffffff",
98
+ alphaStart: 1,
99
+ alphaEnd: 0,
100
+ blend: "add"
101
+ },
102
+ lightning: {
103
+ rate: 90,
104
+ lifetime: [.05, .12],
105
+ speed: [400, 900],
106
+ directionDeg: -90,
107
+ spreadDeg: 14,
108
+ sizeStart: 6,
109
+ sizeEnd: 2,
110
+ colorStart: "#eaf2ff",
111
+ colorEnd: "#7fb4ff",
112
+ blend: "add"
113
+ },
114
+ rain: {
115
+ rate: 160,
116
+ lifetime: [.8, 1.2],
117
+ speed: [300, 420],
118
+ directionDeg: 90,
119
+ spreadDeg: 6,
120
+ sizeStart: 3,
121
+ sizeEnd: 3,
122
+ colorStart: "#9ec8ff",
123
+ colorEnd: "#9ec8ff",
124
+ alphaStart: .5,
125
+ alphaEnd: .2,
126
+ blend: "normal"
127
+ },
128
+ snow: {
129
+ rate: 40,
130
+ lifetime: [3, 5],
131
+ speed: [20, 50],
132
+ directionDeg: 90,
133
+ spreadDeg: 30,
134
+ drag: .2,
135
+ sizeStart: 5,
136
+ sizeEnd: 4,
137
+ colorStart: "#ffffff",
138
+ colorEnd: "#ffffff",
139
+ alphaStart: .9,
140
+ alphaEnd: .6,
141
+ blend: "normal"
142
+ },
143
+ magic: {
144
+ rate: 50,
145
+ lifetime: [.6, 1.4],
146
+ speed: [20, 80],
147
+ spreadDeg: 360,
148
+ gravity: [0, -20],
149
+ sizeStart: 7,
150
+ sizeEnd: 1,
151
+ colorStart: "#9dffe8",
152
+ colorEnd: "#7a5cff",
153
+ blend: "add"
154
+ }
155
+ };
156
+ const PARTICLE_PRESET_NAMES = Object.keys(PARTICLE_PRESETS);
157
+ /**
158
+ * Apply the delta rule across three layers: schema default < preset < the
159
+ * scene's explicit props. A prop still equal to its SCHEMA default is
160
+ * considered "unset" and takes the preset value.
161
+ */
162
+ function applyParticlePreset(target, presetName, schema) {
163
+ if (presetName === "custom") return;
164
+ const preset = PARTICLE_PRESETS[presetName];
165
+ if (!preset) return;
166
+ for (const [key, presetValue] of Object.entries(preset)) {
167
+ const def = schema[key]?.default;
168
+ const current = target[key];
169
+ if (def !== void 0 && jsonEquals(current, def)) target[key] = Array.isArray(presetValue) ? [...presetValue] : presetValue;
170
+ }
171
+ }
172
+ //#endregion
173
+ //#region src/core/particle-sim.ts
174
+ const FIELDS = 9;
175
+ var ParticleSim = class {
176
+ config;
177
+ rng;
178
+ data;
179
+ alive = 0;
180
+ spawnAccumulator = 0;
181
+ everSpawned = false;
182
+ constructor(config, rng) {
183
+ this.config = config;
184
+ this.rng = rng;
185
+ this.data = new Float64Array(Math.max(1, config.maxParticles) * FIELDS);
186
+ }
187
+ /** Change the emission rate LIVE (particles/sec; 0 pauses emission) — lets an
188
+ * emitter toggle on/off at runtime (drift smoke, throttle flames). */
189
+ setRate(rate) {
190
+ this.config.rate = rate;
191
+ if (rate <= 0) this.spawnAccumulator = 0;
192
+ }
193
+ get count() {
194
+ return this.alive;
195
+ }
196
+ /** True when nothing is alive and at least one particle has ever spawned. */
197
+ get done() {
198
+ return this.everSpawned && this.alive === 0;
199
+ }
200
+ /** Spawn n particles immediately (fireworks, explosions, flashes). */
201
+ burst(n) {
202
+ for (let i = 0; i < n; i++) this.spawn();
203
+ }
204
+ update(dt) {
205
+ if (this.config.rate > 0) {
206
+ this.spawnAccumulator += this.config.rate * dt;
207
+ while (this.spawnAccumulator >= 1) {
208
+ this.spawnAccumulator -= 1;
209
+ this.spawn();
210
+ }
211
+ }
212
+ const d = this.data;
213
+ const [gx, gy, gz] = this.config.gravity;
214
+ const damp = this.config.drag > 0 ? Math.exp(-this.config.drag * dt) : 1;
215
+ let i = 0;
216
+ while (i < this.alive) {
217
+ const base = i * FIELDS;
218
+ const age = d[base + 6] + dt;
219
+ if (age >= d[base + 7]) {
220
+ this.kill(i);
221
+ continue;
222
+ }
223
+ d[base + 6] = age;
224
+ d[base + 3] = (d[base + 3] + gx * dt) * damp;
225
+ d[base + 4] = (d[base + 4] + gy * dt) * damp;
226
+ d[base + 5] = (d[base + 5] + gz * dt) * damp;
227
+ d[base + 0] = d[base + 0] + d[base + 3] * dt;
228
+ d[base + 1] = d[base + 1] + d[base + 4] * dt;
229
+ d[base + 2] = d[base + 2] + d[base + 5] * dt;
230
+ i += 1;
231
+ }
232
+ }
233
+ forEach(fn) {
234
+ const d = this.data;
235
+ for (let i = 0; i < this.alive; i++) {
236
+ const base = i * FIELDS;
237
+ const age = d[base + 6];
238
+ const life = d[base + 7];
239
+ fn({
240
+ x: d[base + 0],
241
+ y: d[base + 1],
242
+ z: d[base + 2],
243
+ vx: d[base + 3],
244
+ vy: d[base + 4],
245
+ vz: d[base + 5],
246
+ age,
247
+ life,
248
+ t: life > 0 ? age / life : 1,
249
+ seed: d[base + 8]
250
+ });
251
+ }
252
+ }
253
+ spawn() {
254
+ if (this.alive >= this.config.maxParticles) return;
255
+ const base = this.alive * FIELDS;
256
+ const d = this.data;
257
+ const c = this.config;
258
+ const angle = (c.directionDeg + (this.rng.next() - .5) * c.spreadDeg) * Math.PI / 180;
259
+ const speed = c.speed[0] + this.rng.next() * (c.speed[1] - c.speed[0]);
260
+ const zTilt = c.spreadZ ? (this.rng.next() - .5) * (c.spreadDeg * Math.PI / 180) : 0;
261
+ const planar = Math.cos(zTilt);
262
+ d[base + 0] = 0;
263
+ d[base + 1] = 0;
264
+ d[base + 2] = 0;
265
+ d[base + 3] = Math.cos(angle) * speed * planar;
266
+ d[base + 4] = Math.sin(angle) * speed * planar;
267
+ d[base + 5] = Math.sin(zTilt) * speed;
268
+ d[base + 6] = 0;
269
+ d[base + 7] = c.lifetime[0] + this.rng.next() * (c.lifetime[1] - c.lifetime[0]);
270
+ d[base + 8] = this.rng.next();
271
+ this.alive += 1;
272
+ this.everSpawned = true;
273
+ }
274
+ kill(index) {
275
+ const last = this.alive - 1;
276
+ if (index !== last) this.data.copyWithin(index * FIELDS, last * FIELDS, last * FIELDS + FIELDS);
277
+ this.alive = last;
278
+ }
279
+ };
280
+ //#endregion
281
+ export { ORDER_GROUP_BASE as a, applyParticlePreset as i, PARTICLE_PRESETS as n, effectiveOrder as o, PARTICLE_PRESET_NAMES as r, ParticleSim as t };