incanto 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +36 -0
  3. package/THIRD-PARTY-NOTICES.md +88 -0
  4. package/assets/audio/attacked.mp3 +0 -0
  5. package/assets/audio/explosion.mp3 +0 -0
  6. package/assets/audio/gold_loot.mp3 +0 -0
  7. package/assets/audio/heal.mp3 +0 -0
  8. package/assets/audio/hit_metal_bang.mp3 +0 -0
  9. package/assets/audio/ice_spear.mp3 +0 -0
  10. package/assets/audio/monster_died.mp3 +0 -0
  11. package/assets/audio/slash.mp3 +0 -0
  12. package/assets/audio/smite.mp3 +0 -0
  13. package/assets/audio/spells_cast.mp3 +0 -0
  14. package/assets/audio/ui_click.wav +0 -0
  15. package/assets/audio/walk.mp3 +0 -0
  16. package/assets/catalog.json +390 -0
  17. package/assets/characters/2dbasic.json +41 -0
  18. package/assets/characters/2dbasic.png +0 -0
  19. package/assets/characters/ghost.json +46 -0
  20. package/assets/characters/ghost.png +0 -0
  21. package/assets/characters/goblin.json +40 -0
  22. package/assets/characters/goblin.png +0 -0
  23. package/assets/characters/medieval-knight.json +41 -0
  24. package/assets/characters/medieval-knight.png +0 -0
  25. package/assets/effects/swoosh.png +0 -0
  26. package/assets/items/box.png +0 -0
  27. package/assets/items/buff_potion.png +0 -0
  28. package/assets/items/coin.png +0 -0
  29. package/assets/items/gem.png +0 -0
  30. package/assets/items/gold.png +0 -0
  31. package/assets/items/hp_potion.png +0 -0
  32. package/assets/items/locked_item_box.png +0 -0
  33. package/assets/items/map.png +0 -0
  34. package/assets/items/resurrection_potion.png +0 -0
  35. package/assets/items/super_box.png +0 -0
  36. package/assets/items/trap.png +0 -0
  37. package/assets/tiles/floor00.jpg +0 -0
  38. package/assets/tiles/minecraft-tiles.png +0 -0
  39. package/assets/tiles/wall00.jpg +0 -0
  40. package/assets/vegetation/ash_color.png +0 -0
  41. package/assets/vegetation/aspen_color.png +0 -0
  42. package/assets/vegetation/bark/birch_color_1k.jpg +0 -0
  43. package/assets/vegetation/bark/birch_normal_1k.jpg +0 -0
  44. package/assets/vegetation/bark/birch_roughness_1k.jpg +0 -0
  45. package/assets/vegetation/bark/oak_color_1k.jpg +0 -0
  46. package/assets/vegetation/bark/oak_normal_1k.jpg +0 -0
  47. package/assets/vegetation/bark/oak_roughness_1k.jpg +0 -0
  48. package/assets/vegetation/bark/pine_color_1k.jpg +0 -0
  49. package/assets/vegetation/bark/pine_normal_1k.jpg +0 -0
  50. package/assets/vegetation/bark/pine_roughness_1k.jpg +0 -0
  51. package/assets/vegetation/ground/dirt_color.jpg +0 -0
  52. package/assets/vegetation/ground/dirt_normal.jpg +0 -0
  53. package/assets/vegetation/ground/grass.jpg +0 -0
  54. package/assets/vegetation/oak_color.png +0 -0
  55. package/assets/vegetation/pine_color.png +0 -0
  56. package/bin/incanto-assets.mjs +107 -0
  57. package/bin/incanto-check.mjs +107 -0
  58. package/bin/incanto-editor.mjs +343 -0
  59. package/bin/incanto-env.mjs +144 -0
  60. package/bin/incanto-model.mjs +296 -0
  61. package/bin/incanto-play.mjs +219 -0
  62. package/bin/incanto-skills.mjs +71 -0
  63. package/dist/2d.d.ts +642 -0
  64. package/dist/2d.js +44 -0
  65. package/dist/3d.d.ts +1860 -0
  66. package/dist/3d.js +5 -0
  67. package/dist/agent8-DzU2fFyH.js +129 -0
  68. package/dist/audio-player-DqUR3XFs.d.ts +110 -0
  69. package/dist/behavior-BAQq7HGM.d.ts +851 -0
  70. package/dist/create-game-BdjpTHrW.js +1725 -0
  71. package/dist/create-game-CZHROKcT.js +527 -0
  72. package/dist/debug-draw-CZmOYjL2.js +13 -0
  73. package/dist/debug.d.ts +66 -0
  74. package/dist/debug.js +658 -0
  75. package/dist/duplicate-DP2WPYom.js +22 -0
  76. package/dist/env.d.ts +430 -0
  77. package/dist/env.js +3152 -0
  78. package/dist/errors-BMFaY68Q.d.ts +33 -0
  79. package/dist/errors-BpWbnbb_.js +13 -0
  80. package/dist/gameplay-Ccruc3Wd.js +1501 -0
  81. package/dist/gameplay.d.ts +543 -0
  82. package/dist/gameplay.js +2 -0
  83. package/dist/heightmap-CroQPEER.js +185 -0
  84. package/dist/index.d.ts +305 -0
  85. package/dist/index.js +62 -0
  86. package/dist/json-BLk7H2Qa.js +30 -0
  87. package/dist/loader-CGs_G-r0.js +919 -0
  88. package/dist/loader-Mo0KghCv.d.ts +41 -0
  89. package/dist/net.d.ts +427 -0
  90. package/dist/net.js +772 -0
  91. package/dist/noise-CGUMx44x.js +82 -0
  92. package/dist/particle-sim-CbN4YUuH.d.ts +63 -0
  93. package/dist/particle-sim-DYuSUxvK.js +1319 -0
  94. package/dist/physics-2d-KuMWPTf6.js +288 -0
  95. package/dist/physics-3d-Dl67vOLT.js +434 -0
  96. package/dist/react.d.ts +65 -0
  97. package/dist/react.js +209 -0
  98. package/dist/register-BuUV1_KB.js +561 -0
  99. package/dist/register-CNlYAS1_.js +10634 -0
  100. package/dist/register-DPEV9_9t.js +851 -0
  101. package/dist/register-Dasmnurl.js +374 -0
  102. package/dist/registry-BVJ2HbCn.js +132 -0
  103. package/dist/rng-DP-SR7eg.js +38 -0
  104. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  105. package/dist/schema-CcoWb32N.d.ts +104 -0
  106. package/dist/test.d.ts +158 -0
  107. package/dist/test.js +275 -0
  108. package/dist/touch-031PxtCR.js +208 -0
  109. package/dist/vite.d.ts +26 -0
  110. package/dist/vite.js +57 -0
  111. package/editor/assets/GameServer-C56iOUgF.js +1 -0
  112. package/editor/assets/agent8-Bp7QFI7v.js +1 -0
  113. package/editor/assets/index-DF3tMeKJ.css +1 -0
  114. package/editor/assets/index-Dl2pjA8e.js +7365 -0
  115. package/editor/assets/rapier-CEuLKeCu.js +1 -0
  116. package/editor/assets/rapier-DE6a0vmv.js +1 -0
  117. package/editor/index.html +169 -0
  118. package/package.json +97 -0
  119. package/schemas/scene.schema.json +4254 -0
  120. package/skills/README.md +9 -0
  121. package/skills/incanto-3d-character.md +229 -0
  122. package/skills/incanto-3d-models.md +151 -0
  123. package/skills/incanto-assets.md +118 -0
  124. package/skills/incanto-audio.md +309 -0
  125. package/skills/incanto-behaviors-and-scripts.md +169 -0
  126. package/skills/incanto-building-2d-games.md +242 -0
  127. package/skills/incanto-building-3d-games.md +245 -0
  128. package/skills/incanto-editor.md +163 -0
  129. package/skills/incanto-environment.md +743 -0
  130. package/skills/incanto-gameplay-behaviors.md +707 -0
  131. package/skills/incanto-multiplayer.md +264 -0
  132. package/skills/incanto-node-reference.md +797 -0
  133. package/skills/incanto-physics-and-input.md +164 -0
  134. package/skills/incanto-scene-json-authoring.md +325 -0
  135. package/skills/incanto-verifying-your-game.md +191 -0
  136. package/skills/incanto-web-integration.md +96 -0
  137. package/templates/agent8-server.js +84 -0
  138. package/templates/agent8-server.ts +138 -0
@@ -0,0 +1,185 @@
1
+ import { t as createNoise2D } from "./noise-CGUMx44x.js";
2
+ //#region src/3d/terrain/heightmap.ts
3
+ /**
4
+ * Pure heightfield + splat-weight math for `Terrain3D` — NO three imports, so
5
+ * physics and headless verification can use it without a renderer. The
6
+ * pipeline is a faithful port of the agent8 `vibe-starter-3d-environment`
7
+ * terrain generator (seeded simplex octaves → pow redistribution → flat snap
8
+ * → quarter-circle island edge wrap).
9
+ */
10
+ /** Fixed noise-domain scale of the ported pipeline (world meters per noise unit). */
11
+ const TERRAIN_SCALE = 256;
12
+ /** Low-base flatten knee and factor (ported constants). */
13
+ const FLATNESS = .3;
14
+ /** Flat-snap half-band in meters around maxHeight·flatThreshold. */
15
+ const FLAT_RANGE = .6;
16
+ /** Edge wrap radius hard cap (meters). */
17
+ const MAX_EDGE_WRAP_RADIUS = 45;
18
+ /**
19
+ * Build the deterministic heightfield. Heavy (one noise eval per vertex) —
20
+ * call once and keep the result.
21
+ */
22
+ function buildHeightmap(opts) {
23
+ const { width, depth, segsX, segsZ, maxHeight, seed } = opts;
24
+ const roughness = opts.roughness ?? .5;
25
+ const detail = opts.detail ?? 4;
26
+ const flatThreshold = opts.flatThreshold ?? .95;
27
+ const islandEdge = opts.islandEdge ?? false;
28
+ const edgeWrapRadius = Math.max(0, Math.min(opts.edgeWrapRadius ?? 20, MAX_EDGE_WRAP_RADIUS));
29
+ const edgeBottom = opts.edgeBottom ?? -50;
30
+ const edgeCornerSmoothness = opts.edgeCornerSmoothness ?? 1;
31
+ const basins = (opts.basins ?? []).map((b) => ({
32
+ cx: b.x + width / 2,
33
+ cz: b.z + depth / 2,
34
+ radius: b.radius,
35
+ depth: b.depth
36
+ }));
37
+ const simplex = createNoise2D(seed);
38
+ /** Ported: noise remapped from [-1,1] to [0,1]. */
39
+ const noise01 = (x, y) => (simplex(x, y) + 1) * .5;
40
+ /** The full pipeline EXCEPT edge wrap. x ∈ [0..width], z ∈ [0..depth]. */
41
+ const generate = (x, z) => {
42
+ const normX = x / TERRAIN_SCALE;
43
+ const normZ = z / TERRAIN_SCALE;
44
+ let base = 0;
45
+ let frequency = .2;
46
+ let amplitude = 5;
47
+ for (let i = 0; i < 5; i++) {
48
+ base += noise01(normX * frequency, normZ * frequency) * amplitude;
49
+ amplitude *= .5;
50
+ frequency *= 2;
51
+ }
52
+ let detailHeight = 0;
53
+ frequency = .5;
54
+ amplitude = .1;
55
+ for (let i = 0; i < detail; i++) {
56
+ detailHeight += noise01(normX * frequency, normZ * frequency) * amplitude;
57
+ amplitude *= roughness;
58
+ frequency *= 2;
59
+ }
60
+ base = Math.abs(base) ** 1.5 * Math.sign(base);
61
+ if (base < FLATNESS) base *= .3;
62
+ let final = maxHeight * (base + detailHeight * (base * .5 + .5) + 1) / 2;
63
+ const flatHeight = maxHeight * flatThreshold;
64
+ if (Math.abs(final - flatHeight) < FLAT_RANGE) final = flatHeight;
65
+ if (basins.length > 0) {
66
+ let carve = 0;
67
+ for (const b of basins) {
68
+ const d = Math.hypot(x - b.cx, z - b.cz);
69
+ if (d < b.radius) {
70
+ const t = d / b.radius;
71
+ carve = Math.max(carve, b.depth * (1 - t * t * (3 - 2 * t)));
72
+ }
73
+ }
74
+ final -= carve;
75
+ }
76
+ return final;
77
+ };
78
+ /** Quarter-circle edge wrap: drop = R − √(R²−s²), p-norm corner rounding. */
79
+ const applyEdgeWrap = (x, z, height) => {
80
+ if (!islandEdge || edgeWrapRadius <= 0) return height;
81
+ const R = edgeWrapRadius;
82
+ const dX = Math.min(x, width - x);
83
+ const dZ = Math.min(z, depth - z);
84
+ const jitter = (width / segsX + depth / segsZ) * .002;
85
+ const sX = Math.max(0, R - dX + jitter);
86
+ const sZ = Math.max(0, R - dZ + jitter);
87
+ if (sX <= 0 && sZ <= 0) return height;
88
+ const p = 2 + (1 - Math.min(1, Math.max(0, edgeCornerSmoothness))) * 30;
89
+ const sCombined = (sX ** p + sZ ** p) ** (1 / p);
90
+ let final = height - (R - Math.sqrt(Math.max(0, R * R - sCombined * sCombined)));
91
+ if (sCombined >= R) final = Math.min(final, edgeBottom);
92
+ return final;
93
+ };
94
+ const nx = segsX + 1;
95
+ const nz = segsZ + 1;
96
+ const heights = new Float32Array(nx * nz);
97
+ let minHeight = Number.POSITIVE_INFINITY;
98
+ let maxRealized = Number.NEGATIVE_INFINITY;
99
+ for (let iz = 0; iz < nz; iz++) {
100
+ const z = iz / segsZ * depth;
101
+ for (let ix = 0; ix < nx; ix++) {
102
+ const x = ix / segsX * width;
103
+ const raw = generate(x, z);
104
+ if (raw < minHeight) minHeight = raw;
105
+ if (raw > maxRealized) maxRealized = raw;
106
+ heights[iz * nx + ix] = applyEdgeWrap(x, z, raw);
107
+ }
108
+ }
109
+ const heightAt = (x, z) => {
110
+ const gx = Math.min(Math.max((x + width / 2) / width * segsX, 0), segsX);
111
+ const gz = Math.min(Math.max((z + depth / 2) / depth * segsZ, 0), segsZ);
112
+ const ix = Math.min(Math.floor(gx), segsX - 1);
113
+ const iz = Math.min(Math.floor(gz), segsZ - 1);
114
+ const fx = gx - ix;
115
+ const fz = gz - iz;
116
+ const i00 = heights[iz * nx + ix];
117
+ const i10 = heights[iz * nx + ix + 1];
118
+ const i01 = heights[(iz + 1) * nx + ix];
119
+ const i11 = heights[(iz + 1) * nx + ix + 1];
120
+ return (i00 * (1 - fx) + i10 * fx) * (1 - fz) + (i01 * (1 - fx) + i11 * fx) * fz;
121
+ };
122
+ const baseHeight = (x, z) => generate(x + width / 2, z + depth / 2);
123
+ const slopeAt = (x, z) => {
124
+ const eps = 1;
125
+ const dhdx = (baseHeight(x + eps, z) - baseHeight(x - eps, z)) / (2 * eps);
126
+ const dhdz = (baseHeight(x, z + eps) - baseHeight(x, z - eps)) / (2 * eps);
127
+ return Math.atan(Math.sqrt(dhdx * dhdx + dhdz * dhdz));
128
+ };
129
+ return {
130
+ width,
131
+ depth,
132
+ segsX,
133
+ segsZ,
134
+ heights,
135
+ minHeight,
136
+ maxHeight: maxRealized,
137
+ heightAt,
138
+ baseHeight,
139
+ slopeAt
140
+ };
141
+ }
142
+ /** Trapezoid weight: 1 inside [min,max], linear falloff over blendRange outside. */
143
+ function trapezoid(value, min, max, blendRange) {
144
+ if (value < min - blendRange || value > max + blendRange) return 0;
145
+ if (value >= min && value <= max) return 1;
146
+ if (value < min) return (value - (min - blendRange)) / blendRange;
147
+ return (max + blendRange - value) / blendRange;
148
+ }
149
+ /**
150
+ * Per-vertex splat weights for up to 4 layers. `height01` is the vertex
151
+ * height normalized over the heightmap's realized (no-wrap) span, `slopeRad`
152
+ * is `atan(|∇h|)`. Weight = heightTrapezoid · slopeTrapezoid, sharpened by
153
+ * `pow(w, 1/blendingStrength)`, then normalized to sum 1 — when nothing
154
+ * matches, layer 0 takes everything. Always returns exactly 4 entries
155
+ * (missing layers weigh 0).
156
+ */
157
+ function splatWeights(height01, slopeRad, layers, blendingStrength = 1.2) {
158
+ const out = [
159
+ 0,
160
+ 0,
161
+ 0,
162
+ 0
163
+ ];
164
+ const n = Math.min(layers.length, 4);
165
+ let sum = 0;
166
+ for (let i = 0; i < n; i++) {
167
+ const layer = layers[i];
168
+ const [hMin, hMax] = layer.heightRange ?? [0, 1];
169
+ const [sMin, sMax] = layer.slopeRange ?? [0, Math.PI / 2];
170
+ let w = trapezoid(height01, hMin, hMax, layer.heightBlendRange ?? .1) * trapezoid(slopeRad, sMin, sMax, layer.slopeBlendRange ?? .1);
171
+ w = w ** (1 / blendingStrength);
172
+ out[i] = w;
173
+ sum += w;
174
+ }
175
+ if (sum === 0) return [
176
+ 1,
177
+ 0,
178
+ 0,
179
+ 0
180
+ ];
181
+ for (let i = 0; i < 4; i++) out[i] = out[i] / sum;
182
+ return out;
183
+ }
184
+ //#endregion
185
+ export { splatWeights as n, buildHeightmap as t };
@@ -0,0 +1,305 @@
1
+ import { a as Rng, c as JsonValue, d as jsonKind, i as SceneJson, l as jsonClone, n as NodeJson, o as JsonKind, r as SCENE_FORMAT, s as JsonObject, t as ConnectionJson, u as jsonEquals } from "./schema-CcoWb32N.js";
2
+ import { $ as BusName, A as LogManager, B as spatialGain, C as RendererStats, D as SceneTree, E as NodeLifecycle, F as Listener, G as SfxWave, H as SFX_PRESETS, I as ROLLOFF_MODELS, J as MusicBackend, K as SynthOptions, L as RolloffModel, M as SfxEngine, N as SfxPlayOptions, O as LogEntry, P as isAudioContextAvailable, Q as AudioBuses, R as SpatialParams, S as GameStats, T as Node, U as SFX_PRESET_NAMES, V as spatialPan, W as SfxParams, X as MusicTrack, Y as MusicManager, Z as PlayMusicOptions, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as Signal, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as InputMap, k as LogLevel, l as PropSchema, m as getNodeType, n as BehaviorCtor, o as registeredBehaviors, p as getNodeSignals, q as synthSfx, r as clearBehaviors, s as NodeCtor, t as Behavior, tt as SignalListener, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as Vec3 } from "./behavior-BAQq7HGM.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-Mo0KghCv.js";
4
+ import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-CbN4YUuH.js";
5
+ import { n as AudioPlayer, t as AudioElementLike } from "./audio-player-DqUR3XFs.js";
6
+ import { n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-BMFaY68Q.js";
7
+
8
+ //#region src/core/audio/crossfade.d.ts
9
+ /**
10
+ * PURE crossfade / fade envelope math for the music manager. No WebAudio: just
11
+ * the gain curves over time, so the loudness behaviour is unit-testable in
12
+ * `node` and the headless music state machine can be validated without a backend.
13
+ * The adapter applies these gains to real GainNodes / element volumes each frame.
14
+ */
15
+ /** Linear 0→1 (`in`) or 1→0 (`out`) over `duration` seconds at elapsed `t`. */
16
+ declare function fadeGain(t: number, duration: number, dir: "in" | "out"): number;
17
+ interface CrossfadeGains {
18
+ /** Gain for the outgoing (old) track. */
19
+ out: number;
20
+ /** Gain for the incoming (new) track. */
21
+ in: number;
22
+ }
23
+ /**
24
+ * Equal-power crossfade gains at elapsed `t` over `seconds`. The outgoing track
25
+ * fades cos(¼π·p) and the incoming sin(¼π·p) so `out² + in² ≈ 1` throughout —
26
+ * constant perceived loudness, no mid-fade dip (the classic equal-power law).
27
+ * `p` is `t/seconds` clamped to [0,1]; a zero-second fade swaps instantly.
28
+ */
29
+ declare function crossfadeGains(t: number, seconds: number): CrossfadeGains;
30
+ //#endregion
31
+ //#region src/core/audio/webaudio-music.d.ts
32
+ declare class WebAudioMusicBackend implements MusicBackend {
33
+ private ctx;
34
+ private resolved;
35
+ /** True once a backend is present (lazily created on first use). */
36
+ get available(): boolean;
37
+ private ensure;
38
+ createTrack(src: string): MusicTrack;
39
+ unlock(): void;
40
+ }
41
+ //#endregion
42
+ //#region src/core/debug-draw.d.ts
43
+ /**
44
+ * Renderer-agnostic physics debug drawing: physics runtimes register here as
45
+ * line sources; renderers of the matching dimension pull vertices each frame
46
+ * when `debugDraw` is on. Default OFF — a release game never pays for it.
47
+ */
48
+ interface DebugLineSource {
49
+ readonly dimension: "2d" | "3d";
50
+ /** Toggle at runtime: `physics.debugDraw = true`. */
51
+ debugDraw: boolean;
52
+ /** Flat segment vertices (xy pairs in 2D px, xyz triples in 3D meters), or null while off. */
53
+ debugLines(): Float32Array | null;
54
+ }
55
+ //#endregion
56
+ //#region src/core/node-path.d.ts
57
+ /**
58
+ * Godot-style NodePath grammar:
59
+ *
60
+ * - `'Child/Grand'` relative descent
61
+ * - `'.'` self
62
+ * - `'..'` parent (may repeat: `'../../Other'`)
63
+ * - `'/Level/Player'` absolute from the tree root
64
+ * - `'%Player'` unique-name lookup across the whole tree
65
+ */
66
+ type ParsedNodePath = {
67
+ kind: "relative" | "absolute";
68
+ segments: string[];
69
+ } | {
70
+ kind: "unique";
71
+ name: string;
72
+ };
73
+ declare function parseNodePath(path: string): ParsedNodePath;
74
+ //#endregion
75
+ //#region src/core/nodes/timer.d.ts
76
+ /**
77
+ * The canonical serializable game clock — never `setTimeout` in game logic.
78
+ * Emits `timeout` every `waitTime` seconds (once with `oneShot`).
79
+ */
80
+ declare class Timer extends Node {
81
+ static override readonly typeName: string;
82
+ static override readonly signals: readonly string[];
83
+ static readonly props: PropSchema;
84
+ /** Seconds between timeouts. */
85
+ waitTime: number;
86
+ oneShot: boolean;
87
+ autostart: boolean;
88
+ running: boolean;
89
+ private remaining;
90
+ start(time?: number): void;
91
+ stop(): void;
92
+ override onReady(): void;
93
+ override update(dt: number): void;
94
+ }
95
+ //#endregion
96
+ //#region src/core/noise.d.ts
97
+ /**
98
+ * Seeded 2D simplex noise (Ken Perlin's simplex, Gustavson's reference
99
+ * implementation) returning values in [-1, 1]. The permutation table is a
100
+ * Fisher–Yates shuffle drawn from our deterministic {@link Rng}, so an
101
+ * integer seed reproduces the identical field on every machine — the same
102
+ * contract as every other seeded generator in the engine. Pure math, no deps.
103
+ */
104
+ declare function createNoise2D(seed: number): (x: number, y: number) => number;
105
+ //#endregion
106
+ //#region src/core/particle-presets.d.ts
107
+ /**
108
+ * Predefined particle looks. A node's `preset` seeds these values UNDER its
109
+ * explicit props: schema default < preset < anything the scene JSON wrote.
110
+ * Values are 2D px-space; Particles3D rescales distances to meters.
111
+ */
112
+ interface ParticlePresetValues {
113
+ rate?: number;
114
+ burst?: number;
115
+ lifetime?: [number, number];
116
+ speed?: [number, number];
117
+ directionDeg?: number;
118
+ spreadDeg?: number;
119
+ gravity?: [number, number];
120
+ drag?: number;
121
+ sizeStart?: number;
122
+ sizeEnd?: number;
123
+ colorStart?: string;
124
+ colorEnd?: string;
125
+ alphaStart?: number;
126
+ alphaEnd?: number;
127
+ blend?: "add" | "normal";
128
+ }
129
+ declare const PARTICLE_PRESETS: Record<string, ParticlePresetValues>;
130
+ declare const PARTICLE_PRESET_NAMES: readonly string[];
131
+ /**
132
+ * Apply the delta rule across three layers: schema default < preset < the
133
+ * scene's explicit props. A prop still equal to its SCHEMA default is
134
+ * considered "unset" and takes the preset value.
135
+ */
136
+ declare function applyParticlePreset(target: Record<string, unknown>, presetName: string, schema: PropSchema): void;
137
+ //#endregion
138
+ //#region src/core/preload.d.ts
139
+ interface PreloadResult {
140
+ /** Urls whose fetch failed — show an error UI instead of pretending success. */
141
+ failed: string[];
142
+ }
143
+ /**
144
+ * Warm the HTTP cache for a list of asset urls with a progress callback —
145
+ * the loading-bar pattern every template ships. Failures only warn and still
146
+ * count toward progress (a broken url must not block the game), but they are
147
+ * reported in the result so callers can tell success from 100%-with-holes.
148
+ */
149
+ declare function preloadUrls(urls: string[], onProgress?: (loaded: number, total: number) => void): Promise<PreloadResult>;
150
+ /** Every url found in a scene's `assets` block (preload helper). */
151
+ declare function assetUrls(assets: Record<string, {
152
+ url?: string;
153
+ }> | undefined): string[];
154
+ //#endregion
155
+ //#region src/core/register.d.ts
156
+ /**
157
+ * Register the core node types. Call once in your game entry before loading
158
+ * scenes. Registration is explicit — never an import-time side effect — so
159
+ * bundler tree-shaking can never silently drop node types.
160
+ */
161
+ declare function registerCoreNodes(): void;
162
+ //#endregion
163
+ //#region src/core/rendering-options.d.ts
164
+ /**
165
+ * Scene-declared rendering settings (`environment.rendering`): the scene JSON
166
+ * carries them, so a loaded scene LOOKS the way it was authored everywhere —
167
+ * the editor's play mode included.
168
+ *
169
+ * Precedence: explicit renderer constructor options > scene > renderer
170
+ * fallback. `pixelRatio: "device"` resolves to the device pixel ratio.
171
+ */
172
+ interface ResolvedRendering {
173
+ antialias: boolean;
174
+ pixelRatio: number;
175
+ }
176
+ declare function resolveRendering(environment: JsonObject | undefined, fallback: ResolvedRendering, devicePixelRatio: number, explicit?: {
177
+ antialias?: boolean;
178
+ pixelRatio?: number;
179
+ }): ResolvedRendering;
180
+ //#endregion
181
+ //#region src/core/scene/constants.d.ts
182
+ /** The single reserved key marking a prop value as a constant reference. */
183
+ declare const CONST_REF_KEY = "@const";
184
+ /** A `{"@const": "NAME"}` reference — exactly that one string key, nothing else. */
185
+ declare function isConstRef(value: JsonValue | undefined): value is {
186
+ "@const": string;
187
+ };
188
+ /**
189
+ * Deep-resolve every `{"@const": "NAME"}` reference in `value` to its literal
190
+ * from `constants` (cloned, so the table is never aliased into node state).
191
+ * Unknown names hard-fail — agents self-correct on hard errors.
192
+ */
193
+ declare function resolveConstants<T extends JsonValue | undefined>(value: T, constants: Record<string, JsonValue> | undefined): T;
194
+ //#endregion
195
+ //#region src/core/scene/duplicate.d.ts
196
+ /**
197
+ * Deep-clone a node subtree (detached — add it wherever you like; sibling
198
+ * auto-rename applies on attach). Implemented as serialize → rebuild so a
199
+ * duplicate is exactly what would survive a save/load round-trip — EXCEPT
200
+ * uids: clones are new entities and get a fresh identity (no uid), so
201
+ * duplicating a uid'd template can never mint colliding ids. Names ARE
202
+ * kept — rename clones you need to look up individually.
203
+ */
204
+ declare function duplicateNode(node: Node): Node;
205
+ //#endregion
206
+ //#region src/core/scene/serializer.d.ts
207
+ /**
208
+ * Serialize a node subtree to scene JSON with delta-only props: values equal
209
+ * to the type's schema defaults are omitted (Godot PackedScene behavior —
210
+ * files stay tiny and AI-readable).
211
+ */
212
+ declare function serializeNode(node: Node): NodeJson;
213
+ //#endregion
214
+ //#region src/core/touch.d.ts
215
+ /**
216
+ * Pure joystick math: drag offset (px) → normalized direction, deadzoned at
217
+ * the center and clamped to unit length at the rim.
218
+ */
219
+ declare function joystickVector(dx: number, dy: number, radius: number): {
220
+ x: number;
221
+ y: number;
222
+ };
223
+ interface DocumentLike {
224
+ createElement(tag: string): HTMLElement;
225
+ }
226
+ interface TouchControlsOptions {
227
+ /** Show even on non-touch environments (demos, tests). */
228
+ force?: boolean;
229
+ /** @internal Test seam — replaces `document`. */
230
+ doc?: DocumentLike;
231
+ }
232
+ /**
233
+ * On-screen touch controls driven by the scene's own input map: every action
234
+ * declared with `"touch": "joystick"` gets a left-side virtual stick feeding
235
+ * `setActionVector`, every `"touch": "button"` a right-side button feeding
236
+ * `pressAction`/`releaseAction`. The game never knows the difference between
237
+ * touch and keyboard — both arrive as actions.
238
+ *
239
+ * `attachTouchControls(engine, container)` shows them automatically on
240
+ * coarse-pointer devices ('auto' in createGame); `force` shows them anywhere.
241
+ * The container should be `position: relative/absolute` over the canvas.
242
+ */
243
+ declare function attachTouchControls(engine: Engine, container: HTMLElement, opts?: TouchControlsOptions): TouchControls | null;
244
+ declare class TouchControls {
245
+ private readonly elements;
246
+ private readonly detachers;
247
+ constructor(engine: Engine, container: HTMLElement, controls: Array<{
248
+ action: string;
249
+ kind: "joystick" | "button";
250
+ }>, doc: DocumentLike);
251
+ dispose(): void;
252
+ private buildJoystick;
253
+ private buildButton;
254
+ }
255
+ //#endregion
256
+ //#region src/core/uid-gen.d.ts
257
+ /**
258
+ * The ONE way to mint a node uid: crypto-strength, `n_` + 16 base36 chars
259
+ * (~82 bits). Hand-written uids are forbidden by convention — they break the
260
+ * collision-strength contract and read as fakes next to generated ones.
261
+ */
262
+ declare function newUid(): string;
263
+ //#endregion
264
+ //#region src/core/viewport.d.ts
265
+ type ViewportFit = "expand" | "letterbox" | "integer";
266
+ interface ResolvedViewport {
267
+ /** Design resolution [width, height] — the world space the game is authored in. */
268
+ design: [number, number];
269
+ fit: ViewportFit;
270
+ }
271
+ /**
272
+ * Parse + hard-validate a scene's `viewport` header. With a design resolution,
273
+ * scene JSON owns layout again: the game is authored in fixed design pixels
274
+ * and the renderer maps them onto whatever canvas size the page provides.
275
+ */
276
+ declare function resolveViewport(viewport: JsonObject | undefined): ResolvedViewport | null;
277
+ interface ComputedViewport {
278
+ /** Design-px → canvas-px scale factor. */
279
+ scale: number;
280
+ /** World pixels visible horizontally/vertically. */
281
+ viewW: number;
282
+ viewH: number;
283
+ /** Letterbox bar offsets in canvas px (0 for expand/integer). */
284
+ offsetX: number;
285
+ offsetY: number;
286
+ /** Canvas px actually covered by game content. */
287
+ contentW: number;
288
+ contentH: number;
289
+ }
290
+ /**
291
+ * Pure viewport math (headless-testable):
292
+ * - expand: the design rect is always fully visible; extra world shows beyond it
293
+ * - letterbox: EXACTLY the design rect, centered, bars elsewhere
294
+ * - integer: expand with whole-number scaling (pixel art), floored, min 1
295
+ */
296
+ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
297
+ design: [number, number] | readonly number[];
298
+ fit: ViewportFit;
299
+ }): ComputedViewport;
300
+ //#endregion
301
+ //#region src/index.d.ts
302
+ /** Engine version. Kept in sync with package.json by the release pipeline. */
303
+ declare const VERSION: string;
304
+ //#endregion
305
+ export { AudioBuses, type AudioElementLike, AudioPlayer, Behavior, type BehaviorCtor, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, Engine, type EngineOptions, type EngineStats, type GameStats, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LoadSceneOptions, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, ROLLOFF_MODELS, type RendererStats, type ResolvedRendering, type ResolvedViewport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, type SceneJson, SceneTree, type Scheduler, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, Timer, TouchControls, type TouchControlsOptions, VERSION, type Vec3, type ViewportFit, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, crossfadeGains, duplicateNode, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ import { _ as registerBehavior, a as SCENE_FORMAT, c as SceneTree, d as resolveConstants, f as Node, g as getBehavior, h as clearBehaviors, i as serializeNode, l as CONST_REF_KEY, m as Behavior, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as registeredBehaviors, y as Signal } from "./loader-CGs_G-r0.js";
2
+ import { a as Engine, c as SfxEngine, d as WebAudioMusicBackend, f as crossfadeGains, i as applyParticlePreset, l as isAudioContextAvailable, m as AudioBuses, n as PARTICLE_PRESETS, o as LogManager, p as fadeGain, r as PARTICLE_PRESET_NAMES, s as InputMap, t as ParticleSim, u as MusicManager } from "./particle-sim-DYuSUxvK.js";
3
+ import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
+ import { t as Rng } from "./rng-DP-SR7eg.js";
5
+ import { a as spatialGain, c as SFX_PRESET_NAMES, i as ROLLOFF_MODELS, l as synthSfx, n as Timer, o as spatialPan, r as AudioPlayer, s as SFX_PRESETS, t as registerCoreNodes } from "./register-BuUV1_KB.js";
6
+ import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
7
+ 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-BVJ2HbCn.js";
8
+ import { t as createNoise2D } from "./noise-CGUMx44x.js";
9
+ import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-031PxtCR.js";
10
+ import { t as duplicateNode } from "./duplicate-DP2WPYom.js";
11
+ //#region src/core/preload.ts
12
+ /**
13
+ * Warm the HTTP cache for a list of asset urls with a progress callback —
14
+ * the loading-bar pattern every template ships. Failures only warn and still
15
+ * count toward progress (a broken url must not block the game), but they are
16
+ * reported in the result so callers can tell success from 100%-with-holes.
17
+ */
18
+ async function preloadUrls(urls, onProgress) {
19
+ let loaded = 0;
20
+ const failed = [];
21
+ const total = urls.length;
22
+ if (total === 0) {
23
+ onProgress?.(0, 0);
24
+ return { failed };
25
+ }
26
+ await Promise.all(urls.map(async (url) => {
27
+ try {
28
+ await (await fetch(url)).arrayBuffer();
29
+ } catch (error) {
30
+ failed.push(url);
31
+ console.warn(`[incanto] preload failed for ${url}:`, error);
32
+ }
33
+ loaded += 1;
34
+ onProgress?.(loaded, total);
35
+ }));
36
+ return { failed };
37
+ }
38
+ /** Every url found in a scene's `assets` block (preload helper). */
39
+ function assetUrls(assets) {
40
+ return Object.values(assets ?? {}).map((a) => a.url).filter((u) => typeof u === "string" && !u.startsWith("data:"));
41
+ }
42
+ //#endregion
43
+ //#region src/core/uid-gen.ts
44
+ /**
45
+ * The ONE way to mint a node uid: crypto-strength, `n_` + 16 base36 chars
46
+ * (~82 bits). Hand-written uids are forbidden by convention — they break the
47
+ * collision-strength contract and read as fakes next to generated ones.
48
+ */
49
+ function newUid() {
50
+ const bytes = new Uint8Array(16);
51
+ if (globalThis.crypto?.getRandomValues) globalThis.crypto.getRandomValues(bytes);
52
+ else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
53
+ let id = "";
54
+ for (const b of bytes) id += (b % 36).toString(36);
55
+ return `n_${id.slice(0, 16)}`;
56
+ }
57
+ //#endregion
58
+ //#region src/index.ts
59
+ /** Engine version. Kept in sync with package.json by the release pipeline. */
60
+ const VERSION = "0.1.0";
61
+ //#endregion
62
+ export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, IncantoError, InputMap, LogManager, MusicManager, Node, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, crossfadeGains, duplicateNode, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
@@ -0,0 +1,30 @@
1
+ //#region src/core/json.ts
2
+ function jsonKind(value) {
3
+ if (value === null) return "null";
4
+ if (Array.isArray(value)) return "array";
5
+ const t = typeof value;
6
+ if (t === "string" || t === "number" || t === "boolean") return t;
7
+ return "object";
8
+ }
9
+ /** Deep structural equality over JSON values (used for delta-only serialization). */
10
+ function jsonEquals(a, b) {
11
+ if (a === b) return true;
12
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => jsonEquals(v, b[i]));
13
+ if (a !== null && b !== null && typeof a === "object" && typeof b === "object") {
14
+ if (Array.isArray(a) || Array.isArray(b)) return false;
15
+ const ka = Object.keys(a);
16
+ const kb = Object.keys(b);
17
+ return ka.length === kb.length && ka.every((k) => k in b && jsonEquals(a[k], b[k]));
18
+ }
19
+ return false;
20
+ }
21
+ /**
22
+ * Deep clone of JSON-safe data (defaults must never be shared between
23
+ * instances). The value must contain only JSON types.
24
+ */
25
+ function jsonClone(value) {
26
+ if (value === null || typeof value !== "object") return value;
27
+ return JSON.parse(JSON.stringify(value));
28
+ }
29
+ //#endregion
30
+ export { jsonEquals as n, jsonKind as r, jsonClone as t };