incanto 0.7.1 → 0.8.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-check.mjs +6 -4
- package/dist/2d.d.ts +91 -3
- package/dist/2d.js +4 -4
- package/dist/3d.d.ts +43 -4
- package/dist/3d.js +4 -4
- package/dist/{audio-player-C3bH_eZ5.d.ts → audio-player-Dyjg5k92.d.ts} +1 -1
- package/dist/audit-C6rMyict.js +58 -0
- package/dist/{behavior-q3cejJgs.d.ts → behavior-Bod1AyJS.d.ts} +31 -1
- package/dist/{create-game-rFulaS3s.js → create-game-66c4iYJE.js} +4 -5
- package/dist/{create-game-DuDkqnjl.js → create-game-BNDSbhy-.js} +4 -5
- package/dist/debug.d.ts +1 -1
- package/dist/{errors-BMFaY68Q.d.ts → errors-1dXlIwoR.d.ts} +7 -1
- package/dist/{gameplay-CDFgSG6z.js → gameplay-DPMgZk9W.js} +108 -1
- package/dist/gameplay.d.ts +44 -3
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +151 -9
- package/dist/index.js +189 -4
- package/dist/{loader-DnLMMRy8.d.ts → loader-BcUDfNHn.d.ts} +1 -1
- package/dist/net.d.ts +46 -2
- package/dist/net.js +54 -2
- package/dist/particle-sim-CyUU7HVU.js +281 -0
- package/dist/{physics-2d-CZM5Y90X.js → physics-2d-q2N362ph.js} +45 -4
- package/dist/{physics-3d-DT5e8izo.js → physics-3d-CvHr31d4.js} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-CpXcMiEk.js → register-Cbs7QxoV.js} +139 -4
- package/dist/{register-Cl3pAxX-.js → register-Cqjxrfq0.js} +254 -4
- package/dist/{register-C35HDZpm.js → register-CxmuI9FL.js} +1 -1
- package/dist/{particle-sim-m3CdTGSl.js → register-DRPIjrKG.js} +1178 -278
- package/dist/test.d.ts +4 -10
- package/dist/test.js +11 -16
- package/editor/assets/{agent8-N4bwVH7C.js → agent8-B8QnWCeP.js} +1 -1
- package/editor/assets/{index-CXFNanuR.js → index-CVhnNkb3.js} +81 -81
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +427 -0
- package/skills/incanto-3d-character.md +14 -0
- package/skills/incanto-audio.md +23 -0
- package/skills/incanto-building-2d-games.md +31 -0
- package/skills/incanto-building-3d-games.md +21 -0
- package/skills/incanto-gameplay-behaviors.md +28 -0
- package/skills/incanto-hud.md +24 -0
- package/skills/incanto-multiplayer.md +27 -0
- package/skills/incanto-node-reference.md +73 -0
- package/skills/incanto-physics-and-input.md +9 -0
- package/skills/incanto-verifying-your-game.md +31 -0
- package/dist/register-Dl_ixIJe.js +0 -834
package/dist/gameplay.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
|
|
1
|
+
import { C as FollowCamera, D as Chase, E as Collector, S as restartScene, T as Health, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DamageOnContact, x as goToScene, y as Interactable } from "./gameplay-DPMgZk9W.js";
|
|
2
|
+
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
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
|
|
3
|
-
import { n as loadScene, t as LoadSceneOptions } from "./loader-
|
|
2
|
+
import { $ as Node, A as SfxPlayOptions, B as spatialPan, C as RendererStats, D as LogManager, E as LogLevel, F as ROLLOFF_MODELS, G as SynthOptions, H as SFX_PRESET_NAMES, I as RolloffModel, J as MusicManager, K as synthSfx, L as SpatialParams, M as Voice, N as VoicePreset, O as InputMap, P as Listener, Q as BusName, R as Vec3, S as GameStats, T as LogEntry, U as SfxParams, V as SFX_PRESETS, W as SfxWave, X as PlayMusicOptions, Y as MusicTrack, Z as AudioBuses, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as NodeLifecycle, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as isAudioContextAvailable, k as SfxEngine, l as PropSchema, m as getNodeType, n as BehaviorCtor, nt as SignalListener, o as registeredBehaviors, p as getNodeSignals, q as MusicBackend, r as clearBehaviors, rt as SceneTree, s as NodeCtor, t as Behavior, tt as Signal, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as spatialGain } from "./behavior-Bod1AyJS.js";
|
|
3
|
+
import { n as loadScene, t as LoadSceneOptions } from "./loader-BcUDfNHn.js";
|
|
4
4
|
import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
|
|
5
|
-
import { n as AudioPlayer, t as AudioElementLike } from "./audio-player-
|
|
6
|
-
import { n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-
|
|
5
|
+
import { n as AudioPlayer, t as AudioElementLike } from "./audio-player-Dyjg5k92.js";
|
|
6
|
+
import { i as auditScene, n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-1dXlIwoR.js";
|
|
7
7
|
|
|
8
8
|
//#region src/core/audio/crossfade.d.ts
|
|
9
9
|
/**
|
|
@@ -114,7 +114,7 @@ declare class HudLayer extends Node {
|
|
|
114
114
|
_slot(anchor: HudAnchor): HTMLElement | null;
|
|
115
115
|
}
|
|
116
116
|
/** Shared plumbing: mount into the parent HudLayer's anchor slot. */
|
|
117
|
-
declare abstract class
|
|
117
|
+
declare abstract class HudWidgetBase extends Node {
|
|
118
118
|
static readonly props: PropSchema;
|
|
119
119
|
anchor: HudAnchor;
|
|
120
120
|
visible: boolean;
|
|
@@ -128,7 +128,7 @@ declare abstract class HudWidget extends Node {
|
|
|
128
128
|
protected _sync(): void;
|
|
129
129
|
}
|
|
130
130
|
/** A text line (score, timer, hints). Set `.text` from behaviors. */
|
|
131
|
-
declare class UiText extends
|
|
131
|
+
declare class UiText extends HudWidgetBase {
|
|
132
132
|
static override readonly typeName: string;
|
|
133
133
|
static override readonly props: PropSchema;
|
|
134
134
|
text: string;
|
|
@@ -140,7 +140,7 @@ declare class UiText extends HudWidget {
|
|
|
140
140
|
protected override _sync(): void;
|
|
141
141
|
}
|
|
142
142
|
/** A labeled progress bar (health, stamina, reload, boss HP). */
|
|
143
|
-
declare class UiBar extends
|
|
143
|
+
declare class UiBar extends HudWidgetBase {
|
|
144
144
|
static override readonly typeName: string;
|
|
145
145
|
static override readonly props: PropSchema;
|
|
146
146
|
value: number;
|
|
@@ -164,7 +164,7 @@ declare class UiBar extends HudWidget {
|
|
|
164
164
|
* in/out and a queue — call `.show(text, { color, seconds })`; `sticky:true`
|
|
165
165
|
* (seconds: 0) keeps it until the next show(). Emits `bannerShown(text)`.
|
|
166
166
|
*/
|
|
167
|
-
declare class UiBanner extends
|
|
167
|
+
declare class UiBanner extends HudWidgetBase {
|
|
168
168
|
static override readonly typeName: string;
|
|
169
169
|
static readonly signals: string[];
|
|
170
170
|
static override readonly props: PropSchema;
|
|
@@ -187,6 +187,75 @@ declare class UiBanner extends HudWidget {
|
|
|
187
187
|
override update(dt: number): void;
|
|
188
188
|
}
|
|
189
189
|
//#endregion
|
|
190
|
+
//#region src/core/nodes/dialogue.d.ts
|
|
191
|
+
/**
|
|
192
|
+
* Interactive HUD: clickable buttons and a typewriter dialogue box — the
|
|
193
|
+
* storytelling layer RPG/adventure games kept hand-rolling in DOM.
|
|
194
|
+
* Same family as the other HUD widgets: children of a HudLayer, DOM-backed,
|
|
195
|
+
* silent no-ops headless.
|
|
196
|
+
*/
|
|
197
|
+
/** A clickable button (menus, "START", dialog choices). Emits `pressed`. */
|
|
198
|
+
declare class UiButton extends HudWidgetBase {
|
|
199
|
+
static override readonly typeName: string;
|
|
200
|
+
static readonly signals: readonly string[];
|
|
201
|
+
static override readonly props: PropSchema;
|
|
202
|
+
text: string;
|
|
203
|
+
size: number;
|
|
204
|
+
color: string;
|
|
205
|
+
background: string;
|
|
206
|
+
disabled: boolean;
|
|
207
|
+
private lastText;
|
|
208
|
+
/** Programmatic press — same path as a click (tests, gamepad menus). */
|
|
209
|
+
press(): void;
|
|
210
|
+
protected _build(): HTMLElement;
|
|
211
|
+
protected override _sync(): void;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A typewriter dialogue box with a queue and choices:
|
|
215
|
+
*
|
|
216
|
+
* const talk = node.getNode('%Dialogue') as UiDialogue;
|
|
217
|
+
* talk.say('Elder', 'Welcome to Lumina Village...');
|
|
218
|
+
* talk.say('Elder', 'Will you help us?', ['Yes', 'No']);
|
|
219
|
+
* talk.on('choiceMade', (index) => { ... });
|
|
220
|
+
*
|
|
221
|
+
* Click / `advance()` reveals the full line, then advances. Emits
|
|
222
|
+
* `lineShown(text)`, `choiceMade(index)`, `dialogueFinished()`.
|
|
223
|
+
*/
|
|
224
|
+
declare class UiDialogue extends HudWidgetBase {
|
|
225
|
+
static override readonly typeName: string;
|
|
226
|
+
static readonly signals: readonly string[];
|
|
227
|
+
static override readonly props: PropSchema;
|
|
228
|
+
override anchor: HudAnchor;
|
|
229
|
+
/** Typewriter speed; 0 = instant lines. */
|
|
230
|
+
charsPerSecond: number;
|
|
231
|
+
width: number;
|
|
232
|
+
private queue;
|
|
233
|
+
private current;
|
|
234
|
+
private revealed;
|
|
235
|
+
private speakerEl;
|
|
236
|
+
private textEl;
|
|
237
|
+
private choicesEl;
|
|
238
|
+
private hintEl;
|
|
239
|
+
/** True while a conversation is on screen (pause player input on this). */
|
|
240
|
+
get active(): boolean;
|
|
241
|
+
/** What the box currently shows (grows while typing) — test-friendly. */
|
|
242
|
+
get showing(): string;
|
|
243
|
+
/** Queue a line; optional `choices` renders buttons after the line types out. */
|
|
244
|
+
say(speaker: string, text: string, choices?: string[]): void;
|
|
245
|
+
/**
|
|
246
|
+
* Player pressed "next": mid-typing reveals the whole line; a fully-typed
|
|
247
|
+
* line without choices advances (choice lines wait for `choose`).
|
|
248
|
+
*/
|
|
249
|
+
advance(): void;
|
|
250
|
+
/** Pick a choice on the current line (buttons call this). */
|
|
251
|
+
choose(index: number): void;
|
|
252
|
+
/** Drop the conversation (cutscene interrupts). */
|
|
253
|
+
clear(): void;
|
|
254
|
+
private next;
|
|
255
|
+
protected _build(): HTMLElement;
|
|
256
|
+
override update(dt: number): void;
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
190
259
|
//#region src/core/nodes/timer.d.ts
|
|
191
260
|
/**
|
|
192
261
|
* The canonical serializable game clock — never `setTimeout` in game logic.
|
|
@@ -267,6 +336,33 @@ declare const PARTICLE_PRESET_NAMES: readonly string[];
|
|
|
267
336
|
*/
|
|
268
337
|
declare function applyParticlePreset(target: Record<string, unknown>, presetName: string, schema: PropSchema): void;
|
|
269
338
|
//#endregion
|
|
339
|
+
//#region src/core/pathfinding.d.ts
|
|
340
|
+
/**
|
|
341
|
+
* Grid A* — the pathfinding every top-down/RPG/tower-defense asks for and
|
|
342
|
+
* agents keep re-inventing badly. Pure and dimension-free: cells in, cells
|
|
343
|
+
* out; the caller maps cells to world units (px, meters, tiles).
|
|
344
|
+
*/
|
|
345
|
+
interface PathGrid {
|
|
346
|
+
width: number;
|
|
347
|
+
height: number;
|
|
348
|
+
/** True = impassable. Out-of-bounds is always solid. */
|
|
349
|
+
solid(x: number, y: number): boolean;
|
|
350
|
+
}
|
|
351
|
+
interface FindPathOptions {
|
|
352
|
+
/** Allow diagonal steps (blocked from cutting corners). Default true. */
|
|
353
|
+
diagonal?: boolean;
|
|
354
|
+
/** Abort guard for huge/impossible searches. Default 20000 nodes. */
|
|
355
|
+
maxExpansions?: number;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* A* from `from` to `to` (inclusive cell coords). Returns the cell path
|
|
359
|
+
* INCLUDING both endpoints, or null when unreachable. Straight steps cost
|
|
360
|
+
* 1, diagonals √2; the heuristic is octile (admissible for both modes).
|
|
361
|
+
*/
|
|
362
|
+
declare function findPath(grid: PathGrid, from: [number, number], to: [number, number], opts?: FindPathOptions): [number, number][] | null;
|
|
363
|
+
/** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
|
|
364
|
+
declare function gridFromRows(rows: readonly string[]): PathGrid;
|
|
365
|
+
//#endregion
|
|
270
366
|
//#region src/core/preload.d.ts
|
|
271
367
|
interface PreloadResult {
|
|
272
368
|
/** Urls whose fetch failed — show an error UI instead of pretending success. */
|
|
@@ -310,6 +406,52 @@ declare function resolveRendering(environment: JsonObject | undefined, fallback:
|
|
|
310
406
|
pixelRatio?: number;
|
|
311
407
|
}): ResolvedRendering;
|
|
312
408
|
//#endregion
|
|
409
|
+
//#region src/core/replay.d.ts
|
|
410
|
+
/**
|
|
411
|
+
* Deterministic input replay — the engine already runs on a seeded RNG and
|
|
412
|
+
* an injectable clock, so a recording of (tick timestamps + input events)
|
|
413
|
+
* replays BIT-IDENTICALLY into a fresh engine with the same seed and scene.
|
|
414
|
+
* That turns "does my game still work?" into an assertion an agent can run
|
|
415
|
+
* headlessly: record once while playing, replay forever as a regression.
|
|
416
|
+
*
|
|
417
|
+
* const rec = startRecording(engine); // play the game...
|
|
418
|
+
* const replayJson = rec.stop(); // ...save this JSON
|
|
419
|
+
*
|
|
420
|
+
* const fresh = new Engine({ seed: SAME_SEED }); // same seed + scene JSON
|
|
421
|
+
* fresh.setScene(loadScene(sceneJson));
|
|
422
|
+
* replay(fresh, replayJson); // now assert game state
|
|
423
|
+
*
|
|
424
|
+
* Caveats (documented, by design): game code must use `engine.rng` (not
|
|
425
|
+
* Math.random) and dt/engine.time (not Date.now) — the engine's own rules.
|
|
426
|
+
* Gamepad polling is not recorded (bind pads to actions; actions replay).
|
|
427
|
+
*/
|
|
428
|
+
/** [tickIndex, methodName, ...primitive args] */
|
|
429
|
+
type ReplayEvent = [number, string, ...(string | number | boolean)[]];
|
|
430
|
+
interface ReplayJson {
|
|
431
|
+
/** File-format marker for forward compatibility. */
|
|
432
|
+
replay: 1;
|
|
433
|
+
/** Tick timestamps (ms) exactly as the loop delivered them. */
|
|
434
|
+
ticks: number[];
|
|
435
|
+
events: ReplayEvent[];
|
|
436
|
+
}
|
|
437
|
+
interface Recorder {
|
|
438
|
+
/** Detach the taps and return the recording. */
|
|
439
|
+
stop(): ReplayJson;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Tap the engine's input entry points and tick clock. Recording starts at
|
|
443
|
+
* the CURRENT moment — start it before the first tick for full-run replays.
|
|
444
|
+
*/
|
|
445
|
+
declare function startRecording(engine: Engine): Recorder;
|
|
446
|
+
/**
|
|
447
|
+
* Drive a fresh engine through a recording: for each recorded tick, feed
|
|
448
|
+
* that tick's input events first, then tick with the recorded timestamp.
|
|
449
|
+
* `onTick(index)` lets tests assert mid-run state.
|
|
450
|
+
*/
|
|
451
|
+
declare function replay(engine: Engine, recording: ReplayJson, opts?: {
|
|
452
|
+
onTick?: (index: number) => void;
|
|
453
|
+
}): void;
|
|
454
|
+
//#endregion
|
|
313
455
|
//#region src/core/save.d.ts
|
|
314
456
|
/**
|
|
315
457
|
* Namespaced game persistence — high scores, unlocks, settings. localStorage
|
|
@@ -453,4 +595,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
|
|
|
453
595
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
454
596
|
declare const VERSION: string;
|
|
455
597
|
//#endregion
|
|
456
|
-
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, type HudAnchor, HudLayer, 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, ORDER_GROUP_BASE, type OrderGroup, 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, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiText, VERSION, type Vec3, type ViewportFit, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, 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 };
|
|
598
|
+
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 FindPathOptions, type GameStats, type HudAnchor, HudLayer, 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, ORDER_GROUP_BASE, type OrderGroup, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, ROLLOFF_MODELS, type Recorder, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,128 @@
|
|
|
1
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-B4OEXDZ8.js";
|
|
2
|
-
import { a as
|
|
2
|
+
import { C as crossfadeGains, S as WebAudioMusicBackend, T as AudioBuses, _ as spatialPan, a as HudLayer, b as synthSfx, c as UiText, d as LogManager, f as InputMap, g as spatialGain, h as ROLLOFF_MODELS, i as UiDialogue, l as AudioPlayer, m as isAudioContextAvailable, n as Timer, o as UiBanner, p as SfxEngine, r as UiButton, s as UiBar, t as registerCoreNodes, u as Engine, v as SFX_PRESETS, w as fadeGain, x as MusicManager, y as SFX_PRESET_NAMES } from "./register-DRPIjrKG.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
|
-
import {
|
|
5
|
+
import { t as auditScene } from "./audit-C6rMyict.js";
|
|
6
6
|
import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
7
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
8
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
9
|
+
import { a as ORDER_GROUP_BASE, i as applyParticlePreset, n as PARTICLE_PRESETS, o as effectiveOrder, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-CyUU7HVU.js";
|
|
9
10
|
import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-031PxtCR.js";
|
|
10
11
|
import { t as duplicateNode } from "./duplicate-BEvGBtb_.js";
|
|
12
|
+
//#region src/core/pathfinding.ts
|
|
13
|
+
/**
|
|
14
|
+
* A* from `from` to `to` (inclusive cell coords). Returns the cell path
|
|
15
|
+
* INCLUDING both endpoints, or null when unreachable. Straight steps cost
|
|
16
|
+
* 1, diagonals √2; the heuristic is octile (admissible for both modes).
|
|
17
|
+
*/
|
|
18
|
+
function findPath(grid, from, to, opts) {
|
|
19
|
+
const [sx, sy] = from;
|
|
20
|
+
const [tx, ty] = to;
|
|
21
|
+
const { width, height } = grid;
|
|
22
|
+
const inBounds = (x, y) => x >= 0 && y >= 0 && x < width && y < height;
|
|
23
|
+
if (!inBounds(sx, sy) || !inBounds(tx, ty)) return null;
|
|
24
|
+
if (grid.solid(sx, sy) || grid.solid(tx, ty)) return null;
|
|
25
|
+
if (sx === tx && sy === ty) return [[sx, sy]];
|
|
26
|
+
const diagonal = opts?.diagonal ?? true;
|
|
27
|
+
const maxExpansions = opts?.maxExpansions ?? 2e4;
|
|
28
|
+
const idx = (x, y) => y * width + x;
|
|
29
|
+
const gScore = new Float64Array(width * height).fill(Number.POSITIVE_INFINITY);
|
|
30
|
+
const cameFrom = new Int32Array(width * height).fill(-1);
|
|
31
|
+
const closed = new Uint8Array(width * height);
|
|
32
|
+
const heap = [];
|
|
33
|
+
const fScore = new Float64Array(width * height).fill(Number.POSITIVE_INFINITY);
|
|
34
|
+
const push = (n) => {
|
|
35
|
+
heap.push(n);
|
|
36
|
+
let i = heap.length - 1;
|
|
37
|
+
while (i > 0) {
|
|
38
|
+
const p = i - 1 >> 1;
|
|
39
|
+
if (fScore[heap[p]] <= fScore[heap[i]]) break;
|
|
40
|
+
const t = heap[p];
|
|
41
|
+
heap[p] = heap[i];
|
|
42
|
+
heap[i] = t;
|
|
43
|
+
i = p;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const pop = () => {
|
|
47
|
+
const top = heap[0];
|
|
48
|
+
const last = heap.pop();
|
|
49
|
+
if (heap.length > 0) {
|
|
50
|
+
heap[0] = last;
|
|
51
|
+
let i = 0;
|
|
52
|
+
for (;;) {
|
|
53
|
+
const l = i * 2 + 1;
|
|
54
|
+
const r = l + 1;
|
|
55
|
+
let m = i;
|
|
56
|
+
if (l < heap.length && fScore[heap[l]] < fScore[heap[m]]) m = l;
|
|
57
|
+
if (r < heap.length && fScore[heap[r]] < fScore[heap[m]]) m = r;
|
|
58
|
+
if (m === i) break;
|
|
59
|
+
const t = heap[m];
|
|
60
|
+
heap[m] = heap[i];
|
|
61
|
+
heap[i] = t;
|
|
62
|
+
i = m;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return top;
|
|
66
|
+
};
|
|
67
|
+
const SQRT2 = Math.SQRT2;
|
|
68
|
+
const octile = (x, y) => {
|
|
69
|
+
const dx = Math.abs(x - tx);
|
|
70
|
+
const dy = Math.abs(y - ty);
|
|
71
|
+
return dx > dy ? dx - dy + SQRT2 * dy : dy - dx + SQRT2 * dx;
|
|
72
|
+
};
|
|
73
|
+
const start = idx(sx, sy);
|
|
74
|
+
gScore[start] = 0;
|
|
75
|
+
fScore[start] = octile(sx, sy);
|
|
76
|
+
push(start);
|
|
77
|
+
let expansions = 0;
|
|
78
|
+
while (heap.length > 0) {
|
|
79
|
+
if (++expansions > maxExpansions) return null;
|
|
80
|
+
const current = pop();
|
|
81
|
+
if (closed[current]) continue;
|
|
82
|
+
closed[current] = 1;
|
|
83
|
+
const cx = current % width;
|
|
84
|
+
const cy = current / width | 0;
|
|
85
|
+
if (cx === tx && cy === ty) {
|
|
86
|
+
const path = [];
|
|
87
|
+
let n = current;
|
|
88
|
+
while (n !== -1) {
|
|
89
|
+
path.push([n % width, n / width | 0]);
|
|
90
|
+
n = cameFrom[n];
|
|
91
|
+
}
|
|
92
|
+
path.reverse();
|
|
93
|
+
return path;
|
|
94
|
+
}
|
|
95
|
+
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
|
96
|
+
if (dx === 0 && dy === 0) continue;
|
|
97
|
+
const isDiag = dx !== 0 && dy !== 0;
|
|
98
|
+
if (isDiag && !diagonal) continue;
|
|
99
|
+
const nx = cx + dx;
|
|
100
|
+
const ny = cy + dy;
|
|
101
|
+
if (!inBounds(nx, ny) || grid.solid(nx, ny)) continue;
|
|
102
|
+
if (isDiag && (grid.solid(cx + dx, cy) || grid.solid(cx, cy + dy))) continue;
|
|
103
|
+
const n = idx(nx, ny);
|
|
104
|
+
if (closed[n]) continue;
|
|
105
|
+
const tentative = gScore[current] + (isDiag ? SQRT2 : 1);
|
|
106
|
+
if (tentative < gScore[n]) {
|
|
107
|
+
gScore[n] = tentative;
|
|
108
|
+
fScore[n] = tentative + octile(nx, ny);
|
|
109
|
+
cameFrom[n] = current;
|
|
110
|
+
push(n);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
|
|
117
|
+
function gridFromRows(rows) {
|
|
118
|
+
const height = rows.length;
|
|
119
|
+
return {
|
|
120
|
+
width: rows[0]?.length ?? 0,
|
|
121
|
+
height,
|
|
122
|
+
solid: (x, y) => (rows[y]?.[x] ?? "#") === "#"
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
11
126
|
//#region src/core/preload.ts
|
|
12
127
|
/**
|
|
13
128
|
* Warm the HTTP cache for a list of asset urls with a progress callback —
|
|
@@ -40,6 +155,76 @@ function assetUrls(assets) {
|
|
|
40
155
|
return Object.values(assets ?? {}).map((a) => a.url).filter((u) => typeof u === "string" && !u.startsWith("data:"));
|
|
41
156
|
}
|
|
42
157
|
//#endregion
|
|
158
|
+
//#region src/core/replay.ts
|
|
159
|
+
const RECORDED_METHODS = [
|
|
160
|
+
"handleKey",
|
|
161
|
+
"handleMouseButton",
|
|
162
|
+
"handlePointerMove",
|
|
163
|
+
"handleWheel",
|
|
164
|
+
"pressAction",
|
|
165
|
+
"releaseAction"
|
|
166
|
+
];
|
|
167
|
+
/**
|
|
168
|
+
* Tap the engine's input entry points and tick clock. Recording starts at
|
|
169
|
+
* the CURRENT moment — start it before the first tick for full-run replays.
|
|
170
|
+
*/
|
|
171
|
+
function startRecording(engine) {
|
|
172
|
+
const ticks = [];
|
|
173
|
+
const events = [];
|
|
174
|
+
const input = engine.input;
|
|
175
|
+
const originals = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const name of RECORDED_METHODS) {
|
|
177
|
+
const original = input[name];
|
|
178
|
+
originals.set(name, original);
|
|
179
|
+
input[name] = (...args) => {
|
|
180
|
+
events.push([
|
|
181
|
+
ticks.length,
|
|
182
|
+
name,
|
|
183
|
+
...args
|
|
184
|
+
]);
|
|
185
|
+
return original.apply(engine.input, args);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const originalTick = engine.tick.bind(engine);
|
|
189
|
+
const engineAny = engine;
|
|
190
|
+
engineAny.tick = (nowMs) => {
|
|
191
|
+
ticks.push(nowMs);
|
|
192
|
+
originalTick(nowMs);
|
|
193
|
+
};
|
|
194
|
+
let stopped = false;
|
|
195
|
+
return { stop() {
|
|
196
|
+
if (!stopped) {
|
|
197
|
+
stopped = true;
|
|
198
|
+
for (const [name, fn] of originals) input[name] = fn;
|
|
199
|
+
engineAny.tick = originalTick;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
replay: 1,
|
|
203
|
+
ticks,
|
|
204
|
+
events
|
|
205
|
+
};
|
|
206
|
+
} };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Drive a fresh engine through a recording: for each recorded tick, feed
|
|
210
|
+
* that tick's input events first, then tick with the recorded timestamp.
|
|
211
|
+
* `onTick(index)` lets tests assert mid-run state.
|
|
212
|
+
*/
|
|
213
|
+
function replay(engine, recording, opts) {
|
|
214
|
+
const input = engine.input;
|
|
215
|
+
let cursor = 0;
|
|
216
|
+
for (let i = 0; i < recording.ticks.length; i++) {
|
|
217
|
+
while (cursor < recording.events.length && recording.events[cursor][0] === i) {
|
|
218
|
+
const [, method, ...args] = recording.events[cursor];
|
|
219
|
+
const fn = input[method];
|
|
220
|
+
if (typeof fn === "function") fn.apply(engine.input, args);
|
|
221
|
+
cursor += 1;
|
|
222
|
+
}
|
|
223
|
+
engine.tick(recording.ticks[i]);
|
|
224
|
+
opts?.onTick?.(i);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
43
228
|
//#region src/core/save.ts
|
|
44
229
|
const memoryStores = /* @__PURE__ */ new Map();
|
|
45
230
|
function createSaveStore(namespace) {
|
|
@@ -110,6 +295,6 @@ function newUid() {
|
|
|
110
295
|
//#endregion
|
|
111
296
|
//#region src/index.ts
|
|
112
297
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
113
|
-
const VERSION = "0.
|
|
298
|
+
const VERSION = "0.8.0";
|
|
114
299
|
//#endregion
|
|
115
|
-
export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, 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 };
|
|
300
|
+
export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
|
package/dist/net.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as Node, l as PropSchema, tt as Signal, v as Engine } from "./behavior-Bod1AyJS.js";
|
|
3
3
|
|
|
4
4
|
//#region src/net/types.d.ts
|
|
5
5
|
type Unsubscribe = () => void;
|
|
@@ -424,4 +424,48 @@ declare class NetworkSpawner extends Node {
|
|
|
424
424
|
*/
|
|
425
425
|
declare function registerNodesNet(): void;
|
|
426
426
|
//#endregion
|
|
427
|
-
|
|
427
|
+
//#region src/net/split-screen.d.ts
|
|
428
|
+
interface SplitScreenPlayer {
|
|
429
|
+
account: string;
|
|
430
|
+
engine: Engine;
|
|
431
|
+
manager: NetworkManager;
|
|
432
|
+
}
|
|
433
|
+
interface SplitScreenOptions extends LocalGameServerOptions {
|
|
434
|
+
/** The shared scene JSON every panel loads. */
|
|
435
|
+
scene: SceneJson;
|
|
436
|
+
/** Player accounts, one panel each (default ['p1', 'p2']). */
|
|
437
|
+
accounts?: string[];
|
|
438
|
+
/** Called per player to finish wiring (renderer, input, HUD). */
|
|
439
|
+
setup: (player: SplitScreenPlayer, index: number) => void | Promise<void>;
|
|
440
|
+
/** name → scene JSON registered on every panel's NetworkManager. */
|
|
441
|
+
scenes?: Record<string, SceneJson>;
|
|
442
|
+
/** Engine seed base (each panel gets seed + index). */
|
|
443
|
+
seed?: number;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* N local clients on ONE page sharing ONE in-memory game server — the
|
|
447
|
+
* split-screen preview harness three examples were hand-wiring (~40 lines
|
|
448
|
+
* each). Wires: LocalGameServer + per-player Engine/scene/NetworkManager +
|
|
449
|
+
* the server tick driven from the first panel's clock.
|
|
450
|
+
*
|
|
451
|
+
* const { players, server } = await createSplitScreen({
|
|
452
|
+
* scene: gameJson,
|
|
453
|
+
* server: MyServerClass,
|
|
454
|
+
* scenes: { 'remote-player': remoteJson },
|
|
455
|
+
* setup: ({ engine }, i) => {
|
|
456
|
+
* new Renderer2D({ canvas: canvases[i], engine });
|
|
457
|
+
* engine.input.attachKeyboard(window);
|
|
458
|
+
* engine.start();
|
|
459
|
+
* },
|
|
460
|
+
* });
|
|
461
|
+
*
|
|
462
|
+
* Going live stays the one-line story: replace the transport with
|
|
463
|
+
* `createAgent8Server()` and boot ONE client per browser.
|
|
464
|
+
*/
|
|
465
|
+
declare function createSplitScreen(opts: SplitScreenOptions): Promise<{
|
|
466
|
+
server: ReturnType<typeof createLocalGameServer>;
|
|
467
|
+
players: SplitScreenPlayer[];
|
|
468
|
+
dispose(): void;
|
|
469
|
+
}>;
|
|
470
|
+
//#endregion
|
|
471
|
+
export { type CollectionOptions, LocalGameServer, type LocalGameServerOptions, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, type NetworkManagerOptions, NetworkSpawner, type NetworkTransport, type ServerClass, type SplitScreenOptions, type SplitScreenPlayer, type Unsubscribe, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
|
package/dist/net.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { n as loadScene } from "./loader-B4OEXDZ8.js";
|
|
2
|
+
import { u as Engine } from "./register-DRPIjrKG.js";
|
|
1
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
4
|
import { t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
3
5
|
import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
|
|
4
|
-
import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-
|
|
6
|
+
import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-CxmuI9FL.js";
|
|
5
7
|
//#region src/net/loopback.ts
|
|
6
8
|
/**
|
|
7
9
|
* In-memory multiplayer hub implementing the SAME kernel contract as
|
|
@@ -769,4 +771,54 @@ var LocalGameServerTransport = class {
|
|
|
769
771
|
}
|
|
770
772
|
};
|
|
771
773
|
//#endregion
|
|
772
|
-
|
|
774
|
+
//#region src/net/split-screen.ts
|
|
775
|
+
/**
|
|
776
|
+
* N local clients on ONE page sharing ONE in-memory game server — the
|
|
777
|
+
* split-screen preview harness three examples were hand-wiring (~40 lines
|
|
778
|
+
* each). Wires: LocalGameServer + per-player Engine/scene/NetworkManager +
|
|
779
|
+
* the server tick driven from the first panel's clock.
|
|
780
|
+
*
|
|
781
|
+
* const { players, server } = await createSplitScreen({
|
|
782
|
+
* scene: gameJson,
|
|
783
|
+
* server: MyServerClass,
|
|
784
|
+
* scenes: { 'remote-player': remoteJson },
|
|
785
|
+
* setup: ({ engine }, i) => {
|
|
786
|
+
* new Renderer2D({ canvas: canvases[i], engine });
|
|
787
|
+
* engine.input.attachKeyboard(window);
|
|
788
|
+
* engine.start();
|
|
789
|
+
* },
|
|
790
|
+
* });
|
|
791
|
+
*
|
|
792
|
+
* Going live stays the one-line story: replace the transport with
|
|
793
|
+
* `createAgent8Server()` and boot ONE client per browser.
|
|
794
|
+
*/
|
|
795
|
+
async function createSplitScreen(opts) {
|
|
796
|
+
const server = createLocalGameServer(opts);
|
|
797
|
+
const accounts = opts.accounts ?? ["p1", "p2"];
|
|
798
|
+
const players = [];
|
|
799
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
800
|
+
const account = accounts[i];
|
|
801
|
+
const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + i } : {});
|
|
802
|
+
engine.setScene(loadScene(jsonClone(opts.scene)));
|
|
803
|
+
const manager = await NetworkManager.create(engine, { transport: server.createClient(account) });
|
|
804
|
+
for (const [name, json] of Object.entries(opts.scenes ?? {})) manager.registerScene(name, json);
|
|
805
|
+
const player = {
|
|
806
|
+
account,
|
|
807
|
+
engine,
|
|
808
|
+
manager
|
|
809
|
+
};
|
|
810
|
+
players.push(player);
|
|
811
|
+
await opts.setup(player, i);
|
|
812
|
+
}
|
|
813
|
+
const disconnect = (players[0]?.engine)?.updated.connect((dt) => void server.tick(Math.min(dt, .05) * 1e3));
|
|
814
|
+
return {
|
|
815
|
+
server,
|
|
816
|
+
players,
|
|
817
|
+
dispose() {
|
|
818
|
+
disconnect?.();
|
|
819
|
+
for (const p of players) p.engine.dispose?.();
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
//#endregion
|
|
824
|
+
export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
|