@moku-labs/game 0.15.7 → 0.15.8
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 +2 -2
- package/dist/app.d.mts +2 -2
- package/dist/app.mjs +1 -1
- package/dist/{commands-C8aWbThW.d.mts → commands-DWyII1jn.d.mts} +10 -2
- package/dist/control.d.mts +1 -1
- package/dist/control.mjs +1 -1
- package/dist/{index-CU4Oc2Qv.d.mts → index-B3dfnsUZ.d.mts} +6 -5
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/inspect.d.mts +2 -2
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{run-DlxDjgOO.mjs → run-BqYmcNo7.mjs} +38 -5
- package/dist/{runtime-Bm_guAJR.d.mts → runtime-gnMApclv.d.mts} +1 -1
- package/dist/{src-nJYt0LMr.mjs → src-DNVAJHwd.mjs} +905 -173
- package/dist/testing.d.mts +2 -2
- package/dist/testing.mjs +1 -1
- package/dist/{types-MocH_igX.d.mts → types-BcEtn9XV.d.mts} +3 -3
- package/dist/{types-BkcgFOoB.d.mts → types-CPxx3NSG.d.mts} +58 -6
- package/dist/{types-CZGuMhV8.d.mts → types-CYoGvBkh.d.mts} +113 -5
- package/dist/{visual-BV2A3kpr.d.mts → visual-CxQ4Iqd3.d.mts} +3 -3
- package/dist/visual.d.mts +1 -1
- package/dist/visual.mjs +1 -1
- package/llms.txt +4 -4
- package/package.json +1 -1
package/dist/testing.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Cr as Json, Yn as FeaturePlugin, Zn as AnyFlow, _r as FakeClock, dr as Hint, fr as NodeFx, hr as ClockSource, rr as NodeTable, sr as Descriptor } from "./types-C4D9UVgb.mjs";
|
|
2
|
-
import { t as createApp } from "./index-
|
|
3
|
-
import { b as saveOf, v as WithGameState, y as memory } from "./types-
|
|
2
|
+
import { t as createApp } from "./index-B3dfnsUZ.mjs";
|
|
3
|
+
import { b as saveOf, v as WithGameState, y as memory } from "./types-BcEtn9XV.mjs";
|
|
4
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-DobgwTKK.mjs";
|
|
5
5
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
6
6
|
|
package/dist/testing.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { an as
|
|
1
|
+
import { an as refuseStart, ln as type, m as flattenFeatures, on as exit, t as createApp } from "./src-DNVAJHwd.mjs";
|
|
2
2
|
import { n as saveOf, t as memory } from "./memory-CWlqhS7l.mjs";
|
|
3
3
|
import { t as fakeClock } from "./fake-6mazmHK3.mjs";
|
|
4
4
|
import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BA6ffzqD.mjs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { A as PixiModule, Ar as SaveDoc, Cr as Json, D as Config$2, Fn as Config$1, Nr as SnapshotOf, Or as PlayerStateProvider, Yn as FeaturePlugin, Zn as AnyFlow, _r as FakeClock, a as Config$3, d as Manifest, hr as ClockSource, jt as Orientation, kr as ProviderCall, n as AssetsIo, or as TypeTag, xr as Config } from "./types-C4D9UVgb.mjs";
|
|
2
|
-
import { o as AudioContextLike, s as Config$4 } from "./types-
|
|
3
|
-
import { H as
|
|
4
|
-
import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-
|
|
2
|
+
import { o as AudioContextLike, s as Config$4 } from "./types-CPxx3NSG.mjs";
|
|
3
|
+
import { H as StringTable, L as I18nApi, U as TextKey } from "./types-CYoGvBkh.mjs";
|
|
4
|
+
import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-B3dfnsUZ.mjs";
|
|
5
5
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
6
6
|
|
|
7
7
|
//#region src/plugins/model/store/providers/memory.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $r as Require, A as PixiModule, An as TrackOptions, At as FilterSlotInputs, Cr as Json, E as Api$4, En as NumericFields, M as PixiTexture, Mt as Point, Nn as Api$2, Pt as FilterCache, Qr as Events$2, Qt as Api$3, Sn as Ease, Sr as Events$3, Tn as MotionHandle, Ur as Api$1, an as AnyComponentValue, br as Api$6, cn as ComponentType, j as PixiSprite, jn as TrackSegment, k as PixiFilter, kn as ReducedMotion, kt as FilterSlot, on as ComponentHandle, or as TypeTag, rn as AnyComponent, s as Events$1, sr as Descriptor, t as Api$5, un as Entity, v as Events$4, zr as Events$5 } from "./types-C4D9UVgb.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { C as TextApi, L as I18nApi, M as InputApi, Y as StylesState, c as UiNode, i as Finding, n as ElementChange, o as JsxState, r as ElementMotion } from "./types-CYoGvBkh.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";
|
|
@@ -2809,8 +2809,36 @@ type MusicRequest = {
|
|
|
2809
2809
|
};
|
|
2810
2810
|
//#endregion
|
|
2811
2811
|
//#region src/plugins/ui/layout/types.d.ts
|
|
2812
|
+
/**
|
|
2813
|
+
* The patches the scroll step writes with, one object each, filled in place: a frame builds none.
|
|
2814
|
+
* Every number in them is a whole reference unit.
|
|
2815
|
+
*/
|
|
2816
|
+
type ScrollPatches = {
|
|
2817
|
+
scroll: {
|
|
2818
|
+
offset: number;
|
|
2819
|
+
min: number;
|
|
2820
|
+
};
|
|
2821
|
+
state: {
|
|
2822
|
+
offset: number;
|
|
2823
|
+
max: number;
|
|
2824
|
+
moving: boolean;
|
|
2825
|
+
};
|
|
2826
|
+
scrollable: {
|
|
2827
|
+
moving: boolean;
|
|
2828
|
+
};
|
|
2829
|
+
x: {
|
|
2830
|
+
x: number;
|
|
2831
|
+
};
|
|
2832
|
+
y: {
|
|
2833
|
+
y: number;
|
|
2834
|
+
};
|
|
2835
|
+
};
|
|
2812
2836
|
/**
|
|
2813
2837
|
* layout module state. `nodes` is attach minus detach: Yoga 3.2.1 has no instance counter.
|
|
2838
|
+
* `scrolling` is the scroll the finger holds; `scrollClock` the ms the scroll step has run, the
|
|
2839
|
+
* clock of the samples; `scrollRing` the last pointer samples of the press on its axis, a
|
|
2840
|
+
* position and a time each, `scrollHead` the slot the next one takes and `scrollSamples` how
|
|
2841
|
+
* many it holds; `scrollMoved` when the finger last moved.
|
|
2814
2842
|
*/
|
|
2815
2843
|
type LayoutState = {
|
|
2816
2844
|
yoga: Yoga | undefined;
|
|
@@ -2819,10 +2847,12 @@ type LayoutState = {
|
|
|
2819
2847
|
measured: number;
|
|
2820
2848
|
solves: number;
|
|
2821
2849
|
scrolling: Entity | undefined;
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2850
|
+
scrollClock: number;
|
|
2851
|
+
scrollRing: Float64Array;
|
|
2852
|
+
scrollHead: number;
|
|
2853
|
+
scrollSamples: number;
|
|
2854
|
+
scrollMoved: number;
|
|
2855
|
+
scrollPatches: ScrollPatches; /** One line of the `body` style as the icons were last measured with it; none before that. */
|
|
2826
2856
|
iconLine: number | undefined;
|
|
2827
2857
|
order: number;
|
|
2828
2858
|
cleanups: Array<() => void>;
|
|
@@ -2960,7 +2990,7 @@ type UiCtx = KernelSlice & {
|
|
|
2960
2990
|
/**
|
|
2961
2991
|
* ui plugin API, `app.ui`. The screen is one more projection, so three members read it the way a
|
|
2962
2992
|
* test reads the board: a snapshot, a key lookup and a list of findings; the fourth types into a
|
|
2963
|
-
* text field
|
|
2993
|
+
* text field, the fifth scrolls a `scroll`.
|
|
2964
2994
|
*
|
|
2965
2995
|
* @example
|
|
2966
2996
|
* ```ts
|
|
@@ -3043,6 +3073,28 @@ type UiApi = {
|
|
|
3043
3073
|
* ```
|
|
3044
3074
|
*/
|
|
3045
3075
|
fill(key: string, value: string): boolean;
|
|
3076
|
+
/**
|
|
3077
|
+
* Scrolls a `scroll` element to an offset from its start, at once: the offset is kept inside 0
|
|
3078
|
+
* to `max`, a fling or a glide stops, and `ScrollState` reads the new offset with
|
|
3079
|
+
* `moving: false`. A finger that holds the scroll goes on from there. Works headless, for a
|
|
3080
|
+
* test, an agent or a visual test (the `game.scroll` command, the step `{ scroll: { key, to } }`).
|
|
3081
|
+
*
|
|
3082
|
+
* @param key - The `key` prop of the `scroll` element.
|
|
3083
|
+
* @param to - How far from the start, in reference units. `NaN` leaves the scroll where it is.
|
|
3084
|
+
* @returns True when a live scroll took it. False in two cases, each with one warning `{ key }`:
|
|
3085
|
+
* `ui:scroll-without-scroll` when no live `scroll` has the key, and `ui:scroll-before-solve`
|
|
3086
|
+
* when the `scroll` came in this frame and its first solve has not placed it yet: call it
|
|
3087
|
+
* again after the frame.
|
|
3088
|
+
* @example
|
|
3089
|
+
* ```ts
|
|
3090
|
+
* // A test shows the order strip on its events: the strip is 306 u over a 696 u track.
|
|
3091
|
+
* app.ui.scroll("strip", 0); // true
|
|
3092
|
+
* app.world.ecs.get(app.ui.find("strip") ?? 0, ScrollState); // { offset: 0, max: 390, moving: false }
|
|
3093
|
+
* app.ui.scroll("strip", 999); // true: it stands on the end, offset 390
|
|
3094
|
+
* app.ui.scroll("nothing", 0); // false, with the warning "ui:scroll-without-scroll"
|
|
3095
|
+
* ```
|
|
3096
|
+
*/
|
|
3097
|
+
scroll(key: string, to: number): boolean;
|
|
3046
3098
|
};
|
|
3047
3099
|
//#endregion
|
|
3048
3100
|
export { sequence as $, GlowValue as A, Target as B, BlurValue as C, FilterComponent as D, FilterArea as E, AnimPlayerValue as F, HAPTIC_KINDS as G, HapticKind as H, Config$3 as I, frames as J, defineAnimation as K, FramesValue as L, OutlineValue as M, UniformDeclaration as N, FilterSpec as O, AnimApi as P, play as Q, MotionKeyframe as R, AlphaValue as S, DisplacementValue as T, SfxDescriptor as U, types_d_exports$3 as V, Step as W, mark as X, haptic as Y, parallel as Z, State$2 as _, AudioApi as a, tween as at, EmitterDefinition as b, MusicDescriptor as c, OverlayApi as ct, State$1 as d, set as et, VolumesFor as f, EffectsStats as g, EffectsApi as h, types_d_exports as i, stagger as it, NoiseValue as j, FilterValueOf as k, MusicOptions as l, types_d_exports$4 as lt, Config$2 as m, State as n, spawn as nt, AudioContextLike as o, use as ot, types_d_exports$1 as p, external as q, UiApi as r, spawned as rt, Config$1 as s, wait as st, Config as t, sfx as tt, SoundEntry as u, types_d_exports$2 as v, ColorMatrixValue as w, EmitterValue as x, EmitterConfig as y, State$3 as z };
|
|
@@ -990,7 +990,12 @@ type State$1 = {
|
|
|
990
990
|
* over and cancels the resting press.
|
|
991
991
|
*/
|
|
992
992
|
pointerId: number | undefined; /** The followed pointer left the tap slop since its press: it started a gesture and keeps the pointer. */
|
|
993
|
-
moved: boolean;
|
|
993
|
+
moved: boolean;
|
|
994
|
+
/**
|
|
995
|
+
* The press belongs to a scroll: it landed on a moving one and stopped it, or it was handed to
|
|
996
|
+
* one past the tap slop. Its release answers nothing and runs no `onTap` listener.
|
|
997
|
+
*/
|
|
998
|
+
caught: boolean; /** The pressed or held entity. */
|
|
994
999
|
entity: Entity | undefined; /** The projection key of `entity`, read once at press time. */
|
|
995
1000
|
key: {
|
|
996
1001
|
projection: string;
|
|
@@ -1870,8 +1875,51 @@ type ImageTexture = string & Brand<"ImageTexture">;
|
|
|
1870
1875
|
type IconName = string & Brand<"IconName">;
|
|
1871
1876
|
/** How an image or an icon fills its rect: inside it, over it (cropped), or stretched. */
|
|
1872
1877
|
type ImageFit = ("contain" | "cover" | "fill") & Brand<"ImageFit">;
|
|
1873
|
-
/**
|
|
1878
|
+
/**
|
|
1879
|
+
* The axis a scroll container moves on: `"y"`, the default, or `"x"`. Fixed when the scroll
|
|
1880
|
+
* spawns: another axis on a later render keeps the first one, with one warning
|
|
1881
|
+
* `ui:scroll-axis-fixed`. A windowed scroll moves on `"y"` only: `"x"` with `rows` throws.
|
|
1882
|
+
*
|
|
1883
|
+
* @example
|
|
1884
|
+
* ```tsx
|
|
1885
|
+
* // The order strip of the board moves sideways; the cards in it lay out in a row.
|
|
1886
|
+
* <scroll key="strip" axis="x" style={{ width: 306, height: 120 }}>
|
|
1887
|
+
* <row key="track" style={{ width: 696, height: 120 }} />
|
|
1888
|
+
* </scroll>;
|
|
1889
|
+
* // ScrollState of the strip: { offset: 0, max: 390, moving: false }
|
|
1890
|
+
* ```
|
|
1891
|
+
*/
|
|
1874
1892
|
type ScrollAxis = ("x" | "y") & Brand<"ScrollAxis">;
|
|
1893
|
+
/**
|
|
1894
|
+
* The stops a scroll comes to rest on, as offsets from the start, kept inside 0 to `max`. A
|
|
1895
|
+
* release glides to the stop nearest to where its velocity carries in 260 ms, on the curve
|
|
1896
|
+
* `1 − (1 − p)³`, in `min(620, 260 + distance × 1.1)` ms; without `snap` it flings.
|
|
1897
|
+
*
|
|
1898
|
+
* @example
|
|
1899
|
+
* ```tsx
|
|
1900
|
+
* // Three stops: the events, the orders, the end. A slow release at offset 300 glides to 318.
|
|
1901
|
+
* <scroll key="strip" axis="x" snap={[0, 318, 390]} style={{ width: 306, height: 120 }}>
|
|
1902
|
+
* <row key="track" style={{ width: 696, height: 120 }} />
|
|
1903
|
+
* </scroll>;
|
|
1904
|
+
* ```
|
|
1905
|
+
*/
|
|
1906
|
+
type ScrollSnap = readonly number[] & Brand<"ScrollSnap">;
|
|
1907
|
+
/**
|
|
1908
|
+
* How far a scroll stands from its start, in reference units, kept inside 0 to `max`; 0 when
|
|
1909
|
+
* left out. The first frame draws it there. A later render with another value glides there on
|
|
1910
|
+
* the curve and in the time of `snap`; the same value changes nothing, so a scroll the finger
|
|
1911
|
+
* moved stays where it was left.
|
|
1912
|
+
*
|
|
1913
|
+
* @example
|
|
1914
|
+
* ```tsx
|
|
1915
|
+
* // The strip opens on the orders; closing the pinned column moves the game to 306.
|
|
1916
|
+
* <scroll key="strip" axis="x" offset={318} snap={[0, 318, 390]} style={{ width: 306, height: 120 }}>
|
|
1917
|
+
* <row key="track" style={{ width: 696, height: 120 }} />
|
|
1918
|
+
* </scroll>;
|
|
1919
|
+
* // first drawn frame: the content stands 318 u left of its rest, the orders in view
|
|
1920
|
+
* ```
|
|
1921
|
+
*/
|
|
1922
|
+
type ScrollOffset = number & Brand<"ScrollOffset">;
|
|
1875
1923
|
/** How many rows a windowed scroll has. */
|
|
1876
1924
|
type ScrollRows = number & Brand<"ScrollRows">;
|
|
1877
1925
|
/** The height of every row, in reference units; above 0. */
|
|
@@ -2041,11 +2089,14 @@ type ScrollWindowForm = {
|
|
|
2041
2089
|
};
|
|
2042
2090
|
/**
|
|
2043
2091
|
* What a scroll container takes: children for a short list, or `rows`, `rowHeight` and `row` for
|
|
2044
|
-
* a long one, whose rows exist only in the window
|
|
2092
|
+
* a long one, whose rows exist only in the window; the `axis` it moves on, the `snap` stops a
|
|
2093
|
+
* release glides to, and the `offset` it stands at.
|
|
2045
2094
|
*
|
|
2046
2095
|
* @example
|
|
2047
2096
|
* ```ts
|
|
2048
2097
|
* const props: ScrollProps = { axis: "y" };
|
|
2098
|
+
* // The order strip: sideways, three stops, opened on the orders.
|
|
2099
|
+
* const strip: ScrollProps = { axis: "x", snap: [0, 318, 390], offset: 318 };
|
|
2049
2100
|
* // 1000 orders of 120 u in a 1600 u list: rows 0 to 18 exist at offset 0.
|
|
2050
2101
|
* const orders: ScrollProps = {
|
|
2051
2102
|
* style: { height: 1600 },
|
|
@@ -2057,6 +2108,8 @@ type ScrollWindowForm = {
|
|
|
2057
2108
|
*/
|
|
2058
2109
|
type ScrollProps = CommonProps & {
|
|
2059
2110
|
axis?: ScrollAxis;
|
|
2111
|
+
snap?: ScrollSnap;
|
|
2112
|
+
offset?: ScrollOffset;
|
|
2060
2113
|
} & (ScrollChildForm | ScrollWindowForm);
|
|
2061
2114
|
/**
|
|
2062
2115
|
* What a text field takes. `local` is required: every keystroke writes that field of the local
|
|
@@ -2200,6 +2253,31 @@ declare const Box: ComponentType<{
|
|
|
2200
2253
|
* and `ui` merges the patch into the nearest component instance.
|
|
2201
2254
|
*/
|
|
2202
2255
|
declare const LocalWrite: ComponentType<LocalWriteValue>;
|
|
2256
|
+
/**
|
|
2257
|
+
* On every scroll container: where it stands and whether it moves, for a game system and the
|
|
2258
|
+
* read door. `ui` writes it in a frame where the offset or `max` changed, and once more with
|
|
2259
|
+
* `moving: false` when it stops; a game reads it and never writes it.
|
|
2260
|
+
*
|
|
2261
|
+
* @example
|
|
2262
|
+
* ```ts
|
|
2263
|
+
* // A board system dims the events of the order strip while it rests on the orders.
|
|
2264
|
+
* export const stripMist = system({
|
|
2265
|
+
* name: "board.stripMist",
|
|
2266
|
+
* phase: "animate",
|
|
2267
|
+
* query: [ScrollState, OrderStrip],
|
|
2268
|
+
* run: entities => {
|
|
2269
|
+
* for (const [entity, scroll] of entities) {
|
|
2270
|
+
* // scroll: { offset: 318, max: 390, moving: false }
|
|
2271
|
+
* }
|
|
2272
|
+
* }
|
|
2273
|
+
* });
|
|
2274
|
+
* ```
|
|
2275
|
+
*/
|
|
2276
|
+
declare const ScrollState: ComponentType<{
|
|
2277
|
+
offset: number;
|
|
2278
|
+
max: number;
|
|
2279
|
+
moving: boolean;
|
|
2280
|
+
}>;
|
|
2203
2281
|
/**
|
|
2204
2282
|
* On the view a player taps five times within three seconds to open the editor's overlay on a debug build: the
|
|
2205
2283
|
* version line of the settings, usually. One helper on top of `app.debug.overlay.open()`; a game that wants another
|
|
@@ -2396,6 +2474,35 @@ type ScrollWindow = {
|
|
|
2396
2474
|
first: number;
|
|
2397
2475
|
last: number;
|
|
2398
2476
|
};
|
|
2477
|
+
/**
|
|
2478
|
+
* How a scroll container moves, kept on its element: the axis it spawned with, what moves it now
|
|
2479
|
+
* (the finger `"held"`, a `"fling"` after a release, a `"glide"` to a stop or to a new `offset`
|
|
2480
|
+
* prop, or nothing, `"still"`), and the numbers of that motion in offsets from the start, 0 to
|
|
2481
|
+
* `max`. `offset` is exact: the components get it in whole units. `velocity` is in units a ms,
|
|
2482
|
+
* positive toward `max`. A glide runs from `from` to `to` over `duration` ms, `elapsed` of them
|
|
2483
|
+
* gone. `pointer` and `start` are the finger on the axis and the offset where the press began.
|
|
2484
|
+
* `seen` is the `offset` prop of the last render, 0 when it is left out; `placed` turns true when
|
|
2485
|
+
* the first solve put the scroll at it. `atProp` is true while the scroll follows that prop: it
|
|
2486
|
+
* stands at it or glides to it, so a new range puts it at the prop again. The first place and a
|
|
2487
|
+
* glide to a new prop set it; a press and `app.ui.scroll` clear it, and a fling or a snap glide
|
|
2488
|
+
* only comes after a press. Plain numbers written in place: a frame allocates nothing.
|
|
2489
|
+
*/
|
|
2490
|
+
type ScrollMotion = {
|
|
2491
|
+
axis: "x" | "y";
|
|
2492
|
+
mode: "still" | "held" | "fling" | "glide";
|
|
2493
|
+
offset: number;
|
|
2494
|
+
max: number;
|
|
2495
|
+
velocity: number;
|
|
2496
|
+
from: number;
|
|
2497
|
+
to: number;
|
|
2498
|
+
duration: number;
|
|
2499
|
+
elapsed: number;
|
|
2500
|
+
pointer: number;
|
|
2501
|
+
start: number;
|
|
2502
|
+
seen: number;
|
|
2503
|
+
placed: boolean;
|
|
2504
|
+
atProp: boolean;
|
|
2505
|
+
};
|
|
2399
2506
|
/**
|
|
2400
2507
|
* What the style of an element was resolved on besides its own node: the epoch of the jsx state,
|
|
2401
2508
|
* and the row height when the element is a row of a windowed scroll. While both stand, and the
|
|
@@ -2415,7 +2522,7 @@ type StyleBasis = {
|
|
|
2415
2522
|
* `change` hook of each of them returned; `warnedOwned` the names of the `components` values the
|
|
2416
2523
|
* element owns that were already logged. A windowed scroll carries its `window` and the one-time
|
|
2417
2524
|
* warnings of its props in `warned`; `scrolledIn` marks an element a range change of a windowed
|
|
2418
|
-
* scroll created: it plays no `enter` hook. A text or an icon that sizes itself keeps in
|
|
2525
|
+
* scroll created: it plays no `enter` hook. A scroll carries its `scroll` motion from its enter. A text or an icon that sizes itself keeps in
|
|
2419
2526
|
* `measuredSize` what its measure function last answered, before Yoga's clamp: a bound text whose
|
|
2420
2527
|
* new string measures the same asks for no solve. `basis` is what `style` was resolved on, set
|
|
2421
2528
|
* once the style is on the layout node and the components are written; an element without one is
|
|
@@ -2469,6 +2576,7 @@ type Element = {
|
|
|
2469
2576
|
warned: Set<string>;
|
|
2470
2577
|
scrolledIn: boolean;
|
|
2471
2578
|
window?: ScrollWindow;
|
|
2579
|
+
scroll?: ScrollMotion;
|
|
2472
2580
|
measuredSize?: {
|
|
2473
2581
|
width: number;
|
|
2474
2582
|
height: number;
|
|
@@ -2794,4 +2902,4 @@ type CommonProps = {
|
|
|
2794
2902
|
components?: ElementComponents;
|
|
2795
2903
|
};
|
|
2796
2904
|
//#endregion
|
|
2797
|
-
export {
|
|
2905
|
+
export { defineComponent as A, Part as B, TextApi as C, TextStyles as D, TextStyleInput as E, Argument as F, types_d_exports$2 as G, StringTable as H, Config$2 as I, Style as J, IsFlags as K, I18nApi as L, InputApi as M, State$1 as N, TextValue as O, types_d_exports$1 as P, IntlKit as R, State as S, TextFormat as T, TextKey as U, State$2 as V, TypedTr as W, WhenFlags as X, StylesState as Y, BindField as _, JsxChild as a, CountdownValue as b, UiNode as c, LocalWrite as d, ScrollState as f, UiIntrinsicElements as g, IntrinsicElementsFor as h, Finding as i, Config$1 as j, types_d_exports as k, Box as l, defineTokens as m, ElementChange as n, JsxState as o, popup as p, ResolvedStyle as q, ElementMotion as r, PopupComponent as s, DescriptionNode as t, DebugTrigger as u, BindOptions as v, TextBind as w, Point as x, Config as y, Message as z };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { E as Api, Lr as Api$3, Qt as Api$1, br as Api$2 } from "./types-C4D9UVgb.mjs";
|
|
2
|
-
import { P as AnimApi, r as UiApi } from "./types-
|
|
3
|
-
import {
|
|
2
|
+
import { P as AnimApi, r as UiApi } from "./types-CPxx3NSG.mjs";
|
|
3
|
+
import { M as InputApi } from "./types-CYoGvBkh.mjs";
|
|
4
4
|
import { r as Repro } from "./headless-DobgwTKK.mjs";
|
|
5
5
|
import { a as InputOf, r as ControlApp } from "./types-Mj75CDO6.mjs";
|
|
6
|
-
import { t as commands } from "./commands-
|
|
6
|
+
import { t as commands } from "./commands-DWyII1jn.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.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-
|
|
1
|
+
import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-CxQ4Iqd3.mjs";
|
|
2
2
|
export { type CheckpointResult, type VisualApp, type VisualOptions, type VisualPage, type VisualRenderer, type VisualReport, type VisualSetup, type VisualStart, type VisualStep, type VisualTest, type VisualTestResult, type VisualTolerance, defineVisualTest, parseVisualArgv, runVisualTests };
|
package/dist/visual.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as read, t as sources } from "./sources-OvHvyeJS.mjs";
|
|
2
|
-
import { n as commands, t as run } from "./run-
|
|
2
|
+
import { n as commands, t as run } from "./run-BqYmcNo7.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createBrandConsole } from "@moku-labs/common/cli";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
package/llms.txt
CHANGED
|
@@ -27,7 +27,7 @@ Root exports:
|
|
|
27
27
|
- Class: `SaveUnreadableError`.
|
|
28
28
|
- Plugin instances: `timePlugin`, `lifecyclePlugin`, `modelPlugin`, `clockPlugin`, `flowPlugin`, `worldPlugin`, `rendererPlugin`, `inputPlugin`, `assetsPlugin`, `scenesPlugin`, `animPlugin`, `i18nPlugin`, `textPlugin`, `uiPlugin`, `audioPlugin`, `effectsPlugin`, `platformPlugin`; the list `screen` = `world`, `renderer`, `input`, `assets`, `scenes`, `anim`, `i18n`, `text`, `ui`. `effects`, `audio` and `platform` are opt-in: `plugins: [...screen, effectsPlugin, audioPlugin, platformPlugin]`, `platform` last.
|
|
29
29
|
- Screen helpers from the root: `component`, `tag`, `resource`, `mut`, `system`, `projection`, `Layer`, `Order`, `Exiting`, `Tree`, `Now`, `Transform`, `Sprite`, `NineSlice`, `Shape`, `Parent`, `Display`, `Overlay`, `HudPart`, `sprite`, `Tappable`, `Pressable`, `Draggable`, `DropTarget`, `Swipeable`, `Traceable`, `Touchable`, `Held`, `Hovered`, `PointerOver`, `Pressed`, `Pointer`, `Traced`, `defineBundles`, `load`, `defineScene`.
|
|
30
|
-
- Interface helpers from the root (V3): `defineAnimation`, `sequence`, `parallel`, `stagger`, `tween`, `set`, `wait`, `mark`, `frames`, `sfx`, `haptic`, `use`, `spawn`, `spawned`, `play`, `external`, `defineMotion`, `Animation`, `Frames`, the resource `AnimPlayer`; `tr`, `messageArgument`, `messageDuration`; `Text`, `label`, `defineTextStyles`, `bind`, `Countdown` and the types `BindOptions`, `CountdownValue`, `TextFormat`; `defineComponent`, `popup`, `defineStyle`, `defineTokens`, `resolve`, `Box`, `LocalWrite`, `DebugTrigger`; `music`.
|
|
30
|
+
- Interface helpers from the root (V3): `defineAnimation`, `sequence`, `parallel`, `stagger`, `tween`, `set`, `wait`, `mark`, `frames`, `sfx`, `haptic`, `use`, `spawn`, `spawned`, `play`, `external`, `defineMotion`, `Animation`, `Frames`, the resource `AnimPlayer`; `tr`, `messageArgument`, `messageDuration`; `Text`, `label`, `defineTextStyles`, `bind`, `Countdown` and the types `BindOptions`, `CountdownValue`, `TextFormat`; `defineComponent`, `popup`, `defineStyle`, `defineTokens`, `resolve`, `Box`, `LocalWrite`, `DebugTrigger`, `ScrollState`; `music`.
|
|
31
31
|
- Effects helpers from the root (V5): `defineEmitter`, `Emitter`, `defineFilter`, `Glow`, `Outline`, `Blur`, `ColorMatrix`, `Noise`, `Displacement`, `Alpha`.
|
|
32
32
|
- Platform from the root (V6): the types `PlatformProvider`, `BackResult`, `HapticKind` and the frozen list `HAPTIC_KINDS`.
|
|
33
33
|
- `defineGame<Types>()` returns, typed by the game's `player`, `session`, `assets`, `bundles`, `scenes`, `strings` and `textStyles`: `defineNode`, `defineFlow`, `defineFeature`, `projection`, `sprite`, `Sprite`, `NineSlice`, `defineBundles`, `load`, `defineScene`, `defineAnimation`, `frames`, `sfx`, `play`, `tr`, `label`, `defineTextStyles`, `defineComponent`, `defineStyle`, `defineTokens`, `popup`, `music`, `Frames`, `defineEmitter`, `Emitter`, `Displacement`, and `system` (its `ctx.snapshot` typed by `player` and `session`), `volumes` (the `audio` config's reader of the typed player), `key` (string keys from data, below). `Types` is `{ player; session; assets: string; bundles?: string; scenes?: string; strings: Record<string, unknown>; textStyles?: string; emitters?: string }`.
|
|
@@ -548,7 +548,7 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
|
|
|
548
548
|
|
|
549
549
|
A screen is a projection whose `view` returns JSX. The game's `tsconfig.json` sets `"jsx": "react-jsx"` and `"jsxImportSource": "@moku-labs/game"`; screens are `.tsx` files. There is no DOM and no React.
|
|
550
550
|
|
|
551
|
-
- Tags: `screen`, `layer`, `row`, `column`, `stack`, `spacer`, `panel` (swallows taps), `image` (`texture`, `fit?: "contain" | "cover" | "fill"`), `icon` (`name`, `fit?`), `text` (`content: string | Message`, or `bind={bind(Counter, "value")}` with `components={[Counter({ value })]}`; formats `int`, `mm:ss`, `h:mm:ss`, `duration`; `Countdown({ until })` with `bind(Countdown, "left", { format: "mm:ss" })` counts down to a `clock` moment; `style` is a text style key), `button` (`intent` + `payload`, or `local`, never both), `scroll` (`axis
|
|
551
|
+
- Tags: `screen`, `layer`, `row`, `column`, `stack`, `spacer`, `panel` (swallows taps), `image` (`texture`, `fit?: "contain" | "cover" | "fill"`), `icon` (`name`, `fit?`), `text` (`content: string | Message`, or `bind={bind(Counter, "value")}` with `components={[Counter({ value })]}`; formats `int`, `mm:ss`, `h:mm:ss`, `duration`; `Countdown({ until })` with `bind(Countdown, "left", { format: "mm:ss" })` counts down to a `clock` moment; `style` is a text style key), `button` (`intent` + `payload`, or `local`, never both), `scroll` (`axis?: "x" | "y"` (`"y"`), fixed at spawn, another one later warns `ui:scroll-axis-fixed`; children for a short list, laid out as a column on `y` and a row on `x`, or the windowed form `rows`, `rowHeight`, `overscan?` (5), `row: index => node`, on `y` only: `axis: "x"` with `rows` throws `[game] A windowed scroll moves on y only.`; row state belongs in the model, a row that leaves the window loses its local state; `snap?: number[]` stops in offsets, kept inside `0..max`: a release glides to the stop nearest `offset + v × 260` (v in units a ms) on `1 − (1 − p)³` over `min(620, 260 + distance × 1.1)` ms; without `snap` a release flings, `v ×= 0.998` a ms until under 0.02, 0 when the finger rested over 50 ms; `offset?: number` (0) where it stands from the start, drawn there in its first frame, a new value glides there, the same value does nothing; hard edges, nothing passes on to a parent scroll; a press on a child that leaves the tap slop along the scroll's axis goes to the scroll and the child answers nothing, a finger on a moving scroll only stops it; every number in `time.delta` ms, so a time scale applies; the public component `ScrollState { offset, max, moving }` on its entity, whole units `0..max`, written when it changes and once with `moving: false` when it stops, read by a system and by `game.explain`; `app.ui.scroll(key, to)` and the visual step `{ scroll: { key, to } }` set it at once), `input` (a text field; its text lives in the component's `local`, see the ui README). Common props: `key`, `style`, `state: { active?, disabled?, selected? }`, `motion`, `components` (extra component values for the element's entity, such as a filter: `components={[Glow({ strength: 2 })]}`, or `DebugTrigger()`: five taps within 3000 ms of wall time on that view call `app.debug.overlay.open()` on a page with the agent flag, nothing elsewhere; put it on a `button` naming nothing); containers take `hosts?: string[]` (projections drawn inside the element: world views hang under it from the slot's first frame; a JSX projection is laid out in the slot's content box). Children draw and are hit in markup order at equal `zIndex`; the roots of a scene draw in the order of its `projections` (the `ui` layer sorts by `Order`; `ui: { sort: "none" }` keeps insertion order). `defineMotion({ when: "change" })` plays its `enter` and `exit` only for a commit or a popup, never when its screen mounts or unmounts; declare a motion at module level (`ui:motion-rebuilt` names a loop rebuilt in a view). Style adds `nineSlice`, `tint`, `offsetX`, `offsetY`, `scale`, `origin`, `fit: "contain"`; state variants `is: { hover, pressed, disabled, active, selected, covered }`. `popup(Component, props, { over })` stacks a popup over a kept one.
|
|
552
552
|
- Style vocabulary (`defineStyle`, plain data): flow `direction`, `wrap`, `justify`, `align`, `alignSelf`, `gap`, `grow`, `shrink`; box `padding`, `margin`, `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight`, `aspect`, `overflow`; position `position`, `left`, `top`, `right`, `bottom`, `reason`; visual `fill`, `stroke`, `strokeWidth`, `radius`, `alpha`; variants `is: { pressed?, disabled?, active?, selected? }` and `when: { portrait?, landscape?, tall?, wide? }`, each a partial style merged in when the flag holds. Lengths are reference units or `"safeArea.top" | "safeArea.right" | "safeArea.bottom" | "safeArea.left"`. No font size here: it comes from the text style.
|
|
553
553
|
- `defineComponent(name, { local?, outcomes?, view(props, local) })`. A component with `outcomes` is a popup: `await fx(popup(Component, props))` opens the gate for its outcomes and resolves with `{ intent, payload }` of the pressed button. A button with `local` writes into the nearest component instance; it never reaches the graph.
|
|
554
554
|
- `tr(key, params?)` returns a frozen `Message` `{ key, params }`; no locale is read at the call site. Strings live in `strings/<locale>.json` of any feature or sub-feature, as ICU MessageFormat 1 (`{n, plural, one {# order} other {# orders}}`, `select`, `selectordinal`, `number`, `date`, `time`; no skeletons, no `currency`). `bun run assets:keys` compiles them. The keys of the top-level features and the layers go into `generated/strings.<locale>.ts`, which one feature registers; a sub-feature's keys start with `<name>.` (`ep01.title`) and go into `generated/strings.<name>.<locale>.ts`, which the sub-feature registers lazily, `strings: { en: () => import("@generated/strings.ep01.en"), ru: () => import("@generated/strings.ep01.ru") }`. With `assets` composed those modules load and unload with the bundle named like the sub-feature: every way the bundle comes (a node entry, a scene's `bundles`, `load`, a restore, the `safeNode`) brings its strings, a locale switch brings them in the new locale, and while the bundle is not loaded a key of it gives `⟦key⟧` and `has(key)` is false. A compiled module calls the root helpers `messageArgument(value, intl)` (an element stays an element part, a list goes through `Intl.ListFormat`, a number through `Intl.NumberFormat`, else text) and `messageDuration(ms)` (`95_000` gives `{ minutes: 1, seconds: 35 }`). `app.i18n`: `locale()`, `setLocale(locale)` (async, emits `i18n:locale-changed`), `format(message, locale?)` returns `Part[]`, `plain(message)` returns a string, `has(key)`, `isKey(value)`, `locales()`, `replace(locale, messages)` (dev hot swap of a generated strings file). Config: `locale: "en"`, `fallback: "en"`, `locales: {}`.
|
|
@@ -556,7 +556,7 @@ A screen is a projection whose `view` returns JSX. The game's `tsconfig.json` se
|
|
|
556
556
|
- Text: `Text` component, `label({ text, style, at, anchor? })` for board text in a projection view, `defineTextStyles({ "hud.digits": { font, size, fill, bold?, italic?, stroke?, strokeWidth?, letterSpacing?, align?, wrap?, digits? } })` under the feature's `textStyles` key. Tags inside a string: `<b>`, `<i>`, `<color=#rrggbb>`, `<icon=key>`, `\<`, `\n`. Built-in styles `body` and `digits` from config `fonts: { body: "ui.font-body", digits: "ui.font-digits" }`. `app.text.measure(content, style)` gives the same numbers in a browser and in Bun. `app.text.replaceStyles(styles)` is the dev hot swap of a saved `defineTextStyles` value.
|
|
557
557
|
- Animation is data: `defineAnimation(id, { slots: { name: type<Anim.Target>() }, build: (slots, { at }) => Step })`. Steps: `sequence(...)`, `parallel(...)`, `stagger(items, ms, item => Step)`, `tween(target, Component, to, { ms, ease?, delayMs?, additive?, space?, reduced? })`, `set(target, Component, patch)`, `wait(ms)`, `mark(name)`, `frames(target, { keys, fps, loop? })`, `sfx(key, { bus? })`, `haptic(kind)`, `use(animation, slots, tools?)`, `spawn` (a temporary entity, flying coins, that the timeline despawns when it ends; see the anim README). `Frames({ keys, fps, loop, playing })` is a component that walks a sprite through its textures for the entity's whole life. A target is `{ projection, key }` or an entity. A timeline resolves each `{ projection, key }` target once, when it starts: every step writes the view that held the key then, also after a commit removed it and while its exit plays, until it despawns; a view spawned again under the same key is not followed, and a key nothing held at the start is looked up at its step. `reduced: "keep"` (on a `tween` step, `transition` and `loop` of `defineMotion`, `TweenOptions`, `RestOptions`) plays a track as written under reduced motion, a fade for one; the default `"cut"` lands it at once or holds a loop on its first key. A node plays it with `await fx(play(animation, slots))`; in fast mode it finishes at once. `defineMotion({ states, transition?, on: { enter?, exit?, change? }, when? })` builds the enter, exit and change hooks for a projection or the `motion` prop of a tag; `when: "change"` plays enter and exit only for a commit, so an element that comes or goes with its screen stands at rest and leaves without a motion (default `"always"`). Events `anim:mark` `{ animation, mark }` and `anim:finished` `{ animation }`. `app.anim.replace(definition)` is the dev hot swap: the next `play` builds the new tree. A system cannot `require` a plugin, so it plays through the resource `AnimPlayer`: `res(AnimPlayer).play(animation, slots)` returns the same handle as `app.anim.play`, and the registered animation of `animation.id` wins, so a hot swap reaches the system. Play on a change, never every frame; from phase `input` the tree moves in `animate` of the same frame. Before `anim` starts it throws `[game] AnimPlayer is not ready: anim has not started.` Config `maxTracks: 2000`.
|
|
558
558
|
- Audio is opt-in: `plugins: [...screen, audioPlugin]`. `fx(sfx("orders.complete"))` resolves when the sound started. `await fx(music("board.theme"))` cross-fades; `music(null)` fades out; a scene's `music` key switches the track on `scenes:changed`. Buses `master`, `music`, `sfx`. Volumes come from the committed player: config `volumes: player => (player as Player).settings.audio`, applied on every `model:committed`. `app.audio`: `setVolume(bus, value)`, `volume(bus)`, `mute(bus, on)`, `muted(bus)`, `unlocked()`, `journal()` (the last started sounds, empty unless config `journal` is above 0). Config: `buses: { master: 1, music: 0.6, sfx: 1 }`, `musicFadeMs: 600`, `volumes`, `context`, `journal: 0`, `music: "decode" | "stream"` default `"decode"` (gapless buffer; `"stream"` is an `<audio>` element, less memory, a gap at every loop), `session: "ambient" | "playback" | "auto"` default `"ambient"` (iOS only: ambient mixes with other apps and obeys the silent switch). The context is asked to run at start: where the shell lets audio play (a native webview with the gesture requirement off) `unlocked()` is true with no touch; a browser that wants a gesture unlocks on the first `pointerdown` or `touchend`. Without `audio` every `sfx` and `music` effect resolves `undefined` at once, so the same node runs in a game with no sound.
|
|
559
|
-
- `app.ui`: `tree()` the live screen as plain data (a text node carries `content`, the words it draws after `tr()` and `bind`; a text field carries `value`), `find(key)` the entity of a keyed element, `fill(key, value)` types into the text field with that `key`, `lint()` tap targets under `tapTargetPt` (default `44`), text that overflows in some locale, absolute elements without `reason`. Config: `tapTargetPt: 44`, `breakpoints: { tall: 2, wide: 1.5 }`. `ui` emits only the dev global event `ui:hot-swap`, see "Hot swap".
|
|
559
|
+
- `app.ui`: `tree()` the live screen as plain data (a text node carries `content`, the words it draws after `tr()` and `bind`; a text field carries `value`), `find(key)` the entity of a keyed element, `fill(key, value)` types into the text field with that `key`, `scroll(key, to)` puts the `scroll` with that `key` at `to` from its start at once, inside `0..max`, its fling or glide stopped (`false` and `ui:scroll-without-scroll` for no live `scroll`, `false` and `ui:scroll-before-solve` for a `scroll` its first solve has not placed yet), `lint()` tap targets under `tapTargetPt` (default `44`), text that overflows in some locale, absolute elements without `reason`. Config: `tapTargetPt: 44`, `breakpoints: { tall: 2, wide: 1.5 }`. `ui` emits only the dev global event `ui:hot-swap`, see "Hot swap".
|
|
560
560
|
|
|
561
561
|
```tsx
|
|
562
562
|
// features/hud/view.tsx
|
|
@@ -654,7 +654,7 @@ A plugin that owns a resource stores it in its state and frees it in `onStop: ({
|
|
|
654
654
|
Two subpaths, neither on the root (V4). An editor, MCP tools and e2e scripts reach a running game through them; `docs/doors.md` in the engine repository has the full catalogue.
|
|
655
655
|
|
|
656
656
|
- `@moku-labs/game/inspect`: `read(app, source, input?)` reads once; `watch(app, source, input, fn)` reads on the `signals` phase when the source changes and returns the stop function; `defineSource`; the frozen catalogue `sources`; the types `Source`, `InputSchema`, `InputOf`. Every source only reads, so the door is safe in a production build. Keys of `sources`: `graph`, `position`, `history`, `tainted`, `cheats`, `model`, `entities`, `projections`, `ui`, `locate`, `render`, `sounds`, `audioMuted`, `effects`, `filters`, `assets`, `log`, `explain`, `diff`, `schema`, `at`; their ids are `game.<key>`. `filters` answers `{ kinds, off }` of `effects.diagnostics`. `locate` takes `{ key }` (a ui element) or `{ target }` (a view by projection key) and answers its box in CSS px, the numbers Playwright clicks. `at` takes `{ x, y }` in CSS px and lists the views under that point, topmost first. `explain` takes `{ entity }` and answers that entity in full, `undefined` for a stale id. `diff` takes `{ from, to }` frames and answers what changed per entity; it throws in a production build and for a frame outside the last 120. `schema` lists every component and tag type the world met, with the JSON kind of each field. `audioMuted` answers whether the master bus is muted. `sounds` throws `[game] The source game.sounds needs audioPlugin.`, `audioMuted` throws `[game] The source game.audioMuted needs audioPlugin.` and `effects` and `filters` throw `[game] The source game.effects needs effectsPlugin.` (`game.filters` for `filters`) when that plugin is not installed; the editor lists such a source as not installed.
|
|
657
|
-
- `@moku-labs/game/control`: `run(app, command, input?)` resolves `{ value, state }`, `state` being `{ path, frame, tainted }`; `defineCommand`; `controlRefused`; `controlReadOnly`; the frozen catalogue `commands`; the types `Command`, `Ran`, `ControlReadOnlyError`. Keys of `commands`: `answer`, `tap`, `drag`, `key`, `fill`, `walk`, `bookmark`, `restore`, `saveExport`, `saveImport`, `step`, `pause`, `resume`, `capture`, `debug`, `reducedMotion`, `timeScale`, `frameRate`, `mute`, `trace`, `filter`. `capture` resolves `{ png }`, and `{ png, legend }` with `legend: true`; `undefined` while the renderer is inert. `timeScale` takes `{ scale }`; 0 freezes game time while the frames keep running. `frameRate` takes `{ fps? }`, calls `time.setRate(fps ?? "auto")` and answers `time.rate()`. `mute` takes `{ muted }`, mutes the master bus and answers the flag. Without `audioPlugin` it throws `[game] The command game.mute needs audioPlugin.` `trace` takes `{ path }`, a list of views by projection key, and answers whether the gate took the answer. `filter` takes `{ kind, on }`, switches the filter kind through `effects.diagnostics` (cosmetic) and answers the off kinds; an unknown kind throws `[game] No filter kind "fx.nope".` naming the known ones; without `effectsPlugin` it throws `[game] The command game.filter needs effectsPlugin.`
|
|
657
|
+
- `@moku-labs/game/control`: `run(app, command, input?)` resolves `{ value, state }`, `state` being `{ path, frame, tainted }`; `defineCommand`; `controlRefused`; `controlReadOnly`; the frozen catalogue `commands`; the types `Command`, `Ran`, `ControlReadOnlyError`. Keys of `commands`: `answer`, `tap`, `drag`, `key`, `fill`, `scroll`, `walk`, `bookmark`, `restore`, `saveExport`, `saveImport`, `step`, `pause`, `resume`, `capture`, `debug`, `reducedMotion`, `timeScale`, `frameRate`, `mute`, `trace`, `filter`. `capture` resolves `{ png }`, and `{ png, legend }` with `legend: true`; `undefined` while the renderer is inert. `timeScale` takes `{ scale }`; 0 freezes game time while the frames keep running. `frameRate` takes `{ fps? }`, calls `time.setRate(fps ?? "auto")` and answers `time.rate()`. `mute` takes `{ muted }`, mutes the master bus and answers the flag. Without `audioPlugin` it throws `[game] The command game.mute needs audioPlugin.` `trace` takes `{ path }`, a list of views by projection key, and answers whether the gate took the answer. `scroll` takes `{ key, to }`, calls `ui.scroll(key, to)` and answers what it answers; a visual test writes it as the step `{ scroll: { key, to } }`. `filter` takes `{ kind, on }`, switches the filter kind through `effects.diagnostics` (cosmetic) and answers the off kinds; an unknown kind throws `[game] No filter kind "fx.nope".` naming the known ones; without `effectsPlugin` it throws `[game] The command game.filter needs effectsPlugin.`
|
|
658
658
|
- Commands run in dev builds and debug builds. `__MOKU_GAME_DEV__ === true` (the dev page, a test's `vi.stubGlobal("__MOKU_GAME_DEV__", true)`) or `__MOKU_GAME_CONTROL__ === true` (`--debug`) runs every command; `__MOKU_GAME_AGENT__ === true` alone (`--debug=read`) runs the `read` and `cosmetic` ones and `run` refuses the others with `controlReadOnly(id)`, an error with `reason: "read-only"`: `[game] The command game.tap changes the game: this debug build runs read and cosmetic commands only.`; no flag: `run` throws `[game] Control commands run in dev builds only.` `moku-game build` defines the three flags `false`; a game without the shell uses a bundler `define` and should add `__MOKU_GAME_AGENT__: "false"` and `__MOKU_GAME_CONTROL__: "false"`. `saveExport` answers the committed `{ player, session, rng }` (read); `saveImport` takes `{ save }` of `saveExport`, restores it at the main flow's start and flushes the store (raw: not on a `--debug=read` page).
|
|
659
659
|
- A command's effect is `read`, `route`, `cosmetic`, `cheat` or `raw`. A `cheat` or `raw` command taints the session and is journaled; read it with `sources.tainted` and `sources.cheats`. Of the base commands `restore` and `saveImport` are `raw`; `capture` with `diff` writes the state too, so a `--debug=read` page refuses it. `bookmark` answers `flow.bookmark()` plus `scene`, the mounted scene; `restore` calls `scenes.expect(scene)` before `flow.restore`, so a bookmark at a popup restores the scene under it, also on a page that shows another scene. A bookmark taken while an effect that takes answers waits names the waiting transit node (`info/show` in the mini game) and carries `rest`; `restore` of it answers once that node's gate is open, with `pending.gate` in its value, and the popup shows on the next frame.
|
|
660
660
|
- A game's own: `defineSource({ id, title, input, changes, read })` with `changes` `"frame"`, `"commit"` or `"edge"`, and `defineCommand({ id, title, input, effect, run, overlay?, value? })`: `overlay: "toggle"` (input exactly `{ on: "boolean" }` and `value: app => boolean`, the switch's state) or `overlay: "button"` (no required input, no value) lists the command in the editor's overlay, which shows only such commands; any other combination is a type error. The id is camelCase words joined by dots, at least two: `dice.rolls`. An input schema maps field names to `"string"`, `"number"`, `"boolean"` or `"json"`; a trailing `?` makes one optional. A command body starts with `if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();`: it runs in dev and is refused on a debug page. A `.dev` command meant for a debug page writes the agent form for a read or cosmetic effect, `if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();`, and the control form (`__MOKU_GAME_CONTROL__` in place of `__MOKU_GAME_AGENT__`) for the others. Keep them in `.dev` modules that only the dev page, a debug page and the tests import: the editor's dev page (`preparePage` with agents) and `moku-game build --debug` import every `**/*.dev.ts`, `moku-game build` none.
|