incanto 0.52.0 → 0.54.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 +56 -1
- package/dist/2d.d.ts +95 -4
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +111 -6
- package/dist/3d.js +10 -6
- package/dist/{behavior-uPEuZrUB.d.ts → behavior-CyQoSu4n.d.ts} +212 -10
- package/dist/{create-game-B_e9hJf7.js → create-game-BRt6XKmP.js} +31 -12
- package/dist/{create-game-CGnoypjL.js → create-game-Czzp6ZuE.js} +26 -14
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/{duplicate-B-OtSRFL.js → duplicate-MNLMAcbz.js} +1 -1
- package/dist/editor.js +14 -0
- package/dist/{environment-presets-DSZwsKPs.js → environment-presets-BQ_QsIBY.js} +222 -11
- package/dist/{gameplay-B6jqvYeM.js → gameplay-CZ2yq37J.js} +32 -23
- package/dist/gameplay.d.ts +8 -3
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +178 -6
- package/dist/index.js +9 -9
- package/dist/{loader-B-Gft32x.js → loader-BTkHYrQn.js} +54 -18
- package/dist/{loader-B9iTqs27.d.ts → loader-DhI1jFW_.d.ts} +1 -1
- package/dist/{log-report-lxrQY9cH.js → log-report-CPFm4OXf.js} +0 -0
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{particle-sim-BzJ1yxoE.d.ts → particle-sim-C5OfBbmU.d.ts} +11 -0
- package/dist/{pathfinding-CAR9DjQQ.d.ts → pathfinding-CXGCpRQe.d.ts} +24 -1
- package/dist/{physics-2d-Dns-oZlE.js → physics-2d-CfWAggJ1.js} +2 -2
- package/dist/{physics-3d-BhI0ehpe.js → physics-3d-Bes-GIuR.js} +3 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-Trx7WHnD.js → register-en63AEZO.js} +163 -4
- package/dist/{register-DVwlnZAZ.js → register-p48lHE2o.js} +433 -36
- package/dist/{replay-BU1CCM15.d.ts → replay-ePMz26jw.d.ts} +1 -1
- package/dist/{replay-BicPOMX0.js → replay-t1pP0gQg.js} +2 -2
- package/dist/{split-screen-paxkQs_q.d.ts → split-screen-BsdOHbzP.d.ts} +1 -1
- package/dist/{split-screen-CZ9ccBBQ.js → split-screen-CSb_uZ6W.js} +2 -2
- package/dist/{sprite-animation-D_p28jwU.js → sprite-animation-7qvUxF6Z.js} +19 -0
- package/dist/{src-5gbZO47I.js → src-vNPQeQ1W.js} +1 -1
- package/dist/{teardown-ks3d5W9n.js → teardown-C7uVSJvx.js} +30 -1
- package/dist/{test-DAojuFdb.js → test-Tnf1nQl3.js} +25 -13
- package/dist/test.d.ts +4 -4
- package/dist/test.js +2 -2
- package/dist/{touch-BoNg_MnF.js → touch-BnMyy9tr.js} +4 -4
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-CCvckvbw.js → agent8-BWaW-D85.js} +1 -1
- package/editor/assets/{debug-CLNCOnbc.js → debug-DLF8rUtr.js} +2 -2
- package/editor/assets/{index-Cb5Brupb.js → index-BYfiwbQx.js} +91 -91
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +221 -0
- package/skills/incanto-assets.md +45 -2
- package/skills/incanto-audio.md +97 -10
- package/skills/incanto-building-2d-games.md +55 -3
- package/skills/incanto-building-3d-games.md +3 -1
- package/skills/incanto-environment.md +3 -1
- package/skills/incanto-gameplay-behaviors.md +51 -4
- package/skills/incanto-hud.md +7 -0
- package/skills/incanto-node-reference.md +36 -0
- package/skills/incanto-performance.md +47 -0
- package/skills/incanto-physics-and-input.md +38 -0
- package/skills/incanto-verifying-your-game.md +50 -1
- package/skills/incanto-web-integration.md +1 -0
- package/templates-app/beacon-isle-3d/index.html +3 -3
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/index.html +3 -3
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/index.html +3 -3
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -325,9 +325,42 @@ type BusName = "sfx" | "music";
|
|
|
325
325
|
* engine.audio.muted = true; // mute toggle
|
|
326
326
|
* ```
|
|
327
327
|
*/
|
|
328
|
+
/** One thing that sounded (or would have, headless). */
|
|
329
|
+
interface AudioEvent {
|
|
330
|
+
/** `preset` (procedural one-shot), `src` (a clip), `music`, or `voice`. */
|
|
331
|
+
kind: "preset" | "src" | "music" | "voice";
|
|
332
|
+
/** The preset name, the clip url, or the voice preset. */
|
|
333
|
+
name: string;
|
|
334
|
+
/** Node path for a node-driven sound; `engine.music` / `engine.sfx` otherwise. */
|
|
335
|
+
from: string;
|
|
336
|
+
bus: BusName;
|
|
337
|
+
/** `engine.time` when it fired, in seconds. */
|
|
338
|
+
at: number;
|
|
339
|
+
}
|
|
328
340
|
declare class AudioBuses {
|
|
329
341
|
/** Fires whenever any value changes (drives live re-gain of playing sounds). */
|
|
330
342
|
readonly changed: Signal<[]>;
|
|
343
|
+
/**
|
|
344
|
+
* What has sounded, most recent last.
|
|
345
|
+
*
|
|
346
|
+
* Audio is a no-op headless, which left "did the coin sound actually fire
|
|
347
|
+
* when the coin was collected?" unanswerable — the audio skill's own advice
|
|
348
|
+
* was to *"assert `play()` doesn't throw and check your gameplay logic
|
|
349
|
+
* instead"*, which checks something else. The CALL still happens with no
|
|
350
|
+
* backend, so recording it answers the wiring question everywhere: in the
|
|
351
|
+
* verify VM, in a playtest, in a test.
|
|
352
|
+
*
|
|
353
|
+
* Bounded (200) — a game running for an hour must not grow a log.
|
|
354
|
+
*/
|
|
355
|
+
private readonly events;
|
|
356
|
+
/** @internal Called by the players; games read `recent()`. */
|
|
357
|
+
record(event: AudioEvent): void;
|
|
358
|
+
/** The last `limit` sounds, oldest first. */
|
|
359
|
+
recent(limit?: number): AudioEvent[];
|
|
360
|
+
/** How many times a preset/clip/track sounded — the assertion you want. */
|
|
361
|
+
countOf(name: string): number;
|
|
362
|
+
/** Forget what has sounded so far (between scenes, or between assertions). */
|
|
363
|
+
clearLog(): void;
|
|
331
364
|
private _master;
|
|
332
365
|
private _sfx;
|
|
333
366
|
private _music;
|
|
@@ -377,6 +410,8 @@ interface MusicBackend {
|
|
|
377
410
|
createTrack(src: string): MusicTrack;
|
|
378
411
|
/** Resume a gesture-suspended context (wired to the first user gesture). */
|
|
379
412
|
unlock(): void;
|
|
413
|
+
/** Release the context it owns (optional — teardown). */
|
|
414
|
+
dispose?(): void;
|
|
380
415
|
}
|
|
381
416
|
interface PlayMusicOptions {
|
|
382
417
|
/** Loop the track (default true — music usually loops). */
|
|
@@ -420,6 +455,15 @@ declare class MusicManager {
|
|
|
420
455
|
get current(): string | null;
|
|
421
456
|
/** Per-manager volume (0..1), on top of the bus gain. */
|
|
422
457
|
get volume(): number;
|
|
458
|
+
/**
|
|
459
|
+
* The backend, or an INERT one that mints silent tracks.
|
|
460
|
+
*
|
|
461
|
+
* Never null, on purpose: the manager used to return before touching its
|
|
462
|
+
* state when there was no backend, so `current` stayed null forever in every
|
|
463
|
+
* headless run — including the verify VM, which is the only place a game is
|
|
464
|
+
* checked automatically. "Did the boss music start?" had no answer anywhere.
|
|
465
|
+
* With an inert backend the state machine runs identically and nothing plays.
|
|
466
|
+
*/
|
|
423
467
|
private ensure;
|
|
424
468
|
/** Resume a gesture-suspended context + (re)start any pending track. */
|
|
425
469
|
unlock(): void;
|
|
@@ -432,6 +476,13 @@ declare class MusicManager {
|
|
|
432
476
|
crossfadeTo(src: string, seconds?: number): void;
|
|
433
477
|
/** Stop the current track, optionally fading out over `fadeOut` seconds. */
|
|
434
478
|
stop(fadeOut?: number): void;
|
|
479
|
+
/**
|
|
480
|
+
* Stop instantly and release the backend — music must not outlive the game.
|
|
481
|
+
*
|
|
482
|
+
* `dispose()` is the one call that tears an SPA-mounted game down, and it used
|
|
483
|
+
* to leave the track playing with the engine that owned it gone.
|
|
484
|
+
*/
|
|
485
|
+
dispose(): void;
|
|
435
486
|
/** Set the per-manager volume (re-applied to live tracks on the next tick). */
|
|
436
487
|
setVolume(v: number): void;
|
|
437
488
|
/** Advance fades by `dt` seconds and (re)apply bus×fade gains. Frame-driven. */
|
|
@@ -588,6 +639,14 @@ interface Voice {
|
|
|
588
639
|
//#endregion
|
|
589
640
|
//#region src/core/audio/webaudio-sfx.d.ts
|
|
590
641
|
/** A spatial emitter: where the sound is + where the listener is. */
|
|
642
|
+
/** The shape `AudioBuses.record` takes (structural — no import cycle). */
|
|
643
|
+
interface AudioRecord {
|
|
644
|
+
kind: "preset" | "src" | "music" | "voice";
|
|
645
|
+
name: string;
|
|
646
|
+
from: string;
|
|
647
|
+
bus: "sfx" | "music";
|
|
648
|
+
at: number;
|
|
649
|
+
}
|
|
591
650
|
interface SpatialPlay {
|
|
592
651
|
/** Emitter world position. */
|
|
593
652
|
position: Vec3;
|
|
@@ -615,6 +674,19 @@ declare class SfxEngine {
|
|
|
615
674
|
private ctx;
|
|
616
675
|
private resolved;
|
|
617
676
|
private readonly pcmCache;
|
|
677
|
+
/**
|
|
678
|
+
* The engine's buses, when this SfxEngine belongs to one — so a continuous
|
|
679
|
+
* voice lands in the same audio record as everything else. A record that
|
|
680
|
+
* covered only SOME sounds would answer "did the engine hum start?" with a
|
|
681
|
+
* confident no.
|
|
682
|
+
*/
|
|
683
|
+
private buses;
|
|
684
|
+
/** Live continuous voices, so teardown can silence the ones nobody stopped. */
|
|
685
|
+
private readonly voices;
|
|
686
|
+
/** @internal Engine wiring. */
|
|
687
|
+
_setBuses(buses: {
|
|
688
|
+
record(event: AudioRecord): void;
|
|
689
|
+
}): void;
|
|
618
690
|
/** True once a backend is present (lazily created on first use). */
|
|
619
691
|
get available(): boolean;
|
|
620
692
|
private ensure;
|
|
@@ -624,6 +696,18 @@ declare class SfxEngine {
|
|
|
624
696
|
* done. Headless returns an inert handle (same call sites, no ifs).
|
|
625
697
|
*/
|
|
626
698
|
startVoice(preset: VoicePreset, gain?: number): Voice;
|
|
699
|
+
/** How many continuous voices are still running (teardown/leak checks). */
|
|
700
|
+
get liveVoiceCount(): number;
|
|
701
|
+
/** Silence every continuous voice — the hum must not outlive the game. */
|
|
702
|
+
stopAll(seconds?: number): void;
|
|
703
|
+
/**
|
|
704
|
+
* Silence everything and release the AudioContext.
|
|
705
|
+
*
|
|
706
|
+
* A browser allows only a handful of contexts per page, so an SPA that mounts
|
|
707
|
+
* the game a few times must hand this one back — `dispose()` is documented as
|
|
708
|
+
* the call that tears everything down.
|
|
709
|
+
*/
|
|
710
|
+
dispose(): void;
|
|
627
711
|
/** Resume a gesture-suspended context (wired to the first user gesture). */
|
|
628
712
|
unlock(): void;
|
|
629
713
|
/**
|
|
@@ -634,6 +718,57 @@ declare class SfxEngine {
|
|
|
634
718
|
play(params: SfxParams, gain: number, opts?: SfxPlayOptions): void;
|
|
635
719
|
}
|
|
636
720
|
//#endregion
|
|
721
|
+
//#region src/core/effects-log.d.ts
|
|
722
|
+
/**
|
|
723
|
+
* What SHOWED — the visual half of the audio record.
|
|
724
|
+
*
|
|
725
|
+
* A game's feedback is sound and vision, and only one of them could be checked.
|
|
726
|
+
* `engine.audio.countOf('coin')` answers "did the coin sound fire?"; nothing
|
|
727
|
+
* answered "did the explosion?". `framing()` says where an emitter IS, which is
|
|
728
|
+
* a different question — a particle system that never fired and one that fired
|
|
729
|
+
* a hundred times sit at the same coordinates.
|
|
730
|
+
*
|
|
731
|
+
* Nothing here renders, so nothing here is measured in pixels. It records that
|
|
732
|
+
* the effect WAS ASKED FOR, which is the wiring question a game actually has:
|
|
733
|
+
* the burst that never happened because the signal was never connected looks
|
|
734
|
+
* exactly like the burst that happened off-screen, and this tells them apart.
|
|
735
|
+
*
|
|
736
|
+
* ```ts
|
|
737
|
+
* session.engine.effects.clearLog();
|
|
738
|
+
* smashTheCrystal();
|
|
739
|
+
* session.step(200);
|
|
740
|
+
* session.engine.effects.countOf('explosion'); // 1
|
|
741
|
+
* session.engine.effects.countFrom('/Game/Crystal/Boom'); // 1
|
|
742
|
+
* ```
|
|
743
|
+
*/
|
|
744
|
+
/** The kinds of thing a game does to say "that happened". */
|
|
745
|
+
type EffectKind = "burst" | "emit" | "trail" | "shake" | "flash" | "hitstop";
|
|
746
|
+
/** One effect that fired. */
|
|
747
|
+
interface EffectEvent {
|
|
748
|
+
kind: EffectKind;
|
|
749
|
+
/** The preset name, the flash colour, or `''` when the effect has no name. */
|
|
750
|
+
name: string;
|
|
751
|
+
/** Node path, or `engine` for the screen-wide ones. */
|
|
752
|
+
from: string;
|
|
753
|
+
/** Particles in the burst, shake magnitude, seconds frozen — the amount. */
|
|
754
|
+
amount: number;
|
|
755
|
+
/** `engine.time` when it fired, in seconds. */
|
|
756
|
+
at: number;
|
|
757
|
+
}
|
|
758
|
+
declare class EffectLog {
|
|
759
|
+
private readonly events;
|
|
760
|
+
/** @internal Called by the effects themselves; games read `recent()`. */
|
|
761
|
+
record(event: EffectEvent): void;
|
|
762
|
+
/** The last `limit` effects, oldest first. */
|
|
763
|
+
recent(limit?: number): EffectEvent[];
|
|
764
|
+
/** How many times an effect with this name fired (`explosion`, `#ff0000`). */
|
|
765
|
+
countOf(name: string): number;
|
|
766
|
+
/** How many effects this node fired — the question a wiring check has. */
|
|
767
|
+
countFrom(path: string): number;
|
|
768
|
+
/** Forget what has fired so far (between scenes, or between assertions). */
|
|
769
|
+
clearLog(): void;
|
|
770
|
+
}
|
|
771
|
+
//#endregion
|
|
637
772
|
//#region src/core/input.d.ts
|
|
638
773
|
/**
|
|
639
774
|
* Declarative input from scene JSON `input{}` — DOM-free logic (feed key state
|
|
@@ -760,6 +895,20 @@ declare class InputMap {
|
|
|
760
895
|
connect(fn: () => void): () => void;
|
|
761
896
|
};
|
|
762
897
|
}): () => void;
|
|
898
|
+
/**
|
|
899
|
+
* Pointer input from a mouse, a FINGER or a pen — one code path.
|
|
900
|
+
*
|
|
901
|
+
* This used to bind `mousedown`/`mousemove`/`mouseup`, which on a phone only
|
|
902
|
+
* arrive as COMPATIBILITY events: delayed until the browser has decided the
|
|
903
|
+
* touch is not a scroll, never fired for drags or multi-touch, and suppressed
|
|
904
|
+
* outright once the surface claims its gestures with `touch-action: none`.
|
|
905
|
+
* So every genre the comment below lists — match-3, tower defense, card,
|
|
906
|
+
* point-and-click, RTS — was mouse-only on the device most of them are played
|
|
907
|
+
* on. Pointer events cover all three input kinds and fire immediately.
|
|
908
|
+
*
|
|
909
|
+
* Only the PRIMARY pointer drives it: a second finger (the one on the virtual
|
|
910
|
+
* stick) must not also swing the camera.
|
|
911
|
+
*/
|
|
763
912
|
attachPointer(target: HTMLElement, opts?: {
|
|
764
913
|
lockOnClick?: boolean;
|
|
765
914
|
}): () => void;
|
|
@@ -1306,12 +1455,36 @@ interface QualityCaps {
|
|
|
1306
1455
|
minReflectionIntervalMs: number;
|
|
1307
1456
|
}
|
|
1308
1457
|
//#endregion
|
|
1309
|
-
//#region src/core/
|
|
1458
|
+
//#region src/core/phase-timing.d.ts
|
|
1310
1459
|
/**
|
|
1311
|
-
*
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1460
|
+
* Where the frame went — the breakdown `stats()` could not give.
|
|
1461
|
+
*
|
|
1462
|
+
* fps and frameMs are a TOTAL, and a total has no next question. This engine
|
|
1463
|
+
* has already lost a day to that: a frame drop everyone attributed to the GPU
|
|
1464
|
+
* turned out to be garbage collection, and nothing on screen could have said
|
|
1465
|
+
* so. Three of a frame's four parts are the engine's own work, and the fourth
|
|
1466
|
+
* is what is left — which is exactly where GC and browser work hide.
|
|
1467
|
+
*
|
|
1468
|
+
* Means over the same rolling window as `frameMs`, so the numbers are
|
|
1469
|
+
* comparable to each other and to the whole.
|
|
1314
1470
|
*/
|
|
1471
|
+
/** One frame's own accounting, in milliseconds. */
|
|
1472
|
+
interface FramePhases {
|
|
1473
|
+
/** Fixed steps: physics, and anything on `fixedUpdate`. */
|
|
1474
|
+
fixedMs: number;
|
|
1475
|
+
/** The variable update: behaviors, node logic, tweens. */
|
|
1476
|
+
updateMs: number;
|
|
1477
|
+
/** Whatever renders, as reported by it. 0 when nothing does (headless). */
|
|
1478
|
+
renderMs: number;
|
|
1479
|
+
/**
|
|
1480
|
+
* The frame minus the three above. GC, browser layout, input handling,
|
|
1481
|
+
* anything outside the engine's own loop — and the one to look at when the
|
|
1482
|
+
* others are small and the frame is not.
|
|
1483
|
+
*/
|
|
1484
|
+
otherMs: number;
|
|
1485
|
+
}
|
|
1486
|
+
//#endregion
|
|
1487
|
+
//#region src/core/stats.d.ts
|
|
1315
1488
|
/** Loop-side counters from `engine.stats()` — queryable at any time. */
|
|
1316
1489
|
interface EngineStats {
|
|
1317
1490
|
/** Frames per second over the last ~60 REAL ticks (headless runs: 0). */
|
|
@@ -1329,6 +1502,11 @@ interface EngineStats {
|
|
|
1329
1502
|
* this counter exists for.
|
|
1330
1503
|
*/
|
|
1331
1504
|
errors: number;
|
|
1505
|
+
/**
|
|
1506
|
+
* Where the frame went: fixed (physics), update (behaviors), render, and
|
|
1507
|
+
* everything else. Means over the same window as `frameMs`.
|
|
1508
|
+
*/
|
|
1509
|
+
phases: FramePhases;
|
|
1332
1510
|
}
|
|
1333
1511
|
/** GPU-side counters from `renderer.stats()` — the LAST rendered frame. */
|
|
1334
1512
|
interface RendererStats {
|
|
@@ -1393,6 +1571,12 @@ declare class Engine {
|
|
|
1393
1571
|
* routes its volume through this; games set it for global volume/mute. */
|
|
1394
1572
|
readonly audio: AudioBuses;
|
|
1395
1573
|
/**
|
|
1574
|
+
* What SHOWED — the visual half of the audio record. Particle bursts, camera
|
|
1575
|
+
* shake, screen flash and freeze frames land here, so "did the explosion
|
|
1576
|
+
* fire?" is answerable in a run with no renderer.
|
|
1577
|
+
*/
|
|
1578
|
+
readonly effects: EffectLog;
|
|
1579
|
+
/**
|
|
1396
1580
|
* Persisted player settings — volume, quality, sensitivity.
|
|
1397
1581
|
*
|
|
1398
1582
|
* `createSaveStore` was good and had zero callers, so every game's volume
|
|
@@ -1435,6 +1619,10 @@ declare class Engine {
|
|
|
1435
1619
|
private accumulator;
|
|
1436
1620
|
private disposeScheduler;
|
|
1437
1621
|
private readonly frameStats;
|
|
1622
|
+
/** Where each frame's time went — the breakdown `frameMs` alone cannot give. */
|
|
1623
|
+
private readonly phaseTimings;
|
|
1624
|
+
/** This frame's render cost, as reported by whoever renders. */
|
|
1625
|
+
private renderMs;
|
|
1438
1626
|
/**
|
|
1439
1627
|
* Game-time multiplier: 1 = realtime, 0.5 = slow motion, 0 = frozen
|
|
1440
1628
|
* (hit-stop / pause). Scales BOTH variable and fixed updates — physics,
|
|
@@ -1528,6 +1716,15 @@ declare class Engine {
|
|
|
1528
1716
|
erroredNodes(): Node[];
|
|
1529
1717
|
/** Un-skip every quarantined node — the "try again" after you fix the code. */
|
|
1530
1718
|
resumeErroredNodes(): number;
|
|
1719
|
+
/**
|
|
1720
|
+
* How long the last render took, in ms — called by whoever renders.
|
|
1721
|
+
*
|
|
1722
|
+
* The engine does not render, so this is the one slice it cannot measure for
|
|
1723
|
+
* itself; without it the cost lands in `otherMs` beside the GC and the two
|
|
1724
|
+
* are indistinguishable, which is the confusion this whole breakdown exists
|
|
1725
|
+
* to end.
|
|
1726
|
+
*/
|
|
1727
|
+
noteRenderMs(ms: number): void;
|
|
1531
1728
|
stop(): void;
|
|
1532
1729
|
/**
|
|
1533
1730
|
* Live performance counters, queryable at ANY time. fps/frameMs come from a
|
|
@@ -1553,12 +1750,17 @@ declare class Engine {
|
|
|
1553
1750
|
dispose(): void;
|
|
1554
1751
|
/**
|
|
1555
1752
|
* Advance exactly ONE fixed step and one variable update (both dt = the
|
|
1556
|
-
* fixed step), bypassing the wall-clock accumulator
|
|
1557
|
-
* by construction — the unit of time for headless
|
|
1558
|
-
* runScript): every input edge is visible to BOTH
|
|
1559
|
-
* exactly once.
|
|
1753
|
+
* fixed step scaled by `timeScale`), bypassing the wall-clock accumulator
|
|
1754
|
+
* entirely. Drift-free by construction — the unit of time for headless
|
|
1755
|
+
* harnesses (incanto/test runScript): every input edge is visible to BOTH
|
|
1756
|
+
* fixedUpdate and update exactly once.
|
|
1757
|
+
*
|
|
1758
|
+
* `ignorePause: true` steps a frame at full scale even while the game is
|
|
1759
|
+
* paused — the debug overlay's single-step button, and nothing else.
|
|
1560
1760
|
*/
|
|
1561
|
-
step(
|
|
1761
|
+
step(opts?: {
|
|
1762
|
+
ignorePause?: boolean;
|
|
1763
|
+
}): void;
|
|
1562
1764
|
/** Advance the loop manually. First call after (re)start only primes the clock. */
|
|
1563
1765
|
tick(nowMs: number): void;
|
|
1564
1766
|
}
|
|
@@ -1726,4 +1928,4 @@ declare function registeredBehaviors(): string[];
|
|
|
1726
1928
|
/** Test isolation helper. */
|
|
1727
1929
|
declare function clearBehaviors(): void;
|
|
1728
1930
|
//#endregion
|
|
1729
|
-
export { T_PREFIX as $, SettingsValues as A,
|
|
1931
|
+
export { T_PREFIX as $, SettingsValues as A, Node as At, captureBehaviors as B, EngineStats as C, synthSfx as Ct, QualityCaps as D, PlayMusicOptions as Dt, DeviceHints as E, MusicTrack as Et, BehaviorState as F, LogManager as Ft, ORDER_GROUP_BASE as G, savesWithoutUid as H, RestoreReport as I, Signal as It, effectiveOrder as J, OrderGroup as K, SaveSlot as L, SignalListener as Lt, readDeviceHints as M, SceneTree as Mt, suggestQuality as N, LogEntry as Nt, QualityTier as O, AudioBuses as Ot, Scene as P, LogLevel as Pt, Localization as Q, SaveSlots as R, Scheduler as S, SynthOptions as St, RendererStats as T, MusicManager as Tt, SaveStore as U, restoreBehaviors as V, createSaveStore as W, BASE_LOCALE as X, resolveOrderGroups as Y, LocaleTables as Z, mergeStaticSignals as _, spatialPan as _t, clearBehaviors as a, EffectLog as at, Engine as b, SfxParams as bt, registeredBehaviors as c, isAudioContextAvailable as ct, PropSchema as d, Listener as dt, suggestLocale as et, clearRegistry as f, ROLLOFF_MODELS as ft, getNodeType as g, spatialGain as gt, getNodeSignals as h, Vec3 as ht, behaviorSignals as i, EffectKind as it, qualityEnvironment as j, NodeLifecycle as jt, Settings as k, BusName as kt, NodeCtor as l, Voice as lt, getNodeSchema as m, SpatialParams as mt, BehaviorCtor as n, InputMap as nt, getBehavior as o, SfxEngine as ot, createNode as p, RolloffModel as pt, OrderGroupTable as q, behaviorSchema as r, EffectEvent as rt, registerBehavior as s, SfxPlayOptions as st, Behavior as t, translationKey as tt, PropDef as u, VoicePreset as ut, registerNode as v, SFX_PRESETS as vt, GameStats as w, MusicBackend as wt, EngineOptions as x, SfxWave as xt, registeredTypes as y, SFX_PRESET_NAMES as yt, behaviorsWithoutSave as z };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { A as registerBehavior, C as diagnose, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-
|
|
3
|
-
import {
|
|
2
|
+
import { A as registerBehavior, C as diagnose, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-BTkHYrQn.js";
|
|
3
|
+
import { b as AudioPlayer, x as Engine } from "./register-p48lHE2o.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
|
-
import { i as resolveRendering, n as attachTouchControls } from "./touch-
|
|
6
|
-
import { a as openBundledEditor, i as devServerLibrary, n as pauseWhenHidden, r as crossFade, t as teardown } from "./teardown-
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
-
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-
|
|
5
|
+
import { i as resolveRendering, n as attachTouchControls } from "./touch-BnMyy9tr.js";
|
|
6
|
+
import { a as openBundledEditor, c as audioErrors, i as devServerLibrary, n as pauseWhenHidden, r as crossFade, s as claimCanvasGestures, t as teardown } from "./teardown-C7uVSJvx.js";
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-CZ2yq37J.js";
|
|
8
|
+
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-en63AEZO.js";
|
|
9
9
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
10
|
-
import { n as enablePhysics2D } from "./physics-2d-
|
|
10
|
+
import { n as enablePhysics2D } from "./physics-2d-CfWAggJ1.js";
|
|
11
11
|
import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
|
|
12
12
|
//#region src/2d/assets.ts
|
|
13
13
|
/**
|
|
@@ -333,7 +333,11 @@ var Renderer2D = class {
|
|
|
333
333
|
this.selectionLines.renderOrder = 1e4;
|
|
334
334
|
this.selectionLines.visible = false;
|
|
335
335
|
this.worldScene.add(this.selectionLines);
|
|
336
|
-
this.disconnect = this.engine.updated.connect(() =>
|
|
336
|
+
this.disconnect = this.engine.updated.connect(() => {
|
|
337
|
+
const started = performance.now();
|
|
338
|
+
this.render();
|
|
339
|
+
this.engine.noteRenderMs(performance.now() - started);
|
|
340
|
+
});
|
|
337
341
|
}
|
|
338
342
|
debugLines;
|
|
339
343
|
selectionLines;
|
|
@@ -625,10 +629,15 @@ async function createGame2D(opts) {
|
|
|
625
629
|
const cleanups = [];
|
|
626
630
|
const keyboard = opts.keyboard ?? (typeof window !== "undefined" ? window : false);
|
|
627
631
|
if (keyboard) engine.input.attachKeyboard(keyboard);
|
|
632
|
+
if (opts.pointer) {
|
|
633
|
+
const lockOnClick = typeof opts.pointer === "object" ? opts.pointer.lockOnClick ?? false : false;
|
|
634
|
+
engine.input.attachPointer(opts.canvas, { lockOnClick });
|
|
635
|
+
}
|
|
628
636
|
cleanups.push(wireAudioUnlock(engine, scene.root, opts.canvas));
|
|
629
637
|
cleanups.push(engine.settings.bindAudio(engine.audio));
|
|
630
638
|
cleanups.push(pauseWhenHidden(engine));
|
|
631
639
|
cleanups.push(engine.settings.bindLocale(engine.locale));
|
|
640
|
+
if (!opts.pageGestures) claimCanvasGestures(opts.canvas);
|
|
632
641
|
const touchMode = opts.touch ?? "auto";
|
|
633
642
|
if (touchMode !== false) {
|
|
634
643
|
const host = opts.touchContainer ?? opts.canvas.parentElement ?? null;
|
|
@@ -718,6 +727,9 @@ async function createGame2D(opts) {
|
|
|
718
727
|
pick(x, y) {
|
|
719
728
|
return engine.picker?.(x, y) ?? null;
|
|
720
729
|
},
|
|
730
|
+
assetErrors() {
|
|
731
|
+
return [...renderer.assets?.errors() ?? [], ...audioErrors(engine.scene?.tree.root)];
|
|
732
|
+
},
|
|
721
733
|
stats() {
|
|
722
734
|
return {
|
|
723
735
|
triangles: 0,
|
|
@@ -755,10 +767,17 @@ function wireAudioUnlock(engine, root, canvas) {
|
|
|
755
767
|
target: canvasTarget,
|
|
756
768
|
type: "pointerdown"
|
|
757
769
|
});
|
|
758
|
-
if (typeof window !== "undefined")
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
770
|
+
if (typeof window !== "undefined") {
|
|
771
|
+
const win = window;
|
|
772
|
+
sources.push({
|
|
773
|
+
target: win,
|
|
774
|
+
type: "keydown"
|
|
775
|
+
});
|
|
776
|
+
sources.push({
|
|
777
|
+
target: win,
|
|
778
|
+
type: "pointerdown"
|
|
779
|
+
});
|
|
780
|
+
}
|
|
762
781
|
if (sources.length === 0) return () => {};
|
|
763
782
|
function detachAll() {
|
|
764
783
|
for (const s of sources) s.target.removeEventListener?.(s.type, unlock);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { A as registerBehavior, C as diagnose, n as loadScene } from "./loader-
|
|
3
|
-
import {
|
|
2
|
+
import { A as registerBehavior, C as diagnose, n as loadScene } from "./loader-BTkHYrQn.js";
|
|
3
|
+
import { C as qualityCaps, T as qualityRendering, b as AudioPlayer, x as Engine } from "./register-p48lHE2o.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
|
-
import { r as parseDrive, t as logReport } from "./log-report-
|
|
6
|
-
import { i as resolveRendering, n as attachTouchControls } from "./touch-
|
|
7
|
-
import { a as openBundledEditor, i as devServerLibrary, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, t as teardown } from "./teardown-
|
|
8
|
-
import { I as PhysicsBody3D, V as createCausticsQuad, n as registerGameplayBehaviors, z as Node3D } from "./gameplay-
|
|
5
|
+
import { r as parseDrive, t as logReport } from "./log-report-CPFm4OXf.js";
|
|
6
|
+
import { i as resolveRendering, n as attachTouchControls } from "./touch-BnMyy9tr.js";
|
|
7
|
+
import { a as openBundledEditor, c as audioErrors, i as devServerLibrary, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as claimCanvasGestures, t as teardown } from "./teardown-C7uVSJvx.js";
|
|
8
|
+
import { I as PhysicsBody3D, V as createCausticsQuad, n as registerGameplayBehaviors, z as Node3D } from "./gameplay-CZ2yq37J.js";
|
|
9
9
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
10
|
-
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-
|
|
10
|
+
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-BQ_QsIBY.js";
|
|
11
11
|
import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-njybhZon.js";
|
|
12
|
-
import { n as enablePhysics3D } from "./physics-3d-
|
|
12
|
+
import { n as enablePhysics3D } from "./physics-3d-Bes-GIuR.js";
|
|
13
13
|
import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
|
|
14
14
|
import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
|
|
15
15
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
@@ -1670,7 +1670,11 @@ var Renderer3D = class {
|
|
|
1670
1670
|
this.selectionLines.renderOrder = 1e4;
|
|
1671
1671
|
this.selectionLines.visible = false;
|
|
1672
1672
|
this.threeScene.add(this.selectionLines);
|
|
1673
|
-
this.disconnect = this.engine.updated.connect(() =>
|
|
1673
|
+
this.disconnect = this.engine.updated.connect(() => {
|
|
1674
|
+
const started = performance.now();
|
|
1675
|
+
this.render();
|
|
1676
|
+
this.engine.noteRenderMs(performance.now() - started);
|
|
1677
|
+
});
|
|
1674
1678
|
}
|
|
1675
1679
|
/** The pixel ratio the scene ASKED for — the governor scales this, never replaces it. */
|
|
1676
1680
|
basePixelRatio;
|
|
@@ -2349,6 +2353,7 @@ async function createGame3D(opts) {
|
|
|
2349
2353
|
engine.input.attachPointer(opts.canvas, { lockOnClick });
|
|
2350
2354
|
}
|
|
2351
2355
|
cleanups.push(wireAudioUnlock(engine, scene.root, opts.canvas));
|
|
2356
|
+
if (!opts.pageGestures) claimCanvasGestures(opts.canvas);
|
|
2352
2357
|
const touchMode = opts.touch ?? "auto";
|
|
2353
2358
|
if (touchMode !== false) {
|
|
2354
2359
|
const host = opts.touchContainer ?? opts.canvas.parentElement ?? null;
|
|
@@ -2575,7 +2580,7 @@ async function createGame3D(opts) {
|
|
|
2575
2580
|
renderer,
|
|
2576
2581
|
physics,
|
|
2577
2582
|
assetErrors() {
|
|
2578
|
-
return renderer.assets?.errors() ?? [];
|
|
2583
|
+
return [...renderer.assets?.errors() ?? [], ...audioErrors(engine.scene?.tree.root)];
|
|
2579
2584
|
},
|
|
2580
2585
|
async frame(frameOpts) {
|
|
2581
2586
|
const capture = renderer.captureFrame;
|
|
@@ -2623,10 +2628,17 @@ function wireAudioUnlock(engine, root, canvas) {
|
|
|
2623
2628
|
target: canvasTarget,
|
|
2624
2629
|
type: "pointerdown"
|
|
2625
2630
|
});
|
|
2626
|
-
if (typeof window !== "undefined")
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2631
|
+
if (typeof window !== "undefined") {
|
|
2632
|
+
const win = window;
|
|
2633
|
+
sources.push({
|
|
2634
|
+
target: win,
|
|
2635
|
+
type: "keydown"
|
|
2636
|
+
});
|
|
2637
|
+
sources.push({
|
|
2638
|
+
target: win,
|
|
2639
|
+
type: "pointerdown"
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2630
2642
|
if (sources.length === 0) return () => {};
|
|
2631
2643
|
function detachAll() {
|
|
2632
2644
|
for (const s of sources) s.target.removeEventListener?.(s.type, unlock);
|
package/dist/debug.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Pt as LogLevel, T as RendererStats, b as Engine } from "./behavior-CyQoSu4n.js";
|
|
2
2
|
|
|
3
3
|
//#region src/debug/panel.d.ts
|
|
4
4
|
/** Minimal document surface the overlay needs (injectable for tests). */
|
package/dist/debug.js
CHANGED
|
@@ -1600,7 +1600,7 @@ var DebugOverlay = class {
|
|
|
1600
1600
|
*/
|
|
1601
1601
|
nextFrame() {
|
|
1602
1602
|
if (!this.paused) this.setPaused(true);
|
|
1603
|
-
this.engine.step();
|
|
1603
|
+
this.engine.step({ ignorePause: true });
|
|
1604
1604
|
this.syncTime();
|
|
1605
1605
|
}
|
|
1606
1606
|
/** Push the engine's current time state into the panel, without rebuilding it. */
|
package/dist/editor.js
CHANGED
|
@@ -1181,6 +1181,20 @@ var kt = {
|
|
|
1181
1181
|
en: "Options are the locales the scene declares in \"strings\", each labeled with its own endonym. Picking one switches the game live and persists it. English is the base: any key a locale omits falls back to it, on purpose.",
|
|
1182
1182
|
ko: "옵션은 씬의 \"strings\"가 선언한 로케일이며, 각 언어의 자기 이름으로 표시됩니다. 선택하면 즉시 바뀌고 저장됩니다. 영어가 기준이라 로케일이 빠뜨린 키는 의도적으로 영어로 표시됩니다."
|
|
1183
1183
|
}),
|
|
1184
|
+
L("UiVolumeSlider", {
|
|
1185
|
+
en: "A volume slider, already wired to a bus.",
|
|
1186
|
+
ko: "볼륨 슬라이더 — 버스에 이미 연결되어 있습니다."
|
|
1187
|
+
}, {
|
|
1188
|
+
en: "bus picks which one it owns: master (everything), sfx or music. It writes engine.audio, which persists itself, so nothing needs saving. Left unlabelled it names its own bus. Two of them (music + sfx) is the audio menu most games ship.",
|
|
1189
|
+
ko: "bus로 어느 볼륨을 맡을지 고릅니다 — master(전체), sfx, music. engine.audio에 쓰면 자동 저장되므로 따로 저장할 게 없습니다. label을 비워두면 버스 이름이 표시됩니다. 대부분의 게임은 music + sfx 두 개면 충분합니다."
|
|
1190
|
+
}),
|
|
1191
|
+
L("UiMuteToggle", {
|
|
1192
|
+
en: "The mute switch — checked means silent.",
|
|
1193
|
+
ko: "음소거 스위치 — 체크하면 소리가 꺼집니다."
|
|
1194
|
+
}, {
|
|
1195
|
+
en: "Writes engine.audio.muted and persists with the volumes. Follows a mute made anywhere else, so two menus can never disagree.",
|
|
1196
|
+
ko: "engine.audio.muted에 쓰고 볼륨과 함께 저장됩니다. 다른 곳에서 음소거해도 따라가므로 두 메뉴가 어긋나지 않습니다."
|
|
1197
|
+
}),
|
|
1184
1198
|
L("UiQualitySelect", {
|
|
1185
1199
|
en: "Graphics quality — Low, Medium, High. Already wired.",
|
|
1186
1200
|
ko: "그래픽 품질 — Low, Medium, High. 이미 연결되어 있습니다."
|