incanto 0.73.0 → 0.75.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 +743 -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-CAu-ucHg.js → create-game-DVUhHs2B.js} +79 -10
  10. package/dist/{create-game-CbTLFjis.js → create-game-DnwhJ5nW.js} +83 -7
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-O8mxpgRX.d.ts} +1 -1
  13. package/dist/editor.js +1600 -1529
  14. package/dist/env.d.ts +1 -1
  15. package/dist/{environment-presets-BsFJM3v0.js → environment-presets-BP77Dl0K.js} +2005 -60
  16. package/dist/{gameplay-CRte0OVf.js → gameplay-D-JuGura.js} +7719 -4987
  17. package/dist/gameplay.d.ts +578 -34
  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-CovokK7s.js → physics-2d-x3bCrQf0.js} +250 -31
  25. package/dist/{physics-3d-BiK6Emwn.js → physics-3d-Bz2VPQ6D.js} +673 -51
  26. package/dist/{picking-Dtd8JInh.js → picking-Buita5T9.js} +2 -2
  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-CjociOtt.js → register-B768Qedj.js} +596 -2
  31. package/dist/{register-CY9QCCZs.js → register-DxAbTO2T.js} +191 -430
  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-D-dnVrFY.js → src-DLKVc6Hj.js} +1 -1
  39. package/dist/{test-DZmCFz0Q.js → test-HlVUeCSn.js} +487 -82
  40. package/dist/test.d.ts +90 -12
  41. package/dist/test.js +2 -2
  42. package/dist/vite.js +3 -3
  43. package/editor/assets/{agent8-GuZ5u5bi.js → agent8-BXlYM9rv.js} +1 -1
  44. package/editor/assets/{debug-BbxionO2.js → debug-S42XGP4Y.js} +1 -1
  45. package/editor/assets/index-DgrgUKno.js +11046 -0
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +1630 -299
  49. package/skills/incanto-3d-character.md +358 -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 +17 -6
  54. package/skills/incanto-environment.md +227 -2
  55. package/skills/incanto-game-feel.md +60 -1
  56. package/skills/incanto-gameplay-behaviors.md +675 -23
  57. package/skills/incanto-hud.md +116 -0
  58. package/skills/incanto-multiplayer.md +141 -2
  59. package/skills/incanto-node-reference.md +395 -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 +151 -2
  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-CNlTzQbm.js +0 -11046
@@ -143,18 +143,26 @@ for (const name of spawnerScenes) {
143
143
  * Found where the templates put it, or named with `--server`.
144
144
  */
145
145
  let server;
146
+ let serverAt = null;
146
147
  const serverFlag = flag('--server', null);
148
+ // The project root is one level above `src/game.scene.json` and two above
149
+ // `src/scenes/game.scene.json`; the templates use both. Looking only two up
150
+ // found nothing for every online example, said nothing, and ran the match on
151
+ // the bare kernel — `roomState {}`, nobody seated, two cars on one grid slot.
147
152
  const serverGuesses = serverFlag
148
153
  ? [resolve(serverFlag)]
149
- : ['server/src/server.ts', 'server/src/server.js', 'src/server.ts'].map((g) =>
154
+ : ['server/src/server.ts', 'server/src/server.js', 'src/server.ts'].flatMap((g) => [
155
+ resolve(sceneDir, '..', g),
150
156
  resolve(sceneDir, '..', '..', g),
151
- );
157
+ resolve(g),
158
+ ]);
152
159
  for (const at of serverGuesses) {
153
160
  if (!existsSync(at)) continue;
154
161
  try {
155
162
  const mod = await import(pathToFileURL(at).href);
156
163
  server = mod.Server ?? mod.default;
157
164
  if (server) {
165
+ serverAt = at;
158
166
  if (!argv.includes('--json')) console.log(`· server: ${at} (${server.name || 'default'})`);
159
167
  break;
160
168
  }
@@ -171,7 +179,17 @@ if (serverFlag && !server) {
171
179
  console.error(`--server '${serverFlag}' exports neither \`Server\` nor a default.`);
172
180
  process.exit(1);
173
181
  }
182
+ // A multiplayer scene with no server is a game whose rules are not in the
183
+ // run. Not a failure — the bare kernel joins rooms and replicates — but a
184
+ // thing the reader has to know, or every ✓ below is about a game without its referee.
185
+ const serverNote = server
186
+ ? null
187
+ : 'no server loaded — looked for server/src/server.ts beside the project (or --server FILE); ' +
188
+ 'the match ran on the bare kernel, so room state is empty and no remote function exists';
189
+ if (serverNote && !argv.includes('--json')) console.log(`· ${serverNote}`);
174
190
 
191
+ const driveSeconds = Number(flag('--seconds', 5)) || 5;
192
+ const driveFrames = Math.max(1, Math.round(driveSeconds * 60));
175
193
  let report;
176
194
  try {
177
195
  report = await playMultiplayer({
@@ -179,7 +197,7 @@ try {
179
197
  scenes,
180
198
  ...(server ? { server } : {}),
181
199
  players: Number(flag('--players', 2)) || 2,
182
- seconds: Number(flag('--seconds', 5)) || 5,
200
+ seconds: driveSeconds,
183
201
  seed: Number(flag('--seed', 1)) || 1,
184
202
  lateJoin: !argv.includes('--no-late-join'),
185
203
  load: {
@@ -188,11 +206,41 @@ try {
188
206
  },
189
207
  // Move the owner, or every replicated key holds its authored value and two
190
208
  // motionless clients agree about nothing in particular.
209
+ //
210
+ // A PHYSICS body (it has a `linearVelocity`) is driven by velocity, not by
211
+ // writing its position: teleporting a car every frame dropped it through
212
+ // kerbs into the infield, tumbling, and the rung then compared a body the
213
+ // instrument itself had thrown. The last drive frame stops what it started;
214
+ // whatever still coasts after that is the game's, and the comparison gives
215
+ // it one send window of slack.
191
216
  drive: (player, frame) => {
192
217
  const root = player.engine.scene?.root;
193
218
  const owner = root && findOwner(root);
194
219
  if (!owner || !Array.isArray(owner.position)) return;
195
220
  const spin = (player.account.charCodeAt(1) || 1) * 0.7;
221
+ if (Array.isArray(owner.linearVelocity)) {
222
+ // along the body's OWN forward (+z, the +Z-forward rule; a 2D body's
223
+ // +x) — a car dragged sideways fights its tyres and rolls over
224
+ const last = frame === driveFrames - 1;
225
+ const v = owner.linearVelocity;
226
+ const speed = last ? 0 : 3;
227
+ if (v.length === 3) {
228
+ // the heading from the Euler XYZ BASIS, not from `rotation[1]`: a
229
+ // body facing +x sits on the gimbal lock (yaw 90°) and its triple
230
+ // reads `[96, 88, -95]` — the forward column is right either way
231
+ const r = Array.isArray(owner.rotation) ? owner.rotation : [0, 0, 0];
232
+ const rx = ((r[0] ?? 0) * Math.PI) / 180;
233
+ const ry = ((r[1] ?? 0) * Math.PI) / 180;
234
+ const fx = Math.sin(ry);
235
+ const fz = Math.cos(rx) * Math.cos(ry);
236
+ const len = Math.hypot(fx, fz) || 1;
237
+ owner.linearVelocity = [(fx / len) * speed, v[1], (fz / len) * speed];
238
+ } else {
239
+ const a = ((typeof owner.rotation === 'number' ? owner.rotation : 0) * Math.PI) / 180;
240
+ owner.linearVelocity = [Math.cos(a) * speed, Math.sin(a) * speed];
241
+ }
242
+ return;
243
+ }
196
244
  owner.position = owner.position.map((v, i) =>
197
245
  i === 1 && owner.position.length === 3 ? v : Math.sin(frame / 20 + spin + i) * 4,
198
246
  );
@@ -214,7 +262,7 @@ function findOwner(node) {
214
262
  }
215
263
 
216
264
  if (argv.includes('--json')) {
217
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
265
+ process.stdout.write(`${JSON.stringify({ ...report, server: serverAt, serverNote }, null, 2)}\n`);
218
266
  } else {
219
267
  process.stdout.write(`${multiplayText(report)}\n`);
220
268
  }
package/dist/2d.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-BOrk4eQx.js";
2
- import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-J1cmVah0.js";
1
+ import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-BRo2uvG6.js";
2
+ import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats, x as EngineOptions } from "./behavior-B_245qRy.js";
3
3
  import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
4
- import { a as SceneJson, s as JsonObject } from "./rng-Bb-IutXB.js";
5
- import { t as EditorSwitchOptions } from "./editor-switch-CFU9mCec.js";
4
+ import { a as SceneJson, s as JsonObject } from "./rng-BsXZg3D6.js";
5
+ import { t as EditorSwitchOptions } from "./editor-switch-O8mxpgRX.js";
6
6
  import { r as FrameStats } from "./frame-report-DNxDAb1w.js";
7
- import { t as LoadSceneOptions } from "./loader-BAeWMYfF.js";
7
+ import { t as LoadSceneOptions } from "./loader-CbkVdXL8.js";
8
8
  import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
9
9
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
10
10
  import * as RapierNs from "@dimforge/rapier2d-compat";
@@ -246,6 +246,8 @@ declare class RigidBody2D extends PhysicsBody2D {
246
246
  declare class CharacterBody2D extends PhysicsBody2D {
247
247
  static override readonly typeName: string;
248
248
  static override readonly props: PropSchema;
249
+ /** See CharacterBody3D.pushes — a piston or a sweeping arm pushes; a character stops. */
250
+ pushes: number;
249
251
  /**
250
252
  * Renamed to match the 3D body, where `snapToGround` had to give the name
251
253
  * back to `Node3D`'s PLACEMENT prop. 2D has no placement prop, so nothing is
@@ -395,6 +397,8 @@ declare class Physics2D {
395
397
  * read the AUTHORED `mass` prop and hope it was the number Rapier settled on.
396
398
  */
397
399
  massOf(node: PhysicsBody2D): number;
400
+ /** The solver's rotation of a body, in degrees (the node's own unit). */
401
+ rotationOf(node: PhysicsBody2D): number;
398
402
  /**
399
403
  * Current solver velocity in px/s (fresher than the node prop mid-step).
400
404
  *
@@ -450,6 +454,29 @@ declare class Physics2D {
450
454
  private lastRoot;
451
455
  private readonly jointSet;
452
456
  private readonly joints;
457
+ /** The two bodies each live joint ties, so a departed joint can be unlinked. */
458
+ private readonly jointBodies;
459
+ /** What a pin's limits/motor were last pushed as, so a write re-applies. */
460
+ private readonly hingeState;
461
+ /** Joints refused at creation (bodies that disagree about the axis) — said once. */
462
+ private readonly refusedJoints;
463
+ /** A rope's or spring's numbers as last made, so a write REMAKES the joint. */
464
+ private readonly tetherState;
465
+ private tetherKey;
466
+ /**
467
+ * Who is jointed to whom, and whether that pair's contacts are on — see the
468
+ * 3D twin: a joint's own bodies are not the world to the character
469
+ * controller (not its obstacle when the contacts are off, never its ground).
470
+ */
471
+ private readonly jointed;
472
+ private linkJointed;
473
+ private unlinkJointed;
474
+ /**
475
+ * Push a pin's `limits` and motor to the solver — on creation, and again
476
+ * whenever either changes. Limits and `angle` are this body relative to the
477
+ * target; Rapier measures the other way round, hence the flip.
478
+ */
479
+ private applyHinge;
453
480
  private readonly bodySet;
454
481
  /**
455
482
  * Move every body to its node's current transform — outline mode only.
@@ -530,6 +557,14 @@ interface CreateGame2DOptions {
530
557
  */
531
558
  debug?: boolean;
532
559
  /**
560
+ * Start the frame loop as soon as the game is built (default `true`).
561
+ * `false` hands back a game that has not run a frame; call
562
+ * `game.engine.start()` once everything that must exist BEFORE the first
563
+ * frame does — a `NetworkManager`, whose join takes a moment during which
564
+ * every behaviour would otherwise run the game offline. See the 3D twin.
565
+ */
566
+ autoStart?: boolean;
567
+ /**
533
568
  * LIVE EDITING: the ☰ debug menu gains "edit this scene", which turns this
534
569
  * page into the scene editor — same window, no reload — and the editor's play
535
570
  * button turns it back into the game.
@@ -809,6 +844,16 @@ declare class Camera2D extends Node2D {
809
844
  /** [minX, minY, maxX, maxY] world px; empty = unlimited. */
810
845
  limits: number[];
811
846
  current: boolean;
847
+ /**
848
+ * Make THIS the scene's camera and no other: the renderer takes the first
849
+ * camera whose `current` is true in tree order, so setting your own without
850
+ * clearing the gameplay camera's changed nothing visible. A handler a
851
+ * connection can name — `triggerEnter → DoorCam.makeCurrent`.
852
+ */
853
+ makeCurrent(blendSeconds?: number | undefined): void;
854
+ blendSeconds: number;
855
+ /** @internal `makeCurrent(seconds)` — read once by the renderer. */
856
+ _blendRequest: number | undefined;
812
857
  /** `zoom` clamped to a positive floor — 0/negative zoom must never produce NaN views. */
813
858
  get effectiveZoom(): number;
814
859
  /** The `follow` path already reported as untrackable — cleared by a new one. */
@@ -857,10 +902,17 @@ declare class CharacterController2D extends Node {
857
902
  dashSpeed: number;
858
903
  dashSeconds: number;
859
904
  dashAction: string;
905
+ crouchHeight: number;
906
+ crouchSpeedMultiplier: number;
907
+ crouchAction: string;
908
+ /** Read-only: shorter right now — by the key, or by a ceiling. */
909
+ crouching: boolean;
910
+ /** The collider height to stand back up to. */
911
+ private standingHeight;
860
912
  wallSlideSpeed: number;
861
913
  wallJumpImpulse: number[];
862
914
  /** What the character is doing — drive `AnimatedSprite2D.play(state)` off it. */
863
- state: "idle" | "run" | "jump" | "fall" | "wallSlide" | "dash";
915
+ state: "idle" | "run" | "jump" | "fall" | "wallSlide" | "dash" | "crouch" | "sneak";
864
916
  private coyoteLeft;
865
917
  private bufferLeft;
866
918
  private jumpsUsed;
@@ -936,7 +988,7 @@ declare class ColorRect2D extends Node2D {
936
988
  }
937
989
  //#endregion
938
990
  //#region src/2d/nodes/joint-2d.d.ts
939
- type JointType2D = "fixed" | "revolute" | "rope" | "spring";
991
+ type JointType2D = "fixed" | "revolute" | "prismatic" | "rope" | "spring";
940
992
  /**
941
993
  * A physics joint linking its PARENT body to `target` (a node path to the
942
994
  * other body). Godot-style placement: the joint lives as a child of body A.
@@ -956,6 +1008,27 @@ declare class Joint2D extends Node2D {
956
1008
  target: string;
957
1009
  anchor: number[];
958
1010
  targetAnchor: number[];
1011
+ /**
1012
+ * `[min, max]` in degrees for a `revolute` swing; `[]` = no stops. Measured
1013
+ * as `angle` is: this body turned relative to the target, `rotation`'s sign.
1014
+ */
1015
+ limits: number[];
1016
+ /** Degrees a second the motor drives the pin at; `0` = no motor (free). */
1017
+ motorSpeed: number;
1018
+ /** The motor's gain — how quickly it reaches and holds `motorSpeed`. */
1019
+ motorStrength: number;
1020
+ /**
1021
+ * The pin's current angle in degrees — this body's rotation relative to the
1022
+ * target, written by physics every step. `0` unless `revolute` and simulated.
1023
+ */
1024
+ angle: number;
1025
+ /** The line of a `prismatic` slide (local to this body). */
1026
+ axis: number[];
1027
+ /**
1028
+ * A `prismatic` slide's current travel in px — this body's anchor along
1029
+ * `axis` relative to the target's; `0` where the anchors coincide.
1030
+ */
1031
+ travel: number;
959
1032
  /** rope/spring rest length in px; 0 = measure body distance at creation. */
960
1033
  length: number;
961
1034
  stiffness: number;
@@ -1085,6 +1158,8 @@ declare class Particles2D extends Node2D {
1085
1158
  shimmer: number;
1086
1159
  blend: string;
1087
1160
  maxParticles: number;
1161
+ emitBox: number[];
1162
+ drift: number[];
1088
1163
  /** Loader hook: unknown presets and bad blends fail at LOAD. */
1089
1164
  static validateJson(node: Node): void;
1090
1165
  private sim;
@@ -1353,6 +1428,7 @@ declare class Renderer2D {
1353
1428
  cy: number;
1354
1429
  zoom: number;
1355
1430
  } | null;
1431
+ private readonly viewBlend;
1356
1432
  private readonly webgl;
1357
1433
  /**
1358
1434
  * The pixels of the next drawn frame — how `incanto-frame` sees a 2D game.
package/dist/2d.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
2
  import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
3
3
  import { t as isWebGLAvailable } from "./webgl-unavailable-C8aDbGmR.js";
4
- import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-CbTLFjis.js";
5
- import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-CY9QCCZs.js";
6
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-CovokK7s.js";
4
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-DnwhJ5nW.js";
5
+ import { _ as Node2D, f as Area2D, g as StaticBody2D, h as RigidBody2D, m as PhysicsBody2D, p as CharacterBody2D } from "./gameplay-D-JuGura.js";
6
+ import { a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, i as mergeSolidRects, l as CharacterController2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D } from "./register-DxAbTO2T.js";
7
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-x3bCrQf0.js";
7
8
  //#region src/2d/library-sprite.ts
8
9
  /**
9
10
  * What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in