@moku-labs/game 0.14.11 → 0.14.12
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 +1 -1
- package/dist/assets.d.mts +1 -1
- package/dist/{commands-d5Zlh9hf.d.mts → commands-BaCx2B9j.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-BN15lAWb.d.mts → define-Ce8SVEN-.d.mts} +2 -2
- package/dist/{headless-CrhefcLu.d.mts → headless-CMHNsKjW.d.mts} +1 -1
- package/dist/{index-0GIP3tgm.d.mts → index-CLQ8NOTw.d.mts} +4 -4
- 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-DR1ibF_s.d.mts → memory-pUQ7YZWC.d.mts} +1 -1
- package/dist/{runtime-aLkoeoGj.d.mts → runtime-BCnKn0xf.d.mts} +1 -1
- package/dist/{src-Do697seR.mjs → src-CtC8w3_z.mjs} +331 -45
- package/dist/testing.d.mts +100 -5
- package/dist/testing.mjs +88 -2
- package/dist/{types-DMoi447V.d.mts → types-BX4Ge_j5.d.mts} +2 -2
- package/dist/{types-BT9S_Lo2.d.mts → types-BjTtGSFb.d.mts} +7 -5
- package/dist/{types-Bc9AvZVO.d.mts → types-CAPv9l29.d.mts} +2 -2
- package/dist/{types-BZa187kz.d.mts → types-DOlpeuGx.d.mts} +25 -3
- package/dist/{types-kNiV7iFj.d.mts → types-Y_rCuXtj.d.mts} +4 -3
- package/dist/{types-yiHtzg1Y.d.mts → types-gisvSHoC.d.mts} +4 -4
- package/dist/visual.d.mts +6 -6
- package/llms.txt +4 -3
- package/package.json +1 -1
package/dist/testing.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Dt as AnyFlow,
|
|
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 { Bt as NodeFx, Dt as AnyFlow, Gt as FakeClock, Lt as Descriptor, Nt as NodeTable, Ut as ClockSource, Zt as Json, ht as FeaturePlugin, zt as Hint } from "./types-BjTtGSFb.mjs";
|
|
2
|
+
import { t as createApp } from "./index-CLQ8NOTw.mjs";
|
|
3
|
+
import { n as saveOf, t as memory } from "./memory-pUQ7YZWC.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-CMHNsKjW.mjs";
|
|
5
5
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
6
6
|
|
|
7
7
|
//#region src/plugins/clock/fake.d.ts
|
|
@@ -26,6 +26,101 @@ import { AnyPluginInstance } from "@moku-labs/core";
|
|
|
26
26
|
*/
|
|
27
27
|
declare function fakeClock(start?: number): FakeClock;
|
|
28
28
|
//#endregion
|
|
29
|
+
//#region src/testing/fake-fx.d.ts
|
|
30
|
+
/**
|
|
31
|
+
* What a test tells `fakeFx`.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const options: FakeFxOptions = {
|
|
36
|
+
* answer: descriptor => (descriptor.kind === "popup" ? { intent: "again" } : undefined),
|
|
37
|
+
* path: "board/place"
|
|
38
|
+
* };
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
type FakeFxOptions = {
|
|
42
|
+
/**
|
|
43
|
+
* What an awaited effect resolves with: a value, or a promise of one. Called once for every
|
|
44
|
+
* `await fx(descriptor)`, at the call, with the descriptor itself. Without it every awaited
|
|
45
|
+
* effect resolves `undefined`. A promise that never settles holds the body at its `await`, as a
|
|
46
|
+
* real effect can. A throw and a rejection stand for a handler that fails, and end as they do
|
|
47
|
+
* in the game: the awaited effect rejects, except for a descriptor with `cosmetic: true`
|
|
48
|
+
* (`sfx`, `play`, `haptic`, `music`), which resolves `undefined`. The game also writes the warn
|
|
49
|
+
* entry `flow:fx-cosmetic-failed` there. The fake has no log and writes nothing.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* // The popup is answered, a sound never ends: a body that waits for the sound stays there.
|
|
54
|
+
* fakeFx({
|
|
55
|
+
* answer: descriptor =>
|
|
56
|
+
* descriptor.kind === "sfx" ? new Promise<never>(() => undefined) : { intent: "again" }
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
answer?: (descriptor: Descriptor) => unknown;
|
|
61
|
+
/**
|
|
62
|
+
* The path of the node under test. Only the error of a refused `fx.start` names it. `"test"`
|
|
63
|
+
* by default.
|
|
64
|
+
*/
|
|
65
|
+
path?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* What `fakeFx` returns: the `fx` for the node context, and the three lists it records into.
|
|
69
|
+
* Each list is one array for the whole life of the fake: a list read before the body ran holds
|
|
70
|
+
* the later calls too, and a test starts a list over with `list.length = 0`. The lists hold the
|
|
71
|
+
* objects the body passed, not copies. The holder is frozen, so a list cannot be replaced by
|
|
72
|
+
* another array the fake would not write to.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const { fx, started }: FakeFx = fakeFx();
|
|
77
|
+
*
|
|
78
|
+
* fx.start({ kind: "sfx", payload: { key: "ui.tap" } });
|
|
79
|
+
* started; // [{ kind: "sfx", payload: { key: "ui.tap" } }]
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
type FakeFx = {
|
|
83
|
+
/** The `fx` to put into the node context: `{ ...context, fx: fake.fx }`. */readonly fx: NodeFx; /** Every `await fx(descriptor)` in call order, recorded at the call, answered or not. */
|
|
84
|
+
readonly awaited: Descriptor[]; /** Every `fx.start(descriptor)` in call order. A refused start is not in it. */
|
|
85
|
+
readonly started: Descriptor[]; /** Every `fx.emit(hint)` in call order. */
|
|
86
|
+
readonly emitted: Hint[];
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Builds the `fx` of a node context for a unit test that calls `node.run(context)` itself, with no
|
|
90
|
+
* app and no runner. It records every call and plays no effect: `await fx(descriptor)` goes to
|
|
91
|
+
* `awaited` and resolves with `options.answer(descriptor)`, `fx.start(descriptor)` goes to
|
|
92
|
+
* `started`, `fx.emit(hint)` goes to `emitted`. `fx.start` refuses a descriptor with `answers`
|
|
93
|
+
* and a `guide` with the error the game throws. An `answer` that fails rejects the awaited
|
|
94
|
+
* effect, and resolves `undefined` for a `cosmetic` descriptor, as in the game. There is no
|
|
95
|
+
* handler, no log, no fast mode and no abort signal here: for those, walk the graph with
|
|
96
|
+
* `createHeadless`.
|
|
97
|
+
*
|
|
98
|
+
* @param options - The answer of an awaited effect, and the node path for the refusal message.
|
|
99
|
+
* @returns The `fx` and the lists `awaited`, `started` and `emitted`.
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* // The body of the node "place" runs fx.start(sfx("ui.tap")), then pushes input.cell to
|
|
103
|
+
* // session.marks. `context` is the rest of its context: input, player, session, rng, out,
|
|
104
|
+
* // signal and now.
|
|
105
|
+
* const fx = fakeFx();
|
|
106
|
+
*
|
|
107
|
+
* await place.run?.({ ...context, fx: fx.fx });
|
|
108
|
+
*
|
|
109
|
+
* fx.started; // [{ kind: "sfx", payload: { key: "ui.tap", bus: "sfx" }, cosmetic: true }]
|
|
110
|
+
* fx.awaited; // []: the body waited for no effect
|
|
111
|
+
*
|
|
112
|
+
* // The node "retry" awaits a popup that takes answers. The test gives the player's pick.
|
|
113
|
+
* const picked = fakeFx({
|
|
114
|
+
* answer: descriptor => (descriptor.kind === "popup" ? { intent: "again" } : undefined)
|
|
115
|
+
* });
|
|
116
|
+
*
|
|
117
|
+
* await retry.run?.({ ...context, fx: picked.fx });
|
|
118
|
+
*
|
|
119
|
+
* picked.awaited; // [{ kind: "popup", payload: { name: "Retry" }, answers: ["again", "home"] }]
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
declare function fakeFx(options?: FakeFxOptions): FakeFx;
|
|
123
|
+
//#endregion
|
|
29
124
|
//#region src/testing/isolate.d.ts
|
|
30
125
|
/**
|
|
31
126
|
* A stub for one node or sub-flow of the isolated flow: the outcome it ends with, and its payload.
|
|
@@ -151,4 +246,4 @@ declare function stub<const Name extends string>(outcome: Name, payload?: Json):
|
|
|
151
246
|
*/
|
|
152
247
|
declare function isolate<Nodes extends NodeTable>(feature: FeaturePlugin, options: IsolateOptions<Nodes>): (seams?: IsolateSeams) => IsolatedApp;
|
|
153
248
|
//#endregion
|
|
154
|
-
export { type HeadlessApp, type HeadlessGame, type IsolateOptions, type IsolateSeams, type Repro, type ReproResult, type Stub, type StubsOf, createHeadless, fakeClock, isolate, memory, runRepro, saveOf, stepFrames, stub };
|
|
249
|
+
export { type FakeFx, type FakeFxOptions, type HeadlessApp, type HeadlessGame, type IsolateOptions, type IsolateSeams, type Repro, type ReproResult, type Stub, type StubsOf, createHeadless, fakeClock, fakeFx, isolate, memory, runRepro, saveOf, stepFrames, stub };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,7 +1,93 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ut as refuseStart, Wt as exit, qt as type, t as createApp } from "./src-CtC8w3_z.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";
|
|
5
|
+
//#region src/testing/fake-fx.ts
|
|
6
|
+
/** The node path a refused `fx.start` names when the test gives none. */
|
|
7
|
+
const defaultPath = "test";
|
|
8
|
+
/**
|
|
9
|
+
* Builds the `fx` of a node context for a unit test that calls `node.run(context)` itself, with no
|
|
10
|
+
* app and no runner. It records every call and plays no effect: `await fx(descriptor)` goes to
|
|
11
|
+
* `awaited` and resolves with `options.answer(descriptor)`, `fx.start(descriptor)` goes to
|
|
12
|
+
* `started`, `fx.emit(hint)` goes to `emitted`. `fx.start` refuses a descriptor with `answers`
|
|
13
|
+
* and a `guide` with the error the game throws. An `answer` that fails rejects the awaited
|
|
14
|
+
* effect, and resolves `undefined` for a `cosmetic` descriptor, as in the game. There is no
|
|
15
|
+
* handler, no log, no fast mode and no abort signal here: for those, walk the graph with
|
|
16
|
+
* `createHeadless`.
|
|
17
|
+
*
|
|
18
|
+
* @param options - The answer of an awaited effect, and the node path for the refusal message.
|
|
19
|
+
* @returns The `fx` and the lists `awaited`, `started` and `emitted`.
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // The body of the node "place" runs fx.start(sfx("ui.tap")), then pushes input.cell to
|
|
23
|
+
* // session.marks. `context` is the rest of its context: input, player, session, rng, out,
|
|
24
|
+
* // signal and now.
|
|
25
|
+
* const fx = fakeFx();
|
|
26
|
+
*
|
|
27
|
+
* await place.run?.({ ...context, fx: fx.fx });
|
|
28
|
+
*
|
|
29
|
+
* fx.started; // [{ kind: "sfx", payload: { key: "ui.tap", bus: "sfx" }, cosmetic: true }]
|
|
30
|
+
* fx.awaited; // []: the body waited for no effect
|
|
31
|
+
*
|
|
32
|
+
* // The node "retry" awaits a popup that takes answers. The test gives the player's pick.
|
|
33
|
+
* const picked = fakeFx({
|
|
34
|
+
* answer: descriptor => (descriptor.kind === "popup" ? { intent: "again" } : undefined)
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* await retry.run?.({ ...context, fx: picked.fx });
|
|
38
|
+
*
|
|
39
|
+
* picked.awaited; // [{ kind: "popup", payload: { name: "Retry" }, answers: ["again", "home"] }]
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
function fakeFx(options = {}) {
|
|
43
|
+
const path = options.path ?? defaultPath;
|
|
44
|
+
const awaited = [];
|
|
45
|
+
const started = [];
|
|
46
|
+
const emitted = [];
|
|
47
|
+
/**
|
|
48
|
+
* Records one awaited effect and answers it. Both happen at the call. An `answer` that throws
|
|
49
|
+
* or rejects is a handler that fails: the effect rejects, never the call. A `cosmetic`
|
|
50
|
+
* descriptor swallows that failure and resolves `undefined`, as the gateway of the game does.
|
|
51
|
+
*
|
|
52
|
+
* @param descriptor - The descriptor the body awaits.
|
|
53
|
+
* @returns A promise of what `options.answer` gives for it.
|
|
54
|
+
*/
|
|
55
|
+
const run = async (descriptor) => {
|
|
56
|
+
awaited.push(descriptor);
|
|
57
|
+
try {
|
|
58
|
+
return await options.answer?.(descriptor);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (descriptor.cosmetic !== true) throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const fx = Object.assign(run, {
|
|
64
|
+
/**
|
|
65
|
+
* Records one hint. Nothing releases it: the test has no edge to commit.
|
|
66
|
+
*
|
|
67
|
+
* @param item - The hint.
|
|
68
|
+
*/
|
|
69
|
+
emit: (item) => {
|
|
70
|
+
emitted.push(item);
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Records one started effect, unless the real `fx.start` would refuse it.
|
|
74
|
+
*
|
|
75
|
+
* @param descriptor - The descriptor the body starts.
|
|
76
|
+
* @throws {Error} When the descriptor has `answers` or is a `guide`.
|
|
77
|
+
*/
|
|
78
|
+
start: (descriptor) => {
|
|
79
|
+
refuseStart(descriptor, path);
|
|
80
|
+
started.push(descriptor);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
fx,
|
|
85
|
+
awaited,
|
|
86
|
+
started,
|
|
87
|
+
emitted
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
5
91
|
//#region src/testing/isolate.ts
|
|
6
92
|
const noPayload = null;
|
|
7
93
|
/** Id of the harness main flow. */
|
|
@@ -191,4 +277,4 @@ function isolate(feature, options) {
|
|
|
191
277
|
});
|
|
192
278
|
}
|
|
193
279
|
//#endregion
|
|
194
|
-
export { createHeadless, fakeClock, isolate, memory, runRepro, saveOf, stepFrames, stub };
|
|
280
|
+
export { createHeadless, fakeClock, fakeFx, isolate, memory, runRepro, saveOf, stepFrames, stub };
|
|
@@ -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
|
|
2
|
-
import { x as AnimApi } from "./types-
|
|
1
|
+
import { A as Api$1, B as ComponentType, Cn as Require, D as FilterSlot, H as Entity, O as Point, R as ComponentHandle, Sn as Events$1, _ as PixiFilter, b as PixiTexture, dt as Api, h as Api$2, s as Events, t as Api$3, v as PixiModule, y as PixiSprite } from "./types-BjTtGSFb.mjs";
|
|
2
|
+
import { x as AnimApi } from "./types-Y_rCuXtj.mjs";
|
|
3
3
|
import { Log } from "@moku-labs/common/browser";
|
|
4
4
|
import { PluginCtx } from "@moku-labs/core";
|
|
5
5
|
|
|
@@ -1419,7 +1419,8 @@ type GateApi = {
|
|
|
1419
1419
|
//#region src/plugins/flow/fx/types.d.ts
|
|
1420
1420
|
/**
|
|
1421
1421
|
* An effect a node awaits. With `answers` the runner opens the gate for those intents.
|
|
1422
|
-
* `cosmetic: true`
|
|
1422
|
+
* `cosmetic: true` keeps a handler error away from the node: the effect resolves `undefined` and
|
|
1423
|
+
* the log gets the warn entry `flow:fx-cosmetic-failed` with `{ kind, message }`.
|
|
1423
1424
|
*
|
|
1424
1425
|
* @example
|
|
1425
1426
|
* ```ts
|
|
@@ -1617,9 +1618,10 @@ type NodeFx = ((descriptor: Descriptor) => Promise<unknown>) & {
|
|
|
1617
1618
|
* Starts an effect now and does not wait for it: the handler is called at once and the body goes
|
|
1618
1619
|
* on. Fast mode and the node's signal apply as for `await fx(descriptor)`. A handler that fails
|
|
1619
1620
|
* never fails the node: the log gets the error entry `flow:fx-start-failed` with
|
|
1620
|
-
* `{ path, kind }
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1621
|
+
* `{ path, kind }`. For a `cosmetic` descriptor it gets the warn entry `flow:fx-cosmetic-failed`
|
|
1622
|
+
* with `{ kind, message }` instead, as it does when the effect is awaited. After the abort of the
|
|
1623
|
+
* node nothing is started, and a rejection that comes after the abort is not logged: the handler
|
|
1624
|
+
* was told to stop.
|
|
1623
1625
|
*
|
|
1624
1626
|
* @param descriptor - The effect to start. Not one with `answers` and not a `guide`.
|
|
1625
1627
|
* @throws {Error} When the descriptor has `answers` or is a `guide`: an answer has to be awaited.
|
|
@@ -7489,4 +7491,4 @@ type FlowRest = {
|
|
|
7489
7491
|
*/
|
|
7490
7492
|
type HotSwap = Events$6["ui:hot-swap"];
|
|
7491
7493
|
//#endregion
|
|
7492
|
-
export { DescriptionNode as $,
|
|
7494
|
+
export { DescriptionNode as $, State$5 as $t, Api$2 as A, FlowGraph as At, ComponentType as B, NodeFx as Bt, types_d_exports$1 as C, Require as Cn, slot as Ct, FilterSlot as D, AnyFlow as Dt, DebugSwitches as E, FeatureDescription as Et, AnyComponent as F, SceneIdOf as Ft, Mut as G, FakeClock as Gt, Entity as H, Api$4 as Ht, AnyComponentType as I, TypeTag as It, QueryTerm as J, Api$5 as Jt, Narrowed as K, State$4 as Kt, AnyComponentValue as L, Descriptor as Lt, Explained as M, JournalEntry as Mt, State$2 as N, NodeTable as Nt, Point as O, Bookmark as Ot, types_d_exports$2 as P, RouteStep as Pt, ChangeHook as Q, Root as Qt, ComponentHandle as R, GuideOptions as Rt, Under as S, Events$6 as Sn, exit as St, RenderStats as T, type as Tt, EntitySnapshot as U, ClockSource as Ut, ComponentValue as V, Answer as Vt, FrameDiff as W, Config$4 as Wt, SystemDefinition as X, Events$4 as Xt, ResourceType as Y, Config$5 as Yt, TagType as Z, Json as Zt, PixiFilter as _, Config$7 as _n, Position as _t, Config as a, SaveUnreadableError as an, NumericFields as at, PixiTexture as b, types_d_exports$7 as bn, types_d_exports$3 as bt, LoadBundles as c, RngApi as cn, TrackOptions as ct, State as d, Config$6 as dn, Api$3 as dt, types_d_exports$5 as en, Ease as et, Tier as f, Events$5 as fn, BundlesOf as ft, Config$1 as g, Api$7 as gn, GameTypes as gt, Api$1 as h, types_d_exports$6 as hn, FeaturePlugin as ht, BundleSpec as i, SaveDoc as in, MotionHandle as it, Config$2 as j, FlowState as jt, ViewportSize as k, DefineNode as kt, LoadReason as l, RngState as ln, TrackSegment as lt, types_d_exports as m, State$6 as mn, EmitterIdOf as mt, AssetsIo as n, PlayerStateProvider as nn, LayerSpec as nt, DefineBundles as o, Snapshot as on, ProjectionMotion as ot, Usage as p, PauseReason as pn, Config$3 as pt, Owner as q, types_d_exports$4 as qt, BundleMap as r, ProviderCall as rn, Motion as rt, Events as s, StoreApi as sn, ProjectionSpec as st, Api as t, Patch as tn, LayerSort as tt, Manifest as u, Api$6 as un, ViewHandle as ut, PixiModule as v, State$7 as vn, State$3 as vt, Captured as w, to as wt, State$1 as x, Config$8 as xn, defineFlow as xt, PixiSprite as y, Time as yn, TextStylesOf as yt, ComponentSchema as z, Hint as zt };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as HeadlessApp } from "./headless-
|
|
1
|
+
import { Zt as Json, on as Snapshot } from "./types-BjTtGSFb.mjs";
|
|
2
|
+
import { t as HeadlessApp } from "./headless-CMHNsKjW.mjs";
|
|
3
3
|
import { Log } from "@moku-labs/common/browser";
|
|
4
4
|
|
|
5
5
|
//#region src/plugins/flow/doors/types.d.ts
|
|
@@ -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,
|
|
1
|
+
import { A as Api$2, B as ComponentType, Cn as Require, F as AnyComponent, H as Entity, Ht as Api$5, I as AnyComponentType, L as AnyComponentValue, Lt as Descriptor, O as Point$2, Q as ChangeHook, V as ComponentValue, Zt as Json, at as NumericFields, dt as Api$1, gn as Api, h as Api$3, 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-BjTtGSFb.mjs";
|
|
2
2
|
import { Log } from "@moku-labs/common/browser";
|
|
3
3
|
import { PluginCtx } from "@moku-labs/core";
|
|
4
4
|
|
|
@@ -2461,6 +2461,15 @@ type ScrollWindow = {
|
|
|
2461
2461
|
first: number;
|
|
2462
2462
|
last: number;
|
|
2463
2463
|
};
|
|
2464
|
+
/**
|
|
2465
|
+
* What the style of an element was resolved on besides its own node: the epoch of the jsx state,
|
|
2466
|
+
* and the row height when the element is a row of a windowed scroll. While both stand, and the
|
|
2467
|
+
* raw `style` and the state flags say what they said, the resolved style is kept.
|
|
2468
|
+
*/
|
|
2469
|
+
type StyleBasis = {
|
|
2470
|
+
epoch: number;
|
|
2471
|
+
rowHeight: number | undefined;
|
|
2472
|
+
};
|
|
2464
2473
|
/**
|
|
2465
2474
|
* One live element: the entity, the Yoga node, the resolved style and the place in the tree.
|
|
2466
2475
|
* `rect` is natural: under a `fit` ancestor it is the rect before that ancestor's scale. `fit`
|
|
@@ -2473,7 +2482,9 @@ type ScrollWindow = {
|
|
|
2473
2482
|
* warnings of its props in `warned`; `scrolledIn` marks an element a range change of a windowed
|
|
2474
2483
|
* scroll created: it plays no `enter` hook. A text or an icon that sizes itself keeps in
|
|
2475
2484
|
* `measuredSize` what its measure function last answered, before Yoga's clamp: a bound text whose
|
|
2476
|
-
* new string measures the same asks for no solve.
|
|
2485
|
+
* new string measures the same asks for no solve. `basis` is what `style` was resolved on, set
|
|
2486
|
+
* once the style is on the layout node and the components are written; an element without one is
|
|
2487
|
+
* patched in full at its next patch.
|
|
2477
2488
|
*/
|
|
2478
2489
|
type Element = {
|
|
2479
2490
|
entity: Entity;
|
|
@@ -2519,6 +2530,7 @@ type Element = {
|
|
|
2519
2530
|
width: number;
|
|
2520
2531
|
height: number;
|
|
2521
2532
|
};
|
|
2533
|
+
basis?: StyleBasis;
|
|
2522
2534
|
};
|
|
2523
2535
|
/**
|
|
2524
2536
|
* The popup side of a root: the handler that shows it now and the root it was opened over.
|
|
@@ -2674,7 +2686,8 @@ type TextState = {
|
|
|
2674
2686
|
* jsx module state. `hosts` holds the elements with a `hosts` prop; `hosted` maps a world view to
|
|
2675
2687
|
* the element that hosts it; `fields` holds the text fields by entity. `hints` buffers the hints
|
|
2676
2688
|
* released since the last frame step; `scrolls` holds the scroll containers; `windowRenders`
|
|
2677
|
-
* counts the re-renders of a windowed list its range made.
|
|
2689
|
+
* counts the re-renders of a windowed list its range made. `styled` and `written` count the style
|
|
2690
|
+
* passes and the write passes of the reconcile, for `UiCounters`.
|
|
2678
2691
|
*/
|
|
2679
2692
|
type JsxState = {
|
|
2680
2693
|
components: Map<string, AnyComponentDefinition>;
|
|
@@ -2695,6 +2708,15 @@ type JsxState = {
|
|
|
2695
2708
|
scrolls: Set<Entity>;
|
|
2696
2709
|
windowRenders: number; /** True from a restore or a load until the next reconcile built every root again. */
|
|
2697
2710
|
remount: boolean;
|
|
2711
|
+
/**
|
|
2712
|
+
* Counts what changes every style at once: a new viewport, a dev hot swap. An element last
|
|
2713
|
+
* patched in an earlier epoch is patched in full, however equal its description is. A new
|
|
2714
|
+
* locale and a font that arrives are no epoch: `text` resolves the labels again and the
|
|
2715
|
+
* remeasure step measures them.
|
|
2716
|
+
*/
|
|
2717
|
+
epoch: number;
|
|
2718
|
+
styled: number;
|
|
2719
|
+
written: number;
|
|
2698
2720
|
};
|
|
2699
2721
|
/**
|
|
2700
2722
|
* One node of the snapshot `tree()` answers with. `rect` is natural: under a `fit` ancestor it
|
|
@@ -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
|
|
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, Cn as Require, F as AnyComponent, H as Entity, It as TypeTag, Jt as Api$4, L as AnyComponentValue, Lt as Descriptor, R as ComponentHandle, Sn as Events$5, Xt as Events$2, Zt as Json, at as NumericFields, ct as TrackOptions, dt as Api$1, et as Ease, fn as Events$3, gn as Api, h as Api$5, it as MotionHandle, lt as TrackSegment, nt as LayerSpec, q as Owner, s as Events$4, t as Api$3, tt as LayerSort } from "./types-BjTtGSFb.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-DOlpeuGx.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";
|
|
@@ -1840,7 +1840,8 @@ type LayoutState = {
|
|
|
1840
1840
|
scrollStart: {
|
|
1841
1841
|
pointerY: number;
|
|
1842
1842
|
offset: number;
|
|
1843
|
-
};
|
|
1843
|
+
}; /** One line of the `body` style as the icons were last measured with it; none before that. */
|
|
1844
|
+
iconLine: number | undefined;
|
|
1844
1845
|
order: number;
|
|
1845
1846
|
cleanups: Array<() => void>;
|
|
1846
1847
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Dt as AnyFlow,
|
|
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, Gt as FakeClock, Ut as ClockSource, Yt as Config, Zt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, nn as PlayerStateProvider, pt as Config$1, u as Manifest, v as PixiModule } from "./types-BjTtGSFb.mjs";
|
|
2
|
+
import { o as AudioContextLike, s as Config$4 } from "./types-Y_rCuXtj.mjs";
|
|
3
|
+
import { _t as audioPlugin, ct as PlatformProvider, ht as effectsPlugin, i as screen, ot as platformPlugin, t as createApp } from "./index-CLQ8NOTw.mjs";
|
|
4
|
+
import { t as memory } from "./memory-pUQ7YZWC.mjs";
|
|
5
5
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
6
6
|
|
|
7
7
|
//#region src/app/types.d.ts
|
package/dist/visual.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { A as Api$1,
|
|
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, Jt as Api$2, h as Api, un as Api$3 } from "./types-BjTtGSFb.mjs";
|
|
2
|
+
import { r as UiApi, x as AnimApi } from "./types-Y_rCuXtj.mjs";
|
|
3
|
+
import { A as InputApi } from "./types-DOlpeuGx.mjs";
|
|
4
|
+
import { r as Repro } from "./headless-CMHNsKjW.mjs";
|
|
5
|
+
import { a as InputOf, r as ControlApp } from "./types-CAPv9l29.mjs";
|
|
6
|
+
import { t as commands } from "./commands-BaCx2B9j.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/llms.txt
CHANGED
|
@@ -11,7 +11,7 @@ Facts: Bun only. ESM only. `"sideEffects"` lists the root entry `./dist/index.mj
|
|
|
11
11
|
- `@moku-labs/game/app/page` (the browser): `startPage`, the page the generated `.moku/main.ts` calls.
|
|
12
12
|
- `@moku-labs/game/app/system` (the browser, the native shell): `systemShellOf`, `fromSystem`, `storeSave`, `createSystemApp`, the type `SystemModules`, over the optional peer `@moku-labs/system`. The one entry that names it, in types only: the page hands it the loaders.
|
|
13
13
|
- `@moku-labs/game/cli` (node and bun only): `runCli`, the bin `moku-game`, `preparePage`, the editor's dev page, and `watchKeys`, the watch that keeps `generated/` fresh next to it.
|
|
14
|
-
- `@moku-labs/game/testing`: headless play, repro runs, isolated feature tests (`isolate`, `stub`), fake clock, in-memory save provider. No `node:` import.
|
|
14
|
+
- `@moku-labs/game/testing`: headless play, repro runs, isolated feature tests (`isolate`, `stub`), fake clock, the `fx` of a node for a unit test (`fakeFx`), in-memory save provider. No `node:` import.
|
|
15
15
|
- `@moku-labs/game/visual` (node and bun only): the visual tests `defineVisualTest`, `runVisualTests`, `parseVisualArgv`. The bin runs them: `moku-game visual`.
|
|
16
16
|
- `@moku-labs/game/assets` (node and bun only): the asset key scanner, the production packer and the string compiler. The package bin `moku-game-assets` runs it.
|
|
17
17
|
- `@moku-labs/game/inspect`: the read door of the editor, safe in a production build. See "Doors for the editor".
|
|
@@ -43,7 +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
|
|
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 `cosmetic` descriptor (`sfx`, `play`, `haptic`, `music`) that fails, awaited or started, is the warn entry `flow:fx-cosmetic-failed` with `{ kind, message }` and resolves `undefined`; 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.
|
|
47
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.
|
|
48
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.
|
|
49
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.
|
|
@@ -483,6 +483,7 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
|
|
|
483
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
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
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
|
+
- For a JSX screen ui compares the description prop by prop, by value: inline styles are fine, an equal description costs nothing (no style pass, no layout, no write), hooks stay built once (a function compares by identity). A style object changed in place is not seen: build a new one.
|
|
486
487
|
- 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
488
|
- 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.
|
|
488
489
|
- `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.
|
|
@@ -659,7 +660,7 @@ The scanner keys it `ui.font-body`, which is the `text` config default `fonts.bo
|
|
|
659
660
|
|
|
660
661
|
## Testing entry
|
|
661
662
|
|
|
662
|
-
Exports of `@moku-labs/game/testing`: `createHeadless`, `runRepro`, `stepFrames`, `fakeClock`, `memory`, `saveOf`, `isolate`, `stub`. The visual tests `defineVisualTest`, `runVisualTests`, `parseVisualArgv` come from `@moku-labs/game/visual`, node and bun only (see `docs/testing.md` in the engine repository).
|
|
663
|
+
Exports of `@moku-labs/game/testing`: `createHeadless`, `runRepro`, `stepFrames`, `fakeClock`, `memory`, `saveOf`, `isolate`, `stub`, `fakeFx`. `fakeFx(options?)` is the `fx` of a node context for a unit test that calls `node.run?.({ ...context, fx: fake.fx })`: it returns `{ fx, awaited, started, emitted }`, `options.answer(descriptor)` gives what an awaited effect resolves with, and `fx.start` refuses `answers` and a `guide` as the game does. The visual tests `defineVisualTest`, `runVisualTests`, `parseVisualArgv` come from `@moku-labs/game/visual`, node and bun only (see `docs/testing.md` in the engine repository).
|
|
663
664
|
|
|
664
665
|
- `createHeadless(app): Promise<HeadlessGame>`. Pass an app that is not started. It sets mode `"fast"`, awaits `app.start()`, starts `flow.run()` unless the app's `onStart` already did, and resolves when the loop rests for the first time: `game.state().path` already names the first rest node. A fatal error of `run()` rejects the call.
|
|
665
666
|
- `HeadlessGame`: `walk(route): Promise<FlowState>`, `answer(answer): boolean`, `state()`, `history()`, `stop()`. `walk` and `stop` throw a fatal error of the loop again when the headless runner owns `run()`.
|