incanto 0.70.0 → 0.72.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/bin/incanto-editor.mjs +15 -9
- package/bin/incanto-feel.mjs +11 -1
- package/bin/incanto-play.mjs +11 -1
- package/bin/incanto-playtest.mjs +11 -1
- package/bin/incanto-verify.mjs +36 -98
- package/dist/2d.d.ts +18 -3
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +3 -3
- package/dist/3d.js +6 -6
- package/dist/{agent8-CmNF01gA.js → agent8-BqZ0_O1u.js} +1 -1
- package/dist/{audio-player-DaMxqfNE.d.ts → audio-player-ioZ7VKC6.d.ts} +1 -1
- package/dist/{behavior-DZExDn9o.d.ts → behavior-Bf9P9oB2.d.ts} +12 -1
- package/dist/{create-game-Bwvh6q8A.js → create-game-C9rWqqAC.js} +37 -13
- package/dist/{create-game-C7ffQWW7.js → create-game-CmFAtLZp.js} +8 -7
- package/dist/debug.d.ts +1 -1
- package/dist/editor.js +1083 -1045
- package/dist/{environment-presets-D6Q5BxeE.js → environment-presets-BeHoaGhy.js} +3 -3
- package/dist/{frame-report-Dlq13Gyj.js → frame-report-D-_7YF2G.js} +1 -1
- package/dist/{gameplay-BfHkuzVb.js → gameplay-BlQe-M07.js} +108 -6
- package/dist/gameplay.d.ts +68 -4
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +27 -4
- package/dist/index.js +4 -4
- package/dist/{loader-Cff09LMm.d.ts → loader-CBfQzB6S.d.ts} +10 -3
- package/dist/net.d.ts +2 -2
- package/dist/net.js +2 -2
- package/dist/{physics-2d-CE0Qvy3V.js → physics-2d-DrIgTlLk.js} +2 -2
- package/dist/{physics-3d-CpH-2gn5.js → physics-3d-BzOCw1VS.js} +3 -3
- package/dist/{picking-CQJ_PJKh.js → picking-C9McVJrO.js} +3 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-6DYnKZcy.js → register-5IYfooAN.js} +49 -15
- package/dist/{register-Bkk0wSDB.js → register-C8HuRkOf.js} +100 -8
- package/dist/{replay-Dmw-PKQu.d.ts → replay-CBEChqXq.d.ts} +2 -2
- package/dist/{replay-DjAkAzMq.js → replay-datiAE-b.js} +15 -8
- package/dist/{save-slots-BXVg148r.js → save-slots-CEuJPUle.js} +102 -4
- package/dist/{split-screen-PL78oVXP.js → split-screen-Bm5DTElG.js} +3 -3
- package/dist/{split-screen--k-XpBjr.d.ts → split-screen-DkqLng6q.d.ts} +2 -2
- package/dist/{src-Cxfiv1Hg.js → src-DRSsRleD.js} +1 -1
- package/dist/{test-iHYVUcDK.js → test-Djuq3Vhq.js} +252 -24
- package/dist/test.d.ts +113 -5
- package/dist/test.js +3 -3
- package/dist/vite.d.ts +8 -1
- package/dist/vite.js +23 -6
- package/editor/assets/{agent8-D0MS174y.js → agent8-CMKz2cax.js} +1 -1
- package/editor/assets/{debug-BnXkKuYu.js → debug-DLrQm-az.js} +1 -1
- package/editor/assets/{index-CIu3uc3l.js → index-BEKuxYcE.js} +94 -94
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +4 -0
- package/skills/incanto-behaviors-and-scripts.md +30 -9
- package/skills/incanto-building-2d-games.md +26 -2
- package/skills/incanto-editor.md +4 -0
- package/skills/incanto-gameplay-behaviors.md +63 -1
- package/skills/incanto-hud.md +14 -0
- package/skills/incanto-node-reference.md +18 -2
- package/skills/incanto-playtesting.md +34 -0
- package/skills/incanto-save-slots.md +56 -1
- package/skills/incanto-scene-json-authoring.md +30 -0
- package/skills/incanto-verifying-your-game.md +11 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +396 -386
- package/templates-app/molehill-2d/package.json +1 -1
- package/templates-app/molehill-2d/src/game.scene.json +10 -60
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +268 -504
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/game.scene.json +23 -33
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +90 -105
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/behaviors.ts +21 -4
- package/templates-app/village-quest-3d/src/grove.scene.json +99 -118
- package/templates-app/village-quest-3d/src/village.scene.json +449 -245
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { M as translateOn, t as registerCoreNodes } from "./register-
|
|
1
|
+
import { J as Node, P as effectiveOrder, Y as diagnose } from "./save-slots-CEuJPUle.js";
|
|
2
|
+
import { M as translateOn, t as registerCoreNodes } from "./register-5IYfooAN.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { c as getNodeSchema, h as didYouMean, p as registerNode } from "./touch-BnCyPA0G.js";
|
|
5
5
|
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimations, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CqR2o3SA.js";
|
|
6
|
-
import {
|
|
6
|
+
import { D as effectRng, Q as worldPosition, T as tolerateUnknownAction, Z as localFromWorld } from "./gameplay-BlQe-M07.js";
|
|
7
7
|
import { t as checkSheetGrid } from "./sheet-grid-Cea343VO.js";
|
|
8
8
|
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
|
|
9
9
|
//#region src/2d/physics/collider-validate.ts
|
|
@@ -1617,6 +1617,15 @@ function mergeSolidRects(grid, solid) {
|
|
|
1617
1617
|
//#endregion
|
|
1618
1618
|
//#region src/2d/nodes/tile-map-2d.ts
|
|
1619
1619
|
/**
|
|
1620
|
+
* Where colour cells live in the index space, above any atlas anyone will draw.
|
|
1621
|
+
*
|
|
1622
|
+
* A million tiles is 1000×1000 atlas cells; a texture that size does not exist,
|
|
1623
|
+
* and the loader refuses `colors` and `texture` together anyway.
|
|
1624
|
+
*/
|
|
1625
|
+
const COLOR_BASE = 1e6;
|
|
1626
|
+
/** `#rgb` or `#rrggbb`, which is what every other colour prop in the engine takes. */
|
|
1627
|
+
const HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
1628
|
+
/**
|
|
1620
1629
|
* A whole tile level as ONE node — grid render in a single draw call plus
|
|
1621
1630
|
* greedy-merged static colliders. Author the level as rows of characters:
|
|
1622
1631
|
*
|
|
@@ -1653,6 +1662,23 @@ var TileMap2D = class extends Node2D {
|
|
|
1653
1662
|
cells: { default: [] },
|
|
1654
1663
|
/** char → atlas tile index for non-digit chars. */
|
|
1655
1664
|
legend: { default: {} },
|
|
1665
|
+
/**
|
|
1666
|
+
* Char → COLOUR, for a level you author before you have any art.
|
|
1667
|
+
*
|
|
1668
|
+
* `incanto-assets.md` is titled "No art yet? Ship gameplay anyway", and it
|
|
1669
|
+
* was true of everything except the node a grid game is made of: this one
|
|
1670
|
+
* draws from an atlas, so with no texture it rendered nothing at all. A
|
|
1671
|
+
* sokoban built from the published package came out as 24 hand-placed
|
|
1672
|
+
* `ColorRect2D` walls and a `wallAt()` comparing positions — because the
|
|
1673
|
+
* one node that already answers `cellAt`/`tileAt`/`setTile`/`worldAt`, and
|
|
1674
|
+
* merges the colliders, could not be seen.
|
|
1675
|
+
*
|
|
1676
|
+
* A cell painted with one of these chars is a flat quad of that colour, in
|
|
1677
|
+
* the same single draw call. Exclusive with `texture`: one material, one
|
|
1678
|
+
* draw call, and "the art arrived" is a two-line edit rather than a
|
|
1679
|
+
* silently half-textured level.
|
|
1680
|
+
*/
|
|
1681
|
+
colors: { default: {} },
|
|
1656
1682
|
/** Tile indices that get merged static colliders. */
|
|
1657
1683
|
solid: { default: [] },
|
|
1658
1684
|
opacity: {
|
|
@@ -1693,6 +1719,42 @@ var TileMap2D = class extends Node2D {
|
|
|
1693
1719
|
this.geometryDirty = true;
|
|
1694
1720
|
this.collidersDirty = true;
|
|
1695
1721
|
}
|
|
1722
|
+
_colors = {};
|
|
1723
|
+
get colors() {
|
|
1724
|
+
return this._colors;
|
|
1725
|
+
}
|
|
1726
|
+
set colors(value) {
|
|
1727
|
+
this._colors = value;
|
|
1728
|
+
this.geometryDirty = true;
|
|
1729
|
+
this.collidersDirty = true;
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* The colours, in declaration order — a cell's tile index is `COLOR_BASE + i`.
|
|
1733
|
+
*
|
|
1734
|
+
* A synthetic index rather than a second grid: everything downstream (the
|
|
1735
|
+
* collider merge, `tileAt`, `setTile`, the editor's paint tool) already
|
|
1736
|
+
* speaks in indices, and one number per cell keeps it that way.
|
|
1737
|
+
*/
|
|
1738
|
+
palette() {
|
|
1739
|
+
return Object.values(this._colors);
|
|
1740
|
+
}
|
|
1741
|
+
/** `legend` plus the colour chars, which is what every cell lookup wants. */
|
|
1742
|
+
allChars() {
|
|
1743
|
+
const merged = { ...this._legend };
|
|
1744
|
+
Object.keys(this._colors).forEach((ch, i) => {
|
|
1745
|
+
merged[ch] = COLOR_BASE + i;
|
|
1746
|
+
});
|
|
1747
|
+
return merged;
|
|
1748
|
+
}
|
|
1749
|
+
/** `solid` as INDICES: an entry may name the char that painted the cell. */
|
|
1750
|
+
solidIndices() {
|
|
1751
|
+
const chars = this.allChars();
|
|
1752
|
+
const out = [];
|
|
1753
|
+
for (const entry of this._solid) if (typeof entry === "number") out.push(entry);
|
|
1754
|
+
else if (chars[entry] !== void 0) out.push(chars[entry]);
|
|
1755
|
+
else if (entry.length === 1 && entry >= "0" && entry <= "9") out.push(entry.charCodeAt(0) - 48);
|
|
1756
|
+
return out;
|
|
1757
|
+
}
|
|
1696
1758
|
get solid() {
|
|
1697
1759
|
return this._solid;
|
|
1698
1760
|
}
|
|
@@ -1707,11 +1769,16 @@ var TileMap2D = class extends Node2D {
|
|
|
1707
1769
|
/** Loader hook: bad cells fail at LOAD (unknown chars), not at render. */
|
|
1708
1770
|
static validateJson(node) {
|
|
1709
1771
|
const map = node;
|
|
1710
|
-
|
|
1772
|
+
for (const [ch, value] of Object.entries(map._colors)) {
|
|
1773
|
+
if (typeof value === "string" && HEX.test(value)) continue;
|
|
1774
|
+
throw new IncantoError("PROP_TYPE_MISMATCH", `TileMap2D '${node.name}': "colors" ['${ch}'] is ${JSON.stringify(value)} — a colour here is \`#rgb\` or \`#rrggbb\`, the same as every other colour prop.`, { prop: "colors" });
|
|
1775
|
+
}
|
|
1776
|
+
if (map.texture !== "" && Object.keys(map._colors).length > 0) throw new IncantoError("PROP_TYPE_MISMATCH", `TileMap2D '${node.name}': "colors" and "texture" are exclusive — colours are for a level authored before the art arrives. Drop one of them.`, { prop: "colors" });
|
|
1777
|
+
parseCells(map._cells, map.allChars());
|
|
1711
1778
|
}
|
|
1712
1779
|
/** The parsed index grid (test/tooling hook). */
|
|
1713
1780
|
grid() {
|
|
1714
|
-
return parseCells(this._cells, this.
|
|
1781
|
+
return parseCells(this._cells, this.allChars());
|
|
1715
1782
|
}
|
|
1716
1783
|
/**
|
|
1717
1784
|
* Which CELL is under this world point — `null` outside the grid.
|
|
@@ -1809,7 +1876,7 @@ var TileMap2D = class extends Node2D {
|
|
|
1809
1876
|
if (this._solid.length === 0) return;
|
|
1810
1877
|
const ts = this.tileSize;
|
|
1811
1878
|
const [sx, sy] = this.builtScale;
|
|
1812
|
-
mergeSolidRects(this.grid(), new Set(this.
|
|
1879
|
+
mergeSolidRects(this.grid(), new Set(this.solidIndices())).forEach((r, i) => {
|
|
1813
1880
|
const body = new StaticBody2D(`__tileSolid${i}`);
|
|
1814
1881
|
body.position = [(r.x + r.w / 2) * ts * sx, (r.y + r.h / 2) * ts * sy];
|
|
1815
1882
|
body.collider = {
|
|
@@ -1839,6 +1906,23 @@ var TileMap2D = class extends Node2D {
|
|
|
1839
1906
|
super._syncObject2D(assets);
|
|
1840
1907
|
const mesh = this._mesh();
|
|
1841
1908
|
const mat = mesh.material;
|
|
1909
|
+
if (Object.keys(this._colors).length > 0) {
|
|
1910
|
+
mesh.visible = this.opacity > 0;
|
|
1911
|
+
mesh.renderOrder = this.effectiveRenderOrder;
|
|
1912
|
+
mat.opacity = this.opacity;
|
|
1913
|
+
if (mat.map !== null || !mat.vertexColors) {
|
|
1914
|
+
mat.map = null;
|
|
1915
|
+
mat.vertexColors = true;
|
|
1916
|
+
mat.needsUpdate = true;
|
|
1917
|
+
this.geometryDirty = true;
|
|
1918
|
+
}
|
|
1919
|
+
if (!this.geometryDirty) return;
|
|
1920
|
+
this.geometryDirty = false;
|
|
1921
|
+
this.builtColumns = 1;
|
|
1922
|
+
this.builtRows = 1;
|
|
1923
|
+
this.buildGeometry(mesh.geometry, 1, 1);
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1842
1926
|
if (!this.texture || !assets) {
|
|
1843
1927
|
mesh.visible = false;
|
|
1844
1928
|
return;
|
|
@@ -1882,6 +1966,8 @@ var TileMap2D = class extends Node2D {
|
|
|
1882
1966
|
const positions = [];
|
|
1883
1967
|
const uvs = [];
|
|
1884
1968
|
const indices = [];
|
|
1969
|
+
const palette = this.palette().map((hex) => new Color(hex));
|
|
1970
|
+
const colors = [];
|
|
1885
1971
|
for (let y = 0; y < grid.length; y++) {
|
|
1886
1972
|
const row = grid[y];
|
|
1887
1973
|
for (let x = 0; x < row.length; x++) {
|
|
@@ -1893,18 +1979,24 @@ var TileMap2D = class extends Node2D {
|
|
|
1893
1979
|
const y1 = -((y + 1) * ts);
|
|
1894
1980
|
const base = positions.length / 3;
|
|
1895
1981
|
positions.push(x0, y0, 0, x1, y0, 0, x1, y1, 0, x0, y1, 0);
|
|
1896
|
-
const col = tile % columns;
|
|
1897
|
-
const rowIdx = Math.floor(tile / columns);
|
|
1982
|
+
const col = tile >= COLOR_BASE ? 0 : tile % columns;
|
|
1983
|
+
const rowIdx = tile >= COLOR_BASE ? 0 : Math.floor(tile / columns);
|
|
1898
1984
|
const u0 = col / columns;
|
|
1899
1985
|
const u1 = (col + 1) / columns;
|
|
1900
1986
|
const v0 = 1 - rowIdx / atlasRows;
|
|
1901
1987
|
const v1 = 1 - (rowIdx + 1) / atlasRows;
|
|
1902
1988
|
uvs.push(u0, v0, u1, v0, u1, v1, u0, v1);
|
|
1989
|
+
const paint = tile >= COLOR_BASE ? palette[tile - COLOR_BASE] : void 0;
|
|
1990
|
+
const r = paint?.r ?? 1;
|
|
1991
|
+
const g = paint?.g ?? 1;
|
|
1992
|
+
const b = paint?.b ?? 1;
|
|
1993
|
+
for (let v = 0; v < 4; v++) colors.push(r, g, b);
|
|
1903
1994
|
indices.push(base, base + 2, base + 1, base, base + 3, base + 2);
|
|
1904
1995
|
}
|
|
1905
1996
|
}
|
|
1906
1997
|
geometry.setAttribute("position", new BufferAttribute(new Float32Array(positions), 3));
|
|
1907
1998
|
geometry.setAttribute("uv", new BufferAttribute(new Float32Array(uvs), 2));
|
|
1999
|
+
geometry.setAttribute("color", new BufferAttribute(new Float32Array(colors), 3));
|
|
1908
2000
|
geometry.setIndex(indices);
|
|
1909
2001
|
geometry.computeBoundingSphere();
|
|
1910
2002
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as Engine } from "./behavior-
|
|
1
|
+
import { b as Engine } from "./behavior-Bf9P9oB2.js";
|
|
2
2
|
import { s as JsonObject } from "./rng-Bb-IutXB.js";
|
|
3
3
|
|
|
4
4
|
//#region src/core/audit.d.ts
|
|
@@ -59,7 +59,7 @@ declare function auditScene(scene: JsonObject, opts?: AuditOptions): string[];
|
|
|
59
59
|
* Incanto fails loudly with machine-readable codes and messages that list valid
|
|
60
60
|
* alternatives — AI agents self-correct on hard failures, not warnings.
|
|
61
61
|
*/
|
|
62
|
-
type IncantoErrorCode = "AMBIGUOUS_HANDLER" | "BAD_FORMAT" | "BAD_NODE_PATH" | "DANGLING_CONNECTION" | "DUPLICATE_BEHAVIOR" | "DUPLICATE_NODE_TYPE" | "DUPLICATE_UNIQUE_NAME" | "DUPLICATE_UID" | "NETWORK_UNAVAILABLE" | "NODE_NOT_FOUND" | "PROP_TYPE_MISMATCH" | "TREE_VIOLATION" | "UNKNOWN_ANIMATION" | "UNKNOWN_ASSET" | "UNKNOWN_CONSTANT" | "UNKNOWN_BEHAVIOR" | "UNKNOWN_HANDLER" | "UNKNOWN_NODE_TYPE" | "UNKNOWN_PROP" | "UNKNOWN_SIGNAL" | "UNRESOLVED_INSTANCE" | "WEBGL_UNAVAILABLE";
|
|
62
|
+
type IncantoErrorCode = "AMBIGUOUS_HANDLER" | "BAD_FORMAT" | "BAD_NODE_PATH" | "DANGLING_CONNECTION" | "DUPLICATE_BEHAVIOR" | "DUPLICATE_NODE_TYPE" | "DUPLICATE_UNIQUE_NAME" | "DUPLICATE_UID" | "NETWORK_UNAVAILABLE" | "NODE_NOT_FOUND" | "PROP_TYPE_MISMATCH" | "TREE_VIOLATION" | "UNKNOWN_ANIMATION" | "UNKNOWN_ASSET" | "UNKNOWN_CONSTANT" | "UNKNOWN_BEHAVIOR" | "UNKNOWN_HANDLER" | "UNKNOWN_NODE_TYPE" | "UNKNOWN_PROP" | "UNKNOWN_SIGNAL" | "UNRESOLVED_INSTANCE" | "WRONG_DIMENSION" | "WEBGL_UNAVAILABLE";
|
|
63
63
|
/**
|
|
64
64
|
* Structured context mirrored from the prose message — tools and agents parse
|
|
65
65
|
* THESE instead of regexing the message (which is for humans and may reword).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { N as translationKey } from "./register-
|
|
1
|
+
import { L as handlerShadowWarnings, R as sceneDimension, V as HudWidgetBase, Z as nodeRefWarnings } from "./save-slots-CEuJPUle.js";
|
|
2
|
+
import { N as translationKey } from "./register-5IYfooAN.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { u as getNodeType } from "./touch-BnCyPA0G.js";
|
|
5
5
|
//#region src/core/audit.ts
|
|
@@ -174,7 +174,7 @@ function auditScene(scene, opts = {}) {
|
|
|
174
174
|
/** Nodes whose script produces NOTHING but signals — see `SIGNAL_ONLY`. */
|
|
175
175
|
const silentSignals = [];
|
|
176
176
|
const groupRefs = [];
|
|
177
|
-
const walk = (node, path, insideHud, staticRoot, transformed) => {
|
|
177
|
+
const walk = (node, path, insideHud, staticRoot, transformed, onShelf) => {
|
|
178
178
|
const type = node.type ?? "";
|
|
179
179
|
const props = node.props ?? {};
|
|
180
180
|
const script = node.script?.name;
|
|
@@ -189,6 +189,11 @@ function auditScene(scene, opts = {}) {
|
|
|
189
189
|
if (props.current === true) currentCameras += 1;
|
|
190
190
|
if (script === "FollowCamera") followedCameras.push(path);
|
|
191
191
|
}
|
|
192
|
+
if (type === "TileMap2D") {
|
|
193
|
+
const cells = Array.isArray(props.cells) ? props.cells : [];
|
|
194
|
+
const colours = props.colors ?? {};
|
|
195
|
+
if (cells.length > 0 && (props.texture ?? "") === "" && Object.keys(colours).length === 0) warnings.push(`${path}: TileMap2D has ${cells.length} row(s) and neither "texture" nor "colors", so nothing of it is drawn. Give it a "colors" entry per legend char to see the level before the art arrives. (If it is meant to be an invisible collision map over scenery you drew yourself, this warning is the price of saying so.)`);
|
|
196
|
+
}
|
|
192
197
|
if (type === "CharacterController3D") cameraDrivers.push(path);
|
|
193
198
|
if (type === "Terrain3D") terrains.push(path);
|
|
194
199
|
if (DRAPERS.has(type) && (props.terrain ?? "") === "") autoDrapers.push(path);
|
|
@@ -219,7 +224,7 @@ function auditScene(scene, opts = {}) {
|
|
|
219
224
|
const collider = props.collider;
|
|
220
225
|
if (!collider || collider.shape === void 0) warnings.push(`${path}: ${type} has no collider — physics will skip it (add "collider": { "shape": ... }).`);
|
|
221
226
|
}
|
|
222
|
-
if (isHudWidget(type) && !insideHud) warnings.push(`${path}: ${type} works only as a descendant of a HudLayer — it will not render.`);
|
|
227
|
+
if (isHudWidget(type) && !insideHud && !onShelf) warnings.push(`${path}: ${type} works only as a descendant of a HudLayer — it will not render.`);
|
|
223
228
|
if (staticRoot && ANIMATED_TYPES.has(type)) warnings.push(`${path}: ${type} is frozen inside static subtree '${staticRoot}' — it will not animate/update on screen.`);
|
|
224
229
|
const nextStatic = staticRoot ?? (props.static === true ? node.name ?? path : null);
|
|
225
230
|
const nextHud = insideHud || type === "HudLayer";
|
|
@@ -228,12 +233,14 @@ function auditScene(scene, opts = {}) {
|
|
|
228
233
|
path,
|
|
229
234
|
what: own
|
|
230
235
|
} : transformed;
|
|
231
|
-
|
|
236
|
+
const nextShelf = onShelf || script === "PrefabShelf";
|
|
237
|
+
for (const child of node.children ?? []) walk(child, `${path}/${child.name ?? "?"}`, nextHud, nextStatic, nextTransformed, nextShelf);
|
|
232
238
|
};
|
|
233
|
-
|
|
239
|
+
const rootTransform = transformOf(root.props ?? {});
|
|
240
|
+
walk(root, root.name ?? "root", false, root.props?.static === true ? root.name ?? "root" : null, rootTransform ? {
|
|
234
241
|
path: root.name ?? "root",
|
|
235
|
-
what:
|
|
236
|
-
} : null);
|
|
242
|
+
what: rootTransform
|
|
243
|
+
} : null, root.script?.name === "PrefabShelf");
|
|
237
244
|
if (needUid.length > 0) warnings.push(`these carry state a save keeps and have no uid to key it under, so the save comes back EMPTY and the load reports no problem: ${needUid.join(", ")}. Give each one a "uid" from newUid().`);
|
|
238
245
|
if (terrains.length > 1 && autoDrapers.length > 0) warnings.push(`${autoDrapers.join(", ")}: this scene has ${terrains.length} Terrain3Ds (${terrains.join(", ")}) and these do not say which one they grow on — with no \`terrain\` path they take the FIRST one in the tree, so the rest drape to the wrong height with nothing reported. Name it: "terrain": "%Name".`);
|
|
239
246
|
if (cameraDrivers.length > 1) warnings.push(`${cameraDrivers.join(", ")}: ${cameraDrivers.length} CharacterController3Ds drive the SAME camera (each one finds the first \`current\` camera in the tree), so the last of them wins every frame and the others have no view. For local co-op give each player a panel with \`createSplitScreen\` (one engine each); otherwise only one node may carry a controller.`);
|
|
@@ -113,7 +113,10 @@ var Behavior = class {
|
|
|
113
113
|
*/
|
|
114
114
|
get engine() {
|
|
115
115
|
const engine = this.node?.tree?.engine;
|
|
116
|
-
if (!engine)
|
|
116
|
+
if (!engine) {
|
|
117
|
+
if (this.node?.freed) throw new IncantoError("TREE_VIOLATION", `Behavior on '${this.node.name}' has been FREED — a scene swap frees the tree it replaces, so \`this.engine\` (which reads \`this.node.tree\`) is gone with it. Hold it BEFORE the swap: \`const engine = this.engine; goToScene(engine, next);\` and use that afterwards.`);
|
|
118
|
+
throw new IncantoError("TREE_VIOLATION", `Behavior on '${this.node?.name ?? "?"}' is not attached to a running engine. If this came from \`onReady\` during a load, the scene does not know its engine yet: pass it — \`loadScene(json, { engine })\` — which is what createGame2D/3D and runScript do. Otherwise the node is not in a scene set on an Engine.`);
|
|
119
|
+
}
|
|
117
120
|
return engine;
|
|
118
121
|
}
|
|
119
122
|
/** Shortcut for `this.engine.input`. */
|
|
@@ -669,7 +672,11 @@ var Node = class {
|
|
|
669
672
|
*/
|
|
670
673
|
wiringPath() {
|
|
671
674
|
const parts = [];
|
|
672
|
-
for (let n = this; n; n = n._parent)
|
|
675
|
+
for (let n = this; n; n = n._parent) {
|
|
676
|
+
const authored = n._authoredPath;
|
|
677
|
+
if (authored !== void 0) return parts.length === 0 ? authored : `${authored}/${parts.join("/")}`;
|
|
678
|
+
parts.unshift(n._cloneOf ?? n._name);
|
|
679
|
+
}
|
|
673
680
|
return `/${parts.join("/")}`;
|
|
674
681
|
}
|
|
675
682
|
getNode(path) {
|
|
@@ -874,9 +881,23 @@ var Node = class {
|
|
|
874
881
|
if (this._tree) this._tree._queueFree(this);
|
|
875
882
|
else this.free();
|
|
876
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* Has this node been torn down?
|
|
886
|
+
*
|
|
887
|
+
* A scene swap frees the tree it replaces, so the answer is `true` for every
|
|
888
|
+
* node of the scene you just left — which is exactly the state a behavior is
|
|
889
|
+
* in when it runs one more line after `goToScene`. Kept as a flag rather than
|
|
890
|
+
* inferred from `parent`/`tree`, because a DETACHED node (a prefab on a
|
|
891
|
+
* shelf, a clone waiting to be added) has neither and is perfectly alive.
|
|
892
|
+
*/
|
|
893
|
+
get freed() {
|
|
894
|
+
return this._freed;
|
|
895
|
+
}
|
|
896
|
+
_freed = false;
|
|
877
897
|
/** Immediately detach and tear down this node and its children. */
|
|
878
898
|
free() {
|
|
879
899
|
const tree = this._tree;
|
|
900
|
+
this._freed = true;
|
|
880
901
|
for (const child of [...this._children]) child.free();
|
|
881
902
|
this._parent?.removeChild(this);
|
|
882
903
|
if (this._tree) this._propagateExitTree();
|
|
@@ -2064,6 +2085,36 @@ function inferSceneDimension(root) {
|
|
|
2064
2085
|
function sceneDimension(json) {
|
|
2065
2086
|
return json.dimension ?? inferSceneDimension(json.root);
|
|
2066
2087
|
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Every node whose type name belongs to the OTHER world, with its path.
|
|
2090
|
+
*
|
|
2091
|
+
* The two halves of the engine are strangers by design: `Renderer2D` walks
|
|
2092
|
+
* `Node2D`s and `Physics2D` tracks 2D bodies; the 3D pair do the same for
|
|
2093
|
+
* theirs, and neither knows the other's nodes exist. So a `MeshInstance3D`
|
|
2094
|
+
* dropped into a 2D scene loads, validates, appears in the tree, opens a full
|
|
2095
|
+
* inspector — and is never drawn and never simulated. The editor's add-node
|
|
2096
|
+
* list offers all of them, because the LOADER is what says no, and the loader
|
|
2097
|
+
* did not.
|
|
2098
|
+
*
|
|
2099
|
+
* The suffix is the authority here, as it already is for
|
|
2100
|
+
* {@link inferSceneDimension} and for the registry's missing-registrar hint.
|
|
2101
|
+
*/
|
|
2102
|
+
function foreignDimensionNodes(root, dimension) {
|
|
2103
|
+
const wrong = dimension === "2d" ? "3D" : "2D";
|
|
2104
|
+
const out = [];
|
|
2105
|
+
const visit = (n, parent) => {
|
|
2106
|
+
if (typeof n !== "object" || n === null) return;
|
|
2107
|
+
const node = n;
|
|
2108
|
+
const path = `${parent}/${typeof node.name === "string" ? node.name : "?"}`;
|
|
2109
|
+
if (typeof node.type === "string" && node.type.endsWith(wrong)) out.push({
|
|
2110
|
+
path,
|
|
2111
|
+
type: node.type
|
|
2112
|
+
});
|
|
2113
|
+
if (Array.isArray(node.children)) for (const child of node.children) visit(child, path);
|
|
2114
|
+
};
|
|
2115
|
+
visit(root, "");
|
|
2116
|
+
return out;
|
|
2117
|
+
}
|
|
2067
2118
|
//#endregion
|
|
2068
2119
|
//#region src/core/scene/handler-collision.ts
|
|
2069
2120
|
/**
|
|
@@ -4415,6 +4466,18 @@ function loadScene(json, opts) {
|
|
|
4415
4466
|
validateHeader(sceneJson);
|
|
4416
4467
|
validateStrings(sceneJson.strings);
|
|
4417
4468
|
const dimension = sceneDimension(sceneJson);
|
|
4469
|
+
if (dimension) {
|
|
4470
|
+
const foreign = foreignDimensionNodes(sceneJson.root, dimension);
|
|
4471
|
+
if (foreign.length > 0) {
|
|
4472
|
+
const first = foreign[0];
|
|
4473
|
+
const others = foreign.length > 1 ? ` (and ${foreign.length - 1} more)` : "";
|
|
4474
|
+
const declared = sceneJson.dimension ? `declared "dimension": "${dimension}"` : `no "dimension" key, and its other nodes make it ${dimension}`;
|
|
4475
|
+
throw new IncantoError("WRONG_DIMENSION", `${first.type} '${first.path}'${others} is a ${dimension === "2d" ? "3D" : "2D"} node in a scene with ${declared}. The ${dimension} renderer never draws it and the ${dimension} physics never simulates it. Use the matching node type, or move it to its own scene.`, {
|
|
4476
|
+
path: first.path,
|
|
4477
|
+
nodeType: first.type
|
|
4478
|
+
});
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4418
4481
|
validateEnvironment(sceneJson.environment, dimension);
|
|
4419
4482
|
inheritedStrings = {};
|
|
4420
4483
|
inheritedAssets = {};
|
|
@@ -4837,7 +4900,10 @@ function buildNode(nj, opts, stack, parentPath, constants, bands = []) {
|
|
|
4837
4900
|
rejectUnknownKeys(nj, NODE_KEYS, "node", nj.name ?? "");
|
|
4838
4901
|
rejectWrongKinds(nj, nj.name ?? "");
|
|
4839
4902
|
if (nj.instance !== void 0 && nj.type !== void 0) throw new IncantoError("BAD_FORMAT", `Node '${nj.name}' declares both "type" and "instance" — they are mutually exclusive.`);
|
|
4840
|
-
if (nj.instance !== void 0)
|
|
4903
|
+
if (nj.instance !== void 0) {
|
|
4904
|
+
if (nj.props !== void 0) throw new IncantoError("BAD_FORMAT", `Node '${nj.name}' places an instance and declares "props" — a placement's props go in "overrides", which deep-merge onto the sub-scene root. "props" here is read by nothing.`, { prop: "props" });
|
|
4905
|
+
return buildInstance(nj, opts, stack, path, constants, bands);
|
|
4906
|
+
}
|
|
4841
4907
|
if (nj.type === void 0) throw new IncantoError("BAD_FORMAT", `Node '${nj.name}' must declare either "type" or "instance".`);
|
|
4842
4908
|
const node = createNode(nj.type, resolveConstants(nj.props, constants), { orderGroup: bands });
|
|
4843
4909
|
applyCommon(node, nj, opts, constants);
|
|
@@ -5145,6 +5211,7 @@ function duplicateNode(node, opts) {
|
|
|
5145
5211
|
const clone = buildNodeJson(stripUids(serializeNode(node)), opts ?? rebuildOptions(node));
|
|
5146
5212
|
markCloned(clone);
|
|
5147
5213
|
markTemplateName(clone);
|
|
5214
|
+
markTemplateOrigin(clone, node);
|
|
5148
5215
|
return clone;
|
|
5149
5216
|
}
|
|
5150
5217
|
/**
|
|
@@ -5196,6 +5263,37 @@ function markCloned(node) {
|
|
|
5196
5263
|
function markTemplateName(node) {
|
|
5197
5264
|
node._cloneOf = node.name;
|
|
5198
5265
|
}
|
|
5266
|
+
/**
|
|
5267
|
+
* @internal Where the template this clone came from was AUTHORED.
|
|
5268
|
+
*
|
|
5269
|
+
* `_cloneOf` (the name) is enough only while clones land where the template
|
|
5270
|
+
* was authored — the `Spawner` case (`/Waves/Grunt` template, `/Waves/Grunt2`
|
|
5271
|
+
* clone) and no other. A game that clones from a SHELF authors the template at
|
|
5272
|
+
* `/Game/Prefabs/Tower` and adds the clone under `/Game/Towers`, so its audio
|
|
5273
|
+
* was logged at `/Game/Towers/Tower/Thud`: a path no scene file contains, and
|
|
5274
|
+
* one no report that reads the file can ever match. Recording the address
|
|
5275
|
+
* itself is what lets `wiringPath` answer with the authored node wherever the
|
|
5276
|
+
* clone ends up — including a clone OF a clone, which carries it forward.
|
|
5277
|
+
*
|
|
5278
|
+
* Taken while the source is still in a tree; a template detached before the
|
|
5279
|
+
* first clone (`PrefabShelf`, `SpawnSource`) records its own on the way out.
|
|
5280
|
+
*/
|
|
5281
|
+
function markTemplateOrigin(clone, source) {
|
|
5282
|
+
const origin = source._authoredPath ?? (source.tree ? source.getPath() : null);
|
|
5283
|
+
if (origin) clone._authoredPath = origin;
|
|
5284
|
+
}
|
|
5285
|
+
/**
|
|
5286
|
+
* @internal Remember where a node was authored, before it leaves the tree.
|
|
5287
|
+
*
|
|
5288
|
+
* A shelf or a spawner lifts its templates out at enter; from then on the
|
|
5289
|
+
* template cannot say where it came from, and neither can anything cloned from
|
|
5290
|
+
* it. Called on the way out, while the answer is still there.
|
|
5291
|
+
*/
|
|
5292
|
+
function rememberAuthoredPath(node) {
|
|
5293
|
+
const at = node;
|
|
5294
|
+
if (at._authoredPath !== void 0) return;
|
|
5295
|
+
if (node.tree || node.parent?.tree) at._authoredPath = node.getPath();
|
|
5296
|
+
}
|
|
5199
5297
|
/** @internal Was this node made at runtime rather than loaded from a file? */
|
|
5200
5298
|
function isCloned(node) {
|
|
5201
5299
|
return node._cloned === true;
|
|
@@ -5648,4 +5746,4 @@ function savesWithoutUid(root) {
|
|
|
5648
5746
|
return out;
|
|
5649
5747
|
}
|
|
5650
5748
|
//#endregion
|
|
5651
|
-
export {
|
|
5749
|
+
export { parseNodePath as $, SceneTree as A, HudLayer as B, sunDirectionFromSky as C, computeViewport as D, resolveConstants as E, resolveOrderGroups as F, UiText as G, TAPPABLE as H, InputMap as I, Node as J, requireNumber as K, handlerShadowWarnings as L, resetMemorySaves as M, ORDER_GROUP_BASE as N, resolveViewport as O, effectiveOrder as P, resolveRefInJson as Q, sceneDimension as R, sunDirectionFromElevationAzimuth as S, isConstRef as T, UiBanner as U, HudWidgetBase as V, UiBar as W, describeRefProblem as X, diagnose as Y, nodeRefWarnings as Z, Scene as _, restoreBehaviors as a, getBehavior as at, horizonColorFromSky as b, isCloned as c, Signal as ct, rebuildOptions as d, Behavior as et, rememberAuthoredPath as f, loader_exports as g, loadScene as h, captureBehaviors as i, clearBehaviors as it, createSaveStore as j, newUid as k, markCloned as l, buildSceneSubtree as m, SaveSlots as n, behaviorSchema as nt, savesWithoutUid as o, registerBehavior as ot, stripUids as p, requireText as q, behaviorsWithoutSave as r, behaviorSignals as rt, duplicateNode as s, registeredBehaviors as st, ENGINE_STATE_KEY as t, StubBehavior as tt, markTemplateName as u, serializeNode as v, CONST_REF_KEY as w, parseEnvironment3D as x, SCENE_FORMAT as y, FITS$1 as z };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as jsonEquals, t as jsonClone } from "./json-CfTjpvW8.js";
|
|
2
|
-
import {
|
|
3
|
-
import { t as registerCoreNodes, v as Engine } from "./register-
|
|
2
|
+
import { J as Node, Y as diagnose, ct as Signal, d as rebuildOptions, h as loadScene, l as markCloned, m as buildSceneSubtree, p as stripUids, u as markTemplateName } from "./save-slots-CEuJPUle.js";
|
|
3
|
+
import { t as registerCoreNodes, v as Engine } from "./register-5IYfooAN.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { p as registerNode } from "./touch-BnCyPA0G.js";
|
|
6
6
|
//#region src/net/loopback.ts
|
|
@@ -1150,7 +1150,7 @@ function applySyncPatch(root, patch, setProp, onMissingNode) {
|
|
|
1150
1150
|
}
|
|
1151
1151
|
}
|
|
1152
1152
|
async function createDefaultTransport(config, diagnostics) {
|
|
1153
|
-
const { createAgent8Server } = await import("./agent8-
|
|
1153
|
+
const { createAgent8Server } = await import("./agent8-BqZ0_O1u.js").then((n) => n.t);
|
|
1154
1154
|
return createAgent8Server(config, diagnostics);
|
|
1155
1155
|
}
|
|
1156
1156
|
//#endregion
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Lt as Signal, b as Engine, jt as Node } from "./behavior-
|
|
1
|
+
import { Lt as Signal, b as Engine, jt as Node } from "./behavior-Bf9P9oB2.js";
|
|
2
2
|
import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
|
|
3
3
|
import { a as SceneJson, c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
|
|
4
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
4
|
+
import { t as LoadSceneOptions } from "./loader-CBfQzB6S.js";
|
|
5
5
|
|
|
6
6
|
//#region src/net/types.d.ts
|
|
7
7
|
type Unsubscribe = () => void;
|
|
@@ -199,6 +199,6 @@ async function preloadSceneAssets(assets, title) {
|
|
|
199
199
|
//#endregion
|
|
200
200
|
//#region src/index.ts
|
|
201
201
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
202
|
-
const VERSION = "0.
|
|
202
|
+
const VERSION = "0.72.0";
|
|
203
203
|
//#endregion
|
|
204
204
|
export { findPath as a, preloadUrls as i, assetUrls as n, gridFromRows as o, preloadSceneAssets as r, VERSION as t };
|