incanto 0.74.0 → 0.76.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 (92) hide show
  1. package/bin/incanto-multiplay.mjs +52 -4
  2. package/dist/2d.d.ts +83 -7
  3. package/dist/2d.js +4 -3
  4. package/dist/3d.d.ts +861 -167
  5. package/dist/3d.js +6 -6
  6. package/dist/{agent8-CHTT4unP.js → agent8-B-bh3n0X.js} +1 -1
  7. package/dist/{audio-player-BOrk4eQx.d.ts → audio-player-BRo2uvG6.d.ts} +23 -2
  8. package/dist/{behavior-J1cmVah0.d.ts → behavior-B_245qRy.d.ts} +15 -1
  9. package/dist/{create-game-ssvGvP7W.js → create-game-BMIPKrQW.js} +84 -8
  10. package/dist/{create-game-BAiA-FjP.js → create-game-Dai5vfVu.js} +80 -11
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-Bzt0GzVp.d.ts} +13 -3
  13. package/dist/editor.js +1600 -1529
  14. package/dist/env.d.ts +1 -1
  15. package/dist/{environment-presets-BkqlWewf.js → environment-presets-e_9YCnGu.js} +2361 -104
  16. package/dist/{gameplay-Bxe1sMVT.js → gameplay-Bfff_beb.js} +10919 -6798
  17. package/dist/gameplay.d.ts +573 -31
  18. package/dist/gameplay.js +3 -2
  19. package/dist/index.d.ts +150 -6
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-BAeWMYfF.d.ts → loader-CbkVdXL8.d.ts} +2 -2
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +2 -2
  24. package/dist/{physics-2d-C-SfvjzH.js → physics-2d-Tuf5LAyq.js} +250 -31
  25. package/dist/{physics-3d-rOzeg890.js → physics-3d-C5MOujfQ.js} +673 -51
  26. package/dist/{picking-BMV34Pjl.js → picking-DIwE48sU.js} +10 -7
  27. package/dist/{save-slots-CEUGeLuB.js → pose-ByFB_J3O.js} +217 -11
  28. package/dist/react.d.ts +2 -2
  29. package/dist/react.js +1 -1
  30. package/dist/{register-DRQyZKGU.js → register-7E3P1gz6.js} +191 -430
  31. package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
  32. package/dist/{replay-B6BbgGM_.js → replay-DrqQ6dZj.js} +133 -18
  33. package/dist/{replay-wZK5Ok5c.d.ts → replay-W5nCw_cU.d.ts} +21 -2
  34. package/dist/{rng-Bb-IutXB.d.ts → rng-BsXZg3D6.d.ts} +7 -0
  35. package/dist/{split-screen-BxZ5rBe7.js → split-screen-B-dIcQsQ.js} +10 -5
  36. package/dist/{split-screen-D6sx8z3Z.d.ts → split-screen-CiLJf2-C.d.ts} +3 -3
  37. package/dist/{sprite-animation-Bz3DHD_4.js → sprite-animation-SQa5gIu2.js} +8 -6
  38. package/dist/{src-CqtvYtSN.js → src-D823V07k.js} +1 -1
  39. package/dist/{test-CEl0y2Sw.js → test-CVbxnXlv.js} +487 -82
  40. package/dist/test.d.ts +90 -12
  41. package/dist/test.js +2 -2
  42. package/dist/vite.js +34 -5
  43. package/editor/assets/{agent8-BlzturGi.js → agent8-DA-I5qhv.js} +1 -1
  44. package/editor/assets/{debug-BMUi8usj.js → debug-99tEPoGm.js} +1 -1
  45. package/editor/assets/index-Db74LZY-.js +11579 -0
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +1639 -280
  49. package/skills/incanto-3d-character.md +383 -20
  50. package/skills/incanto-audio.md +24 -2
  51. package/skills/incanto-building-2d-games.md +21 -2
  52. package/skills/incanto-building-3d-games.md +38 -3
  53. package/skills/incanto-editor.md +29 -8
  54. package/skills/incanto-environment.md +257 -14
  55. package/skills/incanto-game-feel.md +60 -1
  56. package/skills/incanto-gameplay-behaviors.md +668 -20
  57. package/skills/incanto-hud.md +116 -0
  58. package/skills/incanto-multiplayer.md +141 -2
  59. package/skills/incanto-node-reference.md +398 -16
  60. package/skills/incanto-performance.md +32 -0
  61. package/skills/incanto-physics-and-input.md +241 -13
  62. package/skills/incanto-playtesting.md +2 -2
  63. package/skills/incanto-save-slots.md +61 -2
  64. package/skills/incanto-scene-json-authoring.md +13 -0
  65. package/skills/incanto-verifying-your-game.md +86 -3
  66. package/templates-app/beacon-isle-3d/PROJECT/Status.md +12 -0
  67. package/templates-app/beacon-isle-3d/package.json +1 -1
  68. package/templates-app/beacon-isle-3d/src/game.scene.json +157 -3
  69. package/templates-app/molehill-2d/PROJECT/Status.md +7 -0
  70. package/templates-app/molehill-2d/package.json +1 -1
  71. package/templates-app/molehill-2d/src/game.scene.json +129 -2
  72. package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
  73. package/templates-app/platformer-2d/docs/project-2d-rules.md +3 -2
  74. package/templates-app/platformer-2d/package.json +1 -1
  75. package/templates-app/platformer-2d/src/behaviors.ts +40 -26
  76. package/templates-app/platformer-2d/src/game.scene.json +144 -2
  77. package/templates-app/platformer-2d/verify.ts +18 -1
  78. package/templates-app/star-survivor/PROJECT/Status.md +7 -0
  79. package/templates-app/star-survivor/package.json +1 -1
  80. package/templates-app/star-survivor/src/behaviors.ts +9 -8
  81. package/templates-app/star-survivor/src/game.scene.json +142 -4
  82. package/templates-app/star-survivor/verify.ts +13 -0
  83. package/templates-app/tps-3d/PROJECT/Status.md +6 -0
  84. package/templates-app/tps-3d/package.json +1 -1
  85. package/templates-app/tps-3d/src/game.scene.json +71 -10
  86. package/templates-app/village-quest-3d/PROJECT/Status.md +7 -0
  87. package/templates-app/village-quest-3d/package.json +1 -1
  88. package/templates-app/village-quest-3d/src/behaviors.ts +8 -16
  89. package/templates-app/village-quest-3d/src/grove.scene.json +121 -5
  90. package/templates-app/village-quest-3d/src/village.scene.json +126 -2
  91. package/templates-app/village-quest-3d/verify.ts +52 -2
  92. package/editor/assets/index-CrUCQoaB.js +0 -11046
@@ -1,5 +1,5 @@
1
- import { L as handlerShadowWarnings, R as sceneDimension, V as HudWidgetBase, Z as nodeRefWarnings } from "./save-slots-CEUGeLuB.js";
2
- import { N as translationKey } from "./register-CjociOtt.js";
1
+ import { H as handlerShadowWarnings, K as HudWidgetBase, U as sceneDimension, nt as nodeRefWarnings } from "./pose-ByFB_J3O.js";
2
+ import { F as translationKey } from "./register-B768Qedj.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { u as getNodeType } from "./touch-CioTZB-y.js";
5
5
  //#region src/core/light-types.ts
@@ -120,10 +120,12 @@ const WORLD_QUERY_TYPES = new Set([
120
120
  * the two lists disagree again.
121
121
  */
122
122
  const SAVEABLE_SCRIPTS = new Set([
123
+ "Checkpoint",
123
124
  "Collector",
124
125
  "Currency",
125
126
  "DayNight",
126
127
  "Health",
128
+ "Phases",
127
129
  "SavePoint",
128
130
  "ScoreKeeper",
129
131
  "WaveSpawner"
@@ -227,10 +229,10 @@ function auditScene(scene, opts = {}) {
227
229
  }[type];
228
230
  if (art && ((props[art[0]] ?? "") === "" || props[art[0]] === void 0)) warnings.push(`${path}: ${type} has no "${art[0]}", so nothing of it is drawn — it loads, validates and takes up a row in the tree, and puts no pixel on screen. Give it ${art[1]}.`);
229
231
  if (type === "InstancedMesh3D") {
230
- if ((Array.isArray(props.transforms) ? props.transforms : []).length === 0) warnings.push(`${path}: InstancedMesh3D has no "transforms", so it draws nothing — a scatter with no rows. Add [x, y, z, yawDeg?, scale?] rows, or fill them from a behaviour and say so.`);
232
+ if ((Array.isArray(props.transforms) ? props.transforms : []).length === 0) warnings.push(`${path}: InstancedMesh3D has no "transforms", so it draws nothing — a scatter with no rows. Add [x, y, z, yawDeg?, scale?] rows. If a behaviour fills them (an aiming arc, a wave of markers), author it "visible": false — the node is genuinely invisible until the rows arrive, and the behaviour that writes them turns it on.`);
231
233
  }
232
234
  }
233
- if (type === "CharacterController3D") cameraDrivers.push(path);
235
+ if (type === "CharacterController3D" && props.enabled !== false && props.camera !== "none") cameraDrivers.push(path);
234
236
  if (type === "Terrain3D") terrains.push(path);
235
237
  if (DRAPERS.has(type) && (props.terrain ?? "") === "") autoDrapers.push(path);
236
238
  if (LIGHT_TYPES.has(type)) lights += 1;
@@ -278,9 +280,12 @@ function auditScene(scene, opts = {}) {
278
280
  what: rootTransform
279
281
  } : null, root.script?.name === "PrefabShelf");
280
282
  if (needUid.length > 0) warnings.push(`these carry state a save keeps and have no uid to key it under, so the save comes back EMPTY and the load reports no problem: ${needUid.join(", ")}. Give each one a "uid" from newUid().`);
283
+ warnings.push(...propsThatAreOverwritten(root));
284
+ const floorless = terrainsWithNoFloor(root);
285
+ if (floorless.length > 0 && floorless.length === terrains.length) warnings.push(`${floorless.join(", ")}: nothing in this scene can be stood on — no Terrain3D is under a body whose collider is \`{ "shape": "heightfield" }\`, so the ground is DRAWN and the player falls through it. Wrap it: a \`StaticBody3D\` with that collider, the Terrain3D as its child. (A terrain that is only scenery is fine beside one that is solid.)`);
281
286
  if (terrains.length > 1 && autoDrapers.length > 0) warnings.push(`${autoDrapers.join(", ")}: this scene has ${terrains.length} Terrain3Ds (${terrains.join(", ")}) and these do not say which one they grow on — with no \`terrain\` path they take the FIRST one in the tree, so the rest drape to the wrong height with nothing reported. Name it: "terrain": "%Name".`);
282
287
  if (cameraDrivers.length > 1) warnings.push(`${cameraDrivers.join(", ")}: ${cameraDrivers.length} CharacterController3Ds drive the SAME camera (each one finds the first \`current\` camera in the tree), so the last of them wins every frame and the others have no view. For local co-op give each player a panel with \`createSplitScreen\` (one engine each); otherwise only one node may carry a controller.`);
283
- if (cameraDrivers.length > 0 && followedCameras.length > 0) warnings.push(`${followedCameras.join(", ")}: a FollowCamera writes this camera's position every frame, and so does the CharacterController3D at ${cameraDrivers.join(", ")} — the controller owns the camera in every view it has. Drop one of them: use the controller's own \`view\`/\`camDistance\`, or take the controller off the camera duty.`);
288
+ if (cameraDrivers.length > 0 && followedCameras.length > 0) warnings.push(`${followedCameras.join(", ")}: a FollowCamera writes this camera's position every frame, and so does the CharacterController3D at ${cameraDrivers.join(", ")} — the controller owns the camera in every view it has. Drop one of them: use the controller's own \`view\`/\`camDistance\`, or set the controller's \`"camera": "none"\` and let the scene own its camera.`);
284
289
  if (cameras > 0 && currentCameras === 0) warnings.push("no camera has \"current\": true — the screen renders from a default view, probably not what you framed.");
285
290
  if (currentCameras > 1) warnings.push(`${currentCameras} cameras claim "current": true — only the first found wins.`);
286
291
  const isFragment = opts.instanced === true || scene.fragment === true;
@@ -349,6 +354,91 @@ function groupsInScene(root) {
349
354
  walk(root);
350
355
  return out;
351
356
  }
357
+ /**
358
+ * Terrains nothing can stand on.
359
+ *
360
+ * A `Terrain3D` DRAWS a landscape; it is solid only when it sits under a body
361
+ * whose collider is `{ "shape": "heightfield" }` — colliders are props on
362
+ * bodies in this engine, never implicit. Get that wrong and the terrain looks
363
+ * perfect, `heightAt` answers correctly, `snapToGround` places everything on it,
364
+ * and the player falls straight through in silence.
365
+ *
366
+ * Measured while building a river-valley game from the packed tarball: the
367
+ * walker fell from 22 m past 0 with no error anywhere, every frame, while the
368
+ * terrain under it reported 22.00. The shipped templates all get it right,
369
+ * which is exactly why nobody had hit it.
370
+ */
371
+ /**
372
+ * Props a node WRITES OVER, per type.
373
+ *
374
+ * `Weather3D` extends `Particles3D` and tunes the whole emitter from `kind` and
375
+ * `intensity`; `Flock3D` extends `InstancedMesh3D` and rewrites `transforms`
376
+ * every frame from the boids. Both INHERIT those props, so the loader accepts
377
+ * them, the editor offers them, and `bun run unused-props` lists them as things
378
+ * no scene sets — which reads as an invitation. An author who writes
379
+ * `"rate": 400` on a Weather3D gets exactly nothing.
380
+ *
381
+ * Hard-coded, like `SAVEABLE_SCRIPTS` and `DRAPERS` above: the audit reads JSON
382
+ * and has no registry, and a node that overwrites an inherited prop is a rare
383
+ * enough thing to name.
384
+ */
385
+ const OVERWRITTEN = {
386
+ Weather3D: {
387
+ props: [
388
+ "rate",
389
+ "burst",
390
+ "lifetime",
391
+ "speed",
392
+ "directionDeg",
393
+ "spreadDeg",
394
+ "gravity",
395
+ "drag",
396
+ "sizeStart",
397
+ "sizeEnd",
398
+ "colorStart",
399
+ "colorEnd",
400
+ "alphaStart",
401
+ "alphaEnd",
402
+ "blend",
403
+ "emitBox",
404
+ "drift",
405
+ "worldSpace",
406
+ "maxParticles",
407
+ "camera"
408
+ ],
409
+ instead: "`kind`, `intensity`, `radius`, `height`, `wind` and `follow` are what steer it"
410
+ },
411
+ Flock3D: {
412
+ props: ["transforms"],
413
+ instead: "`count`, `radius`, `height`, `speed`, `turn`, `separation`, `sight`, `alignment`, `cohesion` and `follow` are what steer it"
414
+ }
415
+ };
416
+ /** Nodes that author a prop their own type will discard. */
417
+ function propsThatAreOverwritten(root) {
418
+ const out = [];
419
+ const walk = (node, path) => {
420
+ const rule = OVERWRITTEN[node.type ?? ""];
421
+ if (rule) {
422
+ const written = rule.props.filter((p) => node.props?.[p] !== void 0);
423
+ if (written.length > 0) out.push(`${path}: ${node.type} overwrites ${written.map((p) => `\`${p}\``).join(", ")} on its first frame — the value here is discarded. ${rule.instead}.`);
424
+ }
425
+ for (const child of node.children ?? []) walk(child, `${path}/${child.name ?? "?"}`);
426
+ };
427
+ walk(root, `/${root.name ?? "?"}`);
428
+ return out;
429
+ }
430
+ function terrainsWithNoFloor(root) {
431
+ const out = [];
432
+ const walk = (node, path, host) => {
433
+ const type = node.type ?? "";
434
+ const collider = (node.props ?? {}).collider;
435
+ const mine = collider && collider.shape === "heightfield" ? path : host;
436
+ if (type === "Terrain3D" && mine === null) out.push(path);
437
+ for (const child of node.children ?? []) walk(child, `${path}/${child.name ?? "?"}`, mine);
438
+ };
439
+ walk(root, `/${root.name ?? "?"}`, null);
440
+ return out;
441
+ }
352
442
  function hurtableGroups(root) {
353
443
  const out = /* @__PURE__ */ new Set();
354
444
  const hasHealth = (node) => node.script?.name === "Health" || (node.children ?? []).some(hasHealth);
@@ -598,19 +688,43 @@ function startRecording(engine) {
598
688
  originalStep();
599
689
  };
600
690
  let stopped = false;
601
- return { stop() {
602
- if (!stopped) {
603
- stopped = true;
604
- for (const [name, fn] of originals) input[name] = fn;
605
- engineAny.tick = originalTick;
606
- engineAny.step = originalStep;
691
+ return {
692
+ stop() {
693
+ if (!stopped) {
694
+ stopped = true;
695
+ for (const [name, fn] of originals) input[name] = fn;
696
+ engineAny.tick = originalTick;
697
+ engineAny.step = originalStep;
698
+ }
699
+ return {
700
+ replay: 1,
701
+ ticks,
702
+ events
703
+ };
704
+ },
705
+ take() {
706
+ return {
707
+ replay: 1,
708
+ ticks: [...ticks],
709
+ events: events.map((e) => [...e])
710
+ };
711
+ },
712
+ rewind(n) {
713
+ const keep = Math.max(0, Math.min(Math.floor(n), ticks.length));
714
+ ticks.length = keep;
715
+ let write = 0;
716
+ for (const event of events) if (event[0] < keep) {
717
+ events[write] = event;
718
+ write += 1;
719
+ }
720
+ events.length = write;
721
+ return {
722
+ replay: 1,
723
+ ticks: [...ticks],
724
+ events: events.map((e) => [...e])
725
+ };
607
726
  }
608
- return {
609
- replay: 1,
610
- ticks,
611
- events
612
- };
613
- } };
727
+ };
614
728
  }
615
729
  /**
616
730
  * Drive a fresh engine through a recording: for each recorded tick, feed
@@ -620,7 +734,8 @@ function startRecording(engine) {
620
734
  function replay(engine, recording, opts) {
621
735
  const input = engine.input;
622
736
  let cursor = 0;
623
- for (let i = 0; i < recording.ticks.length; i++) {
737
+ const last = Number.isFinite(opts?.until) ? Math.max(0, Math.min(Math.floor(opts?.until), recording.ticks.length)) : recording.ticks.length;
738
+ for (let i = 0; i < last; i++) {
624
739
  while (cursor < recording.events.length && recording.events[cursor][0] === i) {
625
740
  const [, method, ...args] = recording.events[cursor];
626
741
  const fn = input[method];
@@ -1,5 +1,5 @@
1
- import { b as Engine } from "./behavior-J1cmVah0.js";
2
- import { s as JsonObject } from "./rng-Bb-IutXB.js";
1
+ import { b as Engine } from "./behavior-B_245qRy.js";
2
+ import { s as JsonObject } from "./rng-BsXZg3D6.js";
3
3
 
4
4
  //#region src/core/audit.d.ts
5
5
  /** Human-readable warnings (empty = clean). Pure JSON walk, no registry. */
@@ -136,6 +136,24 @@ interface ReplayJson {
136
136
  interface Recorder {
137
137
  /** Detach the taps and return the recording. */
138
138
  stop(): ReplayJson;
139
+ /**
140
+ * The tape SO FAR, without ending it.
141
+ *
142
+ * `stop()` was the only way to read a recording, so reading it ended it — and
143
+ * a game that rewinds and keeps playing had to start a second recording whose
144
+ * tick indices restart at zero, then stitch the two together itself.
145
+ */
146
+ take(): ReplayJson;
147
+ /**
148
+ * Drop everything after tick `n` and keep recording from there — the tape
149
+ * half of a REWIND.
150
+ *
151
+ * Returns the recording up to the cut, which is what `replay()` re-runs into
152
+ * a freshly loaded scene: deterministic replay reproduces the world bit for
153
+ * bit, physics included, so a rewind needs no snapshot at all. Clamped to the
154
+ * tape's own ends.
155
+ */
156
+ rewind(n: number): ReplayJson;
139
157
  }
140
158
  /**
141
159
  * Tap the engine's input entry points and tick clock. Recording starts at
@@ -149,6 +167,7 @@ declare function startRecording(engine: Engine): Recorder;
149
167
  */
150
168
  declare function replay(engine: Engine, recording: ReplayJson, opts?: {
151
169
  onTick?: (index: number) => void;
170
+ until?: number;
152
171
  }): void;
153
172
  //#endregion
154
173
  export { startRecording as a, IncantoErrorDetails as c, replay as i, auditScene as l, ReplayEvent as n, IncantoError as o, ReplayJson as r, IncantoErrorCode as s, Recorder as t, instancedScenes as u };
@@ -110,6 +110,13 @@ interface ConnectionJson {
110
110
  group?: string;
111
111
  tag?: JsonObject;
112
112
  };
113
+ /**
114
+ * Arguments to call the handler with, INSTEAD of whatever the signal emitted
115
+ * — `Function.bind` for a wire. `phase2 → Banner.show` with
116
+ * `["ENRAGED", { "color": "#ff5c5c" }]` is a line in the scene file rather
117
+ * than a two-line behaviour whose whole job is one constant.
118
+ */
119
+ args?: JsonValue[];
113
120
  }
114
121
  //#endregion
115
122
  //#region src/core/rng.d.ts
@@ -1,6 +1,6 @@
1
1
  import { n as jsonEquals, t as jsonClone } from "./json-CfTjpvW8.js";
2
- import { J as Node, Y as diagnose, ct as Signal, d as rebuildOptions, h as loadScene, l as markCloned, m as buildSceneSubtree, p as stripUids, u as markTemplateName } from "./save-slots-CEUGeLuB.js";
3
- import { t as registerCoreNodes, v as Engine } from "./register-CjociOtt.js";
2
+ import { $ as Node, _ as stripUids, b as loadScene, et as diagnose, h as rebuildOptions, m as markTemplateName, p as markCloned, pt as Signal, y as buildSceneSubtree } from "./pose-ByFB_J3O.js";
3
+ import { b as Engine, t as registerCoreNodes } from "./register-B768Qedj.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { p as registerNode } from "./touch-CioTZB-y.js";
6
6
  //#region src/net/loopback.ts
@@ -1150,7 +1150,7 @@ function applySyncPatch(root, patch, setProp, onMissingNode) {
1150
1150
  }
1151
1151
  }
1152
1152
  async function createDefaultTransport(config, diagnostics) {
1153
- const { createAgent8Server } = await import("./agent8-CHTT4unP.js").then((n) => n.t);
1153
+ const { createAgent8Server } = await import("./agent8-B-bh3n0X.js").then((n) => n.t);
1154
1154
  return createAgent8Server(config, diagnostics);
1155
1155
  }
1156
1156
  //#endregion
@@ -1217,8 +1217,10 @@ var NetworkSpawner = class extends Node {
1217
1217
  const entries = this.currentEntries(manager);
1218
1218
  for (const [key, state] of Object.entries(entries)) {
1219
1219
  let instance = this.spawned.get(key);
1220
+ const patch = this.patchOf(state);
1220
1221
  if (!instance) {
1221
1222
  if (this.failedKeys.has(key)) continue;
1223
+ if (!patch) continue;
1222
1224
  try {
1223
1225
  const json = manager.resolveScene(this.scene);
1224
1226
  instance = buildSceneSubtree({
@@ -1236,7 +1238,6 @@ var NetworkSpawner = class extends Node {
1236
1238
  this.spawned.set(key, instance);
1237
1239
  this.emit("spawned", instance, key);
1238
1240
  }
1239
- const patch = this.patchOf(state);
1240
1241
  if (patch) applySyncPatch(instance, patch, (target, prop, value) => this.setProp(target, prop, value), (missing) => this.report(`'${missing}' names a node this scene does not have`));
1241
1242
  }
1242
1243
  for (const [key, instance] of [...this.spawned.entries()]) if (!(key in entries)) {
@@ -1283,6 +1284,7 @@ var NetworkSpawner = class extends Node {
1283
1284
  }
1284
1285
  setProp(target, prop, value) {
1285
1286
  if (this.interpolate && prop === "position" && Array.isArray(value)) {
1287
+ if (!this.positionTargets.has(target)) target.position = [...value];
1286
1288
  this.positionTargets.set(target, value);
1287
1289
  return;
1288
1290
  }
@@ -1384,7 +1386,10 @@ async function createSplitScreen(opts) {
1384
1386
  for (let i = 0; i < accounts.length; i++) {
1385
1387
  const account = accounts[i];
1386
1388
  const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + i } : {});
1387
- engine.setScene(loadScene(jsonClone(opts.scene), opts.load));
1389
+ engine.setScene(loadScene(jsonClone(opts.scene), {
1390
+ ...opts.load,
1391
+ engine
1392
+ }));
1388
1393
  const manager = await NetworkManager.create(engine, {
1389
1394
  transport: server.createClient(account),
1390
1395
  ...opts.room !== void 0 || i > 0 ? { room: opts.room ?? players[0].manager.roomId } : {}
@@ -1,7 +1,7 @@
1
- import { Lt as Signal, b as Engine, jt as Node } from "./behavior-J1cmVah0.js";
1
+ import { Lt as Signal, b as Engine, jt as Node } from "./behavior-B_245qRy.js";
2
2
  import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
3
- import { a as SceneJson, c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
4
- import { t as LoadSceneOptions } from "./loader-BAeWMYfF.js";
3
+ import { a as SceneJson, c as JsonValue, s as JsonObject } from "./rng-BsXZg3D6.js";
4
+ import { t as LoadSceneOptions } from "./loader-CbkVdXL8.js";
5
5
 
6
6
  //#region src/net/types.d.ts
7
7
  type Unsubscribe = () => void;
@@ -266,12 +266,14 @@ var ParticleSim = class {
266
266
  const speed = c.speed[0] + this.rng.next() * (c.speed[1] - c.speed[0]);
267
267
  const zTilt = c.spreadZ ? (this.rng.next() - .5) * (c.spreadDeg * Math.PI / 180) : 0;
268
268
  const planar = Math.cos(zTilt);
269
- d[base + 0] = 0;
270
- d[base + 1] = 0;
271
- d[base + 2] = 0;
272
- d[base + 3] = Math.cos(angle) * speed * planar;
273
- d[base + 4] = Math.sin(angle) * speed * planar;
274
- d[base + 5] = Math.sin(zTilt) * speed;
269
+ const box = c.emitBox;
270
+ d[base + 0] = box ? (this.rng.next() * 2 - 1) * box[0] : 0;
271
+ d[base + 1] = box ? (this.rng.next() * 2 - 1) * box[1] : 0;
272
+ d[base + 2] = box ? (this.rng.next() * 2 - 1) * box[2] : 0;
273
+ const drift = c.drift;
274
+ d[base + 3] = Math.cos(angle) * speed * planar + (drift?.[0] ?? 0);
275
+ d[base + 4] = Math.sin(angle) * speed * planar + (drift?.[1] ?? 0);
276
+ d[base + 5] = Math.sin(zTilt) * speed + (drift?.[2] ?? 0);
275
277
  d[base + 6] = 0;
276
278
  d[base + 7] = c.lifetime[0] + this.rng.next() * (c.lifetime[1] - c.lifetime[0]);
277
279
  d[base + 8] = this.rng.next();
@@ -199,6 +199,6 @@ async function preloadSceneAssets(assets, title) {
199
199
  //#endregion
200
200
  //#region src/index.ts
201
201
  /** Engine version. Kept in sync with package.json by the release pipeline. */
202
- const VERSION = "0.74.0";
202
+ const VERSION = "0.76.0";
203
203
  //#endregion
204
204
  export { findPath as a, preloadUrls as i, assetUrls as n, gridFromRows as o, preloadSceneAssets as r, VERSION as t };