incanto 0.1.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 (138) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +36 -0
  3. package/THIRD-PARTY-NOTICES.md +88 -0
  4. package/assets/audio/attacked.mp3 +0 -0
  5. package/assets/audio/explosion.mp3 +0 -0
  6. package/assets/audio/gold_loot.mp3 +0 -0
  7. package/assets/audio/heal.mp3 +0 -0
  8. package/assets/audio/hit_metal_bang.mp3 +0 -0
  9. package/assets/audio/ice_spear.mp3 +0 -0
  10. package/assets/audio/monster_died.mp3 +0 -0
  11. package/assets/audio/slash.mp3 +0 -0
  12. package/assets/audio/smite.mp3 +0 -0
  13. package/assets/audio/spells_cast.mp3 +0 -0
  14. package/assets/audio/ui_click.wav +0 -0
  15. package/assets/audio/walk.mp3 +0 -0
  16. package/assets/catalog.json +390 -0
  17. package/assets/characters/2dbasic.json +41 -0
  18. package/assets/characters/2dbasic.png +0 -0
  19. package/assets/characters/ghost.json +46 -0
  20. package/assets/characters/ghost.png +0 -0
  21. package/assets/characters/goblin.json +40 -0
  22. package/assets/characters/goblin.png +0 -0
  23. package/assets/characters/medieval-knight.json +41 -0
  24. package/assets/characters/medieval-knight.png +0 -0
  25. package/assets/effects/swoosh.png +0 -0
  26. package/assets/items/box.png +0 -0
  27. package/assets/items/buff_potion.png +0 -0
  28. package/assets/items/coin.png +0 -0
  29. package/assets/items/gem.png +0 -0
  30. package/assets/items/gold.png +0 -0
  31. package/assets/items/hp_potion.png +0 -0
  32. package/assets/items/locked_item_box.png +0 -0
  33. package/assets/items/map.png +0 -0
  34. package/assets/items/resurrection_potion.png +0 -0
  35. package/assets/items/super_box.png +0 -0
  36. package/assets/items/trap.png +0 -0
  37. package/assets/tiles/floor00.jpg +0 -0
  38. package/assets/tiles/minecraft-tiles.png +0 -0
  39. package/assets/tiles/wall00.jpg +0 -0
  40. package/assets/vegetation/ash_color.png +0 -0
  41. package/assets/vegetation/aspen_color.png +0 -0
  42. package/assets/vegetation/bark/birch_color_1k.jpg +0 -0
  43. package/assets/vegetation/bark/birch_normal_1k.jpg +0 -0
  44. package/assets/vegetation/bark/birch_roughness_1k.jpg +0 -0
  45. package/assets/vegetation/bark/oak_color_1k.jpg +0 -0
  46. package/assets/vegetation/bark/oak_normal_1k.jpg +0 -0
  47. package/assets/vegetation/bark/oak_roughness_1k.jpg +0 -0
  48. package/assets/vegetation/bark/pine_color_1k.jpg +0 -0
  49. package/assets/vegetation/bark/pine_normal_1k.jpg +0 -0
  50. package/assets/vegetation/bark/pine_roughness_1k.jpg +0 -0
  51. package/assets/vegetation/ground/dirt_color.jpg +0 -0
  52. package/assets/vegetation/ground/dirt_normal.jpg +0 -0
  53. package/assets/vegetation/ground/grass.jpg +0 -0
  54. package/assets/vegetation/oak_color.png +0 -0
  55. package/assets/vegetation/pine_color.png +0 -0
  56. package/bin/incanto-assets.mjs +107 -0
  57. package/bin/incanto-check.mjs +107 -0
  58. package/bin/incanto-editor.mjs +343 -0
  59. package/bin/incanto-env.mjs +144 -0
  60. package/bin/incanto-model.mjs +296 -0
  61. package/bin/incanto-play.mjs +219 -0
  62. package/bin/incanto-skills.mjs +71 -0
  63. package/dist/2d.d.ts +642 -0
  64. package/dist/2d.js +44 -0
  65. package/dist/3d.d.ts +1860 -0
  66. package/dist/3d.js +5 -0
  67. package/dist/agent8-DzU2fFyH.js +129 -0
  68. package/dist/audio-player-DqUR3XFs.d.ts +110 -0
  69. package/dist/behavior-BAQq7HGM.d.ts +851 -0
  70. package/dist/create-game-BdjpTHrW.js +1725 -0
  71. package/dist/create-game-CZHROKcT.js +527 -0
  72. package/dist/debug-draw-CZmOYjL2.js +13 -0
  73. package/dist/debug.d.ts +66 -0
  74. package/dist/debug.js +658 -0
  75. package/dist/duplicate-DP2WPYom.js +22 -0
  76. package/dist/env.d.ts +430 -0
  77. package/dist/env.js +3152 -0
  78. package/dist/errors-BMFaY68Q.d.ts +33 -0
  79. package/dist/errors-BpWbnbb_.js +13 -0
  80. package/dist/gameplay-Ccruc3Wd.js +1501 -0
  81. package/dist/gameplay.d.ts +543 -0
  82. package/dist/gameplay.js +2 -0
  83. package/dist/heightmap-CroQPEER.js +185 -0
  84. package/dist/index.d.ts +305 -0
  85. package/dist/index.js +62 -0
  86. package/dist/json-BLk7H2Qa.js +30 -0
  87. package/dist/loader-CGs_G-r0.js +919 -0
  88. package/dist/loader-Mo0KghCv.d.ts +41 -0
  89. package/dist/net.d.ts +427 -0
  90. package/dist/net.js +772 -0
  91. package/dist/noise-CGUMx44x.js +82 -0
  92. package/dist/particle-sim-CbN4YUuH.d.ts +63 -0
  93. package/dist/particle-sim-DYuSUxvK.js +1319 -0
  94. package/dist/physics-2d-KuMWPTf6.js +288 -0
  95. package/dist/physics-3d-Dl67vOLT.js +434 -0
  96. package/dist/react.d.ts +65 -0
  97. package/dist/react.js +209 -0
  98. package/dist/register-BuUV1_KB.js +561 -0
  99. package/dist/register-CNlYAS1_.js +10634 -0
  100. package/dist/register-DPEV9_9t.js +851 -0
  101. package/dist/register-Dasmnurl.js +374 -0
  102. package/dist/registry-BVJ2HbCn.js +132 -0
  103. package/dist/rng-DP-SR7eg.js +38 -0
  104. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  105. package/dist/schema-CcoWb32N.d.ts +104 -0
  106. package/dist/test.d.ts +158 -0
  107. package/dist/test.js +275 -0
  108. package/dist/touch-031PxtCR.js +208 -0
  109. package/dist/vite.d.ts +26 -0
  110. package/dist/vite.js +57 -0
  111. package/editor/assets/GameServer-C56iOUgF.js +1 -0
  112. package/editor/assets/agent8-Bp7QFI7v.js +1 -0
  113. package/editor/assets/index-DF3tMeKJ.css +1 -0
  114. package/editor/assets/index-Dl2pjA8e.js +7365 -0
  115. package/editor/assets/rapier-CEuLKeCu.js +1 -0
  116. package/editor/assets/rapier-DE6a0vmv.js +1 -0
  117. package/editor/index.html +169 -0
  118. package/package.json +97 -0
  119. package/schemas/scene.schema.json +4254 -0
  120. package/skills/README.md +9 -0
  121. package/skills/incanto-3d-character.md +229 -0
  122. package/skills/incanto-3d-models.md +151 -0
  123. package/skills/incanto-assets.md +118 -0
  124. package/skills/incanto-audio.md +309 -0
  125. package/skills/incanto-behaviors-and-scripts.md +169 -0
  126. package/skills/incanto-building-2d-games.md +242 -0
  127. package/skills/incanto-building-3d-games.md +245 -0
  128. package/skills/incanto-editor.md +163 -0
  129. package/skills/incanto-environment.md +743 -0
  130. package/skills/incanto-gameplay-behaviors.md +707 -0
  131. package/skills/incanto-multiplayer.md +264 -0
  132. package/skills/incanto-node-reference.md +797 -0
  133. package/skills/incanto-physics-and-input.md +164 -0
  134. package/skills/incanto-scene-json-authoring.md +325 -0
  135. package/skills/incanto-verifying-your-game.md +191 -0
  136. package/skills/incanto-web-integration.md +96 -0
  137. package/templates/agent8-server.js +84 -0
  138. package/templates/agent8-server.ts +138 -0
@@ -0,0 +1,374 @@
1
+ import { f as Node, t as buildNodeJson, y as Signal } from "./loader-CGs_G-r0.js";
2
+ import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
+ import { t as registerCoreNodes } from "./register-BuUV1_KB.js";
4
+ import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
5
+ import { l as registerNode } from "./registry-BVJ2HbCn.js";
6
+ //#region src/net/network-manager.ts
7
+ const managers = /* @__PURE__ */ new WeakMap();
8
+ /**
9
+ * Per-engine multiplayer hub: joins a room, exposes the room channels as core
10
+ * Signals, replicates `network: {mode:'owner'}` node props on a throttle
11
+ * window, and resolves registered scenes for NetworkSpawner.
12
+ */
13
+ var NetworkManager = class NetworkManager {
14
+ account;
15
+ roomId;
16
+ roomState = new Signal();
17
+ allUserStates = new Signal();
18
+ userJoined = new Signal();
19
+ userLeft = new Signal();
20
+ /**
21
+ * GLOBAL (cross-room, persistent) channels — populated only when the transport
22
+ * supports them (`LocalGameServer`, the agent8 adapter). On a transport without a
23
+ * global tier they stay empty / never fire.
24
+ */
25
+ globalState = new Signal();
26
+ globalMyState = new Signal();
27
+ /** The local account's `$asset` ledger. */
28
+ asset = new Signal();
29
+ /** Latest snapshots for polling consumers (NetworkSpawner). */
30
+ latestUserStates = {};
31
+ latestRoomState = {};
32
+ latestGlobalState = {};
33
+ latestGlobalMyState = {};
34
+ latestAsset = {};
35
+ server;
36
+ engine;
37
+ throttleMs;
38
+ subs = [];
39
+ messageSignals = /* @__PURE__ */ new Map();
40
+ collectionSignals = /* @__PURE__ */ new Map();
41
+ latestCollections = /* @__PURE__ */ new Map();
42
+ globalMessageSignals = /* @__PURE__ */ new Map();
43
+ globalCollectionSignals = /* @__PURE__ */ new Map();
44
+ latestGlobalCollections = /* @__PURE__ */ new Map();
45
+ scenes = /* @__PURE__ */ new Map();
46
+ lastSent = /* @__PURE__ */ new Map();
47
+ sendAccumulator = 0;
48
+ detachReplication = null;
49
+ lastOwner = null;
50
+ boundScene = null;
51
+ static get(engine) {
52
+ return managers.get(engine) ?? null;
53
+ }
54
+ static async create(engine, opts = {}) {
55
+ await managers.get(engine)?.dispose();
56
+ const server = opts.transport ?? await createDefaultTransport(opts.config);
57
+ await server.connect();
58
+ const requested = opts.room ?? engine.scene?.multiplayer?.room ?? "auto";
59
+ const manager = new NetworkManager(engine, server, await server.remoteFunction("joinRoom", [requested === "auto" ? void 0 : requested], { needResponse: true }), opts.throttleMs ?? 50);
60
+ managers.set(engine, manager);
61
+ return manager;
62
+ }
63
+ constructor(engine, server, roomId, throttleMs) {
64
+ this.engine = engine;
65
+ this.server = server;
66
+ this.roomId = roomId;
67
+ this.account = server.account;
68
+ this.throttleMs = Math.max(30, throttleMs);
69
+ this.boundScene = engine.scene;
70
+ this.subs.push(server.subscribeRoomState(roomId, (state) => {
71
+ this.latestRoomState = state;
72
+ this.roomState.emit(state);
73
+ }), server.subscribeRoomAllUserStates(roomId, (states) => {
74
+ this.latestUserStates = states;
75
+ this.allUserStates.emit(states);
76
+ }), server.onRoomUserJoin(roomId, (account) => this.userJoined.emit(account)), server.onRoomUserLeave(roomId, (account) => this.userLeft.emit(account)));
77
+ if (server.subscribeGlobalState) this.subs.push(server.subscribeGlobalState((state) => {
78
+ this.latestGlobalState = state;
79
+ this.globalState.emit(state);
80
+ }));
81
+ if (server.subscribeGlobalMyState) this.subs.push(server.subscribeGlobalMyState((state) => {
82
+ this.latestGlobalMyState = state;
83
+ this.globalMyState.emit(state);
84
+ }));
85
+ if (server.subscribeAsset) this.subs.push(server.subscribeAsset(this.account, (assets) => {
86
+ this.latestAsset = assets;
87
+ this.asset.emit(assets);
88
+ }));
89
+ this.detachReplication = engine.fixedUpdated.connect((dt) => this.replicate(dt));
90
+ }
91
+ /** Signal for a typed room message (`sendEvent` on any client). */
92
+ message(type) {
93
+ let sig = this.messageSignals.get(type);
94
+ if (!sig) {
95
+ sig = new Signal();
96
+ this.messageSignals.set(type, sig);
97
+ this.subs.push(this.server.onRoomMessage(this.roomId, type, (m) => sig?.emit(m)));
98
+ }
99
+ return sig;
100
+ }
101
+ /** Signal + snapshot for a room collection (spawned entities). */
102
+ collection(id) {
103
+ let sig = this.collectionSignals.get(id);
104
+ if (!sig) {
105
+ sig = new Signal();
106
+ this.collectionSignals.set(id, sig);
107
+ this.subs.push(this.server.subscribeRoomCollection(this.roomId, id, (entities) => {
108
+ this.latestCollections.set(id, entities);
109
+ sig?.emit(entities);
110
+ }));
111
+ }
112
+ return sig;
113
+ }
114
+ latestCollection(id) {
115
+ this.collection(id);
116
+ return this.latestCollections.get(id) ?? {};
117
+ }
118
+ /** Signal for a typed GLOBAL message (`$global.broadcastToAll`/`sendMessageToUser`). */
119
+ globalMessage(type) {
120
+ let sig = this.globalMessageSignals.get(type);
121
+ if (!sig) {
122
+ sig = new Signal();
123
+ this.globalMessageSignals.set(type, sig);
124
+ const sub = this.server.onGlobalMessage?.(type, (m) => sig?.emit(m));
125
+ if (sub) this.subs.push(sub);
126
+ }
127
+ return sig;
128
+ }
129
+ /** Signal + snapshot for a GLOBAL collection (persistent, cross-room). */
130
+ globalCollection(id) {
131
+ let sig = this.globalCollectionSignals.get(id);
132
+ if (!sig) {
133
+ sig = new Signal();
134
+ this.globalCollectionSignals.set(id, sig);
135
+ const sub = this.server.subscribeGlobalCollection?.(id, (entities) => {
136
+ this.latestGlobalCollections.set(id, entities);
137
+ sig?.emit(entities);
138
+ });
139
+ if (sub) this.subs.push(sub);
140
+ }
141
+ return sig;
142
+ }
143
+ latestGlobalCollection(id) {
144
+ this.globalCollection(id);
145
+ return this.latestGlobalCollections.get(id) ?? {};
146
+ }
147
+ setMyState(patch) {
148
+ return this.server.remoteFunction("setMyState", [this.roomId, patch], {
149
+ throttle: this.throttleMs,
150
+ throttleKey: "incanto:myState"
151
+ });
152
+ }
153
+ patchRoomState(patch) {
154
+ return this.server.remoteFunction("patchRoomState", [this.roomId, patch]);
155
+ }
156
+ addEntity(collectionId, entity) {
157
+ return this.server.remoteFunction("addEntity", [
158
+ this.roomId,
159
+ collectionId,
160
+ entity
161
+ ]);
162
+ }
163
+ updateEntity(collectionId, id, patch) {
164
+ return this.server.remoteFunction("updateEntity", [
165
+ this.roomId,
166
+ collectionId,
167
+ id,
168
+ patch
169
+ ]);
170
+ }
171
+ removeEntity(collectionId, id) {
172
+ return this.server.remoteFunction("removeEntity", [
173
+ this.roomId,
174
+ collectionId,
175
+ id
176
+ ]);
177
+ }
178
+ sendEvent(type, payload) {
179
+ return this.server.remoteFunction("sendEvent", [
180
+ this.roomId,
181
+ type,
182
+ payload
183
+ ]);
184
+ }
185
+ /**
186
+ * Invoke a CUSTOM server-authoritative function — a method you added to your
187
+ * `server/src/server.ts` `Server` class (run live on the Agent8 platform, or
188
+ * locally via `createLocalGameServer`). The room id is prepended automatically,
189
+ * matching the `(roomId, …)` shape every server method takes:
190
+ *
191
+ * `manager.call('claimCoin', coinId)` → server `claimCoin(roomId, coinId)`
192
+ *
193
+ * Returns the function's result (use it for server-validated outcomes — a
194
+ * rejected claim, a rolled value). This is THE entry point for game rules the
195
+ * client must not be trusted to compute.
196
+ */
197
+ call(fn, ...args) {
198
+ return this.server.remoteFunction(fn, [this.roomId, ...args], { needResponse: true });
199
+ }
200
+ /** Register a scene for NetworkSpawner (`"scene": "<key>"`). */
201
+ registerScene(key, json) {
202
+ this.scenes.set(key, json);
203
+ }
204
+ resolveScene(key) {
205
+ const json = this.scenes.get(key);
206
+ if (!json) throw new IncantoError("UNRESOLVED_INSTANCE", `No scene registered for '${key}'. Registered: [${[...this.scenes.keys()].join(", ")}]. Call manager.registerScene('${key}', sceneJson).`);
207
+ return json;
208
+ }
209
+ async dispose() {
210
+ this.detachReplication?.();
211
+ for (const off of this.subs) off();
212
+ await this.server.remoteFunction("leaveRoom", [this.roomId]);
213
+ managers.delete(this.engine);
214
+ }
215
+ replicate(dt) {
216
+ const scene = this.engine.scene;
217
+ if (!scene || scene !== this.boundScene) return;
218
+ this.sendAccumulator += dt * 1e3;
219
+ const owner = findOwnerNode(scene.root, true);
220
+ if (!owner) return;
221
+ if (owner !== this.lastOwner) {
222
+ this.lastOwner = owner;
223
+ this.lastSent.clear();
224
+ }
225
+ const net = owner.network;
226
+ const window = Math.max(30, typeof net.throttleMs === "number" ? net.throttleMs : this.throttleMs);
227
+ const syncKeys = Array.isArray(net.sync) ? net.sync : [];
228
+ const patch = {};
229
+ for (const key of syncKeys) {
230
+ const value = readSyncKey(owner, key);
231
+ if (value === void 0) continue;
232
+ if (!jsonEquals(this.lastSent.get(key) ?? null, value)) patch[key] = jsonClone(value);
233
+ }
234
+ if (this.sendAccumulator < window) return;
235
+ if (Object.keys(patch).length === 0) return;
236
+ for (const [key, value] of Object.entries(patch)) this.lastSent.set(key, jsonClone(value));
237
+ this.sendAccumulator = 0;
238
+ this.setMyState({ sync: patch });
239
+ }
240
+ };
241
+ function findOwnerNode(node, enforceSingle = false) {
242
+ const owners = [];
243
+ collectOwners(node, owners);
244
+ if (enforceSingle && owners.length > 1) throw new IncantoError("BAD_FORMAT", `Multiple network owner nodes found (${owners.map((o) => o.getPath()).join(", ")}). Exactly ONE node per player may declare network.mode 'owner' — replicate spawned entities through collections instead.`);
245
+ return owners[0] ?? null;
246
+ }
247
+ function collectOwners(node, out) {
248
+ if (node.network?.mode === "owner") out.push(node);
249
+ for (const c of node.children) collectOwners(c, out);
250
+ }
251
+ /** `'position'` → own prop; `'Sprite.animation'` → child path + prop (last dot splits). */
252
+ function readSyncKey(node, key) {
253
+ const lastDot = key.lastIndexOf(".");
254
+ const target = lastDot === -1 ? node : node.getNodeOrNull(key.slice(0, lastDot)) ?? void 0;
255
+ const prop = lastDot === -1 ? key : key.slice(lastDot + 1);
256
+ if (!target) return void 0;
257
+ return target[prop];
258
+ }
259
+ /**
260
+ * @internal Applies a replicated flat sync patch (`{'position': …,
261
+ * 'Sprite.animation': …}`) onto a spawned subtree (NetworkSpawner).
262
+ */
263
+ function applySyncPatch(root, patch, setProp) {
264
+ for (const [key, value] of Object.entries(patch)) {
265
+ const lastDot = key.lastIndexOf(".");
266
+ const target = lastDot === -1 ? root : root.getNodeOrNull(key.slice(0, lastDot));
267
+ const prop = lastDot === -1 ? key : key.slice(lastDot + 1);
268
+ if (target) setProp(target, prop, value);
269
+ }
270
+ }
271
+ async function createDefaultTransport(config) {
272
+ const { createAgent8Server } = await import("./agent8-DzU2fFyH.js").then((n) => n.t);
273
+ return createAgent8Server(config);
274
+ }
275
+ //#endregion
276
+ //#region src/net/nodes/network-spawner.ts
277
+ /**
278
+ * Materializes a registered scene per remote player or collection entity:
279
+ *
280
+ * - `source: 'users'` — one instance per OTHER account in the room
281
+ * (self is skipped; the local player is your own owned node)
282
+ * - `source: 'collection:<id>'` — one instance per entity (bullets, pickups…)
283
+ *
284
+ * Replicated flat `{sync: {...}}` patches apply onto each instance's root;
285
+ * `position` lerps toward its target when `interpolate` is on. Emits
286
+ * `spawned(node, key)` / `despawned(node, key)`.
287
+ */
288
+ var NetworkSpawner = class extends Node {
289
+ static typeName = "NetworkSpawner";
290
+ static signals = ["spawned", "despawned"];
291
+ static props = {
292
+ source: { default: "users" },
293
+ scene: { default: "" },
294
+ interpolate: { default: true }
295
+ };
296
+ source = "users";
297
+ /** Key registered via `manager.registerScene(key, json)`. */
298
+ scene = "";
299
+ interpolate = true;
300
+ spawned = /* @__PURE__ */ new Map();
301
+ positionTargets = /* @__PURE__ */ new Map();
302
+ failedKeys = /* @__PURE__ */ new Set();
303
+ update(dt) {
304
+ const engine = this.tree?.engine;
305
+ if (!engine) return;
306
+ const manager = NetworkManager.get(engine);
307
+ if (!manager || this.scene === "") return;
308
+ const entries = this.currentEntries(manager);
309
+ for (const [key, state] of Object.entries(entries)) {
310
+ let instance = this.spawned.get(key);
311
+ if (!instance) {
312
+ if (this.failedKeys.has(key)) continue;
313
+ try {
314
+ instance = buildNodeJson(manager.resolveScene(this.scene).root);
315
+ } catch (error) {
316
+ this.failedKeys.add(key);
317
+ throw error;
318
+ }
319
+ instance.name = sanitizeName(key);
320
+ this.addChild(instance);
321
+ this.spawned.set(key, instance);
322
+ this.emit("spawned", instance, key);
323
+ }
324
+ const patch = state.sync;
325
+ if (patch) applySyncPatch(instance, patch, (target, prop, value) => this.setProp(target, prop, value));
326
+ }
327
+ for (const [key, instance] of [...this.spawned.entries()]) if (!(key in entries)) {
328
+ this.spawned.delete(key);
329
+ this.positionTargets.delete(instance);
330
+ this.emit("despawned", instance, key);
331
+ instance.free();
332
+ }
333
+ if (this.interpolate) this.stepInterpolation(dt);
334
+ }
335
+ currentEntries(manager) {
336
+ if (this.source === "users") {
337
+ const out = {};
338
+ for (const [account, state] of Object.entries(manager.latestUserStates)) if (account !== manager.account) out[account] = state;
339
+ return out;
340
+ }
341
+ if (this.source.startsWith("collection:")) return manager.latestCollection(this.source.slice(11));
342
+ return {};
343
+ }
344
+ setProp(target, prop, value) {
345
+ if (this.interpolate && prop === "position" && Array.isArray(value)) {
346
+ this.positionTargets.set(target, value);
347
+ return;
348
+ }
349
+ target[prop] = value;
350
+ }
351
+ stepInterpolation(dt) {
352
+ const f = Math.min(1, dt * 8);
353
+ for (const [target, goal] of this.positionTargets) {
354
+ const pos = target.position;
355
+ if (!Array.isArray(pos)) continue;
356
+ target.position = pos.map((v, i) => v + ((goal[i] ?? v) - v) * f);
357
+ }
358
+ }
359
+ };
360
+ function sanitizeName(key) {
361
+ return key.replace(/[/%]/g, "_") || "remote";
362
+ }
363
+ //#endregion
364
+ //#region src/net/register.ts
365
+ /**
366
+ * Register the networking node types (and the core nodes). Call once in your
367
+ * game entry before loading a multiplayer scene. Explicit — never a side effect.
368
+ */
369
+ function registerNodesNet() {
370
+ registerCoreNodes();
371
+ registerNode(NetworkSpawner);
372
+ }
373
+ //#endregion
374
+ export { applySyncPatch as i, NetworkSpawner as n, NetworkManager as r, registerNodesNet as t };
@@ -0,0 +1,132 @@
1
+ import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
+ import { r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
3
+ //#region src/core/registry.ts
4
+ /**
5
+ * Validate a prop value against its schema default:
6
+ * - JSON kind must match (unless the default is null = "any")
7
+ * - numbers must be finite (NaN/Infinity corrupt JSON files)
8
+ * - non-empty default arrays are FIXED templates: same length, element-wise match
9
+ * (empty default arrays are unconstrained — variable-length lists)
10
+ */
11
+ function validatePropValue(value, def, at) {
12
+ const expected = jsonKind(def);
13
+ if (expected === "null") return;
14
+ const actual = jsonKind(value);
15
+ if (actual !== expected) throw new IncantoError("PROP_TYPE_MISMATCH", `Prop '${at}' expects ${expected} (default ${JSON.stringify(def)}), got ${actual} (${JSON.stringify(value)}).`, { prop: at });
16
+ if (typeof value === "number" && !Number.isFinite(value)) throw new IncantoError("PROP_TYPE_MISMATCH", `Prop '${at}' is ${value} — props must be finite numbers.`, { prop: at });
17
+ if (Array.isArray(def) && Array.isArray(value) && def.length > 0) {
18
+ if (value.length !== def.length) throw new IncantoError("PROP_TYPE_MISMATCH", `Prop '${at}' expects an array of length ${def.length} (default ${JSON.stringify(def)}), got length ${value.length}.`, { prop: at });
19
+ for (let i = 0; i < def.length; i++) validatePropValue(value[i], def[i], `${at}[${i}]`);
20
+ }
21
+ }
22
+ /**
23
+ * Global node-type registry (Godot's ClassDB role).
24
+ *
25
+ * Registration is EXPLICIT — never an import-time side effect, so
26
+ * `sideEffects: false` tree-shaking can never silently drop node types.
27
+ */
28
+ const types = /* @__PURE__ */ new Map();
29
+ function registerNode(ctor, opts) {
30
+ const existing = types.get(ctor.typeName);
31
+ if (existing && existing !== ctor && !opts?.replace) throw new IncantoError("DUPLICATE_NODE_TYPE", `Node type '${ctor.typeName}' is already registered by a different class. Replacing an implementation (e.g. under hot reload) needs registerNode(ctor, { replace: true }).`, { nodeType: ctor.typeName });
32
+ types.set(ctor.typeName, ctor);
33
+ }
34
+ function getNodeType(typeName) {
35
+ const ctor = types.get(typeName);
36
+ if (!ctor) {
37
+ const registered = [...types.keys()];
38
+ throw new IncantoError("UNKNOWN_NODE_TYPE", `Unknown node type '${typeName}'. Registered types: [${registered.join(", ")}].`, {
39
+ nodeType: typeName,
40
+ validOptions: registered
41
+ });
42
+ }
43
+ return ctor;
44
+ }
45
+ function registeredTypes() {
46
+ return [...types.keys()];
47
+ }
48
+ /** Test isolation helper. */
49
+ function clearRegistry() {
50
+ types.clear();
51
+ }
52
+ /**
53
+ * Effective prop schema for a type: own static `props` merged over every
54
+ * ancestor's (subclass keys win).
55
+ */
56
+ function getNodeSchema(typeName) {
57
+ return mergeStaticProps(getNodeType(typeName));
58
+ }
59
+ /** Walk a constructor's prototype chain collecting static `signals` (deduped). */
60
+ function mergeStaticSignals(ctor) {
61
+ const out = [];
62
+ let current = ctor;
63
+ while (current) {
64
+ if (Object.hasOwn(current, "signals") && current.signals) out.unshift(...current.signals);
65
+ const parent = Object.getPrototypeOf(current);
66
+ current = typeof parent === "function" ? parent : null;
67
+ }
68
+ return [...new Set(out)];
69
+ }
70
+ /** Every signal a registered type declares (its own + inherited). */
71
+ function getNodeSignals(typeName) {
72
+ return mergeStaticSignals(getNodeType(typeName));
73
+ }
74
+ /** Walk a constructor's prototype chain merging static `props` (subclass wins). */
75
+ function mergeStaticProps(ctor) {
76
+ const chain = [];
77
+ let current = ctor;
78
+ while (current) {
79
+ if (Object.hasOwn(current, "props") && current.props) chain.unshift(current.props);
80
+ const parent = Object.getPrototypeOf(current);
81
+ current = typeof parent === "function" ? parent : null;
82
+ }
83
+ return Object.assign({}, ...chain);
84
+ }
85
+ /** Walk a constructor's prototype chain merging static `propAliases` (legacy
86
+ * prop key → its current canonical key; subclass wins). Lets a renamed prop keep
87
+ * loading old scenes (e.g. 2D `zIndex` → `renderOrder`) without a format bump. */
88
+ function mergeStaticAliases(ctor) {
89
+ const chain = [];
90
+ let current = ctor;
91
+ while (current) {
92
+ if (Object.hasOwn(current, "propAliases") && current.propAliases) chain.unshift(current.propAliases);
93
+ const parent = Object.getPrototypeOf(current);
94
+ current = typeof parent === "function" ? parent : null;
95
+ }
96
+ return Object.assign({}, ...chain);
97
+ }
98
+ /**
99
+ * Assign schema defaults (cloned) then the given props onto a target,
100
+ * hard-validating every key and JSON kind. Shared by nodes and behaviors.
101
+ * `aliases` maps legacy keys onto current ones (back-compat for renamed props).
102
+ */
103
+ function applySchemaProps(target, schema, props, label, aliases = {}) {
104
+ for (const [key, def] of Object.entries(schema)) target[key] = jsonClone(def.default);
105
+ if (!props) return;
106
+ for (const [rawKey, value] of Object.entries(props)) {
107
+ const key = schema[rawKey] ? rawKey : aliases[rawKey] ?? rawKey;
108
+ const def = schema[key];
109
+ if (!def) {
110
+ const valid = Object.keys(schema);
111
+ throw new IncantoError("UNKNOWN_PROP", `Unknown prop '${rawKey}' on '${label}'. Valid props: [${valid.join(", ")}].`, {
112
+ prop: rawKey,
113
+ nodeType: label,
114
+ validOptions: valid
115
+ });
116
+ }
117
+ validatePropValue(value, def.default, `${label}.${key}`);
118
+ target[key] = jsonClone(value);
119
+ }
120
+ }
121
+ /**
122
+ * Instantiate a registered type, assign schema defaults (cloned), then the
123
+ * given props — validating every key and its JSON kind against the default.
124
+ */
125
+ function createNode(typeName, props) {
126
+ const ctor = getNodeType(typeName);
127
+ const node = new ctor();
128
+ applySchemaProps(node, getNodeSchema(typeName), props, typeName, mergeStaticAliases(ctor));
129
+ return node;
130
+ }
131
+ //#endregion
132
+ export { getNodeSignals as a, mergeStaticSignals as c, getNodeSchema as i, registerNode as l, clearRegistry as n, getNodeType as o, createNode as r, mergeStaticProps as s, applySchemaProps as t, registeredTypes as u };
@@ -0,0 +1,38 @@
1
+ import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
+ //#region src/core/rng.ts
3
+ /**
4
+ * Deterministic seeded PRNG (mulberry32). Game logic should draw randomness
5
+ * from `engine.rng` (or `this.rng` in a Behavior) instead of `Math.random()` —
6
+ * a seeded engine then replays identically, which is what makes scripted
7
+ * headless verification and multiplayer-adjacent determinism possible.
8
+ */
9
+ var Rng = class {
10
+ state;
11
+ constructor(seed) {
12
+ this.state = seed >>> 0;
13
+ if (this.state === 0) this.state = 2654435769;
14
+ }
15
+ /** Next float in [0, 1). */
16
+ next() {
17
+ this.state = this.state + 1831565813 >>> 0;
18
+ let t = this.state;
19
+ t = Math.imul(t ^ t >>> 15, t | 1);
20
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
21
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
22
+ }
23
+ /** Float in [min, max). */
24
+ range(min, max) {
25
+ return min + this.next() * (max - min);
26
+ }
27
+ /** Integer in [min, max] — inclusive of BOTH bounds. */
28
+ int(min, max) {
29
+ return Math.floor(this.range(min, max + 1));
30
+ }
31
+ /** A uniformly random element. Empty arrays are a hard error. */
32
+ pick(items) {
33
+ if (items.length === 0) throw new IncantoError("BAD_FORMAT", "Rng.pick needs a non-empty array.");
34
+ return items[this.int(0, items.length - 1)];
35
+ }
36
+ };
37
+ //#endregion
38
+ export { Rng as t };
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
@@ -0,0 +1,104 @@
1
+ //#region src/core/json.d.ts
2
+ /** JSON-safe value types — the only types allowed in scene files and node props. */
3
+ type JsonValue = string | number | boolean | null | JsonValue[] | JsonObject;
4
+ interface JsonObject {
5
+ [key: string]: JsonValue;
6
+ }
7
+ type JsonKind = "string" | "number" | "boolean" | "null" | "array" | "object";
8
+ declare function jsonKind(value: JsonValue): JsonKind;
9
+ /** Deep structural equality over JSON values (used for delta-only serialization). */
10
+ declare function jsonEquals(a: JsonValue, b: JsonValue): boolean;
11
+ /**
12
+ * Deep clone of JSON-safe data (defaults must never be shared between
13
+ * instances). The value must contain only JSON types.
14
+ */
15
+ declare function jsonClone<T>(value: T): T;
16
+ //#endregion
17
+ //#region src/core/rng.d.ts
18
+ /**
19
+ * Deterministic seeded PRNG (mulberry32). Game logic should draw randomness
20
+ * from `engine.rng` (or `this.rng` in a Behavior) instead of `Math.random()` —
21
+ * a seeded engine then replays identically, which is what makes scripted
22
+ * headless verification and multiplayer-adjacent determinism possible.
23
+ */
24
+ declare class Rng {
25
+ private state;
26
+ constructor(seed: number);
27
+ /** Next float in [0, 1). */
28
+ next(): number;
29
+ /** Float in [min, max). */
30
+ range(min: number, max: number): number;
31
+ /** Integer in [min, max] — inclusive of BOTH bounds. */
32
+ int(min: number, max: number): number;
33
+ /** A uniformly random element. Empty arrays are a hard error. */
34
+ pick<T>(items: readonly T[]): T;
35
+ }
36
+ //#endregion
37
+ //#region src/core/scene/schema.d.ts
38
+ /** Current scene file format version. Defaults are frozen per format version. */
39
+ declare const SCENE_FORMAT: 1;
40
+ interface SceneJson {
41
+ format: typeof SCENE_FORMAT;
42
+ type: "scene";
43
+ name: string;
44
+ dimension?: "2d" | "3d";
45
+ /** Asset declarations, keyed by stable human-readable keys referenced as `$key`. */
46
+ assets?: Record<string, JsonObject>;
47
+ /**
48
+ * Named, reusable typed values, keyed by stable name (e.g. `{ "UI": 1000 }`).
49
+ * A node prop may reference one as `{"@const": "NAME"}`; the loader replaces it
50
+ * with the literal value at load (see scene/constants.ts).
51
+ */
52
+ constants?: Record<string, JsonValue>;
53
+ /** Declarative input action map (loaded into Engine.input). */
54
+ input?: JsonObject;
55
+ /** Physics config — `{gravity: [x,y]}` (2D px/s²) or `{gravity: [x,y,z]}` (3D m/s²). */
56
+ physics?: JsonObject;
57
+ /** Multiplayer config (interpreted in M6; preserved verbatim until then). */
58
+ multiplayer?: JsonObject;
59
+ /** Scene-level rendering environment (ambient light, background — interpreted by renderers). */
60
+ environment?: JsonObject;
61
+ /**
62
+ * Design-resolution viewport: `{design: [w, h], fit: 'expand'|'letterbox'|'integer'}`.
63
+ * Author the world in fixed design px; the renderer maps them onto any canvas size.
64
+ */
65
+ viewport?: JsonObject;
66
+ root: NodeJson;
67
+ connections?: ConnectionJson[];
68
+ }
69
+ interface NodeJson {
70
+ name: string;
71
+ /** Optional scene-wide-unique stable id (addressable via getNodeByUid). */
72
+ uid?: string;
73
+ /** Registered node type. Mutually exclusive with `instance`. */
74
+ type?: string;
75
+ /** Sub-scene reference (resolved via `LoadSceneOptions.resolveScene`). */
76
+ instance?: string;
77
+ /** Deep-merged onto the instanced scene root's props. Only valid with `instance`. */
78
+ overrides?: JsonObject;
79
+ groups?: string[];
80
+ tags?: JsonObject;
81
+ /** Delta-only: values equal to the type's schema defaults are omitted. */
82
+ props?: JsonObject;
83
+ /** Behavior attachment (resolved in M5; preserved verbatim until then). */
84
+ script?: JsonObject;
85
+ /** Replication config (resolved in M6; preserved verbatim until then). */
86
+ network?: JsonObject;
87
+ children?: NodeJson[];
88
+ }
89
+ interface ConnectionJson {
90
+ signal: string;
91
+ /** NodePath relative to the scene root ('.' = the root itself). */
92
+ from: string;
93
+ to: string;
94
+ /** Method name on the target node (or its behavior, once M5 lands). */
95
+ handler: string;
96
+ once?: boolean;
97
+ /** Only fire when the first emitted arg is a Node matching the filter. */
98
+ filter?: {
99
+ group?: string;
100
+ tag?: JsonObject;
101
+ };
102
+ }
103
+ //#endregion
104
+ export { Rng as a, JsonValue as c, jsonKind as d, SceneJson as i, jsonClone as l, NodeJson as n, JsonKind as o, SCENE_FORMAT as r, JsonObject as s, ConnectionJson as t, jsonEquals as u };