@moku-labs/game 0.14.9 → 0.14.11
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/dist/app/page.d.mts +1 -1
- package/dist/app/system.d.mts +3 -3
- package/dist/app.d.mts +2 -2
- package/dist/app.mjs +4 -3
- package/dist/assets.d.mts +1 -1
- package/dist/{commands-BiZwnDBd.d.mts → commands-d5Zlh9hf.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-TUpLvorL.d.mts → define-BN15lAWb.d.mts} +2 -2
- package/dist/{headless-TowWxARz.d.mts → headless-CrhefcLu.d.mts} +1 -1
- package/dist/{index-BRdctuIb.d.mts → index-0GIP3tgm.d.mts} +7 -7
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +1 -1
- package/dist/inspect.d.mts +7 -7
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{memory-D6S7BUAo.d.mts → memory-DR1ibF_s.d.mts} +1 -1
- package/dist/{runtime-BYbkAbM9.d.mts → runtime-aLkoeoGj.d.mts} +1 -1
- package/dist/{src-DiPISDVO.mjs → src-Do697seR.mjs} +605 -111
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-CUNcMkD1.d.mts → types-BT9S_Lo2.d.mts} +103 -31
- package/dist/{types-p12uOu00.d.mts → types-BZa187kz.d.mts} +17 -5
- package/dist/{types-CsejHij6.d.mts → types-Bc9AvZVO.d.mts} +2 -2
- package/dist/{types-Ck18SF1P.d.mts → types-DMoi447V.d.mts} +2 -2
- package/dist/{types-CmhczThS.d.mts → types-kNiV7iFj.d.mts} +15 -4
- package/dist/{types-HbY-NiRP.d.mts → types-yiHtzg1Y.d.mts} +18 -8
- package/dist/visual.d.mts +6 -6
- package/dist/visual.mjs +6 -5
- package/llms.txt +9 -4
- package/package.json +6 -6
package/dist/testing.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Dt as AnyFlow, Ht as ClockSource, Nt as NodeTable, Wt as FakeClock, Xt as Json, ht as FeaturePlugin } from "./types-
|
|
2
|
-
import { t as createApp } from "./index-
|
|
3
|
-
import { n as saveOf, t as memory } from "./memory-
|
|
4
|
-
import { a as createHeadless, i as ReproResult, n as HeadlessGame, o as runRepro, r as Repro, s as stepFrames, t as HeadlessApp } from "./headless-
|
|
1
|
+
import { Dt as AnyFlow, Ht as ClockSource, Nt as NodeTable, Wt as FakeClock, Xt as Json, ht as FeaturePlugin } from "./types-BT9S_Lo2.mjs";
|
|
2
|
+
import { t as createApp } from "./index-0GIP3tgm.mjs";
|
|
3
|
+
import { n as saveOf, t as memory } from "./memory-DR1ibF_s.mjs";
|
|
4
|
+
import { a as createHeadless, i as ReproResult, n as HeadlessGame, o as runRepro, r as Repro, s as stepFrames, t as HeadlessApp } from "./headless-CrhefcLu.mjs";
|
|
5
5
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
6
6
|
|
|
7
7
|
//#region src/plugins/clock/fake.d.ts
|
package/dist/testing.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Kt as type, Ut as exit, t as createApp } from "./src-
|
|
1
|
+
import { Kt as type, Ut as exit, t as createApp } from "./src-Do697seR.mjs";
|
|
2
2
|
import { n as saveOf, t as memory } from "./memory-CWlqhS7l.mjs";
|
|
3
3
|
import { t as fakeClock } from "./fake-MpzwZhIZ.mjs";
|
|
4
4
|
import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BA6ffzqD.mjs";
|
|
@@ -1583,7 +1583,9 @@ type FxApi = {
|
|
|
1583
1583
|
onHint(listener: HintListener): () => void;
|
|
1584
1584
|
};
|
|
1585
1585
|
/**
|
|
1586
|
-
* The `fx` of a node context
|
|
1586
|
+
* The `fx` of a node context, and of the context of `recover`. Three ways to reach what is not
|
|
1587
|
+
* logic: `await fx(descriptor)` runs an effect and waits for it, `fx.start(descriptor)` starts one
|
|
1588
|
+
* now and does not wait, `fx.emit(hint)` keeps a cosmetic hint until the edge has committed.
|
|
1587
1589
|
*
|
|
1588
1590
|
* @example
|
|
1589
1591
|
* ```ts
|
|
@@ -1594,7 +1596,45 @@ type FxApi = {
|
|
|
1594
1596
|
* ```
|
|
1595
1597
|
*/
|
|
1596
1598
|
type NodeFx = ((descriptor: Descriptor) => Promise<unknown>) & {
|
|
1599
|
+
/**
|
|
1600
|
+
* Buffers one cosmetic hint. It reaches its handler and the `onHint` listeners only after the
|
|
1601
|
+
* commit of the edge, so a node that fails shows nothing. Fast mode drops it.
|
|
1602
|
+
*
|
|
1603
|
+
* @param hint - The hint, built with `hint(kind, payload)`.
|
|
1604
|
+
* @example
|
|
1605
|
+
* ```ts
|
|
1606
|
+
* // The "merge" node: the sparkle belongs to the merge, so it is shown once the merge is real.
|
|
1607
|
+
* run: ({ fx, session, input, out }) => {
|
|
1608
|
+
* session.merged.push(input.cell);
|
|
1609
|
+
* fx.emit(hint("sparkle", { cell: "c3" })); // nothing is shown yet
|
|
1610
|
+
*
|
|
1611
|
+
* return out.done(); // the edge commits, then the handler of "sparkle" gets the hint
|
|
1612
|
+
* }
|
|
1613
|
+
* ```
|
|
1614
|
+
*/
|
|
1597
1615
|
emit(hint: Hint): void;
|
|
1616
|
+
/**
|
|
1617
|
+
* Starts an effect now and does not wait for it: the handler is called at once and the body goes
|
|
1618
|
+
* on. Fast mode and the node's signal apply as for `await fx(descriptor)`. A handler that fails
|
|
1619
|
+
* never fails the node: the log gets the error entry `flow:fx-start-failed` with
|
|
1620
|
+
* `{ path, kind }`, and a `cosmetic` descriptor is silent. After the abort of the node nothing is
|
|
1621
|
+
* started, and a rejection that comes after the abort is not logged: the handler was told to
|
|
1622
|
+
* stop.
|
|
1623
|
+
*
|
|
1624
|
+
* @param descriptor - The effect to start. Not one with `answers` and not a `guide`.
|
|
1625
|
+
* @throws {Error} When the descriptor has `answers` or is a `guide`: an answer has to be awaited.
|
|
1626
|
+
* @example
|
|
1627
|
+
* ```ts
|
|
1628
|
+
* // The "place" node of a board game: the tap sound starts, the mark is placed at once.
|
|
1629
|
+
* run: ({ fx, session, input, out }) => {
|
|
1630
|
+
* fx.start(sfx("ui.tap")); // undefined: the handler of "sfx" is already playing "ui.tap"
|
|
1631
|
+
* session.marks.push(input.cell);
|
|
1632
|
+
*
|
|
1633
|
+
* return out.placed(); // the edge commits while the sound still plays
|
|
1634
|
+
* }
|
|
1635
|
+
* ```
|
|
1636
|
+
*/
|
|
1637
|
+
start(descriptor: Descriptor): void;
|
|
1598
1638
|
};
|
|
1599
1639
|
//#endregion
|
|
1600
1640
|
//#region src/plugins/flow/inbox/types.d.ts
|
|
@@ -3284,10 +3324,19 @@ type TrackOptions = {
|
|
|
3284
3324
|
* and goes through `ecs.set`, so `changed()` sees it. Muted fields are never written, also not on
|
|
3285
3325
|
* `finish()`.
|
|
3286
3326
|
*
|
|
3327
|
+
* A driver with additive tracks keeps the value under them: an additive track adds an offset to
|
|
3328
|
+
* it, and an absolute track of 0 ms to a field moves it. Such a driver reports the offsets with
|
|
3329
|
+
* `offsetsOf`, so the world can compare the value under a loop with the rest pose.
|
|
3330
|
+
*
|
|
3287
3331
|
* @example
|
|
3288
3332
|
* ```ts
|
|
3289
3333
|
* // `anim` hands the world its own tween core in `onStart`.
|
|
3290
|
-
* const driver: TweenDriver = {
|
|
3334
|
+
* const driver: TweenDriver = {
|
|
3335
|
+
* track: startTrack,
|
|
3336
|
+
* cancelAll: cancelTracksOf,
|
|
3337
|
+
* // A card sways 0.04 rad to one side right now: { rotation: 0.04 }
|
|
3338
|
+
* offsetsOf: addedOffsets
|
|
3339
|
+
* };
|
|
3291
3340
|
* const off = ctx.require(worldPlugin).projection.setDriver(driver);
|
|
3292
3341
|
*
|
|
3293
3342
|
* off(); // `anim` stopped: the world writes instantly again
|
|
@@ -3311,6 +3360,18 @@ type TweenDriver = {
|
|
|
3311
3360
|
* @param entity - The entity whose tracks end.
|
|
3312
3361
|
*/
|
|
3313
3362
|
cancelAll(entity: Entity): void;
|
|
3363
|
+
/**
|
|
3364
|
+
* What the running additive tracks add to each field of one component of one entity right now:
|
|
3365
|
+
* the offset of a loop. The world takes it off the stored value before it compares a view with
|
|
3366
|
+
* its rest pose, so a view that only sways is home. Optional: a driver without additive tracks
|
|
3367
|
+
* leaves it out, and the world reads that as no offset.
|
|
3368
|
+
*
|
|
3369
|
+
* @param entity - The entity to ask about.
|
|
3370
|
+
* @param component - The component to ask about.
|
|
3371
|
+
* @returns Field name to the sum of the offsets on it. A field no additive track adds to is
|
|
3372
|
+
* left out.
|
|
3373
|
+
*/
|
|
3374
|
+
offsetsOf?(entity: Entity, component: AnyComponent): Readonly<Record<string, number>>;
|
|
3314
3375
|
};
|
|
3315
3376
|
/**
|
|
3316
3377
|
* One node of an element description, as `ui` builds it. The world never imports `ui`: it knows
|
|
@@ -3385,7 +3446,10 @@ type ChangeHooks<Item> = {
|
|
|
3385
3446
|
* The motion hooks of a projection. Every one is optional; without a hook the component diff is
|
|
3386
3447
|
* written directly. `loop` starts what moves a view for its whole life (an additive sway, a spin):
|
|
3387
3448
|
* it is played wherever `enter` plays, next to it, and its motion is kept apart from the view's
|
|
3388
|
-
* other motions, so a change never cancels it; it ends when the view dies.
|
|
3449
|
+
* other motions, so a change never cancels it; it ends when the view dies. A view that entered
|
|
3450
|
+
* directly (a mount, a load, a restore, a paused or fast world) plays no `enter`, and a direct
|
|
3451
|
+
* reconcile ends every track of a view: the loop is then started by the first frame in which the
|
|
3452
|
+
* world is live and a tween driver is installed, once.
|
|
3389
3453
|
*
|
|
3390
3454
|
* @example
|
|
3391
3455
|
* ```ts
|
|
@@ -3473,7 +3537,9 @@ type AnyProjectionSpec = {
|
|
|
3473
3537
|
* Why a reconcile runs. Merged over the frame; the cause table decides play or direct. A
|
|
3474
3538
|
* `"restore"` and a `"load"` replace the state, so their reconcile also brings every live view to
|
|
3475
3539
|
* its rest pose, whether its item changed or not, and every element to the rest pose its owner
|
|
3476
|
-
* recorded with `setRest`.
|
|
3540
|
+
* recorded with `setRest`. A `"rollback"` commits the state of the last rest point: a live view
|
|
3541
|
+
* whose item did not change and that stands off its rest pose is settled, as `settle(entity)`
|
|
3542
|
+
* does. An element another plugin owns is not.
|
|
3477
3543
|
*
|
|
3478
3544
|
* @example
|
|
3479
3545
|
* ```ts
|
|
@@ -3484,21 +3550,6 @@ type Cause = "edge" | "rollback" | "restore" | "load" | "mount" | "rerun";
|
|
|
3484
3550
|
/**
|
|
3485
3551
|
* One live or exiting view: the entity of one key of one projection, its rest pose and the
|
|
3486
3552
|
* motions that still run on it.
|
|
3487
|
-
*
|
|
3488
|
-
* @example
|
|
3489
|
-
* ```ts
|
|
3490
|
-
* const view: View = {
|
|
3491
|
-
* entity: 1_048_576,
|
|
3492
|
-
* projection: "board.items",
|
|
3493
|
-
* key: "i7",
|
|
3494
|
-
* item: { id: "i7" },
|
|
3495
|
-
* rest: new Map(),
|
|
3496
|
-
* handles: [],
|
|
3497
|
-
* lifted: false,
|
|
3498
|
-
* dropWhenStill: false,
|
|
3499
|
-
* exiting: false
|
|
3500
|
-
* };
|
|
3501
|
-
* ```
|
|
3502
3553
|
*/
|
|
3503
3554
|
type View$1 = {
|
|
3504
3555
|
entity: Entity;
|
|
@@ -3509,6 +3560,13 @@ type View$1 = {
|
|
|
3509
3560
|
handles: MotionHandle[];
|
|
3510
3561
|
lifted: boolean; /** `lift(false)` is waiting for the last motion to end. */
|
|
3511
3562
|
dropWhenStill: boolean;
|
|
3563
|
+
/**
|
|
3564
|
+
* The `loop` hook of the view is still to be started. Set when the view enters in a direct
|
|
3565
|
+
* reconcile, which plays no hook, and whenever a flush ends its tracks, the loop among them.
|
|
3566
|
+
* The sweep starts the loop and clears the flag, only while the world is live and a tween
|
|
3567
|
+
* driver is installed. It is the only trigger, so a loop starts once.
|
|
3568
|
+
*/
|
|
3569
|
+
loopOwed: boolean;
|
|
3512
3570
|
exiting: boolean;
|
|
3513
3571
|
};
|
|
3514
3572
|
/**
|
|
@@ -3534,17 +3592,16 @@ type Mounted = {
|
|
|
3534
3592
|
/**
|
|
3535
3593
|
* One track the projection started on the driver. The world keeps the component name and the
|
|
3536
3594
|
* handle, so it knows which components are driven without knowing how the driver runs them.
|
|
3537
|
-
*
|
|
3538
|
-
* @example
|
|
3539
|
-
* ```ts
|
|
3540
|
-
* const track: Track = { entity: 1_048_576, component: "Transform", handle: flight };
|
|
3541
|
-
* track.handle.active(); // true while the flight runs
|
|
3542
|
-
* ```
|
|
3543
3595
|
*/
|
|
3544
3596
|
type Track = {
|
|
3545
3597
|
entity: Entity;
|
|
3546
3598
|
component: string;
|
|
3547
3599
|
handle: MotionHandle;
|
|
3600
|
+
/**
|
|
3601
|
+
* Whether the track adds to its fields instead of owning them, as the loop of a view does. An
|
|
3602
|
+
* additive track drives no component: a settle may bring the component home under it.
|
|
3603
|
+
*/
|
|
3604
|
+
additive: boolean;
|
|
3548
3605
|
};
|
|
3549
3606
|
/**
|
|
3550
3607
|
* The rest pose of one entity, by component name: the last output of `view(item)` for a view, or
|
|
@@ -3589,9 +3646,17 @@ type ProjectionState = {
|
|
|
3589
3646
|
dirty: Dirty | undefined;
|
|
3590
3647
|
hints: Hint[]; /** The tracks the projection started, with the handle that says whether they still run. */
|
|
3591
3648
|
tracks: Track[]; /** The tween engine `anim` installed, or `undefined`: then every target is written at once. */
|
|
3592
|
-
driver: TweenDriver | undefined;
|
|
3649
|
+
driver: TweenDriver | undefined;
|
|
3650
|
+
/**
|
|
3651
|
+
* The rest pose of an entity that is not a view, recorded by its owner through `setRest`. The
|
|
3652
|
+
* entry of an entity that is gone is dropped by the sweep and by the converge step.
|
|
3653
|
+
*/
|
|
3593
3654
|
rests: Map<Entity, RestPose>; /** Projection name to key to the entity a plugin above registered under it. */
|
|
3594
|
-
keys: Map<string, Map<string, Entity>>;
|
|
3655
|
+
keys: Map<string, Map<string, Entity>>;
|
|
3656
|
+
/**
|
|
3657
|
+
* The reverse of `keys`, so `keyOf` answers for a registered element too. The entry of an
|
|
3658
|
+
* entity that is gone is dropped with its rest pose, and its side of `keys` with it.
|
|
3659
|
+
*/
|
|
3595
3660
|
keysByEntity: Map<Entity, ProjectionKey>; /** Entity to component name to the fields another writer owns. */
|
|
3596
3661
|
mutes: Map<Entity, Map<string, Set<string>>>; /** The `onConverged` listeners: the owners of elements, told after a restore or a load. */
|
|
3597
3662
|
converged: Array<() => void>;
|
|
@@ -3700,8 +3765,8 @@ type ProjectionApi = {
|
|
|
3700
3765
|
unmount(names: readonly string[]): void;
|
|
3701
3766
|
/**
|
|
3702
3767
|
* A drop with no commit: cancels the view's motions and plays settle for every loose component.
|
|
3703
|
-
*
|
|
3704
|
-
* a live view.
|
|
3768
|
+
* A view that loops comes home too, and its loop runs on. In mode `paused` or `fast` the rest
|
|
3769
|
+
* pose is written at once. No-op for an entity that is not a live view.
|
|
3705
3770
|
*
|
|
3706
3771
|
* @param entity - The entity of the view that was dropped.
|
|
3707
3772
|
* @example
|
|
@@ -3732,7 +3797,8 @@ type ProjectionApi = {
|
|
|
3732
3797
|
/**
|
|
3733
3798
|
* Moves a view into or out of its projection's `lift` layer. `lift(entity, false)` on a view
|
|
3734
3799
|
* that still moves takes effect when its last motion ends, so a settling view stays above the
|
|
3735
|
-
* board until it is home.
|
|
3800
|
+
* board until it is home. A reconcile or a `settle(entity)` that ends the motions of the view
|
|
3801
|
+
* applies the pending drop itself.
|
|
3736
3802
|
*
|
|
3737
3803
|
* @param entity - The entity of the view.
|
|
3738
3804
|
* @param on - True to lift, false to drop back.
|
|
@@ -3865,6 +3931,7 @@ type ProjectionApi = {
|
|
|
3865
3931
|
* It is also where a restore and a load put the element: after their reconcile every track on
|
|
3866
3932
|
* the entity is ended and every recorded component it still carries is written, muted fields
|
|
3867
3933
|
* left out. So the owner records the pose it draws the element with, every time that changes.
|
|
3934
|
+
* The pose of an entity that is gone is dropped in the next live frame.
|
|
3868
3935
|
*
|
|
3869
3936
|
* @param entity - The entity of the element.
|
|
3870
3937
|
* @param component - The component whose rest value is recorded.
|
|
@@ -3882,6 +3949,8 @@ type ProjectionApi = {
|
|
|
3882
3949
|
/**
|
|
3883
3950
|
* Gives an entity a projection and key of its own, so `entityOf` and `keyOf` answer for it next
|
|
3884
3951
|
* to the live views. That is how an element becomes a target for `anim`, `input` and `guide`.
|
|
3952
|
+
* A key whose entity is gone is dropped in the next live frame, also when the remover was never
|
|
3953
|
+
* called.
|
|
3885
3954
|
*
|
|
3886
3955
|
* @param projection - Name the element is addressed under.
|
|
3887
3956
|
* @param key - Key the element is addressed under.
|
|
@@ -3957,7 +4026,10 @@ type ProjectionInternal = {
|
|
|
3957
4026
|
pushHint(hint: Hint): void;
|
|
3958
4027
|
/**
|
|
3959
4028
|
* Sweeps the views whose motions ended: the despawn queue, the convergence check and the
|
|
3960
|
-
* pending drops.
|
|
4029
|
+
* pending drops. Then it drops the rest pose and the registered key of every element whose
|
|
4030
|
+
* entity is gone, and starts the loop of every live view that owes one. It runs once per
|
|
4031
|
+
* frame and does nothing unless the world is live. The tracks themselves are advanced by the
|
|
4032
|
+
* driver.
|
|
3961
4033
|
*/
|
|
3962
4034
|
sweep(): void;
|
|
3963
4035
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, I as AnyComponentType, L as AnyComponentValue, Lt as Descriptor, O as Point$2, Q as ChangeHook, Sn as Require, V as ComponentValue, Vt as Api$5, Xt as Json, at as NumericFields, dt as Api$1, h as Api$3, hn as Api, it as MotionHandle, k as ViewportSize, ot as ProjectionMotion, rt as Motion, s as Events$1, t as Api$4, ut as ViewHandle, zt as Hint } from "./types-
|
|
1
|
+
import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, I as AnyComponentType, L as AnyComponentValue, Lt as Descriptor, O as Point$2, Q as ChangeHook, Sn as Require, V as ComponentValue, Vt as Api$5, Xt as Json, at as NumericFields, dt as Api$1, h as Api$3, hn as Api, it as MotionHandle, k as ViewportSize, ot as ProjectionMotion, rt as Motion, s as Events$1, t as Api$4, ut as ViewHandle, zt as Hint } from "./types-BT9S_Lo2.mjs";
|
|
2
2
|
import { Log } from "@moku-labs/common/browser";
|
|
3
3
|
import { PluginCtx } from "@moku-labs/core";
|
|
4
4
|
|
|
@@ -2699,10 +2699,10 @@ type JsxState = {
|
|
|
2699
2699
|
/**
|
|
2700
2700
|
* One node of the snapshot `tree()` answers with. `rect` is natural: under a `fit` ancestor it
|
|
2701
2701
|
* is the rect before that ancestor's scale. An element with `fit: "contain"` adds `fitScale`,
|
|
2702
|
-
* the scale it is drawn at. A `text` adds `content`: the words it draws.
|
|
2703
|
-
* the text being typed while it is edited,
|
|
2704
|
-
* adds `window`: the rows that exist, which
|
|
2705
|
-
* list has.
|
|
2702
|
+
* the scale it is drawn at. A `text` adds `content`: the words it draws. A `text` given a text
|
|
2703
|
+
* style key adds `textStyle`. An `input` adds `value`: the text being typed while it is edited,
|
|
2704
|
+
* else the local field it writes. A windowed `scroll` adds `window`: the rows that exist, which
|
|
2705
|
+
* are the children of its content, and how many the list has.
|
|
2706
2706
|
*
|
|
2707
2707
|
* @example
|
|
2708
2708
|
* ```ts
|
|
@@ -2740,6 +2740,18 @@ type UiNode = {
|
|
|
2740
2740
|
* ```
|
|
2741
2741
|
*/
|
|
2742
2742
|
content?: string;
|
|
2743
|
+
/**
|
|
2744
|
+
* The text style key a `text` was given: its `style` prop while that is a key. Absent when the
|
|
2745
|
+
* style is a `Style` object, when there is none, and on any other tag.
|
|
2746
|
+
*
|
|
2747
|
+
* @example
|
|
2748
|
+
* ```ts
|
|
2749
|
+
* // Home: `<text key="homePlayLabel" style="ui.button" content="Play" />` under the root row.
|
|
2750
|
+
* const label = app.ui.tree().children.find(node => node.key === "homePlayLabel");
|
|
2751
|
+
* label?.textStyle; // "ui.button"
|
|
2752
|
+
* ```
|
|
2753
|
+
*/
|
|
2754
|
+
textStyle?: string;
|
|
2743
2755
|
value?: string;
|
|
2744
2756
|
/**
|
|
2745
2757
|
* The rows of a windowed scroll that exist and how many the list has.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Xt as Json, an as Snapshot } from "./types-
|
|
2
|
-
import { t as HeadlessApp } from "./headless-
|
|
1
|
+
import { Xt as Json, an as Snapshot } from "./types-BT9S_Lo2.mjs";
|
|
2
|
+
import { t as HeadlessApp } from "./headless-CrhefcLu.mjs";
|
|
3
3
|
import { Log } from "@moku-labs/common/browser";
|
|
4
4
|
|
|
5
5
|
//#region src/plugins/flow/doors/types.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as Api$1, B as ComponentType, D as FilterSlot, H as Entity, O as Point, R as ComponentHandle, Sn as Require, _ as PixiFilter, b as PixiTexture, dt as Api, h as Api$2, s as Events, t as Api$3, v as PixiModule, xn as Events$1, y as PixiSprite } from "./types-
|
|
2
|
-
import { x as AnimApi } from "./types-
|
|
1
|
+
import { A as Api$1, B as ComponentType, D as FilterSlot, H as Entity, O as Point, R as ComponentHandle, Sn as Require, _ as PixiFilter, b as PixiTexture, dt as Api, h as Api$2, s as Events, t as Api$3, v as PixiModule, xn as Events$1, y as PixiSprite } from "./types-BT9S_Lo2.mjs";
|
|
2
|
+
import { x as AnimApi } from "./types-kNiV7iFj.mjs";
|
|
3
3
|
import { Log } from "@moku-labs/common/browser";
|
|
4
4
|
import { PluginCtx } from "@moku-labs/core";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, It as TypeTag, L as AnyComponentValue, Lt as Descriptor, R as ComponentHandle, Sn as Require, Xt as Json, Yt as Events$2, at as NumericFields, ct as TrackOptions, dn as Events$3, dt as Api$1, et as Ease, h as Api$5, hn as Api, it as MotionHandle, lt as TrackSegment, nt as LayerSpec, q as Owner, qt as Api$4, s as Events$4, t as Api$3, tt as LayerSort, xn as Events$5 } from "./types-
|
|
2
|
-
import { A as InputApi, c as UiNode, i as Finding, n as ElementChange, o as JsxState, q as I18nApi, r as ElementMotion, rt as StylesState, x as TextApi } from "./types-
|
|
1
|
+
import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, It as TypeTag, L as AnyComponentValue, Lt as Descriptor, R as ComponentHandle, Sn as Require, Xt as Json, Yt as Events$2, at as NumericFields, ct as TrackOptions, dn as Events$3, dt as Api$1, et as Ease, h as Api$5, hn as Api, it as MotionHandle, lt as TrackSegment, nt as LayerSpec, q as Owner, qt as Api$4, s as Events$4, t as Api$3, tt as LayerSort, xn as Events$5 } from "./types-BT9S_Lo2.mjs";
|
|
2
|
+
import { A as InputApi, c as UiNode, i as Finding, n as ElementChange, o as JsxState, q as I18nApi, r as ElementMotion, rt as StylesState, x as TextApi } from "./types-BZa187kz.mjs";
|
|
3
3
|
import { Log } from "@moku-labs/common/browser";
|
|
4
4
|
import { PluginCtx } from "@moku-labs/core";
|
|
5
5
|
import { Node, Yoga } from "yoga-layout/load";
|
|
@@ -16,7 +16,7 @@ import { Node, Yoga } from "yoga-layout/load";
|
|
|
16
16
|
* id: 1, entity: 1_048_576, component: Transform, to: { x: 200 },
|
|
17
17
|
* keys: { x: "1048576:Transform:x" }, ms: 350, ease: "out", delayMs: 0, elapsed: 0,
|
|
18
18
|
* from: undefined, muted: () => new Set(), additive: false, driven: false, bornFrame: 4,
|
|
19
|
-
* ended: false, segments: undefined, repeatsLeft: 0, held: false
|
|
19
|
+
* ended: false, segments: undefined, repeatsLeft: 0, held: false, masked: new Set()
|
|
20
20
|
* };
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
@@ -55,6 +55,12 @@ type Track = {
|
|
|
55
55
|
* walk clears it, so the next hold writes the first key again.
|
|
56
56
|
*/
|
|
57
57
|
held: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The fields an additive track skipped at its last write because another writer owned them.
|
|
60
|
+
* When such a field is given back, the track adds to the value it finds there and not to the
|
|
61
|
+
* base it kept. Empty for a track that owns its fields.
|
|
62
|
+
*/
|
|
63
|
+
masked: Set<string>;
|
|
58
64
|
};
|
|
59
65
|
/**
|
|
60
66
|
* A motion handle that can also be advanced by hand: what the timeline cursor needs, so a track
|
|
@@ -901,7 +907,12 @@ type State$3 = {
|
|
|
901
907
|
nextId: number; /** `"<entity>:<component>:<field>"` to the id of the absolute track that drives the field. */
|
|
902
908
|
owner: Map<string, number>; /** Per field key, the delta each additive track contributes. */
|
|
903
909
|
offsets: Map<string, Map<number, number>>; /** Per field key, the value the offsets are added to. Dropped when no track drives the field. */
|
|
904
|
-
bases: Map<string, number>;
|
|
910
|
+
bases: Map<string, number>;
|
|
911
|
+
/**
|
|
912
|
+
* Per entity, its running additive tracks. `offsetsOf` of the driver reads it, so the cost of
|
|
913
|
+
* one answer is the loops of that entity and not every track of the game.
|
|
914
|
+
*/
|
|
915
|
+
additive: Map<Entity, Set<Track>>; /** The running timelines, by id. */
|
|
905
916
|
timelines: Map<number, RunningTimeline>; /** The `animations` of every feature, read in `onStart`. */
|
|
906
917
|
registry: Map<string, AnyAnimationDefinition>; /** `onMark` subscribers, in subscription order. */
|
|
907
918
|
markListeners: Set<MarkListener>; /** Counter of the frame steps. A track born in the running step is not advanced by it. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Dt as AnyFlow, Ht as ClockSource, Jt as Config, Wt as FakeClock, Xt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, pt as Config$1, tn as PlayerStateProvider, u as Manifest, v as PixiModule } from "./types-
|
|
2
|
-
import { o as AudioContextLike, s as Config$4 } from "./types-
|
|
3
|
-
import { _t as audioPlugin, ct as PlatformProvider, ht as effectsPlugin, i as screen, ot as platformPlugin, t as createApp } from "./index-
|
|
4
|
-
import { t as memory } from "./memory-
|
|
1
|
+
import { Dt as AnyFlow, Ht as ClockSource, Jt as Config, Wt as FakeClock, Xt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, pt as Config$1, tn as PlayerStateProvider, u as Manifest, v as PixiModule } from "./types-BT9S_Lo2.mjs";
|
|
2
|
+
import { o as AudioContextLike, s as Config$4 } from "./types-kNiV7iFj.mjs";
|
|
3
|
+
import { _t as audioPlugin, ct as PlatformProvider, ht as effectsPlugin, i as screen, ot as platformPlugin, t as createApp } from "./index-0GIP3tgm.mjs";
|
|
4
|
+
import { t as memory } from "./memory-DR1ibF_s.mjs";
|
|
5
5
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
6
6
|
|
|
7
7
|
//#region src/app/types.d.ts
|
|
@@ -154,9 +154,18 @@ type GameDefinition<Plugins extends readonly AnyPluginInstance[] = readonly [],
|
|
|
154
154
|
session: Session; /** The long side the layout needs, in reference units. Default 1920. */
|
|
155
155
|
referenceLong?: number; /** The rng seed of a new save. Default 42. */
|
|
156
156
|
seed?: number; /** The shared layer: composed first among the features. */
|
|
157
|
-
shared?: FeaturePlugin;
|
|
157
|
+
shared?: FeaturePlugin;
|
|
158
|
+
/**
|
|
159
|
+
* The features of the screen app, in order, after `shared`. The headless app composes their
|
|
160
|
+
* logic too, unless `headless.features` names its own list.
|
|
161
|
+
*/
|
|
158
162
|
features?: readonly FeaturePlugin[]; /** The game's own plugins, after the features. Their APIs land on `screen().app`. */
|
|
159
|
-
plugins?: Plugins;
|
|
163
|
+
plugins?: Plugins;
|
|
164
|
+
/**
|
|
165
|
+
* What the headless app composes: the logic of these features and these plugins, nothing else.
|
|
166
|
+
* `headless.features` left out is the definition's `features`; `headless: { features: [] }`
|
|
167
|
+
* composes no feature. `shared` and `plugins` are never taken by default.
|
|
168
|
+
*/
|
|
160
169
|
headless?: {
|
|
161
170
|
features?: readonly FeaturePlugin[];
|
|
162
171
|
plugins?: HeadlessPlugins;
|
|
@@ -360,8 +369,9 @@ type ScreenSeams<Player, Session, Source, Provider> = HeadlessSeams<Player, Sess
|
|
|
360
369
|
type GameApp<Plugins extends readonly AnyPluginInstance[] = readonly [], HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json> = {
|
|
361
370
|
/**
|
|
362
371
|
* Makes the logic app: the core plugins, the logic of `headless.features` and the
|
|
363
|
-
* `headless.plugins`.
|
|
364
|
-
*
|
|
372
|
+
* `headless.plugins`. A definition without `headless.features` gives the logic of its
|
|
373
|
+
* `features`. No screen plugin, no `shared`, no game plugin unless listed. Each call gives a
|
|
374
|
+
* fresh app, clock and provider.
|
|
365
375
|
*
|
|
366
376
|
* @param seams - What the test pins: seed, clock, provider, player, session.
|
|
367
377
|
* @returns The app, not started, and its clock and provider.
|
package/dist/visual.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { A as Api$1, h as Api, ln as Api$3, qt as Api$2 } from "./types-
|
|
2
|
-
import { r as UiApi, x as AnimApi } from "./types-
|
|
3
|
-
import { A as InputApi } from "./types-
|
|
4
|
-
import { r as Repro } from "./headless-
|
|
5
|
-
import { a as InputOf, r as ControlApp } from "./types-
|
|
6
|
-
import { t as commands } from "./commands-
|
|
1
|
+
import { A as Api$1, h as Api, ln as Api$3, qt as Api$2 } from "./types-BT9S_Lo2.mjs";
|
|
2
|
+
import { r as UiApi, x as AnimApi } from "./types-kNiV7iFj.mjs";
|
|
3
|
+
import { A as InputApi } from "./types-BZa187kz.mjs";
|
|
4
|
+
import { r as Repro } from "./headless-CrhefcLu.mjs";
|
|
5
|
+
import { a as InputOf, r as ControlApp } from "./types-Bc9AvZVO.mjs";
|
|
6
|
+
import { t as commands } from "./commands-d5Zlh9hf.mjs";
|
|
7
7
|
|
|
8
8
|
//#region src/visual/types.d.ts
|
|
9
9
|
/** The base commands of the `/control` door, keyed by their short names. */
|
package/dist/visual.mjs
CHANGED
|
@@ -576,17 +576,18 @@ function stateOf(app) {
|
|
|
576
576
|
};
|
|
577
577
|
}
|
|
578
578
|
/**
|
|
579
|
-
* The ui tree without the words of its texts, at every depth.
|
|
579
|
+
* The ui tree without the words of its texts and without their style keys, at every depth.
|
|
580
580
|
*
|
|
581
581
|
* @param node - A node of the ui source.
|
|
582
|
-
* @returns The node and its children, none with `content`.
|
|
582
|
+
* @returns The node and its children, none with `content` or `textStyle`.
|
|
583
583
|
*/
|
|
584
|
-
function
|
|
584
|
+
function withoutText(node) {
|
|
585
585
|
const copy = {
|
|
586
586
|
...node,
|
|
587
|
-
children: node.children.map((child) =>
|
|
587
|
+
children: node.children.map((child) => withoutText(child))
|
|
588
588
|
};
|
|
589
589
|
delete copy.content;
|
|
590
|
+
delete copy.textStyle;
|
|
590
591
|
return copy;
|
|
591
592
|
}
|
|
592
593
|
/**
|
|
@@ -609,7 +610,7 @@ function describeOf(app) {
|
|
|
609
610
|
});
|
|
610
611
|
}
|
|
611
612
|
return {
|
|
612
|
-
ui:
|
|
613
|
+
ui: withoutText(read(app, sources.ui)),
|
|
613
614
|
views: views.toSorted(byAddress)
|
|
614
615
|
};
|
|
615
616
|
}
|
package/llms.txt
CHANGED
|
@@ -43,6 +43,7 @@ Root exports:
|
|
|
43
43
|
- Transit node: has `run`, no `rest`. Rest node: `rest: true`. The graph waits there. Without `run` it is a pure wait.
|
|
44
44
|
- A pure wait ends in two ways. A gate answer whose `intent` is an outcome name: the answer payload becomes the outcome payload. Or a world event whose `type` is listed in the node's `inbox`: the event payload becomes the outcome payload.
|
|
45
45
|
- Transition: rest node to rest node. It is a transaction. On an error the runner discards, rolls back to the last rest point, retries `retries` times, then enters `safeNode`. A failure inside `safeNode` rejects `run()`.
|
|
46
|
+
- Three ways to run an effect in a node. `await fx(descriptor)`: the node waits for it. `fx.start(descriptor)`: it starts now and the node goes on; write it in place of `void fx(...)`. A failure of a started effect is the log entry `flow:fx-start-failed` and never fails the node; a descriptor with `answers` and a `guide` are refused, they have to be awaited. `fx.emit(hint(...))`: released after the commit of the edge, dropped in fast mode.
|
|
46
47
|
- Every failure is written into the log by the engine: an `error` entry `flow:error` with `{ path, rolledBackTo, retry, error: { message, stack } }`. Read it with `app.log.trace()`. A game needs no plugin for it.
|
|
47
48
|
- `recover` on a rest node: `recover: async ({ player, session, rng, fx, signal, now }) => { ... }`. It runs only when the runner gave up on a transition and enters this node as `safeNode`, before the node is entered. The rollback left the state of the failed rest point; `recover` writes the drafts so the safe node can draw itself. What it wrote commits as one edge with `outcome: "recover"` and becomes the rest point. A `recover` that throws rejects `run()`. A retry does not run it. So the safe node stays a pure wait: no `run` body, no extra outcome, no self edge.
|
|
48
49
|
- Entering a rest node calls `model.store.markRest()`: the save provider gets the patches. Leaving a `barrier` node calls `markBarrier(txId)`: a durable write, and rollback cannot cross it.
|
|
@@ -121,7 +122,7 @@ type NodeContext = {
|
|
|
121
122
|
player: Player; // draft of the saved player tree, mutate it
|
|
122
123
|
session: Session; // draft of the session tree, never saved
|
|
123
124
|
rng: RngView; // rng.stream(id) gives a deterministic stream
|
|
124
|
-
fx: NodeFx; // await fx(descriptor), fx.emit(hint(...))
|
|
125
|
+
fx: NodeFx; // await fx(descriptor), fx.start(descriptor), fx.emit(hint(...))
|
|
125
126
|
out: Out; // one method per declared outcome
|
|
126
127
|
signal: AbortSignal; // aborted on stop, restore and inbox delivery
|
|
127
128
|
now: number; // clock.now() read once at node entry
|
|
@@ -260,9 +261,9 @@ The game never calls `flow.run()`. The page runs the graph after `app.start()`;
|
|
|
260
261
|
|
|
261
262
|
A game is a folder: `index.ts` (`export default defineGameApp({ ... })`), `config.ts` (plain data, `satisfies GameConfig`), `core/`, `shared/`, `features/`, `plugins/`, `generated/`, `tests/` with `tests/scenarios/<name>.ts`. No `web/`, no `native.ts`, no `platform-bridge.ts`, no `bunfig.toml`, no `createApp` call. `.moku/` holds what `moku-game` writes; the game's `.gitignore` lists it. Scripts of the game's `package.json`: `"dev": "moku-game dev"`, `"build": "moku-game build"`, `"native:ios-sim": "moku-game native build ios --simulator"`.
|
|
262
263
|
|
|
263
|
-
- `defineGameApp(definition)` returns the game, `{ headless(seams?), screen(seams?) }`, and creates no app. Every type comes from the object; no generic at the call site. Keys: `flow` (required), `safeNode` (default: the start of the main flow), `player` and `session` (required), `referenceLong` (1920), `seed` (42), `shared` (composed first among the features), `features`, `plugins` (their APIs land on `screen().app`), `headless: { features?, plugins? }`, `pluginConfigs`.
|
|
264
|
+
- `defineGameApp(definition)` returns the game, `{ headless(seams?), screen(seams?) }`, and creates no app. Every type comes from the object; no generic at the call site. Keys: `flow` (required), `safeNode` (default: the start of the main flow), `player` and `session` (required), `referenceLong` (1920), `seed` (42), `shared` (composed first among the features), `features`, `plugins` (their APIs land on `screen().app`), `headless: { features?, plugins? }` (`features` left out takes the game's `features`, `[]` means none, `shared` is not composed), `pluginConfigs`.
|
|
264
265
|
- `pluginConfigs` takes every plugin of the screen app but the keys the shell writes. A seam-owned key is a compile error: `model.playerProvider`, `initialPlayer`, `initialSession`, `seed` (the game keeps `schemaVersion`, `migrations`); all of `clock` and `platform`; `flow.mainFlow`, `safeNode`; `renderer.mount`; `assets.manifest`, `io`; `audio.context`.
|
|
265
|
-
- `game.headless(seams?)` composes the core plugins, `logicOnly` of `headless.features
|
|
266
|
+
- `game.headless(seams?)` composes the core plugins, `logicOnly` of `headless.features` (of `features` when left out), then `headless.plugins`. `game.screen(seams?)` composes the screen set, `audio`, `effects`, `platform`, then `shared`, the features and the game's plugins. Both return `{ app, clock, provider }`, the app not started; each call is fresh. Defaults: `fakeClock(startMoment)` (`startMoment` is `1_000_000`), a fresh `memory()` save, the seed of the definition or 42.
|
|
266
267
|
- Seams of both: `seed`, `clock`, `provider`, `player`, `session`. Screen only: `manifest` (a URL or the parsed file), `io`, `platform`, `keepAwake` (false), `audio: { context?, journal? }`, `renderer: { mount?, loadPixi?, preference? }`. Without a mount the renderer is inert, so `game.screen()` runs in plain Bun.
|
|
267
268
|
- `defineGameApp` throws `[game] defineGameApp needs flow.` and `[game] defineGameApp: features[2] is not a feature.` for what TypeScript cannot stop.
|
|
268
269
|
- A game's exit plugin calls `ctx.require(platformPlugin).exit()` itself.
|
|
@@ -479,7 +480,11 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
|
|
|
479
480
|
- `features.register(name, description)`, `features.all()`, `features.contributions(slotName)`.
|
|
480
481
|
- `FlowState` is `{ running, path, stack, pending: { fx?, gate? }, mode }`.
|
|
481
482
|
- `Bookmark` is `{ path, input, player, session, rng, graph, scene?, rest? }`. Serialisable. `scene` is written by the `game.bookmark` door and read by `game.restore`; `flow.restore` ignores it. `path` names a rest node, or the transit node that waits at the gate for an effect that takes answers (a `popup`, or a hand-written descriptor with `answers`); `rest: { path, input }` then names the rest point before it (left out when the graph started at that node or a restore entered it), and the state is the one committed when that node was entered. `restore` needs a running graph. A rest checkpoint is always accepted. Any other node is accepted only when the graph did not change since the bookmark; with another graph a transit bookmark is entered at `rest` instead when that is a checkpoint, and the log gets `flow:restore-fell-back`.
|
|
482
|
-
- After a
|
|
483
|
+
- After a rollback (a node failed) a view of a projection that the node moved and that still stands off its rest pose is settled, also when its item did not change. Ui elements are not settled by a rollback, and a timeline the failed node started without `await` runs on.
|
|
484
|
+
- A loop adds its sway on top of the value under it. The world compares that value with the rest pose, so a looping view that stands at home is never settled, and one a node moved is brought home while its loop runs on. A custom tween driver may report what its additive tracks add with the optional `offsetsOf(entity, component)`; without it the stored value is compared as it stands.
|
|
485
|
+
- The `loop` hook of a view runs while the world is live and `anim` is installed: a view that entered on a load, a mount or a restore gets its loop from the next live frame. `anim.finishAll()` ends it; the next flush of the world (a load, a restore, a commit in a paused or fast world) owes it again.
|
|
486
|
+
- A projection may build a new item object on every commit. A view whose components did not change keeps its running motions; only a real component change cancels them. No frozen item tables are needed.
|
|
487
|
+
- After a `restore` or a load the picture is the one of the restored state, whatever played before: every view of a mounted projection stands on its rest pose, its `loop` starts again on the next live frame and no other motion runs, and every ui root is built again through the mount path, so `enter` hooks run and a loop starts as after a fresh mount. Scroll offsets, local state, hover and focus start anew. Look an element up again with `ui.find(key)` after a restore. A rest node names the scene it is shown on (`scene: "splash"`): a restore into a node without `scene` keeps the mounted scene.
|
|
483
488
|
- `restore` of a rest node resolves when the node is entered. `restore` of a transit node runs it again from its start, so its effects before the gate play again, and resolves once its gate is open. The open gate is not the draw: the popup shows on the next frame. `restore` also resolves when the loop ends before the bookmark was entered, after a stop or a fatal error, and nothing is entered then.
|
|
484
489
|
- Events: `flow:edge` `{ flow, node, outcome, payload, next, patches: { doc, session }, index, now }` after the commit of an edge. `flow:rest` `{ path, checkpoint }`. `flow:error` `{ path, error, rolledBackTo, retry }`.
|
|
485
490
|
- Config: `mainFlow` default `undefined` and required before `run()`, `safeNode: string | undefined` default `undefined` which means the main flow's `start`, `retries` default `1`, `settleTimeoutMs` default `2000`, `journalLimit` default `500`, `holdMs` default `0`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moku-labs/game",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.11",
|
|
4
4
|
"description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -155,11 +155,11 @@
|
|
|
155
155
|
"@arethetypeswrong/core": "0.18.3",
|
|
156
156
|
"@biomejs/biome": "2.4.16",
|
|
157
157
|
"@formatjs/icu-messageformat-parser": "3.5.20",
|
|
158
|
-
"@moku-labs/ci": "1.5.
|
|
159
|
-
"@moku-labs/common": "0.3.
|
|
160
|
-
"@moku-labs/core": "1.7.
|
|
161
|
-
"@moku-labs/native": "0.3.
|
|
162
|
-
"@moku-labs/system": "0.3.
|
|
158
|
+
"@moku-labs/ci": "1.5.7",
|
|
159
|
+
"@moku-labs/common": "0.3.10",
|
|
160
|
+
"@moku-labs/core": "1.7.6",
|
|
161
|
+
"@moku-labs/native": "0.3.8",
|
|
162
|
+
"@moku-labs/system": "0.3.7",
|
|
163
163
|
"@types/bun": "1.4.2",
|
|
164
164
|
"@vitest/coverage-istanbul": "4.0.18",
|
|
165
165
|
"eslint": "9.39.3",
|