incanto 0.61.0 → 0.62.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 (46) hide show
  1. package/bin/incanto-verify.mjs +134 -55
  2. package/dist/2d.d.ts +42 -0
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.js +5 -5
  5. package/dist/{create-game-DH7JI5xx.js → create-game-BpunnGPX.js} +8 -8
  6. package/dist/{create-game-IX5lEH0P.js → create-game-Caut3bqN.js} +6 -6
  7. package/dist/{duplicate-CGqAmK2h.js → duplicate-E4FUs5Bn.js} +1 -1
  8. package/dist/editor.js +39 -18
  9. package/dist/{environment-presets-CNxCuhZF.js → environment-presets-BAWeOeqf.js} +5 -3
  10. package/dist/{gameplay-Dtzd2itW.js → gameplay-CaHqDiQD.js} +126 -15
  11. package/dist/gameplay.js +1 -1
  12. package/dist/index.d.ts +92 -1
  13. package/dist/index.js +6 -6
  14. package/dist/{loader-D7jTvDQv.js → loader-DEe272nY.js} +103 -0
  15. package/dist/net.js +1 -1
  16. package/dist/{physics-2d-CBnor8Zf.js → physics-2d-BXmu2i7W.js} +11 -3
  17. package/dist/{physics-3d-BTUfUSWO.js → physics-3d-ClxP6Uv7.js} +121 -17
  18. package/dist/react.js +1 -1
  19. package/dist/{register-DL3izw8j.js → register-CNh4FlbD.js} +60 -9
  20. package/dist/{register-xuSRyD6b.js → register-D3yx8D4r.js} +158 -2
  21. package/dist/{replay-D7-yle3s.js → replay-DlgHItNv.js} +15 -2
  22. package/dist/{split-screen-5Ban4q4n.js → split-screen-CBM9wcMX.js} +2 -2
  23. package/dist/{src-DozXvyZS.js → src-CH00_JsR.js} +1 -1
  24. package/dist/{teardown-B6rwJOyS.js → teardown-C7qP-dcC.js} +1 -1
  25. package/dist/{test-Dch_7VQD.js → test-BMkg8zMV.js} +27 -15
  26. package/dist/test.d.ts +37 -0
  27. package/dist/test.js +2 -2
  28. package/dist/vite.js +2 -2
  29. package/editor/assets/{agent8-CF1JL2tR.js → agent8-m5mtAO_A.js} +1 -1
  30. package/editor/assets/{debug-3QzYhOPA.js → debug-CPhzCT8f.js} +1 -1
  31. package/editor/assets/{index-CAD2c5ug.js → index-D422P4kW.js} +91 -91
  32. package/editor/index.html +1 -1
  33. package/package.json +1 -1
  34. package/schemas/scene.schema.json +87 -0
  35. package/skills/incanto-behaviors-and-scripts.md +37 -0
  36. package/skills/incanto-building-2d-games.md +20 -1
  37. package/skills/incanto-building-3d-games.md +1 -1
  38. package/skills/incanto-gameplay-behaviors.md +15 -1
  39. package/skills/incanto-node-reference.md +11 -0
  40. package/skills/incanto-physics-and-input.md +23 -2
  41. package/skills/incanto-verifying-your-game.md +31 -1
  42. package/templates-app/beacon-isle-3d/package.json +1 -1
  43. package/templates-app/platformer-2d/package.json +1 -1
  44. package/templates-app/star-survivor/package.json +1 -1
  45. package/templates-app/tps-3d/package.json +1 -1
  46. package/templates-app/village-quest-3d/package.json +1 -1
@@ -1,7 +1,7 @@
1
- import { O as Node, P as Behavior, _ as SaveSlots, k as diagnose, n as loadScene, w as effectiveOrder, z as registerBehavior } from "./loader-D7jTvDQv.js";
1
+ import { O as Node, P as Behavior, _ as SaveSlots, k as diagnose, n as loadScene, w as effectiveOrder, z as registerBehavior } from "./loader-DEe272nY.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { t as jsonClone } from "./json-CwwhxQgb.js";
4
- import { t as duplicateNode } from "./duplicate-CGqAmK2h.js";
4
+ import { t as duplicateNode } from "./duplicate-E4FUs5Bn.js";
5
5
  import { Color, CubeCamera, DepthTexture, DoubleSide, Euler, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
6
6
  //#region src/3d/frustum.ts
7
7
  const scratchFrustum = new Frustum();
@@ -2625,6 +2625,51 @@ var CharacterBody3D = class extends PhysicsBody3D {
2625
2625
  }
2626
2626
  };
2627
2627
  //#endregion
2628
+ //#region src/3d/nodes/unscalable.ts
2629
+ /**
2630
+ * Refuse `scale` on a node that keeps a CPU twin of itself.
2631
+ *
2632
+ * `scale` is a plain `Node3D` prop and the 3D skill actively recommends it —
2633
+ * *"resize with `scale`, not `size` — resizing a mesh rebuilds its geometry
2634
+ * every call, scaling is free"*. On a mesh that is true. On `Terrain3D`,
2635
+ * `Water3D` and `River3D` it resizes only the PICTURE: everything the gameplay
2636
+ * layer reads is scale-blind, because it reads `size`.
2637
+ *
2638
+ * - `Terrain3D.heightAt` indexes a heightmap built from `size` and composes
2639
+ * ancestor POSITIONS only
2640
+ * - `buildHeightfieldDesc3D` passes `{x: hm.width, y: 1, z: hm.depth}` with no
2641
+ * scale term
2642
+ * - `Water3D._waterYAt` rejects anything outside `size / 2` of the node
2643
+ *
2644
+ * Measured on `size: [100, 100]` at `scale: [2, 1, 2]`:
2645
+ *
2646
+ * ```
2647
+ * terrain rendered X extent = [-100.0, 100.0] (was [-50, 50])
2648
+ * terrain heightAt(70, 0) = -50.000 (unchanged: the island floor)
2649
+ * water rendered X extent = [-40.0, 40.0]
2650
+ * water _waterYAt(30, 0) = null (unchanged)
2651
+ * ```
2652
+ *
2653
+ * So the player walks off an invisible cliff at ±50 m onto ground that is
2654
+ * visibly there for another 50; drape, spawn placement and terrain-nav all
2655
+ * agree with the collider and disagree with the picture; a scaled water's
2656
+ * underwater fog, splash and `avoidWater` culling stop at half the visible
2657
+ * shoreline. `incanto check` called the scene valid and `auditScene` said
2658
+ * nothing.
2659
+ *
2660
+ * A hard load error rather than a warning, per the engine's own guardrail: the
2661
+ * dial the author wanted exists and is one word away.
2662
+ */
2663
+ function rejectScale(node, reads) {
2664
+ const scale = node.scale;
2665
+ if (!Array.isArray(scale)) return;
2666
+ if (scale.every((v) => Math.abs((v ?? 1) - 1) < 1e-4)) return;
2667
+ const type = node.constructor.typeName ?? "node";
2668
+ const size = node.size;
2669
+ const suggestion = Array.isArray(size) ? `size: [${size.map((v, i) => (v ?? 0) * (scale[i === 1 ? 2 : i] ?? 1)).join(", ")}]` : "size";
2670
+ throw new IncantoError("BAD_FORMAT", `${type} '${node.name}' has scale ${JSON.stringify(scale)}. On this node scale resizes only the MESH — ${reads} read "size" — so the ground you can see would reach past the ground you can stand on. Resize with "${suggestion}" instead.`, { prop: "scale" });
2671
+ }
2672
+ //#endregion
2628
2673
  //#region src/3d/nodes/water-cutout-3d.ts
2629
2674
  /**
2630
2675
  * A box the water does not enter.
@@ -3155,6 +3200,7 @@ var Water3D = class Water3D extends Node3D {
3155
3200
  }
3156
3201
  /** Loader hook: degenerate planes and bad enums fail at LOAD, not at render. */
3157
3202
  static validateJson(node) {
3203
+ rejectScale(node, "the water surface query, underwater fog, splashes and buoyancy all");
3158
3204
  const w = node;
3159
3205
  if (!((w.size[0] ?? 0) > 0) || !((w.size[1] ?? 0) > 0)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' size must be positive [width, depth] meters, got ${JSON.stringify(w.size)}.`, { prop: "size" });
3160
3206
  if (!WATER_QUALITIES.includes(w.quality)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' quality must be one of [${WATER_QUALITIES.join(", ")}], got '${w.quality}'.`, {
@@ -4388,6 +4434,56 @@ function smoothingFactor(smoothing, dt) {
4388
4434
  const s = Math.min(Math.max(smoothing, 0), .99);
4389
4435
  return s <= 0 ? 1 : 1 - s ** (dt * 60);
4390
4436
  }
4437
+ /**
4438
+ * A node's position with every spatial ancestor's added in.
4439
+ *
4440
+ * The tree composes transforms for DRAWING — three does it for free — and every
4441
+ * piece of engine logic that reads `node.position` gets the LOCAL number
4442
+ * instead. Physics already knew (`worldPosition2D`/`worldPosition3D` in the
4443
+ * adapters); the camera did not, and neither did anything that chases a target.
4444
+ *
4445
+ * A camera parented to the player is the Godot and Phaser idiom, and the
4446
+ * authoring skill explicitly permits it — *"EVERY node type can hold children …
4447
+ * any node works as a grouping container"*. Measured on exactly that scene:
4448
+ *
4449
+ * ```
4450
+ * player world position : [1400, 900]
4451
+ * renderer view centre : {"x":0,"y":0}
4452
+ * framing : camera /Level/Player/Cam centred [1400, 900]
4453
+ * 1 in view, 0 outside it
4454
+ * ```
4455
+ *
4456
+ * The renderer drew empty world 1600 px from the player while `framing` — the
4457
+ * instrument the skills tell you to trust before handing a game back —
4458
+ * certified the player on screen.
4459
+ *
4460
+ * Arity comes from the node itself: a 2D node has two components, a 3D node
4461
+ * three, and an ancestor that is neither contributes nothing.
4462
+ */
4463
+ function worldPosition(node) {
4464
+ const own = node.position;
4465
+ if (!Array.isArray(own)) return [];
4466
+ const out = own.slice();
4467
+ for (let at = node.parent; at; at = at.parent) {
4468
+ const p = at.position;
4469
+ if (!Array.isArray(p)) continue;
4470
+ for (let i = 0; i < out.length; i++) out[i] = (out[i] ?? 0) + (p[i] ?? 0);
4471
+ }
4472
+ return out;
4473
+ }
4474
+ /**
4475
+ * Turn a world position into the local one `node.position` has to hold for the
4476
+ * node to END UP there — the inverse of `worldPosition` for its parent chain.
4477
+ */
4478
+ function localFromWorld(node, world) {
4479
+ const out = world.slice();
4480
+ for (let at = node.parent; at; at = at.parent) {
4481
+ const p = at.position;
4482
+ if (!Array.isArray(p)) continue;
4483
+ for (let i = 0; i < out.length; i++) out[i] = (out[i] ?? 0) - (p[i] ?? 0);
4484
+ }
4485
+ return out;
4486
+ }
4391
4487
  //#endregion
4392
4488
  //#region src/gameplay/chase.ts
4393
4489
  /**
@@ -5062,11 +5158,12 @@ var FollowCamera = class extends Behavior {
5062
5158
  if (!hasPosition$1(cam)) return;
5063
5159
  const target = cam.getNodeOrNull(this.target);
5064
5160
  if (!target || !hasPosition$1(target)) return;
5065
- const desired = add(target.position, this.offset);
5066
- if (!(this.deadzone > 0 && distance$1(cam.position, desired) <= this.deadzone)) {
5161
+ const here = worldPosition(cam);
5162
+ const desired = add(worldPosition(target), this.offset);
5163
+ if (!(this.deadzone > 0 && distance$1(here, desired) <= this.deadzone)) {
5067
5164
  const f = smoothingFactor(this.smoothing, dt);
5068
- if (f >= 1) cam.position = [...desired];
5069
- else cam.position = cam.position.map((c, i) => c + ((desired[i] ?? c) - c) * f);
5165
+ if (f >= 1) cam.position = localFromWorld(cam, desired);
5166
+ else cam.position = localFromWorld(cam, here.map((c, i) => c + ((desired[i] ?? c) - c) * f));
5070
5167
  }
5071
5168
  if (this.shakeT > 0) {
5072
5169
  this.shakeT -= dt;
@@ -6069,12 +6166,12 @@ var Projectile = class extends Behavior {
6069
6166
  return [Math.cos(rad), Math.sin(rad)];
6070
6167
  }
6071
6168
  if (Array.isArray(rot)) {
6072
- const yaw = (rot[1] ?? 0) * Math.PI / 180;
6073
- const pitch = (rot[0] ?? 0) * Math.PI / 180;
6169
+ const ry = (rot[1] ?? 0) * Math.PI / 180;
6170
+ const rx = (rot[0] ?? 0) * Math.PI / 180;
6074
6171
  return normalize([
6075
- -Math.sin(yaw) * Math.cos(pitch),
6076
- Math.sin(pitch),
6077
- -Math.cos(yaw) * Math.cos(pitch)
6172
+ Math.sin(ry),
6173
+ -Math.sin(rx) * Math.cos(ry),
6174
+ Math.cos(rx) * Math.cos(ry)
6078
6175
  ]);
6079
6176
  }
6080
6177
  return [1, 0];
@@ -6360,13 +6457,27 @@ var SpawnSource = class {
6360
6457
  * spawner's node), and return the clone.
6361
6458
  *
6362
6459
  * Placement: with no `at`, the clone keeps the template's authored position;
6363
- * with `at`, it spawns at the spawner's position plus `at` (a non-spatial
6364
- * spawner like a plain Node treats `at` as absolute).
6460
+ * with `at`, it ends up at the spawner's position plus `at`.
6461
+ *
6462
+ * `at` is written as the clone's LOCAL position, because the clone is added
6463
+ * as a CHILD of the spawner's node and that parent's transform already
6464
+ * contributes the spawner's own position. This used to add the two, so the
6465
+ * spawner's position was applied twice and the further from the origin you
6466
+ * put the nest, the further away its enemies appeared. Measured on a spawner
6467
+ * at [820, 240] with `"at": [0, -50]`:
6468
+ *
6469
+ * ```
6470
+ * expected world = [820, 190]
6471
+ * clone WORLD position = [1640, 430] error = [820, 240]
6472
+ * ```
6473
+ *
6474
+ * The scene loads, the spawner spawns, the clone is alive and visible — it is
6475
+ * just somewhere else in the level, and nothing has anything to report.
6365
6476
  */
6366
6477
  spawn(template, opts) {
6367
6478
  const clone = duplicateNode(template);
6368
6479
  const at = opts?.at;
6369
- if (at && at.length > 0 && hasPosition$1(clone)) clone.position = add(hasPosition$1(this.behavior.node) ? this.behavior.node.position : [], at);
6480
+ if (at && at.length > 0 && hasPosition$1(clone)) clone.position = add((opts?.parent ?? this.behavior.node) === this.behavior.node || !hasPosition$1(this.behavior.node) ? [] : this.behavior.node.position, at);
6370
6481
  if (typeof clone.visible === "boolean") clone.visible = true;
6371
6482
  (opts?.parent ?? this.behavior.node).addChild(clone);
6372
6483
  return clone;
@@ -6867,4 +6978,4 @@ function registerGameplayBehaviors(opts) {
6867
6978
  for (const [name, ctor] of Object.entries(GAMEPLAY_BEHAVIORS)) registerBehavior(name, ctor, { replace });
6868
6979
  }
6869
6980
  //#endregion
6870
- export { Health as A, StaticBody3D as B, restartScene as C, DayNight as D, FloatAway as E, WaterCutout3D as F, colliderFootDrop as G, validateCollider3D as H, Area3D as I, CharacterBody3D as L, Chase as M, Water3D as N, phaseOf as O, WATER_CUTOUT_MAX as P, PhysicsBody3D as R, goToScene as S, FollowCamera as T, createCausticsQuad as U, Node3D as V, WATER_MAX_RIPPLES as W, Cooldown as _, Wander as a, Interactable as b, SavePoint as c, Patrol as d, PathFollow as f, CameraShake as g, Lifetime as h, WaveSpawner as i, Collector as j, DamageOnContact as k, Projectile as l, MoveTo as m, registerGameplayBehaviors as n, Spawner as o, Oscillate as p, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, Pickup as u, hitStop as v, tolerateUnknownAction as w, GameFlow as x, screenFlash as y, RigidBody3D as z };
6981
+ export { Health as A, CharacterBody3D as B, restartScene as C, DayNight as D, FloatAway as E, Water3D as F, validateCollider3D as G, RigidBody3D as H, WATER_CUTOUT_MAX as I, colliderFootDrop as J, createCausticsQuad as K, WaterCutout3D as L, Chase as M, localFromWorld as N, phaseOf as O, worldPosition as P, rejectScale as R, goToScene as S, FollowCamera as T, StaticBody3D as U, PhysicsBody3D as V, Node3D as W, Cooldown as _, Wander as a, Interactable as b, SavePoint as c, Patrol as d, PathFollow as f, CameraShake as g, Lifetime as h, WaveSpawner as i, Collector as j, DamageOnContact as k, Projectile as l, MoveTo as m, registerGameplayBehaviors as n, Spawner as o, Oscillate as p, WATER_MAX_RIPPLES as q, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, Pickup as u, hitStop as v, tolerateUnknownAction as w, GameFlow as x, screenFlash as y, Area3D as z };
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as Health, C as restartScene, D as DayNight, E as FloatAway, M as Chase, O as phaseOf, S as goToScene, T as FollowCamera, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as Collector, k as DamageOnContact, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-Dtzd2itW.js";
1
+ import { A as Health, C as restartScene, D as DayNight, E as FloatAway, M as Chase, O as phaseOf, S as goToScene, T as FollowCamera, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as Collector, k as DamageOnContact, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-CaHqDiQD.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.d.ts CHANGED
@@ -624,6 +624,97 @@ declare class UiDialogue extends HudWidgetBase {
624
624
  override update(dt: number): void;
625
625
  }
626
626
  //#endregion
627
+ //#region src/core/nodes/respawn.d.ts
628
+ /**
629
+ * Catch whatever this node hangs off when it leaves the world, and put it back.
630
+ *
631
+ * Nothing in the engine did this. Not a node, not a behavior, not a scene
632
+ * header — and the shipped examples prove what that costs: with the `plays`
633
+ * rung finally counting defects, `basic-3d-sideview` fails 8 seeded runs out of
634
+ * 8, `water-lake-3d` 7, `water-ocean-3d` 5, `water-pool-3d` 4,
635
+ * `water-river-3d` 1. Every one of them the same way — the player walks off the
636
+ * edge of the terrain and falls forever, and the only fix available was a
637
+ * per-frame `y` check hand-written in TypeScript, which is exactly the kind of
638
+ * thing this engine exists to keep out of TypeScript.
639
+ *
640
+ * A CHILD NODE rather than a Behavior, deliberately: a node holds one behavior
641
+ * and the player's is already spoken for (`PlayerControl`, `Health`, the game's
642
+ * own script). Children stack, the way `CharacterController3D` already hangs
643
+ * off its body.
644
+ *
645
+ * ```jsonc
646
+ * { "name": "Player", "type": "RigidBody3D", "props": { … },
647
+ * "children": [
648
+ * { "name": "Ctl", "type": "CharacterController3D" },
649
+ * { "name": "Catch", "type": "Respawn" } // that is the whole thing
650
+ * ] }
651
+ * ```
652
+ *
653
+ * With no props it guards its parent, uses the same line the playtest's fall
654
+ * detector uses (50 m under the spawn in 3D, 1000 px in 2D), and puts the node
655
+ * back where it started. `respawned` fires each time, so a life can be taken
656
+ * off a `ScoreKeeper` or a `Health` with an ordinary connection — this node
657
+ * does not decide what falling COSTS, which is a game-design question and not
658
+ * a physics one.
659
+ */
660
+ declare class Respawn extends Node {
661
+ static override readonly typeName: string;
662
+ static override readonly signals: readonly string[];
663
+ static readonly props: PropSchema;
664
+ /**
665
+ * Who is being caught. Defaults to the parent, which is the whole point of
666
+ * hanging this off the thing it guards.
667
+ */
668
+ target: string;
669
+ /**
670
+ * The line past which the target has left the world, in the scene's own
671
+ * down: 3D counts down (−y), 2D counts down the screen (+y).
672
+ *
673
+ * `null` is AUTO — the spawn position offset by the same distance the
674
+ * playtest's own fall detector uses, so "the level says it fell" and "the
675
+ * ladder says it fell" cannot disagree. Author a number when the level has a
676
+ * real floor to name; leave it alone otherwise.
677
+ */
678
+ below: number | null;
679
+ /** Where to put it back. Empty = wherever it started. */
680
+ to: number[];
681
+ /**
682
+ * Zero the body's velocity on the way back.
683
+ *
684
+ * On by default because the alternative is invisible: a body respawned with
685
+ * 40 m/s of accumulated fall still in it drops straight back through the
686
+ * line on the next frame, and the level flickers instead of resuming.
687
+ */
688
+ resetVelocity: boolean;
689
+ /** How many times this has caught the target — for HUDs and tests. */
690
+ falls: number;
691
+ private home;
692
+ private line;
693
+ /** +1 when "out" means y greater (2D screen-down), −1 when smaller (3D). */
694
+ private down;
695
+ override onReady(): void;
696
+ private spawnY;
697
+ /**
698
+ * Which way is out, and where the line is.
699
+ *
700
+ * Resolved on the first `update` rather than in `onReady`, because
701
+ * `onReady` runs inside `loadScene` — BEFORE `setScene` attaches the engine —
702
+ * so the scene's `dimension` is not readable there yet. Reading it there
703
+ * silently gives every 2D scene the 3D answer, and a line above a falling
704
+ * player fires on frame one, forever.
705
+ *
706
+ * Not called `resolve`: `Node` has a private method by that name and
707
+ * `getNodeOrNull` calls it, so a subclass method with the same name
708
+ * overrides it on the prototype — `private` is a TypeScript fiction that
709
+ * costs nothing at runtime — and every path lookup this node made returned a
710
+ * number. `node-method-shadowing.test.ts` refuses the whole family now.
711
+ */
712
+ private fallLine;
713
+ /** The node this guards, or itself if the path no longer resolves. */
714
+ private guarded;
715
+ override update(_dt: number): void;
716
+ }
717
+ //#endregion
627
718
  //#region src/core/nodes/timer.d.ts
628
719
  /**
629
720
  * The canonical serializable game clock — never `setTimeout` in game logic.
@@ -1174,4 +1265,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
1174
1265
  /** Engine version. Kept in sync with package.json by the release pipeline. */
1175
1266
  declare const VERSION: string;
1176
1267
  //#endregion
1177
- export { type AnimationDef, type AnimationEntry, type AssetFailure, AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, type DriveScript, type DriveStep, type EffectEvent, type EffectKind, EffectLog, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, type OrderGroupTable, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RefNode, type RefProblem, type RefResult, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
1268
+ export { type AnimationDef, type AnimationEntry, type AssetFailure, AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, type DriveScript, type DriveStep, type EffectEvent, type EffectKind, EffectLog, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, type OrderGroupTable, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RefNode, type RefProblem, type RefResult, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, Respawn, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { A as describeRefProblem, B as registeredBehaviors, C as ORDER_GROUP_BASE, E as InputMap, F as behaviorSchema, I as behaviorSignals, L as clearBehaviors, M as resolveRefInJson, N as parseNodePath, O as Node, P as Behavior, R as getBehavior, S as createSaveStore, T as resolveOrderGroups, V as Signal, _ as SaveSlots, a as serializeNode, b as restoreBehaviors, d as computeViewport, f as resolveViewport, g as resolveConstants, h as isConstRef, i as Scene, j as nodeRefWarnings, m as CONST_REF_KEY, n as loadScene, o as SCENE_FORMAT, p as SceneTree, v as behaviorsWithoutSave, w as effectiveOrder, x as savesWithoutUid, y as captureBehaviors, z as registerBehavior } from "./loader-D7jTvDQv.js";
2
- import { A as UiBar, B as ROLLOFF_MODELS, C as readDeviceHints, D as HudLayer, E as EffectLog, F as suggestLocale, G as sfxDuration, H as spatialPan, J as WebAudioMusicBackend, K as synthSfx, L as translationKey, M as BASE_LOCALE, N as Localization, P as T_PREFIX, R as SfxEngine, T as LogManager, U as SFX_PRESETS, V as spatialGain, W as SFX_PRESET_NAMES, X as fadeGain, Y as crossfadeGains, Z as AudioBuses, _ as Engine, a as UiMuteToggle, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as AudioPlayer, h as UiDialogue, i as UiLanguageSelect, j as UiText, k as UiBanner, l as UiSelect, m as UiButton, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as MusicManager, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, w as suggestQuality, x as qualityEnvironment, y as Settings, z as isAudioContextAvailable } from "./register-xuSRyD6b.js";
1
+ import { A as describeRefProblem, B as registeredBehaviors, C as ORDER_GROUP_BASE, E as InputMap, F as behaviorSchema, I as behaviorSignals, L as clearBehaviors, M as resolveRefInJson, N as parseNodePath, O as Node, P as Behavior, R as getBehavior, S as createSaveStore, T as resolveOrderGroups, V as Signal, _ as SaveSlots, a as serializeNode, b as restoreBehaviors, d as computeViewport, f as resolveViewport, g as resolveConstants, h as isConstRef, i as Scene, j as nodeRefWarnings, m as CONST_REF_KEY, n as loadScene, o as SCENE_FORMAT, p as SceneTree, v as behaviorsWithoutSave, w as effectiveOrder, x as savesWithoutUid, y as captureBehaviors, z as registerBehavior } from "./loader-DEe272nY.js";
2
+ import { A as UiBanner, B as isAudioContextAvailable, D as EffectLog, E as LogManager, F as T_PREFIX, G as SFX_PRESET_NAMES, H as spatialGain, I as suggestLocale, J as MusicManager, K as sfxDuration, M as UiText, N as BASE_LOCALE, O as HudLayer, P as Localization, Q as AudioBuses, R as translationKey, S as qualityEnvironment, T as suggestQuality, U as spatialPan, V as ROLLOFF_MODELS, W as SFX_PRESETS, X as crossfadeGains, Y as WebAudioMusicBackend, Z as fadeGain, _ as AudioPlayer, a as UiMuteToggle, b as Settings, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as UiDialogue, h as UiButton, i as UiLanguageSelect, j as UiBar, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as synthSfx, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SfxEngine } from "./register-D3yx8D4r.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-CwwhxQgb.js";
6
6
  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-CF70EArN.js";
7
- import { n as startRecording, r as auditScene, t as replay } from "./replay-D7-yle3s.js";
7
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-DlgHItNv.js";
8
8
  import { a as logReport, i as resolveRendering, n as attachTouchControls, o as logText, r as joystickVector, s as parseDrive, t as TouchControls } from "./touch-DESwnpOc.js";
9
9
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
10
10
  import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
11
- import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-DozXvyZS.js";
12
- import { t as duplicateNode } from "./duplicate-CGqAmK2h.js";
13
- export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
11
+ import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-CH00_JsR.js";
12
+ import { t as duplicateNode } from "./duplicate-E4FUs5Bn.js";
13
+ export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Respawn, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -854,6 +854,92 @@ function nodeTypeMethods(type) {
854
854
  return out;
855
855
  }
856
856
  const methodCache = /* @__PURE__ */ new Map();
857
+ /** Every code the engine can ever receive, keyboard + mouse + gamepad. */
858
+ const INPUT_CODES = new Set([
859
+ ...[..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"].map((c) => `Key${c}`),
860
+ ...Array.from({ length: 10 }, (_, i) => `Digit${i}`),
861
+ ...Array.from({ length: 10 }, (_, i) => `Numpad${i}`),
862
+ "NumpadAdd",
863
+ "NumpadSubtract",
864
+ "NumpadMultiply",
865
+ "NumpadDivide",
866
+ "NumpadDecimal",
867
+ "NumpadEnter",
868
+ ...Array.from({ length: 12 }, (_, i) => `F${i + 1}`),
869
+ "ArrowUp",
870
+ "ArrowDown",
871
+ "ArrowLeft",
872
+ "ArrowRight",
873
+ "Space",
874
+ "Enter",
875
+ "Escape",
876
+ "Tab",
877
+ "Backspace",
878
+ "Delete",
879
+ "Insert",
880
+ "Home",
881
+ "End",
882
+ "PageUp",
883
+ "PageDown",
884
+ "CapsLock",
885
+ "ShiftLeft",
886
+ "ShiftRight",
887
+ "ControlLeft",
888
+ "ControlRight",
889
+ "AltLeft",
890
+ "AltRight",
891
+ "MetaLeft",
892
+ "MetaRight",
893
+ "ContextMenu",
894
+ "Backquote",
895
+ "Minus",
896
+ "Equal",
897
+ "BracketLeft",
898
+ "BracketRight",
899
+ "Backslash",
900
+ "Semicolon",
901
+ "Quote",
902
+ "Comma",
903
+ "Period",
904
+ "Slash",
905
+ "IntlBackslash",
906
+ "IntlRo",
907
+ "IntlYen",
908
+ ...Array.from({ length: 5 }, (_, i) => `Mouse${i}`),
909
+ ...Array.from({ length: 17 }, (_, i) => `Pad${i}`)
910
+ ]);
911
+ /** The one a typo probably meant, or null when nothing is close enough. */
912
+ function nearestCode(written) {
913
+ const lower = written.toLowerCase();
914
+ if (/^[a-z]$/.test(lower)) return `Key${lower.toUpperCase()}`;
915
+ if (/^[0-9]$/.test(written)) return `Digit${written}`;
916
+ if (lower === "shift" || lower === "ctrl" || lower === "control" || lower === "alt") {
917
+ const side = lower === "ctrl" ? "Control" : lower[0]?.toUpperCase() + lower.slice(1);
918
+ return `${side}Left" or "${side}Right`;
919
+ }
920
+ if (lower === "esc") return "Escape";
921
+ if (lower === "return") return "Enter";
922
+ if (lower === "up" || lower === "down" || lower === "left" || lower === "right") return `Arrow${lower[0]?.toUpperCase()}${lower.slice(1)}`;
923
+ let best = null;
924
+ let bestScore = 3;
925
+ for (const code of INPUT_CODES) {
926
+ const d = distance$1(lower, code.toLowerCase());
927
+ if (d < bestScore) {
928
+ bestScore = d;
929
+ best = code;
930
+ }
931
+ }
932
+ return best;
933
+ }
934
+ function distance$1(a, b) {
935
+ const rows = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
936
+ for (let j = 0; j <= b.length; j++) rows[0][j] = j;
937
+ for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) {
938
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
939
+ rows[i][j] = Math.min(rows[i - 1][j] + 1, rows[i][j - 1] + 1, rows[i - 1][j - 1] + cost);
940
+ }
941
+ return rows[a.length][b.length];
942
+ }
857
943
  //#endregion
858
944
  //#region src/core/input.ts
859
945
  /** Keys typed into editable elements belong to the page UI, not the game. */
@@ -1322,6 +1408,21 @@ const DIRECTIONS = [
1322
1408
  "left",
1323
1409
  "right"
1324
1410
  ];
1411
+ /**
1412
+ * Refuse a key code the engine can never receive.
1413
+ *
1414
+ * The map is looked up by EXACT string against what the browser hands us, so
1415
+ * `"W"`, `"space"` and `"Shift"` — the three most natural things to write — bind
1416
+ * to nothing at all. They loaded clean, passed `incanto check`, and left the
1417
+ * player without that control, with nothing anywhere to say so.
1418
+ */
1419
+ function checkCodes(action, where, keys) {
1420
+ for (const key of keys) {
1421
+ if (typeof key !== "string" || INPUT_CODES.has(key)) continue;
1422
+ const guess = nearestCode(key);
1423
+ throw new IncantoError("BAD_FORMAT", `Input action '${action}'${where}: '${key}' is not a key code, so nothing can ever press it${guess ? ` — did you mean "${guess}"?` : ""}. Codes are KeyboardEvent.code values (KeyW, Space, ArrowLeft, ShiftLeft), plus Mouse0..4 and Pad0..16.`, { prop: "keys" });
1424
+ }
1425
+ }
1325
1426
  function parseInputDecls(decls) {
1326
1427
  const out = /* @__PURE__ */ new Map();
1327
1428
  for (const [name, raw] of Object.entries(decls)) {
@@ -1330,6 +1431,7 @@ function parseInputDecls(decls) {
1330
1431
  const keys = decl.keys;
1331
1432
  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.`);
1332
1433
  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)}.`);
1434
+ checkCodes(name, "", keys);
1333
1435
  out.set(name, {
1334
1436
  type: "button",
1335
1437
  keys: [...keys],
@@ -1345,6 +1447,7 @@ function parseInputDecls(decls) {
1345
1447
  }
1346
1448
  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)}.`);
1347
1449
  const d = dirs;
1450
+ for (const dir of DIRECTIONS) checkCodes(name, ` (${dir})`, d[dir]);
1348
1451
  out.set(name, {
1349
1452
  type: "vector2",
1350
1453
  keys: {
package/dist/net.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
2
- import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-5Ban4q4n.js";
2
+ import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-CBM9wcMX.js";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { k as diagnose } from "./loader-D7jTvDQv.js";
2
+ import { k as diagnose } from "./loader-DEe272nY.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-DL3izw8j.js";
4
+ import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-CNh4FlbD.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  import { t as withoutRapierInitNoise } from "./quiet-rapier-BAJ4K94N.js";
7
7
  //#region src/2d/physics/physics-2d.ts
@@ -100,7 +100,10 @@ var Physics2D = class {
100
100
  node.position = [t.x * SCALE - ox, t.y * SCALE - oy];
101
101
  }
102
102
  if (node instanceof RigidBody2D) {
103
- if (!node.fixedRotation) node.rotation = e.body.rotation() / DEG;
103
+ if (!node.fixedRotation) {
104
+ node.rotation = e.body.rotation() / DEG;
105
+ e.lastRot = node.rotation;
106
+ }
104
107
  const lv = e.body.linvel();
105
108
  node.linearVelocity = [lv.x * SCALE, lv.y * SCALE];
106
109
  e.lastVX = node.linearVelocity[0] ?? 0;
@@ -428,6 +431,7 @@ var Physics2D = class {
428
431
  collider,
429
432
  lastX: wx,
430
433
  lastY: wy,
434
+ lastRot: node.rotation,
431
435
  lastVX: 0,
432
436
  lastVY: 0
433
437
  };
@@ -456,6 +460,10 @@ var Physics2D = class {
456
460
  e.lastX = px;
457
461
  e.lastY = py;
458
462
  }
463
+ if (node.rotation !== e.lastRot) {
464
+ e.body.setRotation(node.rotation * DEG, true);
465
+ e.lastRot = node.rotation;
466
+ }
459
467
  if (node instanceof RigidBody2D) {
460
468
  const vx = node.linearVelocity[0] ?? 0;
461
469
  const vy = node.linearVelocity[1] ?? 0;