incanto 0.48.0 → 0.50.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-model.mjs +13 -1
- package/bin/incanto-play.mjs +42 -17
- package/bin/incanto-playtest.mjs +7 -0
- package/bin/incanto-verify.mjs +10 -2
- package/dist/2d.d.ts +21 -12
- package/dist/2d.js +57 -3
- package/dist/3d.d.ts +38 -1
- package/dist/3d.js +114 -4
- package/dist/{create-game-CniOiWzN.js → create-game-BLDjy_PW.js} +5 -5
- package/dist/{create-game-akjPkFv5.js → create-game-DqqxEax1.js} +5 -5
- package/dist/{duplicate-CRtihGmC.js → duplicate-CI9WF_bg.js} +1 -1
- package/dist/{environment-presets-D1b0ydTS.js → environment-presets-XFuqu5jv.js} +15 -20
- package/dist/{gameplay-BQOeAid6.js → gameplay-DbaI313d.js} +96 -11
- package/dist/gameplay.d.ts +17 -0
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +40 -2
- package/dist/index.js +7 -7
- package/dist/{loader-r49nDwB4.js → loader-DwazzlQb.js} +36 -6
- package/dist/net.js +3 -3
- package/dist/{physics-2d-BmgXBNDB.js → physics-2d-_9VBOHn6.js} +4 -4
- package/dist/{physics-3d-CSoGjM8P.js → physics-3d-DrpF5hcG.js} +9 -9
- package/dist/react.js +1 -1
- package/dist/{register-R2JTnIMw.js → register-BNPZYJmd.js} +23 -22
- package/dist/{register-D651it1J.js → register-CB11yp21.js} +2 -2
- package/dist/{register-BSXV8T9F.js → register-uvaZj1KX.js} +62 -2
- package/dist/{replay-CAphXMyM.d.ts → replay-DYdy1wb0.d.ts} +5 -1
- package/dist/{replay-DilbZgQI.js → replay-j-m6lJ4W.js} +23 -3
- package/dist/sprite-animation-CMr6f1K2.d.ts +44 -0
- package/dist/{particle-sim-Bw7hB93B.js → sprite-animation-D_p28jwU.js} +63 -1
- package/dist/{src-Bl1Kire1.js → src-B3HrKuAi.js} +1 -1
- package/dist/{test-E4-otKqK.js → test-D16igj4C.js} +245 -38
- package/dist/test.d.ts +39 -7
- package/dist/test.js +2 -2
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-_007gPF8.js → agent8-t3kl5q9K.js} +1 -1
- package/editor/assets/{debug-0DI_MJaq.js → debug-Bu3eeAlO.js} +1 -1
- package/editor/assets/{index-B-6eYZEi.js → index-Df5g8ofT.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +10 -3
- package/skills/incanto-3d-character.md +13 -2
- package/skills/incanto-3d-models.md +40 -0
- package/skills/incanto-assets.md +15 -0
- package/skills/incanto-building-2d-games.md +57 -5
- package/skills/incanto-building-3d-games.md +7 -0
- package/skills/incanto-gameplay-behaviors.md +29 -1
- package/skills/incanto-hud.md +39 -0
- package/skills/incanto-node-reference.md +5 -3
- package/skills/incanto-physics-and-input.md +1 -1
- package/skills/incanto-playtesting.md +22 -4
- package/skills/incanto-verifying-your-game.md +14 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as Signal, _ as InputMap, b as behaviorSchema, f as Node, g as resolveOrderGroups, m as ORDER_GROUP_BASE, v as diagnose } from "./loader-
|
|
1
|
+
import { E as Signal, _ as InputMap, b as behaviorSchema, f as Node, g as resolveOrderGroups, m as ORDER_GROUP_BASE, v as diagnose } from "./loader-DwazzlQb.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
3
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
4
4
|
import { l as registerNode } from "./registry-IyWCGe4q.js";
|
|
@@ -2558,28 +2558,73 @@ var UiText = class extends HudWidgetBase {
|
|
|
2558
2558
|
static props = {
|
|
2559
2559
|
...HudWidgetBase.props,
|
|
2560
2560
|
text: { default: "" },
|
|
2561
|
+
format: { default: "" },
|
|
2561
2562
|
size: { default: 16 },
|
|
2562
2563
|
color: { default: "#ffffff" },
|
|
2563
2564
|
shadow: { default: true }
|
|
2564
2565
|
};
|
|
2565
2566
|
text = "";
|
|
2567
|
+
/**
|
|
2568
|
+
* A template with a `{}` slot that `setText` fills — `"Gems {} / 8"`.
|
|
2569
|
+
*
|
|
2570
|
+
* Empty (the default) means `setText` replaces the whole line. Resolved at
|
|
2571
|
+
* PAINT like `text`, so `"@t:hud.gems"` works and changing locale re-reads
|
|
2572
|
+
* it: the value lives here, the words live in the strings table.
|
|
2573
|
+
*/
|
|
2574
|
+
format = "";
|
|
2566
2575
|
size = 16;
|
|
2567
2576
|
color = "#ffffff";
|
|
2568
2577
|
shadow = true;
|
|
2569
2578
|
last = "\0";
|
|
2579
|
+
/** null until something has actually set a value — see `shown`. */
|
|
2580
|
+
slot = null;
|
|
2581
|
+
/**
|
|
2582
|
+
* Put a signal's value on screen — `scoreChanged → setText`.
|
|
2583
|
+
*
|
|
2584
|
+
* A `connections[]` handler must be a METHOD and `text` is a property, so the
|
|
2585
|
+
* one wire every game needs (score to screen) could not be declared: every
|
|
2586
|
+
* HUD began with a behavior whose whole job was one assignment.
|
|
2587
|
+
*/
|
|
2588
|
+
setText(value) {
|
|
2589
|
+
this.slot = String(value ?? "");
|
|
2590
|
+
if (!this.format) this.text = this.slot;
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* What it actually paints, after the template and the locale.
|
|
2594
|
+
*
|
|
2595
|
+
* Before the first value arrives, a formatted widget shows `text` — its
|
|
2596
|
+
* authored opening line. Filling the slot with nothing instead would put
|
|
2597
|
+
* `"Gems / 8"` on screen for every game's first frame, and a score line
|
|
2598
|
+
* that starts blank looks broken rather than empty.
|
|
2599
|
+
*/
|
|
2600
|
+
get shown() {
|
|
2601
|
+
if (this.format && this.slot !== null) return this._t(this.format).replace("{}", this.slot);
|
|
2602
|
+
return this._t(this.text);
|
|
2603
|
+
}
|
|
2570
2604
|
_build() {
|
|
2571
2605
|
const el = document.createElement("div");
|
|
2572
2606
|
el.style.cssText = `font-size:${this.size}px;color:${this.color};${this.shadow ? "text-shadow:0 1px 3px rgba(0,0,0,.7);" : ""}white-space:pre;`;
|
|
2573
2607
|
return el;
|
|
2574
2608
|
}
|
|
2575
2609
|
_sync() {
|
|
2576
|
-
const shown = this.
|
|
2610
|
+
const shown = this.shown;
|
|
2577
2611
|
if (shown !== this.last && this._element) {
|
|
2578
2612
|
this.last = shown;
|
|
2579
2613
|
this._element.textContent = shown;
|
|
2580
2614
|
}
|
|
2581
2615
|
}
|
|
2582
2616
|
};
|
|
2617
|
+
/**
|
|
2618
|
+
* A bar fed the wrong argument shows a plausible wrong number forever.
|
|
2619
|
+
*
|
|
2620
|
+
* The trap is one signal away: `Health.damaged` is `(amount, current)`, so
|
|
2621
|
+
* wiring it to `setValue` paints the DAMAGE as the health and looks like a
|
|
2622
|
+
* working health bar. Say which signal has the shape a bar wants.
|
|
2623
|
+
*/
|
|
2624
|
+
function requireNumber(value, node, method) {
|
|
2625
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2626
|
+
throw new IncantoError("PROP_TYPE_MISMATCH", `UiBar '${node.getPath()}'.${method} needs a number, got ${JSON.stringify(value)}. Check what the wired signal passes FIRST — 'damaged' is (amount, current), 'healthChanged' is (current, max), which is the shape a bar wants.`);
|
|
2627
|
+
}
|
|
2583
2628
|
/** A labeled progress bar (health, stamina, reload, boss HP). */
|
|
2584
2629
|
var UiBar = class extends HudWidgetBase {
|
|
2585
2630
|
static typeName = "UiBar";
|
|
@@ -2612,6 +2657,21 @@ var UiBar = class extends HudWidgetBase {
|
|
|
2612
2657
|
get ratio() {
|
|
2613
2658
|
return this.max > 0 ? Math.min(1, Math.max(0, this.value / this.max)) : 0;
|
|
2614
2659
|
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Drive the bar from a signal — `healthChanged → setValue`.
|
|
2662
|
+
*
|
|
2663
|
+
* Takes the max as a second argument because that is the shape the signal
|
|
2664
|
+
* already has, so a bar whose ceiling moves (a max-HP upgrade) needs no
|
|
2665
|
+
* second wire.
|
|
2666
|
+
*/
|
|
2667
|
+
setValue(value, max) {
|
|
2668
|
+
this.value = requireNumber(value, this, "setValue");
|
|
2669
|
+
if (max !== void 0) this.max = requireNumber(max, this, "setValue");
|
|
2670
|
+
}
|
|
2671
|
+
/** Move the ceiling on its own, leaving the fill where it is. */
|
|
2672
|
+
setMax(max) {
|
|
2673
|
+
this.max = requireNumber(max, this, "setMax");
|
|
2674
|
+
}
|
|
2615
2675
|
_build() {
|
|
2616
2676
|
const wrap = document.createElement("div");
|
|
2617
2677
|
wrap.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
@@ -62,7 +62,11 @@ interface ReplayJson {
|
|
|
62
62
|
/** File-format marker for forward compatibility. */
|
|
63
63
|
replay: 1;
|
|
64
64
|
/** Tick timestamps (ms) exactly as the loop delivered them. */
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* One entry per recorded FRAME: the `nowMs` it was ticked with, or `null` for
|
|
67
|
+
* a fixed `step()`. Older recordings are all numbers and still replay.
|
|
68
|
+
*/
|
|
69
|
+
ticks: Array<number | null>;
|
|
66
70
|
events: ReplayEvent[];
|
|
67
71
|
}
|
|
68
72
|
interface Recorder {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as translationKey } from "./register-
|
|
1
|
+
import { M as translationKey } from "./register-uvaZj1KX.js";
|
|
2
2
|
//#region src/core/audit.ts
|
|
3
3
|
const ANIMATED_TYPES = new Set([
|
|
4
4
|
"Particles2D",
|
|
@@ -109,13 +109,25 @@ function auditStrings(scene) {
|
|
|
109
109
|
}
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/core/replay.ts
|
|
112
|
+
/**
|
|
113
|
+
* Every entry point that can change what the game is being told, because one
|
|
114
|
+
* left out is a replay that reproduces a character standing still.
|
|
115
|
+
*
|
|
116
|
+
* `setActionVector` was the omission that mattered: the playtest driver moves
|
|
117
|
+
* with it and presses buttons with `pressAction`, so its replays recorded all
|
|
118
|
+
* the jumping and none of the walking, and played back as a character hopping
|
|
119
|
+
* on the spot where the real run had walked off a ledge.
|
|
120
|
+
*/
|
|
112
121
|
const RECORDED_METHODS = [
|
|
113
122
|
"handleKey",
|
|
114
123
|
"handleMouseButton",
|
|
115
124
|
"handlePointerMove",
|
|
116
125
|
"handleWheel",
|
|
117
126
|
"pressAction",
|
|
118
|
-
"releaseAction"
|
|
127
|
+
"releaseAction",
|
|
128
|
+
"setActionVector",
|
|
129
|
+
"setPointerPosition",
|
|
130
|
+
"clearInjected"
|
|
119
131
|
];
|
|
120
132
|
/**
|
|
121
133
|
* Tap the engine's input entry points and tick clock. Recording starts at
|
|
@@ -139,17 +151,23 @@ function startRecording(engine) {
|
|
|
139
151
|
};
|
|
140
152
|
}
|
|
141
153
|
const originalTick = engine.tick.bind(engine);
|
|
154
|
+
const originalStep = engine.step.bind(engine);
|
|
142
155
|
const engineAny = engine;
|
|
143
156
|
engineAny.tick = (nowMs) => {
|
|
144
157
|
ticks.push(nowMs);
|
|
145
158
|
originalTick(nowMs);
|
|
146
159
|
};
|
|
160
|
+
engineAny.step = () => {
|
|
161
|
+
ticks.push(null);
|
|
162
|
+
originalStep();
|
|
163
|
+
};
|
|
147
164
|
let stopped = false;
|
|
148
165
|
return { stop() {
|
|
149
166
|
if (!stopped) {
|
|
150
167
|
stopped = true;
|
|
151
168
|
for (const [name, fn] of originals) input[name] = fn;
|
|
152
169
|
engineAny.tick = originalTick;
|
|
170
|
+
engineAny.step = originalStep;
|
|
153
171
|
}
|
|
154
172
|
return {
|
|
155
173
|
replay: 1,
|
|
@@ -173,7 +191,9 @@ function replay(engine, recording, opts) {
|
|
|
173
191
|
if (typeof fn === "function") fn.apply(engine.input, args);
|
|
174
192
|
cursor += 1;
|
|
175
193
|
}
|
|
176
|
-
|
|
194
|
+
const at = recording.ticks[i];
|
|
195
|
+
if (at === null) engine.step();
|
|
196
|
+
else engine.tick(at);
|
|
177
197
|
opts?.onTick?.(i);
|
|
178
198
|
}
|
|
179
199
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/core/sprite-animation.d.ts
|
|
2
|
+
interface AnimationDef {
|
|
3
|
+
/**
|
|
4
|
+
* Two numbers = inclusive [start, end] frame RANGE.
|
|
5
|
+
* Any other length = explicit frame list.
|
|
6
|
+
*/
|
|
7
|
+
frames: number[];
|
|
8
|
+
fps: number;
|
|
9
|
+
loop?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A clip, or the NAME of another clip in the same map.
|
|
13
|
+
*
|
|
14
|
+
* The alias exists because the character a game is built from and the character
|
|
15
|
+
* a controller describes are not the same vocabulary.
|
|
16
|
+
* `CharacterController2D`/`3D` emit `idle`/`run`/`jump`/`fall`/`wallSlide`/
|
|
17
|
+
* `dash`; the sheets in the engine's own catalog carry `idle`/`move`/`attack`,
|
|
18
|
+
* and no sheet anywhere ships a `fall`. So the documented one-line wiring —
|
|
19
|
+
* `movementStateChanged → play` — asked for clips that do not exist, and the
|
|
20
|
+
* hard error was correct and useless: there is nothing to rename it TO.
|
|
21
|
+
*
|
|
22
|
+
* An alias is the missing half. `"fall": "idle"` says what to show while
|
|
23
|
+
* falling, in the scene, next to everything else about that character.
|
|
24
|
+
*/
|
|
25
|
+
type AnimationEntry = AnimationDef | string;
|
|
26
|
+
/**
|
|
27
|
+
* Follow aliases to the clip that actually plays.
|
|
28
|
+
*
|
|
29
|
+
* Chains are allowed (`fall → jump → idle`) because they are how a fallback
|
|
30
|
+
* ladder reads, and a cycle is a hard error rather than a hang.
|
|
31
|
+
*/
|
|
32
|
+
declare function resolveAnimation(animations: Record<string, AnimationEntry>, name: string, owner: string): {
|
|
33
|
+
name: string;
|
|
34
|
+
def: AnimationDef;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* `[start, end]` is a RANGE; anything else is an explicit list.
|
|
38
|
+
*
|
|
39
|
+
* Two ascending numbers are overwhelmingly a range in every sheet format, and
|
|
40
|
+
* a two-frame flip-book is spelled `[3, 3, 4, 4]` or a descending pair.
|
|
41
|
+
*/
|
|
42
|
+
declare function resolveFrames(frames: number[], name: string): number[];
|
|
43
|
+
//#endregion
|
|
44
|
+
export { resolveFrames as i, AnimationEntry as n, resolveAnimation as r, AnimationDef as t };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
1
2
|
import { n as jsonEquals } from "./json-BLk7H2Qa.js";
|
|
2
3
|
//#region src/core/particle-presets.ts
|
|
3
4
|
const PARTICLE_PRESETS = {
|
|
@@ -264,4 +265,65 @@ var ParticleSim = class {
|
|
|
264
265
|
}
|
|
265
266
|
};
|
|
266
267
|
//#endregion
|
|
267
|
-
|
|
268
|
+
//#region src/core/sprite-animation.ts
|
|
269
|
+
/**
|
|
270
|
+
* The animation MAP a spritesheet node is driven by — shared by 2D and 3D.
|
|
271
|
+
*
|
|
272
|
+
* Core, not an adapter: no three.js, and the two sprite nodes had grown
|
|
273
|
+
* byte-identical copies of the frame reader.
|
|
274
|
+
*/
|
|
275
|
+
/**
|
|
276
|
+
* Follow aliases to the clip that actually plays.
|
|
277
|
+
*
|
|
278
|
+
* Chains are allowed (`fall → jump → idle`) because they are how a fallback
|
|
279
|
+
* ladder reads, and a cycle is a hard error rather than a hang.
|
|
280
|
+
*/
|
|
281
|
+
function resolveAnimation(animations, name, owner) {
|
|
282
|
+
const seen = [];
|
|
283
|
+
let key = name;
|
|
284
|
+
for (;;) {
|
|
285
|
+
const entry = animations[key];
|
|
286
|
+
if (entry === void 0) {
|
|
287
|
+
const via = seen.length > 0 ? ` (via ${seen.join(" → ")})` : "";
|
|
288
|
+
throw new IncantoError("UNKNOWN_ANIMATION", `No animation '${key}' on '${owner}'${via}. Available: [${Object.keys(animations).join(", ")}].`);
|
|
289
|
+
}
|
|
290
|
+
if (typeof entry !== "string") return {
|
|
291
|
+
name: key,
|
|
292
|
+
def: entry
|
|
293
|
+
};
|
|
294
|
+
if (seen.includes(key)) throw new IncantoError("UNKNOWN_ANIMATION", `Animation aliases on '${owner}' point in a circle: ${[...seen, key].join(" → ")}.`);
|
|
295
|
+
seen.push(key);
|
|
296
|
+
key = entry;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Every alias resolves — checked once, at ready, rather than on the frame the
|
|
301
|
+
* character first jumps.
|
|
302
|
+
*
|
|
303
|
+
* A cosmetic map that only fails the moment a state is first entered is the
|
|
304
|
+
* shape of bug that reaches players: the game loads, plays, and dies on a
|
|
305
|
+
* ledge twenty minutes in.
|
|
306
|
+
*/
|
|
307
|
+
function validateAnimationAliases(animations, owner) {
|
|
308
|
+
for (const [name, entry] of Object.entries(animations)) if (typeof entry === "string") resolveAnimation(animations, name, owner);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* `[start, end]` is a RANGE; anything else is an explicit list.
|
|
312
|
+
*
|
|
313
|
+
* Two ascending numbers are overwhelmingly a range in every sheet format, and
|
|
314
|
+
* a two-frame flip-book is spelled `[3, 3, 4, 4]` or a descending pair.
|
|
315
|
+
*/
|
|
316
|
+
function resolveFrames(frames, name) {
|
|
317
|
+
if (!Array.isArray(frames) || frames.length === 0) throw new IncantoError("BAD_FORMAT", `Animation '${name}': "frames" must be a non-empty array.`);
|
|
318
|
+
if (frames.length === 2) {
|
|
319
|
+
const [start, end] = frames;
|
|
320
|
+
if (end >= start) {
|
|
321
|
+
const out = [];
|
|
322
|
+
for (let f = start; f <= end; f++) out.push(f);
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return [...frames];
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
export { PARTICLE_PRESETS as a, ParticleSim as i, resolveFrames as n, PARTICLE_PRESET_NAMES as o, validateAnimationAliases as r, applyParticlePreset as s, resolveAnimation as t };
|
|
@@ -161,6 +161,6 @@ function newUid() {
|
|
|
161
161
|
//#endregion
|
|
162
162
|
//#region src/index.ts
|
|
163
163
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
164
|
-
const VERSION = "0.
|
|
164
|
+
const VERSION = "0.50.0";
|
|
165
165
|
//#endregion
|
|
166
166
|
export { findPath as a, preloadUrls as i, newUid as n, gridFromRows as o, assetUrls as r, VERSION as t };
|