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.
- package/LICENSE +30 -0
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.md +88 -0
- package/assets/audio/attacked.mp3 +0 -0
- package/assets/audio/explosion.mp3 +0 -0
- package/assets/audio/gold_loot.mp3 +0 -0
- package/assets/audio/heal.mp3 +0 -0
- package/assets/audio/hit_metal_bang.mp3 +0 -0
- package/assets/audio/ice_spear.mp3 +0 -0
- package/assets/audio/monster_died.mp3 +0 -0
- package/assets/audio/slash.mp3 +0 -0
- package/assets/audio/smite.mp3 +0 -0
- package/assets/audio/spells_cast.mp3 +0 -0
- package/assets/audio/ui_click.wav +0 -0
- package/assets/audio/walk.mp3 +0 -0
- package/assets/catalog.json +390 -0
- package/assets/characters/2dbasic.json +41 -0
- package/assets/characters/2dbasic.png +0 -0
- package/assets/characters/ghost.json +46 -0
- package/assets/characters/ghost.png +0 -0
- package/assets/characters/goblin.json +40 -0
- package/assets/characters/goblin.png +0 -0
- package/assets/characters/medieval-knight.json +41 -0
- package/assets/characters/medieval-knight.png +0 -0
- package/assets/effects/swoosh.png +0 -0
- package/assets/items/box.png +0 -0
- package/assets/items/buff_potion.png +0 -0
- package/assets/items/coin.png +0 -0
- package/assets/items/gem.png +0 -0
- package/assets/items/gold.png +0 -0
- package/assets/items/hp_potion.png +0 -0
- package/assets/items/locked_item_box.png +0 -0
- package/assets/items/map.png +0 -0
- package/assets/items/resurrection_potion.png +0 -0
- package/assets/items/super_box.png +0 -0
- package/assets/items/trap.png +0 -0
- package/assets/tiles/floor00.jpg +0 -0
- package/assets/tiles/minecraft-tiles.png +0 -0
- package/assets/tiles/wall00.jpg +0 -0
- package/assets/vegetation/ash_color.png +0 -0
- package/assets/vegetation/aspen_color.png +0 -0
- package/assets/vegetation/bark/birch_color_1k.jpg +0 -0
- package/assets/vegetation/bark/birch_normal_1k.jpg +0 -0
- package/assets/vegetation/bark/birch_roughness_1k.jpg +0 -0
- package/assets/vegetation/bark/oak_color_1k.jpg +0 -0
- package/assets/vegetation/bark/oak_normal_1k.jpg +0 -0
- package/assets/vegetation/bark/oak_roughness_1k.jpg +0 -0
- package/assets/vegetation/bark/pine_color_1k.jpg +0 -0
- package/assets/vegetation/bark/pine_normal_1k.jpg +0 -0
- package/assets/vegetation/bark/pine_roughness_1k.jpg +0 -0
- package/assets/vegetation/ground/dirt_color.jpg +0 -0
- package/assets/vegetation/ground/dirt_normal.jpg +0 -0
- package/assets/vegetation/ground/grass.jpg +0 -0
- package/assets/vegetation/oak_color.png +0 -0
- package/assets/vegetation/pine_color.png +0 -0
- package/bin/incanto-assets.mjs +107 -0
- package/bin/incanto-check.mjs +107 -0
- package/bin/incanto-editor.mjs +343 -0
- package/bin/incanto-env.mjs +144 -0
- package/bin/incanto-model.mjs +296 -0
- package/bin/incanto-play.mjs +219 -0
- package/bin/incanto-skills.mjs +71 -0
- package/dist/2d.d.ts +642 -0
- package/dist/2d.js +44 -0
- package/dist/3d.d.ts +1860 -0
- package/dist/3d.js +5 -0
- package/dist/agent8-DzU2fFyH.js +129 -0
- package/dist/audio-player-DqUR3XFs.d.ts +110 -0
- package/dist/behavior-BAQq7HGM.d.ts +851 -0
- package/dist/create-game-BdjpTHrW.js +1725 -0
- package/dist/create-game-CZHROKcT.js +527 -0
- package/dist/debug-draw-CZmOYjL2.js +13 -0
- package/dist/debug.d.ts +66 -0
- package/dist/debug.js +658 -0
- package/dist/duplicate-DP2WPYom.js +22 -0
- package/dist/env.d.ts +430 -0
- package/dist/env.js +3152 -0
- package/dist/errors-BMFaY68Q.d.ts +33 -0
- package/dist/errors-BpWbnbb_.js +13 -0
- package/dist/gameplay-Ccruc3Wd.js +1501 -0
- package/dist/gameplay.d.ts +543 -0
- package/dist/gameplay.js +2 -0
- package/dist/heightmap-CroQPEER.js +185 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.js +62 -0
- package/dist/json-BLk7H2Qa.js +30 -0
- package/dist/loader-CGs_G-r0.js +919 -0
- package/dist/loader-Mo0KghCv.d.ts +41 -0
- package/dist/net.d.ts +427 -0
- package/dist/net.js +772 -0
- package/dist/noise-CGUMx44x.js +82 -0
- package/dist/particle-sim-CbN4YUuH.d.ts +63 -0
- package/dist/particle-sim-DYuSUxvK.js +1319 -0
- package/dist/physics-2d-KuMWPTf6.js +288 -0
- package/dist/physics-3d-Dl67vOLT.js +434 -0
- package/dist/react.d.ts +65 -0
- package/dist/react.js +209 -0
- package/dist/register-BuUV1_KB.js +561 -0
- package/dist/register-CNlYAS1_.js +10634 -0
- package/dist/register-DPEV9_9t.js +851 -0
- package/dist/register-Dasmnurl.js +374 -0
- package/dist/registry-BVJ2HbCn.js +132 -0
- package/dist/rng-DP-SR7eg.js +38 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/schema-CcoWb32N.d.ts +104 -0
- package/dist/test.d.ts +158 -0
- package/dist/test.js +275 -0
- package/dist/touch-031PxtCR.js +208 -0
- package/dist/vite.d.ts +26 -0
- package/dist/vite.js +57 -0
- package/editor/assets/GameServer-C56iOUgF.js +1 -0
- package/editor/assets/agent8-Bp7QFI7v.js +1 -0
- package/editor/assets/index-DF3tMeKJ.css +1 -0
- package/editor/assets/index-Dl2pjA8e.js +7365 -0
- package/editor/assets/rapier-CEuLKeCu.js +1 -0
- package/editor/assets/rapier-DE6a0vmv.js +1 -0
- package/editor/index.html +169 -0
- package/package.json +97 -0
- package/schemas/scene.schema.json +4254 -0
- package/skills/README.md +9 -0
- package/skills/incanto-3d-character.md +229 -0
- package/skills/incanto-3d-models.md +151 -0
- package/skills/incanto-assets.md +118 -0
- package/skills/incanto-audio.md +309 -0
- package/skills/incanto-behaviors-and-scripts.md +169 -0
- package/skills/incanto-building-2d-games.md +242 -0
- package/skills/incanto-building-3d-games.md +245 -0
- package/skills/incanto-editor.md +163 -0
- package/skills/incanto-environment.md +743 -0
- package/skills/incanto-gameplay-behaviors.md +707 -0
- package/skills/incanto-multiplayer.md +264 -0
- package/skills/incanto-node-reference.md +797 -0
- package/skills/incanto-physics-and-input.md +164 -0
- package/skills/incanto-scene-json-authoring.md +325 -0
- package/skills/incanto-verifying-your-game.md +191 -0
- package/skills/incanto-web-integration.md +96 -0
- package/templates/agent8-server.js +84 -0
- 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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|