incanto 0.11.0 → 0.13.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 (83) hide show
  1. package/README.md +22 -0
  2. package/bin/incanto-new.mjs +87 -0
  3. package/dist/2d.d.ts +2 -2
  4. package/dist/2d.js +3 -3
  5. package/dist/3d.d.ts +246 -71
  6. package/dist/3d.js +143 -4
  7. package/dist/{behavior-Bod1AyJS.d.ts → behavior-CPibUfnH.d.ts} +4 -2
  8. package/dist/{create-game-CdWhP6ve.js → create-game-Ct86zczq.js} +5 -5
  9. package/dist/{create-game-DZ_4Nfq8.js → create-game-J6g4QInv.js} +75 -24
  10. package/dist/debug.d.ts +1 -1
  11. package/dist/{duplicate-BEvGBtb_.js → duplicate-BINwP0WG.js} +1 -1
  12. package/dist/{gameplay-Dn3yidJw.js → gameplay-sSqrUcnz.js} +75 -3
  13. package/dist/gameplay.d.ts +37 -2
  14. package/dist/gameplay.js +2 -2
  15. package/dist/index.d.ts +3 -30
  16. package/dist/index.js +4 -4
  17. package/dist/{loader-B4OEXDZ8.js → loader-BZqOKfI2.js} +3 -1
  18. package/dist/{loader-BcUDfNHn.d.ts → loader-DILt9PGC.d.ts} +1 -1
  19. package/dist/net.d.ts +1 -1
  20. package/dist/net.js +3 -3
  21. package/dist/{audio-player-Dyjg5k92.d.ts → pathfinding-RWYkNKx9.d.ts} +29 -2
  22. package/dist/{physics-2d-DweTURFt.js → physics-2d-BiIdl51r.js} +8 -4
  23. package/dist/{physics-3d-Br97Ans2.js → physics-3d-DhaAaKJo.js} +8 -4
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-Cwc-yCYx.js → register-BFFE1Mh1.js} +2 -2
  27. package/dist/{register-ysLR3WQS.js → register-BFw4c83z.js} +508 -123
  28. package/dist/{register-BD6zgrv_.js → register-Djwckzgx.js} +2 -2
  29. package/dist/{register-DJZXxwAn.js → register-nObreUQR.js} +1 -1
  30. package/dist/test.d.ts +2 -2
  31. package/dist/test.js +10 -10
  32. package/editor/assets/{agent8-BxNvAcLA.js → agent8-BF8Jfbkl.js} +1 -1
  33. package/editor/assets/{index-DMCQXowF.js → index-DSq0gdhE.js} +53 -41
  34. package/editor/index.html +1 -1
  35. package/package.json +4 -2
  36. package/schemas/scene.schema.json +263 -0
  37. package/skills/incanto-3d-character.md +37 -1
  38. package/skills/incanto-building-3d-games.md +83 -0
  39. package/skills/incanto-editor.md +3 -3
  40. package/skills/incanto-gameplay-behaviors.md +23 -1
  41. package/skills/incanto-node-reference.md +51 -0
  42. package/templates-app/beacon-isle-3d/PROJECT/Context.md +44 -0
  43. package/templates-app/beacon-isle-3d/PROJECT/Requirements.md +16 -0
  44. package/templates-app/beacon-isle-3d/PROJECT/Status.md +38 -0
  45. package/templates-app/beacon-isle-3d/PROJECT/Structure.md +24 -0
  46. package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +38 -0
  47. package/templates-app/beacon-isle-3d/generate-world.ts +814 -0
  48. package/templates-app/beacon-isle-3d/index.html +68 -0
  49. package/templates-app/beacon-isle-3d/package.json +26 -0
  50. package/templates-app/beacon-isle-3d/src/behaviors.ts +570 -0
  51. package/templates-app/beacon-isle-3d/src/game.scene.json +2992 -0
  52. package/templates-app/beacon-isle-3d/src/main.ts +33 -0
  53. package/templates-app/beacon-isle-3d/tsconfig.json +13 -0
  54. package/templates-app/beacon-isle-3d/verify.ts +285 -0
  55. package/templates-app/beacon-isle-3d/vite.config.ts +5 -0
  56. package/templates-app/tps-3d/PROJECT/Context.md +49 -0
  57. package/templates-app/tps-3d/PROJECT/Requirements.md +38 -0
  58. package/templates-app/tps-3d/PROJECT/Status.md +41 -0
  59. package/templates-app/tps-3d/PROJECT/Structure.md +44 -0
  60. package/templates-app/tps-3d/docs/project-3d-rules.md +38 -0
  61. package/templates-app/tps-3d/index.html +244 -0
  62. package/templates-app/tps-3d/package.json +25 -0
  63. package/templates-app/tps-3d/src/behaviors.ts +405 -0
  64. package/templates-app/tps-3d/src/game.scene.json +677 -0
  65. package/templates-app/tps-3d/src/main.ts +64 -0
  66. package/templates-app/tps-3d/tsconfig.json +13 -0
  67. package/templates-app/tps-3d/verify.ts +167 -0
  68. package/templates-app/tps-3d/vite.config.ts +5 -0
  69. package/templates-app/village-quest-3d/PROJECT/Context.md +39 -0
  70. package/templates-app/village-quest-3d/PROJECT/Requirements.md +36 -0
  71. package/templates-app/village-quest-3d/PROJECT/Status.md +26 -0
  72. package/templates-app/village-quest-3d/PROJECT/Structure.md +30 -0
  73. package/templates-app/village-quest-3d/docs/project-3d-rules.md +38 -0
  74. package/templates-app/village-quest-3d/index.html +68 -0
  75. package/templates-app/village-quest-3d/package.json +25 -0
  76. package/templates-app/village-quest-3d/src/behaviors.ts +529 -0
  77. package/templates-app/village-quest-3d/src/grove.scene.json +826 -0
  78. package/templates-app/village-quest-3d/src/main.ts +35 -0
  79. package/templates-app/village-quest-3d/src/quest.ts +18 -0
  80. package/templates-app/village-quest-3d/src/village.scene.json +680 -0
  81. package/templates-app/village-quest-3d/tsconfig.json +13 -0
  82. package/templates-app/village-quest-3d/verify.ts +254 -0
  83. package/templates-app/village-quest-3d/vite.config.ts +5 -0
package/README.md CHANGED
@@ -3,6 +3,28 @@
3
3
  **Vibe-coding-first web game engine SDK.** Every scene, character, event, and multiplayer
4
4
  rule is JSON an AI agent can read, diff, and rewrite — rendered by three.js.
5
5
 
6
+ ## Start with a whole game
7
+
8
+ ```bash
9
+ bunx incanto-new my-game # Beacon Isle — the flagship 3D template
10
+ cd my-game && bun install && bun run dev
11
+ ```
12
+
13
+ One command scaffolds a complete island action-adventure: a **generated
14
+ golden-hour world** (terrain, sea, groves, grass — `bun run world` re-rolls the
15
+ whole island deterministically), a quest NPC with dialogue and choices, enemies
16
+ that **hunt you across the terrain** via grid navigation, melee with a sword
17
+ riding the character's hand bone, bloom + color-grade, touch controls on
18
+ phones, win fireworks and a death/respawn loop — in ~2 files of game code, the
19
+ rest declared in JSON. `bun run verify` plays the entire quest **headlessly**
20
+ and replays recorded input bit-identically: the agent loop is author → verify →
21
+ fix, no browser needed.
22
+
23
+ `bunx incanto-new --list` shows the other starters (third-person shooter,
24
+ quest vignette).
25
+
26
+ ## Or wire the engine yourself
27
+
6
28
  ```bash
7
29
  bun add incanto three
8
30
  ```
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * incanto-new — scaffold a ready-to-run incanto game from a shipped template:
4
+ *
5
+ * bunx incanto-new my-game # Beacon Isle (3D flagship)
6
+ * bunx incanto-new my-game --template tps-3d # third-person shooter
7
+ * bunx incanto-new --list
8
+ *
9
+ * Copies the template, names the package after the directory, prints the
10
+ * three commands that get you playing. Templates are real npm consumers —
11
+ * no monorepo, no aliases.
12
+ */
13
+ import { cpSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { basename, dirname, join, resolve } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const PKG = join(dirname(fileURLToPath(import.meta.url)), '..');
18
+ const TEMPLATES_DIR = join(PKG, 'templates-app');
19
+
20
+ const DESCRIPTIONS = {
21
+ 'beacon-isle-3d':
22
+ '3D island action-adventure (the flagship): generated world, quest NPC, terrain-nav enemies, melee',
23
+ 'village-quest-3d': '3D quest vignette: dialogue, scene transitions, patrol AI, sword combat',
24
+ 'tps-3d': 'third-person arena shooter: GLB soldier, hitscan rifle, enemy waves',
25
+ };
26
+
27
+ function listTemplates() {
28
+ if (!existsSync(TEMPLATES_DIR)) return [];
29
+ return readdirSync(TEMPLATES_DIR, { withFileTypes: true })
30
+ .filter((e) => e.isDirectory())
31
+ .map((e) => e.name)
32
+ .sort();
33
+ }
34
+
35
+ const argv = process.argv.slice(2);
36
+ if (argv.includes('--list') || argv.includes('-l')) {
37
+ for (const t of listTemplates()) {
38
+ console.log(` ${t.padEnd(20)} ${DESCRIPTIONS[t] ?? ''}`);
39
+ }
40
+ process.exit(0);
41
+ }
42
+
43
+ let target;
44
+ let template = 'beacon-isle-3d';
45
+ for (let i = 0; i < argv.length; i++) {
46
+ if (argv[i] === '--template' || argv[i] === '-t') template = argv[++i];
47
+ else if (!argv[i].startsWith('-') && !target) target = argv[i];
48
+ }
49
+
50
+ if (!target) {
51
+ console.error('usage: incanto-new <dir> [--template <name>] | --list');
52
+ process.exit(1);
53
+ }
54
+
55
+ const templates = listTemplates();
56
+ if (!templates.includes(template)) {
57
+ console.error(`unknown template '${template}'. Available: ${templates.join(', ')}`);
58
+ process.exit(1);
59
+ }
60
+
61
+ const dest = resolve(process.cwd(), target);
62
+ if (existsSync(dest)) {
63
+ console.error(`refusing to scaffold: '${dest}' already exists.`);
64
+ process.exit(1);
65
+ }
66
+
67
+ cpSync(join(TEMPLATES_DIR, template), dest, { recursive: true });
68
+
69
+ const pkgPath = join(dest, 'package.json');
70
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
71
+ pkg.name = basename(dest)
72
+ .toLowerCase()
73
+ .replace(/[^a-z0-9-]/g, '-');
74
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
75
+
76
+ console.log(`
77
+ ${template} → ${target}
78
+
79
+ cd ${target}
80
+ bun install # or: npm install
81
+ bun run dev # play it
82
+ bun run verify # prove it plays, headlessly
83
+ bun run check # validate the scene JSON
84
+
85
+ Vibe-coding? Install the agent skills too:
86
+ bunx incanto-skills claude # or: opencode
87
+ `);
package/dist/2d.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { s as JsonObject } from "./schema-CcoWb32N.js";
2
- import { $ as Node, C as RendererStats, S as GameStats, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-Bod1AyJS.js";
3
- import { t as LoadSceneOptions } from "./loader-BcUDfNHn.js";
2
+ import { $ as Node, C as RendererStats, S as GameStats, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-CPibUfnH.js";
3
+ import { t as LoadSceneOptions } from "./loader-DILt9PGC.js";
4
4
  import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
5
5
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
6
6
  import * as RapierNs from "@dimforge/rapier2d-compat";
package/dist/2d.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-CdWhP6ve.js";
3
- 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-BD6zgrv_.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-DweTURFt.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-Ct86zczq.js";
3
+ 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-Djwckzgx.js";
4
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BiIdl51r.js";
5
5
  //#region src/2d/library-sprite.ts
6
6
  /**
7
7
  * Turn a library sprite-animation JSON into a scene-ready spritesheet asset
package/dist/3d.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { s as JsonObject } from "./schema-CcoWb32N.js";
2
- import { $ as Node, C as RendererStats, S as GameStats, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-Bod1AyJS.js";
3
- import { t as LoadSceneOptions } from "./loader-BcUDfNHn.js";
2
+ import { $ as Node, C as RendererStats, S as GameStats, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-CPibUfnH.js";
3
+ import { t as LoadSceneOptions } from "./loader-DILt9PGC.js";
4
4
  import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
5
- import { r as SpatialPose } from "./audio-player-Dyjg5k92.js";
5
+ import { n as PathGrid, s as SpatialPose } from "./pathfinding-RWYkNKx9.js";
6
6
  import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
7
7
  import { VRM } from "@pixiv/three-vrm";
8
8
  import { Sky } from "three/examples/jsm/objects/Sky.js";
@@ -474,6 +474,14 @@ interface BloomEnvironment {
474
474
  /** How strongly the blurred glow is added back (0 = off, ~1.2 = neon). */
475
475
  strength: number;
476
476
  }
477
+ interface PostEnvironment {
478
+ /** Corner darkening, 0 (off) … 1 (heavy). Default 0. */
479
+ vignette: number;
480
+ /** Color saturation multiplier: 0 = grayscale, 1 = neutral. Default 1. */
481
+ saturation: number;
482
+ /** Contrast around mid-gray: 1 = neutral. Default 1. */
483
+ contrast: number;
484
+ }
477
485
  interface Environment3DConfig {
478
486
  /** Tone-mapping exposure (ACES), default 1. */
479
487
  exposure: number;
@@ -485,6 +493,8 @@ interface Environment3DConfig {
485
493
  clouds: CloudsEnvironment | null;
486
494
  /** Bloom post pass (bright-pass → blur → additive composite), or null. */
487
495
  bloom: BloomEnvironment | null;
496
+ /** Color grade + vignette (tone-mapped composite tail), or null. */
497
+ post: PostEnvironment | null;
488
498
  /**
489
499
  * `null` = not declared (shadow maps stay available, free until a light
490
500
  * casts), `false` = force-disabled, object = declared ON with quality knobs
@@ -567,6 +577,8 @@ declare class Environment3D {
567
577
  get clouds(): CloudsEnvironment | null;
568
578
  /** Parsed bloom config, or null when no bloom pass is declared. */
569
579
  get bloom(): BloomEnvironment | null;
580
+ /** Parsed color-grade/vignette config, or null when not declared. */
581
+ get post(): PostEnvironment | null;
570
582
  /** The live scene fog (color/near/far) the cloud composite fades into, or null. */
571
583
  get sceneFog(): {
572
584
  color: Color;
@@ -606,6 +618,21 @@ declare class Environment3D {
606
618
  dispose(): void;
607
619
  }
608
620
  //#endregion
621
+ //#region src/3d/environment-runtime.d.ts
622
+ /**
623
+ * Live environment editing — the renderer re-applies `scene.environment`
624
+ * every frame (cheap when unchanged), so changing the LOOK at runtime is
625
+ * just writing that object. This helper does it safely:
626
+ *
627
+ * setEnvironment3D(engine, { sky: { elevationDeg: 4 }, exposure: 0.8 });
628
+ * setEnvironment3D(engine, { fog: null }); // null DELETES a key
629
+ *
630
+ * - one-level deep merge (patching `sky.elevationDeg` keeps the other sky keys)
631
+ * - hard-validates the merged result NOW (in your call stack), so a bad patch
632
+ * throws where you wrote it — never inside the render loop
633
+ */
634
+ declare function setEnvironment3D(engine: Engine, patch: JsonObject): void;
635
+ //#endregion
609
636
  //#region src/3d/nodes/billboard-3d.d.ts
610
637
  /** Valid `Billboard3D.mode` values — drives the prop options AND validateJson. */
611
638
  declare const BILLBOARD_GROUP_MODES: readonly ["screen", "y", "none"];
@@ -680,6 +707,37 @@ declare class BoneAttachment3D extends Node3D {
680
707
  override free(): void;
681
708
  }
682
709
  //#endregion
710
+ //#region src/3d/nodes/bone-look-at-3d.d.ts
711
+ /**
712
+ * Look-at IK for one bone — the character's head (or chest, or turret) turns
713
+ * toward a target node ON TOP of whatever the animation is doing. Runs after
714
+ * the mixer each rendered frame, blends in/out smoothly, and DISENGAGES when
715
+ * the target leaves the comfort cone (no owl necks).
716
+ *
717
+ * { "name": "HeadTrack", "type": "BoneLookAt3D",
718
+ * "props": { "target": "../Skin", "bone": "Head", "lookAt": "%Player" } }
719
+ *
720
+ * `forwardAxis` names the bone's local facing axis (mixamo heads face +z,
721
+ * the default). Purely visual — headless it's a no-op.
722
+ */
723
+ declare class BoneLookAt3D extends Node3D {
724
+ static override readonly typeName: string;
725
+ static override readonly props: PropSchema;
726
+ target: string;
727
+ bone: string;
728
+ lookAt: string;
729
+ maxAngleDeg: number;
730
+ weight: number;
731
+ forwardAxis: string;
732
+ private boneObj;
733
+ private lookedUpFor;
734
+ /** 0..1 engage blend — eases in/out so tracking never snaps. */
735
+ private engage;
736
+ /** Test hook. */
737
+ get attachedBone(): Object3D | null;
738
+ _onRender3D(_ctx: RenderContext3D): void;
739
+ }
740
+ //#endregion
683
741
  //#region src/3d/nodes/camera-3d.d.ts
684
742
  /**
685
743
  * A perspective camera. Mark exactly one camera `current: true`; with none
@@ -1064,73 +1122,6 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
1064
1122
  override free(): void;
1065
1123
  }
1066
1124
  //#endregion
1067
- //#region src/3d/nodes/joint-3d.d.ts
1068
- type JointType3D = "fixed" | "spherical" | "rope" | "spring";
1069
- /**
1070
- * A physics joint linking its PARENT body to `target` (a node path to the
1071
- * other body). Godot-style placement: the joint lives as a child of body A.
1072
- *
1073
- * { "name": "Hinge", "type": "Joint3D",
1074
- * "props": { "type": "spherical", "target": "%Anchor", "anchor": [0, -20] } }
1075
- *
1076
- * Types: `fixed` welds the bodies rigidly · `spherical` is a ball joint at the
1077
- * anchors · `rope` caps the anchor distance at `length` (m; 0 = measured at
1078
- * creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
1079
- * Anchors are LOCAL meter offsets on each body.
1080
- */
1081
- declare class Joint3D extends Node3D {
1082
- static override readonly typeName: string;
1083
- static override readonly props: PropSchema;
1084
- type: JointType3D;
1085
- target: string;
1086
- anchor: number[];
1087
- targetAnchor: number[];
1088
- /** rope/spring rest length in m; 0 = measure body distance at creation. */
1089
- length: number;
1090
- stiffness: number;
1091
- damping: number;
1092
- override onEnterTree(): void;
1093
- /** @internal The two bodies, resolved (throws on a bad target at step time). */
1094
- _resolveBodies(): {
1095
- a: PhysicsBody3D;
1096
- b: PhysicsBody3D;
1097
- };
1098
- }
1099
- //#endregion
1100
- //#region src/3d/nodes/lights-3d.d.ts
1101
- /** Sun-style light. Direction comes from the node's rotation. */
1102
- declare class DirectionalLight3D extends Node3D {
1103
- static override readonly typeName: string;
1104
- static override readonly props: PropSchema;
1105
- color: string;
1106
- intensity: number;
1107
- castShadow: boolean;
1108
- /** Half-extent of the directional shadow frustum (template default ±75). */
1109
- shadowArea: number;
1110
- shadowMapSize: number;
1111
- /**
1112
- * Re-center the shadow frustum on the camera every frame. A directional sun's
1113
- * shadow map covers a fixed ortho box; in a big roaming world that box must be
1114
- * huge (and so low-resolution that the player's own shadow nearly vanishes).
1115
- * With this on, a SMALL `shadowArea` tracks the camera, so contact shadows stay
1116
- * crisp wherever you go. The renderer supplies the camera position.
1117
- */
1118
- shadowFollowsCamera: boolean;
1119
- protected override _createObject3D(): Object3D;
1120
- override _syncObject3D(): void;
1121
- }
1122
- /** Point light with a range (Godot's OmniLight3D). */
1123
- declare class OmniLight3D extends Node3D {
1124
- static override readonly typeName: string;
1125
- static override readonly props: PropSchema;
1126
- color: string;
1127
- intensity: number;
1128
- /** 0 = unlimited (three `distance` semantics). */
1129
- range: number;
1130
- protected override _createObject3D(): Object3D;
1131
- override _syncObject3D(): void;
1132
- }
1133
- //#endregion
1134
1125
  //#region src/3d/nodes/mesh-instance-3d.d.ts
1135
1126
  type MeshKind = "box" | "sphere" | "capsule" | "plane" | "cylinder" | "gem";
1136
1127
  interface MeshMaterialProps {
@@ -1208,6 +1199,109 @@ declare class MeshInstance3D extends Node3D {
1208
1199
  override free(): void;
1209
1200
  }
1210
1201
  //#endregion
1202
+ //#region src/3d/nodes/instanced-mesh-3d.d.ts
1203
+ /**
1204
+ * Hundreds of copies of one mesh in ONE draw call — scattered rocks, fence
1205
+ * posts, pillars, crates, gravestones. Same `mesh`/`size`/`material` surface
1206
+ * as MeshInstance3D; `transforms` places the copies:
1207
+ *
1208
+ * { "name": "Rocks", "type": "InstancedMesh3D",
1209
+ * "props": { "mesh": "gem", "size": [0.7, 0.5, 0.7],
1210
+ * "material": { "color": "#8a8f98", "roughness": 0.9, "flatShading": true },
1211
+ * "transforms": [[12, 0.2, -8, 40, 1.3], [-6, 0.1, 14, 210, 0.8]] } }
1212
+ *
1213
+ * Each row is `[x, y, z, yawDeg?, scale?]` (yaw 0 and scale 1 when omitted) —
1214
+ * dense enough to generate hundreds inline. REPLACE the array to update
1215
+ * (mutations are not watched).
1216
+ */
1217
+ declare class InstancedMesh3D extends Node3D {
1218
+ static override readonly typeName: string;
1219
+ static override readonly props: PropSchema;
1220
+ mesh: string;
1221
+ size: number[];
1222
+ material: MeshMaterialProps;
1223
+ castShadow: boolean;
1224
+ receiveShadow: boolean;
1225
+ private _transforms;
1226
+ private transformsDirty;
1227
+ get transforms(): number[][];
1228
+ set transforms(value: number[][]);
1229
+ private instanced;
1230
+ private builtKey;
1231
+ static validateJson(node: Node): void;
1232
+ /** Test hook: the live instance count. */
1233
+ get instanceCount(): number;
1234
+ override _syncObject3D(alpha?: number): void;
1235
+ override free(): void;
1236
+ }
1237
+ //#endregion
1238
+ //#region src/3d/nodes/joint-3d.d.ts
1239
+ type JointType3D = "fixed" | "spherical" | "rope" | "spring";
1240
+ /**
1241
+ * A physics joint linking its PARENT body to `target` (a node path to the
1242
+ * other body). Godot-style placement: the joint lives as a child of body A.
1243
+ *
1244
+ * { "name": "Hinge", "type": "Joint3D",
1245
+ * "props": { "type": "spherical", "target": "%Anchor", "anchor": [0, -20] } }
1246
+ *
1247
+ * Types: `fixed` welds the bodies rigidly · `spherical` is a ball joint at the
1248
+ * anchors · `rope` caps the anchor distance at `length` (m; 0 = measured at
1249
+ * creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
1250
+ * Anchors are LOCAL meter offsets on each body.
1251
+ */
1252
+ declare class Joint3D extends Node3D {
1253
+ static override readonly typeName: string;
1254
+ static override readonly props: PropSchema;
1255
+ type: JointType3D;
1256
+ target: string;
1257
+ anchor: number[];
1258
+ targetAnchor: number[];
1259
+ /** rope/spring rest length in m; 0 = measure body distance at creation. */
1260
+ length: number;
1261
+ stiffness: number;
1262
+ damping: number;
1263
+ override onEnterTree(): void;
1264
+ /** @internal The two bodies, resolved (throws on a bad target at step time). */
1265
+ _resolveBodies(): {
1266
+ a: PhysicsBody3D;
1267
+ b: PhysicsBody3D;
1268
+ };
1269
+ }
1270
+ //#endregion
1271
+ //#region src/3d/nodes/lights-3d.d.ts
1272
+ /** Sun-style light. Direction comes from the node's rotation. */
1273
+ declare class DirectionalLight3D extends Node3D {
1274
+ static override readonly typeName: string;
1275
+ static override readonly props: PropSchema;
1276
+ color: string;
1277
+ intensity: number;
1278
+ castShadow: boolean;
1279
+ /** Half-extent of the directional shadow frustum (template default ±75). */
1280
+ shadowArea: number;
1281
+ shadowMapSize: number;
1282
+ /**
1283
+ * Re-center the shadow frustum on the camera every frame. A directional sun's
1284
+ * shadow map covers a fixed ortho box; in a big roaming world that box must be
1285
+ * huge (and so low-resolution that the player's own shadow nearly vanishes).
1286
+ * With this on, a SMALL `shadowArea` tracks the camera, so contact shadows stay
1287
+ * crisp wherever you go. The renderer supplies the camera position.
1288
+ */
1289
+ shadowFollowsCamera: boolean;
1290
+ protected override _createObject3D(): Object3D;
1291
+ override _syncObject3D(): void;
1292
+ }
1293
+ /** Point light with a range (Godot's OmniLight3D). */
1294
+ declare class OmniLight3D extends Node3D {
1295
+ static override readonly typeName: string;
1296
+ static override readonly props: PropSchema;
1297
+ color: string;
1298
+ intensity: number;
1299
+ /** 0 = unlimited (three `distance` semantics). */
1300
+ range: number;
1301
+ protected override _createObject3D(): Object3D;
1302
+ override _syncObject3D(): void;
1303
+ }
1304
+ //#endregion
1211
1305
  //#region src/3d/nodes/loft-mesh-3d.d.ts
1212
1306
  /**
1213
1307
  * One station of a loft: the hull's cross-section at local `z`.
@@ -1281,6 +1375,16 @@ declare class ModelInstance3D extends Node3D {
1281
1375
  castShadow: boolean;
1282
1376
  /** false = play the clip once, then emit `animationFinished`. */
1283
1377
  animationLoop: boolean;
1378
+ /**
1379
+ * Two-layer animation: play this clip on the UPPER BODY ONLY (the
1380
+ * `upperBodyRoot` bone subtree) while `animation` keeps driving the rest —
1381
+ * attack while running, wave while walking. One-shot by default
1382
+ * (`animationUpperLoop: false`): finishes, emits `animationFinished` with
1383
+ * this key, and auto-clears back to ''.
1384
+ */
1385
+ animationUpper: string;
1386
+ animationUpperLoop: boolean;
1387
+ upperBodyRoot: string;
1284
1388
  receiveShadow: boolean;
1285
1389
  /** Hex colour multiplied into every material — RESKIN one shared GLB into many
1286
1390
  * variants (e.g. a sickly-green zombie from the base human). '' = untinted. */
@@ -1299,6 +1403,12 @@ declare class ModelInstance3D extends Node3D {
1299
1403
  private mixer;
1300
1404
  private playing;
1301
1405
  private currentAction;
1406
+ private playingUpper;
1407
+ private upperAction;
1408
+ /** Which variant the BASE action is playing ('lower' while a layer is on). */
1409
+ private baseMode;
1410
+ /** Masked-clip cache: source clip uuid + layer root → {upper, lower}. */
1411
+ private readonly maskCache;
1302
1412
  /** action → the `animation` key that played it, so `animationFinished` reports
1303
1413
  * the clip that ACTUALLY finished (a crossfaded-away one-shot can finish a few
1304
1414
  * frames after `this.animation` already moved on). */
@@ -1340,7 +1450,15 @@ declare class ModelInstance3D extends Node3D {
1340
1450
  private applyMaterialMods;
1341
1451
  /** Re-apply every queued named-node pose onto the mounted instance. */
1342
1452
  private applyPoses;
1453
+ /** Resolve a clip ref: embedded name → '$asset' / URL (VRM-retargeted when
1454
+ * needed). 'loading' = asset still fetching, try again next frame. */
1455
+ private resolveClip;
1456
+ /** Masked upper/lower variants of a clip for two-layer playback (cached). */
1457
+ private layerVariants;
1458
+ private upperLayerActive;
1343
1459
  private syncAnimation;
1460
+ /** The upper-body layer: masked clip on the `upperBodyRoot` subtree. */
1461
+ private syncUpperAnimation;
1344
1462
  override update(dt: number): void;
1345
1463
  override onExitTree(): void;
1346
1464
  private unmount;
@@ -2207,4 +2325,61 @@ interface WalkState {
2207
2325
  ignoreStatic: boolean;
2208
2326
  }
2209
2327
  //#endregion
2210
- export { Area3D, AssetStore3D, Billboard3D, type BillboardGroupMode, BoneAttachment3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type Heightmap, type HeightmapOptions, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainTheme, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, cameraRelative, createGame3D, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers };
2328
+ //#region src/3d/terrain-nav.d.ts
2329
+ /**
2330
+ * Terrain navigation — the bridge from a `Terrain3D` heightfield to the grid
2331
+ * A* (`findPath`). Sample the surface into walkable cells (slope- and
2332
+ * height-limited), path on the grid, then lift waypoints back onto the
2333
+ * surface with `toWorld` (its y IS the terrain height):
2334
+ *
2335
+ * const nav = buildTerrainNav(terrain, { cellSize: 2, maxSlopeDeg: 40 });
2336
+ * const cells = findPath(nav.grid, nav.toCell(wolfX, wolfZ), nav.toCell(px, pz));
2337
+ * follow.setPath(cells.map(([cx, cy]) => nav.toWorld(cx, cy)));
2338
+ *
2339
+ * Trees/rocks/buildings aren't in the heightfield — stamp them out with
2340
+ * `setSolid(...nav.toCell(x, z))` after building.
2341
+ */
2342
+ interface TerrainNav {
2343
+ /** Feed this straight into `findPath`. */
2344
+ grid: PathGrid;
2345
+ cellSize: number;
2346
+ /** World x/z → cell coords (unclamped — check bounds via grid width/height). */
2347
+ toCell(x: number, z: number): [number, number];
2348
+ /** Cell → world position AT THE SURFACE ([x, terrainHeight, z], cell center). */
2349
+ toWorld(cx: number, cy: number): [number, number, number];
2350
+ /** Mark a cell unwalkable (props, buildings, no-go zones). */
2351
+ setSolid(cx: number, cy: number): void;
2352
+ }
2353
+ /** What we need from the terrain — Terrain3D satisfies this. */
2354
+ interface HeightSampler extends Node {
2355
+ size: number[];
2356
+ heightAt(x: number, z: number): number;
2357
+ }
2358
+ interface TerrainNavOptions {
2359
+ /** Meters per cell (default 2 — character-scale corridors). */
2360
+ cellSize?: number;
2361
+ /** Steepest walkable slope, degrees (default 40). */
2362
+ maxSlopeDeg?: number;
2363
+ /** Cells whose surface is BELOW this world y are unwalkable (sea level). */
2364
+ minHeight?: number;
2365
+ }
2366
+ declare function buildTerrainNav(terrain: HeightSampler, opts?: TerrainNavOptions): TerrainNav;
2367
+ //#endregion
2368
+ //#region src/3d/terrain-nav-debug.d.ts
2369
+ /**
2370
+ * See what the enemies see: one node that paints every WALKABLE nav cell as a
2371
+ * translucent quad hugging the terrain surface. Add it while tuning
2372
+ * `cellSize`/`maxSlopeDeg`, drop it when the routes look right:
2373
+ *
2374
+ * if (DEBUG_NAV) this.node.addChild(createNavDebugNode(this.nav));
2375
+ *
2376
+ * One InstancedMesh — a few thousand cells render as a single draw call.
2377
+ */
2378
+ declare function createNavDebugNode(nav: TerrainNav, opts?: {
2379
+ color?: string;
2380
+ opacity?: number;
2381
+ lift?: number;
2382
+ name?: string;
2383
+ }): InstancedMesh3D;
2384
+ //#endregion
2385
+ export { Area3D, AssetStore3D, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, setEnvironment3D, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers };