incanto 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/2d.d.ts +59 -2
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +8 -3
  4. package/dist/3d.js +4 -4
  5. package/dist/{behavior-D_jMpFh8.d.ts → behavior-CtPScDMo.d.ts} +149 -1
  6. package/dist/{create-game-bKHgHcsZ.js → create-game-CFNqoqOX.js} +13 -6
  7. package/dist/{create-game-gXI7PYl0.js → create-game-QTfghzwM.js} +11 -5
  8. package/dist/debug.d.ts +1 -1
  9. package/dist/{duplicate-KRPtUtzl.js → duplicate-DlOvknDO.js} +1 -1
  10. package/dist/editor.js +35 -0
  11. package/dist/{environment-presets--DigHNg4.js → environment-presets-B6WiUsaO.js} +2 -2
  12. package/dist/{gameplay-BftxM_It.js → gameplay-BxFtmfSe.js} +90 -2
  13. package/dist/gameplay.d.ts +1 -1
  14. package/dist/gameplay.js +1 -1
  15. package/dist/index.d.ts +175 -23
  16. package/dist/index.js +5 -58
  17. package/dist/{loader-BlaRQGaA.js → loader-BwR0DxeM.js} +66 -0
  18. package/dist/{loader-BYBrqTxP.d.ts → loader-DmXfj6Uv.d.ts} +1 -1
  19. package/dist/net.d.ts +1 -1
  20. package/dist/net.js +3 -3
  21. package/dist/{pathfinding-BwhqPD3i.d.ts → pathfinding-DRCe89SI.d.ts} +1 -1
  22. package/dist/{physics-2d-D9wquBvK.js → physics-2d-BuyAwsW5.js} +12 -2
  23. package/dist/{physics-3d-CnPygVGo.js → physics-3d-C8Kn_Nyb.js} +55 -9
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-tkR_8tWg.js → register-BLPC10Mj.js} +145 -8
  27. package/dist/{register-Dzkd6-os.js → register-G3edsjJ2.js} +752 -16
  28. package/dist/{register-CUY284Is.js → register-OodmuUMK.js} +2 -2
  29. package/dist/{test-WwRIlXsK.js → test-C4B5znap.js} +10 -10
  30. package/dist/test.d.ts +2 -2
  31. package/dist/test.js +1 -1
  32. package/dist/vite.js +1 -1
  33. package/editor/assets/{agent8-C5k1nTCH.js → agent8-BTODHtdM.js} +1 -1
  34. package/editor/assets/{debug-BT_0mjk-.js → debug-CJEz4EwC.js} +1 -1
  35. package/editor/assets/{index-gfyrByWw.js → index-BYCso8Bf.js} +90 -90
  36. package/editor/index.html +1 -1
  37. package/package.json +1 -1
  38. package/schemas/scene.schema.json +667 -11
  39. package/skills/incanto-audio.md +43 -0
  40. package/skills/incanto-building-2d-games.md +36 -0
  41. package/skills/incanto-hud.md +97 -0
  42. package/skills/incanto-node-reference.md +131 -3
  43. package/skills/incanto-physics-and-input.md +50 -0
  44. package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +40 -19
  45. package/templates-app/beacon-isle-3d/package.json +1 -1
  46. package/templates-app/tps-3d/docs/project-3d-rules.md +41 -19
  47. package/templates-app/tps-3d/package.json +1 -1
  48. package/templates-app/village-quest-3d/docs/project-3d-rules.md +40 -19
  49. package/templates-app/village-quest-3d/package.json +1 -1
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ 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, k as Chase, 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 DayNight, x as goToScene, y as Interactable } from "./gameplay-BftxM_It.js";
1
+ import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ 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, k as Chase, 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 DayNight, x as goToScene, y as Interactable } from "./gameplay-BxFtmfSe.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { $ as LogEntry, A as VoicePreset, B as SfxParams, C as RendererStats, D as SfxPlayOptions, E as SfxEngine, F as Vec3, G as MusicManager, H as SynthOptions, I as spatialGain, J as AudioBuses, K as MusicTrack, L as spatialPan, M as ROLLOFF_MODELS, N as RolloffModel, O as isAudioContextAvailable, P as SpatialParams, Q as SceneTree, R as SFX_PRESETS, S as GameStats, T as InputMap, U as synthSfx, V as SfxWave, W as MusicBackend, X as Node, Y as BusName, Z as NodeLifecycle, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as LogLevel, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as Listener, k as Voice, l as PropSchema, m as getNodeType, n as BehaviorCtor, nt as Signal, o as registeredBehaviors, p as getNodeSignals, q as PlayMusicOptions, r as clearBehaviors, rt as SignalListener, s as NodeCtor, t as Behavior, tt as LogManager, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as SFX_PRESET_NAMES } from "./behavior-D_jMpFh8.js";
1
+ import { $ as MusicBackend, A as suggestQuality, B as Listener, C as RendererStats, D as SettingsValues, E as Settings, F as SfxEngine, G as spatialGain, H as RolloffModel, I as SfxPlayOptions, J as SFX_PRESET_NAMES, K as spatialPan, L as isAudioContextAvailable, M as createSaveStore, N as Scene, O as qualityEnvironment, P as InputMap, Q as synthSfx, R as Voice, S as GameStats, T as QualityTier, U as SpatialParams, V as ROLLOFF_MODELS, W as Vec3, X as SfxWave, Y as SfxParams, Z as SynthOptions, _ as registeredTypes, a as registerBehavior, at as Node, b as Scheduler, c as PropDef, ct as LogEntry, d as createNode, dt as Signal, et as MusicManager, f as getNodeSchema, ft as SignalListener, g as registerNode, h as mergeStaticSignals, i as getBehavior, it as BusName, j as SaveStore, k as readDeviceHints, l as PropSchema, lt as LogLevel, m as getNodeType, n as BehaviorCtor, nt as PlayMusicOptions, o as registeredBehaviors, ot as NodeLifecycle, p as getNodeSignals, q as SFX_PRESETS, r as clearBehaviors, rt as AudioBuses, s as NodeCtor, st as SceneTree, t as Behavior, tt as MusicTrack, u as clearRegistry, ut as LogManager, v as Engine, w as DeviceHints, x as EngineStats, y as EngineOptions, z as VoicePreset } from "./behavior-CtPScDMo.js";
2
2
  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";
3
- import { n as loadScene, t as LoadSceneOptions } from "./loader-BYBrqTxP.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-DmXfj6Uv.js";
4
4
  import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
5
- import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-BwhqPD3i.js";
5
+ import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-DRCe89SI.js";
6
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
@@ -120,6 +120,7 @@ declare class HudLayer extends Node {
120
120
  static readonly props: PropSchema;
121
121
  zIndex: number;
122
122
  visible: boolean;
123
+ focusNavigation: boolean;
123
124
  /** @internal root overlay element (null headless). */
124
125
  _element: HTMLElement | null;
125
126
  private readonly slots;
@@ -138,19 +139,68 @@ declare class HudLayer extends Node {
138
139
  private _mount;
139
140
  override onExitTree(): void;
140
141
  override update(): void;
142
+ /**
143
+ * Arrow keys / d-pad move the focus, Enter / A activates it.
144
+ *
145
+ * Off by default: a game whose HUD has a button must not lose its arrow keys
146
+ * to a menu the moment one exists. Turn it on for the screens that ARE menus
147
+ * (`"focusNavigation": true` on the pause panel's layer), and off again when
148
+ * play resumes.
149
+ */
150
+ private stepFocus;
151
+ /** Focusable widgets under this layer, in tree order. */
152
+ private focusables;
153
+ /** Move focus to a widget by hand — opening a menu should start somewhere. */
154
+ focus(widget: HudWidgetBase | null): void;
141
155
  /** @internal Widgets mount into per-anchor flex columns. */
142
156
  _slot(anchor: HudAnchor): HTMLElement | null;
143
157
  }
144
158
  /** Shared plumbing: mount into the parent HudLayer's anchor slot. */
145
159
  declare abstract class HudWidgetBase extends Node {
146
160
  static readonly props: PropSchema;
161
+ static readonly signals: readonly string[];
147
162
  anchor: HudAnchor;
148
163
  visible: boolean;
164
+ focusable: boolean;
165
+ draggable: boolean;
166
+ dropTarget: boolean;
167
+ /** @internal Set by the owning HudLayer while this widget has focus. */
168
+ _focused: boolean;
169
+ /**
170
+ * @internal Confirm (`dir` 0) or nudge (-1 left / +1 right) — what pressing
171
+ * A or an arrow ON this widget means. Default: nothing.
172
+ */
173
+ _activate(_dir: number): void;
174
+ /** @internal Draw the focus ring. Overridable if a widget wants its own. */
175
+ _paintFocus(): void;
149
176
  /** @internal */
150
177
  _element: HTMLElement | null;
151
178
  protected abstract _build(): HTMLElement;
152
179
  private layer;
153
180
  override onReady(): void;
181
+ /**
182
+ * @internal Build and attach, once a host exists.
183
+ *
184
+ * A widget inside a UiPanel mounts into the PANEL, not the layer's anchor
185
+ * slot — that is what turns structure in the tree into structure on screen,
186
+ * and it is what a menu is. Ready order does not decide it: the panel builds
187
+ * on demand when a child asks (`_container`), and `update` retries for
188
+ * anything attached at runtime before its host existed.
189
+ */
190
+ _mountWidget(): void;
191
+ /**
192
+ * Drag and drop, on the DOM the engine already builds.
193
+ *
194
+ * An inventory is the one screen where "click it" is not enough, and every
195
+ * game that wanted one dropped out of scene JSON to hand-roll pointer
196
+ * handlers. The whole gesture is four signals and no new node type: a
197
+ * `draggable` widget emits `dragStarted` and, if it lands on one,
198
+ * `droppedOn(target)`; a `dropTarget` emits `dropped(source)`. Who owns the
199
+ * ITEM is the game's business — this reports the gesture, not a model.
200
+ */
201
+ private _wireDrag;
202
+ /** The nearest ancestor that holds widgets itself (a UiPanel). */
203
+ private host;
154
204
  override onExitTree(): void;
155
205
  override update(_dt: number): void;
156
206
  protected _sync(): void;
@@ -232,6 +282,9 @@ declare class UiButton extends HudWidgetBase {
232
282
  color: string;
233
283
  background: string;
234
284
  disabled: boolean;
285
+ override focusable: boolean;
286
+ /** Enter / A on a focused button is a press. */
287
+ override _activate(_dir: number): void;
235
288
  private lastText;
236
289
  /** Programmatic press — same path as a click (tests, gamepad menus). */
237
290
  press(): void;
@@ -305,6 +358,124 @@ declare class Timer extends Node {
305
358
  override update(dt: number): void;
306
359
  }
307
360
  //#endregion
361
+ //#region src/core/nodes/ui-kit.d.ts
362
+ /**
363
+ * A box that holds other widgets — the thing a menu actually is.
364
+ *
365
+ * Widgets normally mount into their HudLayer's anchor slot, which is why a
366
+ * "menu" used to be a flat pile of anchored items with no way to sit them in a
367
+ * panel, space them, or lay them out in a grid. A `UiPanel` becomes the parent
368
+ * for every widget under it, so structure in the TREE is structure on SCREEN.
369
+ *
370
+ * ```json
371
+ * { "name": "Pause", "type": "UiPanel", "props": { "anchor": "center", "layout": "column", "gap": 12 },
372
+ * "children": [
373
+ * { "name": "Title", "type": "UiText", "props": { "text": "PAUSED", "size": 28 } },
374
+ * { "name": "Resume", "type": "UiButton", "props": { "label": "Resume" } }
375
+ * ] }
376
+ * ```
377
+ */
378
+ declare class UiPanel extends HudWidgetBase {
379
+ static override readonly typeName: string;
380
+ static override readonly props: PropSchema;
381
+ layout: "column" | "row" | "grid";
382
+ columns: number;
383
+ gap: number;
384
+ padding: number;
385
+ background: string;
386
+ radius: number;
387
+ width: number;
388
+ height: number;
389
+ border: string;
390
+ private lastKey;
391
+ protected _build(): HTMLElement;
392
+ /**
393
+ * @internal Children mount HERE, not in the layer's anchor slot.
394
+ *
395
+ * Builds on demand: a child asking for its host must not depend on whose
396
+ * `onReady` the tree happened to run first.
397
+ */
398
+ _container(): HTMLElement | null;
399
+ private css;
400
+ protected override _sync(): void;
401
+ }
402
+ /** A picture: an item icon, a portrait, a logo. `src` takes a url or `$assetKey`. */
403
+ declare class UiImage extends HudWidgetBase {
404
+ static override readonly typeName: string;
405
+ static override readonly props: PropSchema;
406
+ src: string;
407
+ width: number;
408
+ height: number;
409
+ fit: "contain" | "cover" | "fill";
410
+ tint: string;
411
+ opacity: number;
412
+ private last;
413
+ protected _build(): HTMLElement;
414
+ private resolved;
415
+ private key;
416
+ private css;
417
+ protected override _sync(): void;
418
+ }
419
+ /**
420
+ * A value the player sets — volume, sensitivity, difficulty.
421
+ *
422
+ * Emits `changed(value)`. `incanto-audio.md` promised "a settings slider needs
423
+ * no extra wiring" while no slider existed; this is that slider.
424
+ */
425
+ declare class UiSlider extends HudWidgetBase {
426
+ static override readonly typeName: string;
427
+ static override readonly props: PropSchema;
428
+ static override readonly signals: readonly string[];
429
+ label: string;
430
+ value: number;
431
+ min: number;
432
+ max: number;
433
+ step: number;
434
+ width: number;
435
+ color: string;
436
+ override focusable: boolean;
437
+ /** Left/right nudge by one step — the only sane thing a d-pad can mean here. */
438
+ override _activate(dir: number): void;
439
+ private input;
440
+ private text;
441
+ private last;
442
+ protected _build(): HTMLElement;
443
+ protected override _sync(): void;
444
+ }
445
+ /** An on/off switch — mute, invert-Y, fullscreen. Emits `changed(boolean)`. */
446
+ declare class UiToggle extends HudWidgetBase {
447
+ static override readonly typeName: string;
448
+ static override readonly props: PropSchema;
449
+ static override readonly signals: readonly string[];
450
+ label: string;
451
+ value: boolean;
452
+ override focusable: boolean;
453
+ /** Confirm flips it; left/right set it explicitly, which reads better on a pad. */
454
+ override _activate(dir: number): void;
455
+ private input;
456
+ private last;
457
+ protected _build(): HTMLElement;
458
+ protected override _sync(): void;
459
+ }
460
+ /** One of several — quality, resolution, language. Emits `changed(value)`. */
461
+ declare class UiSelect extends HudWidgetBase {
462
+ static override readonly typeName: string;
463
+ static override readonly props: PropSchema;
464
+ static override readonly signals: readonly string[];
465
+ label: string;
466
+ options: string;
467
+ value: string;
468
+ override focusable: boolean;
469
+ /** Left/right (and confirm) walk the list — a select has no other gesture. */
470
+ override _activate(dir: number): void;
471
+ private select;
472
+ private lastOptions;
473
+ private last;
474
+ protected _build(): HTMLElement;
475
+ private fillOptions;
476
+ protected override _sync(): void;
477
+ }
478
+ //#endregion
308
479
  //#region src/core/noise.d.ts
309
480
  /**
310
481
  * Seeded 2D simplex noise (Ken Perlin's simplex, Gustavson's reference
@@ -463,25 +634,6 @@ declare function replay(engine: Engine, recording: ReplayJson, opts?: {
463
634
  onTick?: (index: number) => void;
464
635
  }): void;
465
636
  //#endregion
466
- //#region src/core/save.d.ts
467
- /**
468
- * Namespaced game persistence — high scores, unlocks, settings. localStorage
469
- * in the browser, in-memory headless (tests and SSR stay green). Values are
470
- * JSON round-tripped, so what you get back is what you saved.
471
- *
472
- * const save = createSaveStore('my-game');
473
- * save.set('highScore', 4200);
474
- * const best = save.get('highScore', 0);
475
- */
476
- interface SaveStore {
477
- get<T extends JsonValue>(key: string, fallback: T): T;
478
- set(key: string, value: JsonValue): void;
479
- remove(key: string): void;
480
- /** Wipe THIS namespace only. */
481
- clear(): void;
482
- }
483
- declare function createSaveStore(namespace: string): SaveStore;
484
- //#endregion
485
637
  //#region src/core/scene/constants.d.ts
486
638
  /** The single reserved key marking a prop value as a constant reference. */
487
639
  declare const CONST_REF_KEY = "@const";
@@ -606,4 +758,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
606
758
  /** Engine version. Kept in sync with package.json by the release pipeline. */
607
759
  declare const VERSION: string;
608
760
  //#endregion
609
- 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 };
761
+ export { AudioBuses, type AudioElementLike, AudioPlayer, Behavior, type BehaviorCtor, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, 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, type QualityTier, 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, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, 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, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, suggestQuality, synthSfx };
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import { _ as clearBehaviors, a as SCENE_FORMAT, b as registeredBehaviors, c as SceneTree, d as resolveConstants, f as Node, g as Behavior, h as InputMap, i as serializeNode, l as CONST_REF_KEY, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as getBehavior, x as Signal, y as registerBehavior } from "./loader-BlaRQGaA.js";
2
- import { C as fadeGain, S as crossfadeGains, _ as SFX_PRESETS, a as HudLayer, b as MusicManager, c as UiText, d as LogManager, f as SfxEngine, g as spatialPan, h as spatialGain, i as UiDialogue, l as AudioPlayer, m as ROLLOFF_MODELS, n as Timer, o as UiBanner, p as isAudioContextAvailable, r as UiButton, s as UiBar, t as registerCoreNodes, u as Engine, v as SFX_PRESET_NAMES, w as AudioBuses, x as WebAudioMusicBackend, y as synthSfx } from "./register-Dzkd6-os.js";
1
+ import { _ as clearBehaviors, a as SCENE_FORMAT, b as registeredBehaviors, c as SceneTree, d as resolveConstants, f as Node, g as Behavior, h as InputMap, i as serializeNode, l as CONST_REF_KEY, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as getBehavior, x as Signal, y as registerBehavior } from "./loader-BwR0DxeM.js";
2
+ import { A as MusicManager, C as isAudioContextAvailable, D as SFX_PRESETS, E as spatialPan, M as crossfadeGains, N as fadeGain, O as SFX_PRESET_NAMES, P as AudioBuses, S as SfxEngine, T as spatialGain, _ as qualityEnvironment, a as UiSlider, b as createSaveStore, c as UiButton, d as UiBanner, f as UiBar, g as Settings, h as Engine, i as UiSelect, j as WebAudioMusicBackend, k as synthSfx, l as UiDialogue, m as AudioPlayer, n as UiImage, o as UiToggle, p as UiText, r as UiPanel, s as Timer, t as registerCoreNodes, u as HudLayer, v as readDeviceHints, w as ROLLOFF_MODELS, x as LogManager, y as suggestQuality } from "./register-G3edsjJ2.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { t as auditScene } from "./audit-C4kmDK0o.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-CJdGpT2V.js";
8
8
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
9
- import { n as ORDER_GROUP_BASE, r as effectiveOrder, t as duplicateNode } from "./duplicate-KRPtUtzl.js";
9
+ import { n as ORDER_GROUP_BASE, r as effectiveOrder, t as duplicateNode } from "./duplicate-DlOvknDO.js";
10
10
  import { i as applyParticlePreset, n as PARTICLE_PRESETS, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-Bw7hB93B.js";
11
11
  import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-BoNg_MnF.js";
12
12
  //#region src/core/pathfinding.ts
@@ -225,59 +225,6 @@ function replay(engine, recording, opts) {
225
225
  }
226
226
  }
227
227
  //#endregion
228
- //#region src/core/save.ts
229
- const memoryStores = /* @__PURE__ */ new Map();
230
- function createSaveStore(namespace) {
231
- const prefix = `incanto:${namespace}:`;
232
- const local = storageOrNull();
233
- if (!local) {
234
- let mem = memoryStores.get(namespace);
235
- if (!mem) {
236
- mem = /* @__PURE__ */ new Map();
237
- memoryStores.set(namespace, mem);
238
- }
239
- const backing = mem;
240
- return {
241
- get: (key, fallback) => parse(backing.get(key), fallback),
242
- set: (key, value) => void backing.set(key, JSON.stringify(value)),
243
- remove: (key) => void backing.delete(key),
244
- clear: () => backing.clear()
245
- };
246
- }
247
- return {
248
- get: (key, fallback) => parse(local.getItem(prefix + key) ?? void 0, fallback),
249
- set: (key, value) => local.setItem(prefix + key, JSON.stringify(value)),
250
- remove: (key) => local.removeItem(prefix + key),
251
- clear: () => {
252
- const doomed = [];
253
- for (let i = 0; i < local.length; i++) {
254
- const k = local.key(i);
255
- if (k?.startsWith(prefix)) doomed.push(k);
256
- }
257
- for (const k of doomed) local.removeItem(k);
258
- }
259
- };
260
- }
261
- function parse(raw, fallback) {
262
- if (raw === void 0 || raw === null) return fallback;
263
- try {
264
- return JSON.parse(raw);
265
- } catch {
266
- return fallback;
267
- }
268
- }
269
- function storageOrNull() {
270
- try {
271
- if (typeof localStorage === "undefined") return null;
272
- const probe = "__incanto_probe__";
273
- localStorage.setItem(probe, "1");
274
- localStorage.removeItem(probe);
275
- return localStorage;
276
- } catch {
277
- return null;
278
- }
279
- }
280
- //#endregion
281
228
  //#region src/core/uid-gen.ts
282
229
  /**
283
230
  * The ONE way to mint a node uid: crypto-strength, `n_` + 16 base36 chars
@@ -295,6 +242,6 @@ function newUid() {
295
242
  //#endregion
296
243
  //#region src/index.ts
297
244
  /** Engine version. Kept in sync with package.json by the release pipeline. */
298
- const VERSION = "0.32.0";
245
+ const VERSION = "0.34.0";
299
246
  //#endregion
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 };
247
+ 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, Settings, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiImage, UiPanel, UiSelect, UiSlider, UiText, UiToggle, 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, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, suggestQuality, synthSfx };
@@ -141,6 +141,40 @@ var InputMap = class {
141
141
  declare(decls) {
142
142
  for (const [name, action] of parseInputDecls(decls)) this.actions.set(name, action);
143
143
  }
144
+ /**
145
+ * Where the pointer is, in CANVAS pixels (top-left origin), or null when it
146
+ * has never been over the canvas.
147
+ *
148
+ * This is what a click-driven game asks for. `pointerDelta()` answers "how far
149
+ * did the mouse move" — the mouse-look question — and for a whole class of
150
+ * genres that is the wrong question and the only one the engine could answer.
151
+ * Pair it with `renderer.pick(x, y)` to get the node under the cursor.
152
+ */
153
+ pointerPosition() {
154
+ return this.pointerPos ? { ...this.pointerPos } : null;
155
+ }
156
+ /**
157
+ * @internal Feed a pointer position. `target` scales client → canvas pixels
158
+ * so a CSS-stretched canvas still reports coordinates its renderer can use.
159
+ */
160
+ setPointerPosition(clientX, clientY, target) {
161
+ const rect = target?.getBoundingClientRect?.();
162
+ if (!rect || rect.width === 0 || rect.height === 0) {
163
+ this.pointerPos = {
164
+ x: clientX,
165
+ y: clientY
166
+ };
167
+ return;
168
+ }
169
+ const el = target;
170
+ const scaleX = typeof el.width === "number" && el.width > 0 ? el.width / rect.width : 1;
171
+ const scaleY = typeof el.height === "number" && el.height > 0 ? el.height / rect.height : 1;
172
+ this.pointerPos = {
173
+ x: (clientX - rect.left) * scaleX,
174
+ y: (clientY - rect.top) * scaleY
175
+ };
176
+ }
177
+ pointerPos = null;
144
178
  /** Feed a key state change (code = KeyboardEvent.code, e.g. 'Space', 'KeyW'). */
145
179
  handleKey(code, isDown) {
146
180
  if (isDown) {
@@ -318,6 +352,7 @@ var InputMap = class {
318
352
  let buttonsHeld = 0;
319
353
  const onDown = (e) => {
320
354
  buttonsHeld++;
355
+ this.setPointerPosition(e.clientX, e.clientY, target);
321
356
  this.handleMouseButton(e.button, true);
322
357
  if (opts?.lockOnClick && document.pointerLockElement !== target) target.requestPointerLock?.();
323
358
  };
@@ -326,6 +361,7 @@ var InputMap = class {
326
361
  this.handleMouseButton(e.button, false);
327
362
  };
328
363
  const onMove = (e) => {
364
+ this.setPointerPosition(e.clientX, e.clientY, target);
329
365
  if (document.pointerLockElement === target || buttonsHeld > 0) this.handlePointerMove(e.movementX, e.movementY);
330
366
  };
331
367
  const onWheel = (e) => {
@@ -409,6 +445,36 @@ var InputMap = class {
409
445
  justReleased(action) {
410
446
  return this.button(action).keys.some((k) => this.releasedEdge.has(k)) || this.injectedReleased.has(action);
411
447
  }
448
+ /**
449
+ * Mouse button state WITHOUT declaring an action for it.
450
+ *
451
+ * A click on a tile is not a bindable action — there is no key to remap, and
452
+ * asking a match-3 to declare `"click": { "keys": ["Mouse0"] }` in every scene
453
+ * is ceremony. Buttons already live in the key set as `Mouse0`/`Mouse1`/…;
454
+ * these read them directly. 0 left, 1 middle, 2 right.
455
+ */
456
+ /**
457
+ * Is this raw CODE down / just pressed? (`'ArrowUp'`, `'Enter'`, `'Pad12'`…)
458
+ *
459
+ * Menu navigation is not a bindable action — there is no key to remap, and
460
+ * asking every scene to declare `"menuUp": { "keys": ["ArrowUp", "Pad12"] }`
461
+ * is ceremony for something every menu on every platform does the same way.
462
+ */
463
+ keyPressed(code) {
464
+ return this.down.has(code);
465
+ }
466
+ keyJustPressed(code) {
467
+ return this.pressedEdge.has(code);
468
+ }
469
+ mousePressed(button = 0) {
470
+ return this.down.has(`Mouse${button}`);
471
+ }
472
+ mouseJustPressed(button = 0) {
473
+ return this.pressedEdge.has(`Mouse${button}`);
474
+ }
475
+ mouseJustReleased(button = 0) {
476
+ return this.releasedEdge.has(`Mouse${button}`);
477
+ }
412
478
  /** Normalized direction for a vector2 action (y-down: up = -y). */
413
479
  getVector(action) {
414
480
  const def = this.get(action);
@@ -1,4 +1,4 @@
1
- import { v as Engine, w as Scene } from "./behavior-D_jMpFh8.js";
1
+ import { N as Scene, v as Engine } from "./behavior-CtPScDMo.js";
2
2
  import { i as SceneJson } from "./schema-CcoWb32N.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
package/dist/net.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { X as Node, l as PropSchema, nt as Signal, v as Engine } from "./behavior-D_jMpFh8.js";
1
+ import { at as Node, dt as Signal, l as PropSchema, v as Engine } from "./behavior-CtPScDMo.js";
2
2
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
3
3
 
4
4
  //#region src/net/types.d.ts
package/dist/net.js CHANGED
@@ -1,9 +1,9 @@
1
- import { n as loadScene } from "./loader-BlaRQGaA.js";
2
- import { u as Engine } from "./register-Dzkd6-os.js";
1
+ import { n as loadScene } from "./loader-BwR0DxeM.js";
2
+ import { h as Engine } from "./register-G3edsjJ2.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
6
- import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-CUY284Is.js";
6
+ import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-OodmuUMK.js";
7
7
  //#region src/net/loopback.ts
8
8
  /**
9
9
  * In-memory multiplayer hub implementing the SAME kernel contract as
@@ -1,4 +1,4 @@
1
- import { X as Node, j as Listener, l as PropSchema } from "./behavior-D_jMpFh8.js";
1
+ import { B as Listener, at as Node, l as PropSchema } from "./behavior-CtPScDMo.js";
2
2
 
3
3
  //#region src/core/nodes/audio-player.d.ts
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { m as diagnose } from "./loader-BlaRQGaA.js";
2
+ import { m as diagnose } from "./loader-BwR0DxeM.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-tkR_8tWg.js";
4
+ import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-BLPC10Mj.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  //#region src/2d/physics/physics-2d.ts
7
7
  var physics_2d_exports = /* @__PURE__ */ __exportAll({
@@ -153,6 +153,16 @@ var Physics2D = class {
153
153
  y: t.y + corrected.y
154
154
  });
155
155
  node._grounded = this.kcc.computedGrounded();
156
+ node._onWall = 0;
157
+ node._onCeiling = false;
158
+ const hits = this.kcc.numComputedCollisions();
159
+ for (let i = 0; i < hits; i++) {
160
+ const n = this.kcc.computedCollision(i)?.normal1;
161
+ if (!n) continue;
162
+ if (Math.abs(n.y) > .7) {
163
+ if (!node._grounded) node._onCeiling = true;
164
+ } else if (Math.abs(n.x) > .7) node._onWall = n.x > 0 ? -1 : 1;
165
+ }
156
166
  if (node._grounded) {
157
167
  const footX = (t.x + corrected.x) * SCALE;
158
168
  const footY = (t.y + corrected.y) * SCALE + charHalfHeight(node) - 2;
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { m as diagnose } from "./loader-BlaRQGaA.js";
2
+ import { m as diagnose } from "./loader-BwR0DxeM.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-BftxM_It.js";
4
+ import { F as PhysicsBody3D, I as RigidBody3D, N as Area3D, P as CharacterBody3D, R as Node3D, z as validateCollider3D } from "./gameplay-BxFtmfSe.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
- import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets--DigHNg4.js";
6
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-B6WiUsaO.js";
7
7
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
8
8
  //#region src/3d/physics/collider-lines.ts
9
9
  /**
@@ -376,20 +376,53 @@ var Physics3D = class {
376
376
  if (node.snapToGround && vy <= 0) this.kcc.enableSnapToGround(.1);
377
377
  else this.kcc.disableSnapToGround();
378
378
  this.kcc.setMaxSlopeClimbAngle(node.slopeLimitDeg * DEG);
379
+ let carryX = 0;
380
+ let carryY = 0;
381
+ let carryZ = 0;
382
+ if (e.ground && e.ground._physics === this && e.groundAt) {
383
+ const [gx, gy, gz] = worldPosition3D(e.ground);
384
+ carryX = gx - e.groundAt[0];
385
+ carryY = gy - e.groundAt[1];
386
+ carryZ = gz - e.groundAt[2];
387
+ } else e.ground = null;
379
388
  const desired = {
380
- x: (node.velocity[0] ?? 0) * this.lastDt,
381
- y: vy * this.lastDt,
382
- z: (node.velocity[2] ?? 0) * this.lastDt
389
+ x: (node.velocity[0] ?? 0) * this.lastDt + carryX,
390
+ y: vy * this.lastDt + carryY,
391
+ z: (node.velocity[2] ?? 0) * this.lastDt + carryZ
383
392
  };
384
393
  this.kcc.computeColliderMovement(e.collider, desired, this.R.QueryFilterFlags.EXCLUDE_SENSORS);
385
394
  const c = this.kcc.computedMovement();
386
395
  const t = e.body.translation();
396
+ const nx = t.x + c.x;
397
+ const ny = t.y + c.y;
398
+ const nz = t.z + c.z;
387
399
  e.body.setNextKinematicTranslation({
388
- x: t.x + c.x,
389
- y: t.y + c.y,
390
- z: t.z + c.z
400
+ x: nx,
401
+ y: ny,
402
+ z: nz
391
403
  });
392
404
  node._grounded = this.kcc.computedGrounded();
405
+ if (node._grounded) {
406
+ const half = characterHalfHeight(node);
407
+ const hit = this.castRay([
408
+ nx,
409
+ ny - half + .05,
410
+ nz
411
+ ], [
412
+ 0,
413
+ -1,
414
+ 0
415
+ ], .4, node);
416
+ if (hit?.node && !(hit.node instanceof CharacterBody3D)) {
417
+ e.ground = hit.node;
418
+ const at = worldPosition3D(hit.node);
419
+ e.groundAt = [
420
+ at[0],
421
+ at[1],
422
+ at[2]
423
+ ];
424
+ } else e.ground = null;
425
+ } else e.ground = null;
393
426
  }
394
427
  /** Rapier debug segments (meters). Null while debugDraw is off. */
395
428
  debugLines() {
@@ -900,6 +933,19 @@ const posScratch = [
900
933
  0
901
934
  ];
902
935
  /** Writes into the shared scratch — consume before the next call. */
936
+ /**
937
+ * Half the character's own height, for placing a ground probe just inside its
938
+ * feet. Falls back to a metre — a probe an inch out of place misses a platform,
939
+ * and a body with no declared size is not a case worth guessing hard about.
940
+ */
941
+ function characterHalfHeight(node) {
942
+ const collider = node.collider;
943
+ if (!collider) return 1;
944
+ if (collider.shape === "capsule" && typeof collider.height === "number") return collider.height / 2 + (collider.radius ?? 0);
945
+ if (collider.shape === "box" && Array.isArray(collider.size)) return (collider.size[1] ?? 2) / 2;
946
+ if (collider.shape === "sphere" && typeof collider.radius === "number") return collider.radius;
947
+ return 1;
948
+ }
903
949
  function worldPosition3D(node) {
904
950
  let x = 0;
905
951
  let y = 0;
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as BehaviorCtor, v as Engine, w as Scene } from "./behavior-D_jMpFh8.js";
1
+ import { N as Scene, n as BehaviorCtor, v as Engine } from "./behavior-CtPScDMo.js";
2
2
  import { c as JsonValue } from "./schema-CcoWb32N.js";
3
3
  import { CSSProperties, ReactNode } from "react";
4
4
 
package/dist/react.js CHANGED
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
156
156
  pointer: latest.pointer,
157
157
  ...keyboard !== void 0 ? { keyboard } : {}
158
158
  };
159
- const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-gXI7PYl0.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-bKHgHcsZ.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-QTfghzwM.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CFNqoqOX.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;