@moku-labs/game 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +5 -4
  2. package/dist/assets.d.mts +1 -1
  3. package/dist/{capture-fpd83Mi5.mjs → capture-BX5Gfoty.mjs} +1 -384
  4. package/dist/{commands-mzE9PX0H.d.mts → commands-C1kIfOG5.d.mts} +6 -4
  5. package/dist/control.d.mts +4 -3
  6. package/dist/control.mjs +2 -2
  7. package/dist/{define-BbgXv4cy.d.mts → define-BPpZcpxW.d.mts} +4 -3
  8. package/dist/headless-CvamCUcR.mjs +186 -0
  9. package/dist/headless-q98SwADR.d.mts +181 -0
  10. package/dist/index.d.mts +23 -22
  11. package/dist/index.mjs +17 -6
  12. package/dist/{input-CK4nxBO1.mjs → input-z_35Jwfn.mjs} +2 -2
  13. package/dist/inspect.d.mts +8 -6
  14. package/dist/inspect.mjs +2 -2
  15. package/dist/jsx-dev-runtime.d.mts +1 -1
  16. package/dist/jsx-runtime.d.mts +1 -1
  17. package/dist/project.d.mts +28 -28
  18. package/dist/project.mjs +7 -7
  19. package/dist/registry-DlpRCibU.mjs +384 -0
  20. package/dist/{run-Bcyg32M5.mjs → run-CUPSFM85.mjs} +4 -187
  21. package/dist/{runtime-HTlmRb81.d.mts → runtime-CvDZZg1S.d.mts} +1 -1
  22. package/dist/{sources-DVWJ8nfr.mjs → sources-BBUd15vQ.mjs} +1 -1
  23. package/dist/testing.d.mts +3 -328
  24. package/dist/testing.mjs +2 -2142
  25. package/dist/{types-BMEn0tSC.d.mts → types-BWzCSRM2.d.mts} +12 -2974
  26. package/dist/{types-CYYKjvLn.d.mts → types-CDSKa0vl.d.mts} +5 -182
  27. package/dist/{types-ClF8say0.d.mts → types-DF5kkW-C.d.mts} +5 -4
  28. package/dist/{types-BaUUcIaN.d.mts → types-DgdMLZnV.d.mts} +8 -7
  29. package/dist/{types-CXyipKhE.d.mts → types-DlgsfOmW.d.mts} +10 -9
  30. package/dist/types-ERb2Y9U5.d.mts +2967 -0
  31. package/dist/visual.d.mts +331 -0
  32. package/dist/visual.mjs +2142 -0
  33. package/llms.txt +6 -5
  34. package/package.json +7 -8
@@ -0,0 +1,181 @@
1
+ import { A as Api, C as RouteStep, R as Json, X as RngState, b as FlowState, k as Answer, t as Api$1, x as JournalEntry } from "./types-ERb2Y9U5.mjs";
2
+
3
+ //#region src/plugins/flow/headless.d.ts
4
+ /**
5
+ * The part of an app the headless helpers use. Structural on purpose: any app created with the
6
+ * default plugins fits.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const app: HeadlessApp = createApp({ pluginConfigs: { flow: { mainFlow } } });
11
+ * ```
12
+ */
13
+ type HeadlessApp = {
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ time: Api;
17
+ flow: Api$1;
18
+ };
19
+ /**
20
+ * A game played without a screen: answers go through the gate, effects resolve instantly.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // A test of the dice game: two rolls, no screen, no waiting.
25
+ * const game = await createHeadless(app);
26
+ * const state = await game.walk([{ at: "home", intent: "roll" }, { at: "home", intent: "roll" }]);
27
+ *
28
+ * state.path; // "home"
29
+ * await game.stop();
30
+ * ```
31
+ */
32
+ type HeadlessGame = {
33
+ /**
34
+ * Walks a route through the running loop.
35
+ *
36
+ * @param route - The player's answers and substituted sub-flow results, in order.
37
+ * @returns The state the walk ended in.
38
+ * @throws {Error} When a step is never reached, or the loop failed fatally.
39
+ * @example
40
+ * ```ts
41
+ * // Open the board and tap the generator twice. Every node's logic runs for real.
42
+ * const tap = { at: "board/awaitIntent", intent: "tap", payload: { generatorId: "sawmill" } };
43
+ * const state = await game.walk([{ at: "home", intent: "play" }, tap, tap]);
44
+ *
45
+ * state.path; // "board/awaitIntent"
46
+ * ```
47
+ */
48
+ walk(route: readonly RouteStep[]): Promise<FlowState>;
49
+ /**
50
+ * Gives one answer to the gate, the way a tap does.
51
+ *
52
+ * @param answer - Intent and optional payload.
53
+ * @returns Whether the gate took it.
54
+ * @example
55
+ * ```ts
56
+ * // One tap without a route. The test then waits for the loop by itself.
57
+ * game.answer({ intent: "play" }); // true: "home" rests and lists "play"
58
+ * ```
59
+ */
60
+ answer(answer: Answer): boolean;
61
+ /**
62
+ * Reads where the graph stands.
63
+ *
64
+ * @returns Whether it runs, the path, the stack, what it waits for and the mode.
65
+ * @example
66
+ * ```ts
67
+ * // Right after createHeadless: the graph rests at its first rest node.
68
+ * game.state().path; // "home"
69
+ * game.state().pending; // { gate: ["play"] }
70
+ * ```
71
+ */
72
+ state(): FlowState;
73
+ /**
74
+ * Reads the edges taken since the last checkpoint.
75
+ *
76
+ * @returns A copy of the journal.
77
+ * @example
78
+ * ```ts
79
+ * // After a merge onto an empty cell: the last edge says why the move was refused.
80
+ * const last = game.history().at(-1);
81
+ * // last?.path: "board/merge", last?.outcome: "rejected", last?.payload: { reason: "empty" }
82
+ * ```
83
+ */
84
+ history(): readonly JournalEntry[];
85
+ /**
86
+ * Stops the app, then re-throws a fatal error of the loop.
87
+ *
88
+ * @throws {Error} When the loop failed fatally.
89
+ * @example
90
+ * ```ts
91
+ * // The last line of a headless test. A fatal error of the loop fails the test here.
92
+ * await game.stop();
93
+ * ```
94
+ */
95
+ stop(): Promise<void>;
96
+ };
97
+ /**
98
+ * A reproducible run: a starting state, an optional checkpoint and the route played from it.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const repro: Repro = { player: { coins: 0 }, checkpoint: "home", route: [{ at: "home", intent: "play" }] };
103
+ * ```
104
+ */
105
+ type Repro = {
106
+ player: Json;
107
+ session?: Json;
108
+ rng?: RngState;
109
+ checkpoint?: string;
110
+ route: RouteStep[];
111
+ };
112
+ /**
113
+ * What a repro run ends with: the path where the loop rests and the committed state.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const result: ReproResult = { path: ["home"], player: { coins: 3 }, session: { visits: 1 } };
118
+ * ```
119
+ */
120
+ type ReproResult = {
121
+ path: string[];
122
+ player: Json;
123
+ session: Json;
124
+ };
125
+ /**
126
+ * Starts an app headless: sets flow mode `"fast"`, awaits `app.start()`, starts `flow.run()`
127
+ * unless the app's own `onStart` already did, and waits until the loop rests for the first time.
128
+ * `game.state().path` therefore names the first rest node of the graph as soon as the call
129
+ * resolves. A fatal error of the loop rejects this call, and a later one is re-thrown by `walk`
130
+ * and by `stop`, so a headless test never loses it.
131
+ *
132
+ * @param app - An app that is not started yet.
133
+ * @returns The game: walk it, answer it, read it, stop it.
134
+ * @throws {Error} When the loop failed fatally before it reached its first rest node.
135
+ * @example
136
+ * ```ts
137
+ * // A test starts the merge game without a screen. The call resolves at the first rest node.
138
+ * const game = await createHeadless(app);
139
+ *
140
+ * game.state().path; // "home": the transit node "boot" was played out
141
+ * game.state().mode; // "fast"
142
+ * await game.stop();
143
+ * ```
144
+ */
145
+ declare function createHeadless(app: HeadlessApp): Promise<HeadlessGame>;
146
+ /**
147
+ * Plays a repro: starts the app headless, restores the bookmark built from its state and
148
+ * checkpoint, then walks its route. The app keeps running, so the caller can read more than the
149
+ * result and stops it itself.
150
+ *
151
+ * @param app - An app that is not started yet.
152
+ * @param repro - Starting state, optional checkpoint and the route.
153
+ * @returns The path the run ended at, and the committed state.
154
+ * @example
155
+ * ```ts
156
+ * // A bug report on the dice game: "reset keeps my coins". Replay it from the "home" checkpoint.
157
+ * const reset = { at: "home", intent: "reset" };
158
+ * const result = await runRepro(app, { player: { coins: 7 }, checkpoint: "home", route: [reset] });
159
+ *
160
+ * result.player; // { coins: 0 }; result.path is ["home"]
161
+ * await app.stop(); // the app keeps running after the run
162
+ * ```
163
+ */
164
+ declare function runRepro(app: HeadlessApp, repro: Repro): Promise<ReproResult>;
165
+ /**
166
+ * Steps the frame loop by hand: `count` calls of `app.time.step(deltaMs)`. A headless game has no
167
+ * frame source, so this is the only thing that moves the frame phases.
168
+ *
169
+ * @param app - A started app.
170
+ * @param count - Number of frames.
171
+ * @param deltaMs - Milliseconds per frame.
172
+ * @example
173
+ * ```ts
174
+ * // A headless game has no frame source: the test moves the six frame phases by hand.
175
+ * stepFrames(app, 2, 16);
176
+ * app.time.snapshot(); // frame: 2, delta: 16, elapsed: 32
177
+ * ```
178
+ */
179
+ declare function stepFrames(app: HeadlessApp, count: number, deltaMs: number): void;
180
+ //#endregion
181
+ export { createHeadless as a, ReproResult as i, HeadlessGame as n, runRepro as o, Repro as r, stepFrames as s, HeadlessApp as t };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
- import { $ as Ease, A as Config$8, B as ComponentValue, Bt as State$7, Ct as to, Dt as DefineNode, Ft as GuideOptions, G as Narrowed, Ht as types_d_exports$14, It as Hint, J as ResourceType, Jt as State$5, Kt as Json, L as ComponentHandle, M as State$8, Mt as SceneIdOf, N as types_d_exports$16, O as ViewportSize, Pt as Descriptor, Q as DescriptionNode, Rt as Api$5, S as types_d_exports$11, St as slot, Tt as FeatureDescription, W as Mut, Wt as Config$5, X as TagType, Xt as Patch, Y as SystemDefinition, Yt as types_d_exports$9, _n as Events$1, _t as State$3, a as DefineBundles, an as Api$3, at as ProjectionMotion, b as State$6, bt as defineFlow, c as LoadReason, cn as PauseReason, d as Tier, dn as Api$1, dt as BundlesOf, en as SaveUnreadableError, fn as Config$2, ft as Config$3, gn as Config$9, h as Config$6, hn as types_d_exports$3, ht as GameTypes, i as Config$1, it as NumericFields, k as Api$6, ln as State$4, m as Api$4, mn as State$2, mt as FeaturePlugin, n as BundleMap, nn as StoreApi, on as Config$4, ot as ProjectionSpec, p as types_d_exports$1, pt as EmitterIdOf, q as QueryTerm, qt as Root, r as BundleSpec, rn as RngApi, s as LoadBundles, sn as Events, t as Api, u as State$1, un as types_d_exports$8, ut as Api$2, vn as Require, vt as TextStylesOf, wt as type, xt as exit, yt as types_d_exports$5, z as ComponentType, zt as Config$7 } from "./types-BMEn0tSC.mjs";
2
- import { $ as SpriteValue, A as TextStyleInput, B as types_d_exports$6, C as Config$12, D as TextApi, E as State$11, F as Config$10, G as Display, H as InputApi, I as I18nApi, J as Parent, K as NineSlice, L as Message, M as TextValue, N as types_d_exports$13, O as TextBind, P as Argument, Q as SpriteOptions, R as State$9, S as BindOptions, T as Point, U as State$10, V as Config$11, W as types_d_exports$7, X as ShapeValue, Y as Shape, Z as Sprite, _ as Style, b as defineComponent, d as popup, et as Transform, f as defineTokens, g as ResolvedStyle, h as IsFlags, j as TextStyles, k as TextFormat, l as Box, nt as sprite, p as IntrinsicElementsFor, q as NineSliceValue, s as PopupComponent, tt as TransformValue, u as LocalWrite, w as CountdownValue, x as BindField, y as WhenFlags, z as TypedTr } from "./types-BaUUcIaN.mjs";
3
- import { A as HAPTIC_KINDS, B as sfx, C as MotionKeyframe, D as HapticKind, E as types_d_exports, F as mark, G as use, H as spawned, I as parallel, K as wait, L as play, M as external, N as frames, O as SfxDescriptor, P as haptic, R as sequence, S as FramesValue, T as Target, U as stagger, V as spawn, W as tween, _ as State$15, b as AnimApi, c as AnySceneProjection, d as SceneDefinition, f as SceneSpec, g as Config$15, h as types_d_exports$12, i as MusicOptions, j as defineAnimation, k as Step, l as DefineScene, m as State$14, n as Config$14, o as State$13, p as ScenesApi, r as MusicDescriptor, s as types_d_exports$2, t as AudioApi, u as LayerMap, v as UiApi, w as State$12, x as Config$13, y as types_d_exports$15, z as set } from "./types-CXyipKhE.mjs";
4
- import { _ as NoiseValue, a as types_d_exports$4, c as EmitterValue, d as ColorMatrixValue, f as DisplacementValue, g as GlowValue, h as FilterValueOf, i as State$16, l as AlphaValue, m as FilterSpec, n as EffectsApi, o as EmitterConfig, p as FilterComponent, s as EmitterDefinition, t as Config$16, u as BlurValue, v as OutlineValue, y as UniformDeclaration } from "./types-ClF8say0.mjs";
1
+ import { $ as Events, A as Api$3, B as State$4, D as GuideOptions, E as Descriptor, H as Patch, I as Config$4, J as StoreApi, K as SaveUnreadableError, M as State$5, O as Hint, P as types_d_exports$14, Q as Config$3, R as Json, V as types_d_exports$9, Y as RngApi, Z as Api$2, a as FeaturePlugin, c as State$2, ct as Config$6, d as defineFlow, et as PauseReason, f as exit, g as FeatureDescription, h as type, i as EmitterIdOf, it as Config$1, j as Config$5, l as TextStylesOf, lt as Events$1, m as to, n as BundlesOf, nt as types_d_exports$8, o as GameTypes, ot as State$1, p as slot, r as Config$2, rt as Api, st as types_d_exports$3, t as Api$1, tt as State$3, u as types_d_exports$5, ut as Require, v as DefineNode, w as SceneIdOf, z as Root } from "./types-ERb2Y9U5.mjs";
2
+ import { $ as Ease, A as Config$9, B as ComponentValue, G as Narrowed, J as ResourceType, L as ComponentHandle, M as State$8, N as types_d_exports$16, O as ViewportSize, Q as DescriptionNode, S as types_d_exports$11, W as Mut, X as TagType, Y as SystemDefinition, a as DefineBundles, at as ProjectionMotion, b as State$7, c as LoadReason, d as Tier, h as Config$8, i as Config$7, it as NumericFields, k as Api$6, m as Api$5, n as BundleMap, ot as ProjectionSpec, p as types_d_exports$1, q as QueryTerm, r as BundleSpec, s as LoadBundles, t as Api$4, u as State$6, z as ComponentType } from "./types-BWzCSRM2.mjs";
3
+ import { $ as SpriteValue, A as TextStyleInput, B as types_d_exports$6, C as Config$12, D as TextApi, E as State$11, F as Config$10, G as Display, H as InputApi, I as I18nApi, J as Parent, K as NineSlice, L as Message, M as TextValue, N as types_d_exports$13, O as TextBind, P as Argument, Q as SpriteOptions, R as State$9, S as BindOptions, T as Point, U as State$10, V as Config$11, W as types_d_exports$7, X as ShapeValue, Y as Shape, Z as Sprite, _ as Style, b as defineComponent, d as popup, et as Transform, f as defineTokens, g as ResolvedStyle, h as IsFlags, j as TextStyles, k as TextFormat, l as Box, nt as sprite, p as IntrinsicElementsFor, q as NineSliceValue, s as PopupComponent, tt as TransformValue, u as LocalWrite, w as CountdownValue, x as BindField, y as WhenFlags, z as TypedTr } from "./types-DgdMLZnV.mjs";
4
+ import { A as HAPTIC_KINDS, B as sfx, C as MotionKeyframe, D as HapticKind, E as types_d_exports, F as mark, G as use, H as spawned, I as parallel, K as wait, L as play, M as external, N as frames, O as SfxDescriptor, P as haptic, R as sequence, S as FramesValue, T as Target, U as stagger, V as spawn, W as tween, _ as State$15, b as AnimApi, c as AnySceneProjection, d as SceneDefinition, f as SceneSpec, g as Config$15, h as types_d_exports$12, i as MusicOptions, j as defineAnimation, k as Step, l as DefineScene, m as State$14, n as Config$14, o as State$13, p as ScenesApi, r as MusicDescriptor, s as types_d_exports$2, t as AudioApi, u as LayerMap, v as UiApi, w as State$12, x as Config$13, y as types_d_exports$15, z as set } from "./types-DlgsfOmW.mjs";
5
+ import { _ as NoiseValue, a as types_d_exports$4, c as EmitterValue, d as ColorMatrixValue, f as DisplacementValue, g as GlowValue, h as FilterValueOf, i as State$16, l as AlphaValue, m as FilterSpec, n as EffectsApi, o as EmitterConfig, p as FilterComponent, s as EmitterDefinition, t as Config$16, u as BlurValue, v as OutlineValue, y as UniformDeclaration } from "./types-DF5kkW-C.mjs";
5
6
  import { Log } from "@moku-labs/common/browser";
6
7
  import { PluginCtx } from "@moku-labs/core";
7
8
 
@@ -177,7 +178,7 @@ declare const animPlugin: import("@moku-labs/core").PluginInstance<"anim", Confi
177
178
  * app.assets.usage().textureMb; // 0.188, the boot tier
178
179
  * ```
179
180
  */
180
- declare const assetsPlugin: import("@moku-labs/core").PluginInstance<"assets", Config$1, State$1, Api, {
181
+ declare const assetsPlugin: import("@moku-labs/core").PluginInstance<"assets", Config$7, State$6, Api$4, {
181
182
  "assets:bundle-loaded": {
182
183
  bundle: string;
183
184
  tier: Tier;
@@ -227,7 +228,7 @@ declare const audioPlugin: import("@moku-labs/core").PluginInstance<"audio", Con
227
228
  * });
228
229
  * ```
229
230
  */
230
- declare const clockPlugin: import("@moku-labs/core").PluginInstance<"clock", Config$2, State$2, Api$1, {}> & Record<never, never>;
231
+ declare const clockPlugin: import("@moku-labs/core").PluginInstance<"clock", Config$1, State$1, Api, {}> & Record<never, never>;
231
232
  //#endregion
232
233
  //#region src/plugins/effects/index.d.ts
233
234
  /**
@@ -261,7 +262,7 @@ declare const effectsPlugin: import("@moku-labs/core").PluginInstance<"effects",
261
262
  * });
262
263
  * ```
263
264
  */
264
- declare const flowPlugin: import("@moku-labs/core").PluginInstance<"flow", Config$3, State$3, Api$2, {
265
+ declare const flowPlugin: import("@moku-labs/core").PluginInstance<"flow", Config$2, State$2, Api$1, {
265
266
  "flow:edge": {
266
267
  flow: string;
267
268
  node: string;
@@ -342,7 +343,7 @@ declare const inputPlugin: import("@moku-labs/core").PluginInstance<"input", Con
342
343
  * });
343
344
  * ```
344
345
  */
345
- declare const lifecyclePlugin: import("@moku-labs/core").PluginInstance<"lifecycle", Config$4, State$4, Api$3, {
346
+ declare const lifecyclePlugin: import("@moku-labs/core").PluginInstance<"lifecycle", Config$3, State$3, Api$2, {
346
347
  "lifecycle:changed": {
347
348
  reason: PauseReason;
348
349
  action: "push" | "pop";
@@ -367,7 +368,7 @@ declare const lifecyclePlugin: import("@moku-labs/core").PluginInstance<"lifecyc
367
368
  * });
368
369
  * ```
369
370
  */
370
- declare const modelPlugin: import("@moku-labs/core").PluginInstance<"model", Config$5, State$5, {
371
+ declare const modelPlugin: import("@moku-labs/core").PluginInstance<"model", Config$4, State$4, {
371
372
  store: StoreApi;
372
373
  rng: RngApi;
373
374
  }, {
@@ -529,7 +530,7 @@ declare const platformPlugin: import("@moku-labs/core").PluginInstance<"platform
529
530
  * app.renderer.host.kind(); // "webgpu" in Chrome, "webgl" on an older device
530
531
  * ```
531
532
  */
532
- declare const rendererPlugin: import("@moku-labs/core").PluginInstance<"renderer", Config$6, State$6, Api$4, {
533
+ declare const rendererPlugin: import("@moku-labs/core").PluginInstance<"renderer", Config$8, State$7, Api$5, {
533
534
  "renderer:device-lost": {
534
535
  kind: "webgpu" | "webgl";
535
536
  reason: string;
@@ -592,7 +593,7 @@ declare const textPlugin: import("@moku-labs/core").PluginInstance<"text", Confi
592
593
  * });
593
594
  * ```
594
595
  */
595
- declare const timePlugin: import("@moku-labs/core").PluginInstance<"time", Config$7, State$7, Api$5, {}> & Record<never, never>;
596
+ declare const timePlugin: import("@moku-labs/core").PluginInstance<"time", Config$5, State$5, Api$3, {}> & Record<never, never>;
596
597
  //#endregion
597
598
  //#region src/plugins/ui/index.d.ts
598
599
  /**
@@ -625,7 +626,7 @@ declare const uiPlugin: import("@moku-labs/core").PluginInstance<"ui", Config$15
625
626
  * app.world.projection.entityOf("board.items", "i7"); // 1048576
626
627
  * ```
627
628
  */
628
- declare const worldPlugin: import("@moku-labs/core").PluginInstance<"world", Config$8, State$8, Api$6, {
629
+ declare const worldPlugin: import("@moku-labs/core").PluginInstance<"world", Config$9, State$8, Api$6, {
629
630
  "world:reconciled": {
630
631
  mode: "play" | "direct";
631
632
  projections: number;
@@ -1356,7 +1357,7 @@ declare function defineEmitter(id: string, config: EmitterConfig): EmitterDefini
1356
1357
  * });
1357
1358
  * ```
1358
1359
  */
1359
- declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<Config$9, Events$1, (import("@moku-labs/core").PluginInstance<"time", Config$7, State$7, Api$5, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"lifecycle", Config$4, State$4, Api$3, {
1360
+ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<Config$6, Events$1, (import("@moku-labs/core").PluginInstance<"time", Config$5, State$5, Api$3, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"lifecycle", Config$3, State$3, Api$2, {
1360
1361
  "lifecycle:changed": {
1361
1362
  reason: PauseReason;
1362
1363
  action: "push" | "pop";
@@ -1364,7 +1365,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
1364
1365
  paused: boolean;
1365
1366
  resumed: boolean;
1366
1367
  };
1367
- }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"model", Config$5, State$5, {
1368
+ }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"model", Config$4, State$4, {
1368
1369
  store: StoreApi;
1369
1370
  rng: RngApi;
1370
1371
  }, {
@@ -1372,7 +1373,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
1372
1373
  roots: readonly Root[];
1373
1374
  cause: "edge" | "rollback" | "restore" | "load";
1374
1375
  };
1375
- }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"clock", Config$2, State$2, Api$1, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"flow", Config$3, State$3, Api$2, {
1376
+ }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"clock", Config$1, State$1, Api, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"flow", Config$2, State$2, Api$1, {
1376
1377
  "flow:edge": {
1377
1378
  flow: string;
1378
1379
  node: string;
@@ -1396,7 +1397,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
1396
1397
  rolledBackTo: string;
1397
1398
  retry: boolean;
1398
1399
  };
1399
- }> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>]>, import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi> | import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>> | undefined) => import("@moku-labs/core").App<Config$9, Events$1, (import("@moku-labs/core").PluginInstance<"time", Config$7, State$7, Api$5, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"lifecycle", Config$4, State$4, Api$3, {
1400
+ }> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>]>, import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi> | import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>> | undefined) => import("@moku-labs/core").App<Config$6, Events$1, (import("@moku-labs/core").PluginInstance<"time", Config$5, State$5, Api$3, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"lifecycle", Config$3, State$3, Api$2, {
1400
1401
  "lifecycle:changed": {
1401
1402
  reason: PauseReason;
1402
1403
  action: "push" | "pop";
@@ -1404,7 +1405,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
1404
1405
  paused: boolean;
1405
1406
  resumed: boolean;
1406
1407
  };
1407
- }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"model", Config$5, State$5, {
1408
+ }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"model", Config$4, State$4, {
1408
1409
  store: StoreApi;
1409
1410
  rng: RngApi;
1410
1411
  }, {
@@ -1412,7 +1413,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
1412
1413
  roots: readonly Root[];
1413
1414
  cause: "edge" | "rollback" | "restore" | "load";
1414
1415
  };
1415
- }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"clock", Config$2, State$2, Api$1, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"flow", Config$3, State$3, Api$2, {
1416
+ }> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"clock", Config$1, State$1, Api, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"flow", Config$2, State$2, Api$1, {
1416
1417
  "flow:edge": {
1417
1418
  flow: string;
1418
1419
  node: string;
@@ -1449,7 +1450,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
1449
1450
  * });
1450
1451
  * ```
1451
1452
  */
1452
- declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$9, Events$1, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>]>>;
1453
+ declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$6, Events$1, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>]>>;
1453
1454
  /**
1454
1455
  * Binds the authoring helpers to the types of one game: "createApp for a game". Each plugin binds
1455
1456
  * its own helpers (`flowFor`, `projectionFor`, `componentsFor`, `bundlesFor`, `scenesFor`); this
@@ -1537,7 +1538,7 @@ declare function defineGame<Types extends GameTypes>(): {
1537
1538
  * createApp({ plugins: [...screen, boardFeature], pluginConfigs: { renderer: { mount: "#game" } } });
1538
1539
  * ```
1539
1540
  */
1540
- declare const screen: readonly [import("@moku-labs/core").PluginInstance<"world", Config$8, State$8, Api$6, {
1541
+ declare const screen: readonly [import("@moku-labs/core").PluginInstance<"world", Config$9, State$8, Api$6, {
1541
1542
  "world:reconciled": {
1542
1543
  mode: "play" | "direct";
1543
1544
  projections: number;
@@ -1549,12 +1550,12 @@ declare const screen: readonly [import("@moku-labs/core").PluginInstance<"world"
1549
1550
  hintsRouted: number;
1550
1551
  hintsDropped: number;
1551
1552
  };
1552
- }> & Record<never, never>, import("@moku-labs/core").PluginInstance<"renderer", Config$6, State$6, Api$4, {
1553
+ }> & Record<never, never>, import("@moku-labs/core").PluginInstance<"renderer", Config$8, State$7, Api$5, {
1553
1554
  "renderer:device-lost": {
1554
1555
  kind: "webgpu" | "webgl";
1555
1556
  reason: string;
1556
1557
  };
1557
- }> & Record<never, never>, import("@moku-labs/core").PluginInstance<"input", Config$11, State$10, InputApi, {}> & Record<never, never>, import("@moku-labs/core").PluginInstance<"assets", Config$1, State$1, Api, {
1558
+ }> & Record<never, never>, import("@moku-labs/core").PluginInstance<"input", Config$11, State$10, InputApi, {}> & Record<never, never>, import("@moku-labs/core").PluginInstance<"assets", Config$7, State$6, Api$4, {
1558
1559
  "assets:bundle-loaded": {
1559
1560
  bundle: string;
1560
1561
  tier: Tier;
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import { a as enableDraftPatches, i as dropDrafts, o as finishDrafts, r as deepFreeze, s as openDrafts, t as memory } from "./memory-CvgdnsQO.mjs";
3
- import { c as findNode, d as slotNames, f as compact, i as servePaused, l as framePath, n as captureFrame, o as collectFlows, p as pushEntry, r as serveCaptures, s as describeGraph, t as cancelCaptures, u as graphHash } from "./capture-fpd83Mi5.mjs";
3
+ import { a as graphHash, c as pushEntry, i as framePath, n as describeGraph, o as slotNames, r as findNode, s as compact, t as collectFlows } from "./registry-DlpRCibU.mjs";
4
4
  import { A as system, C as Exiting, D as component, E as Tree, O as mut, S as sprite, T as Order, _ as Parent, a as visualOf$1, b as Transform, c as Text, d as defineTextStyles, f as label, g as NineSlice, h as Display, j as tag, k as resource, l as bind, m as textFor, n as fitScaleOf, o as visualRectOf, p as readStyle, r as samePose, s as Countdown, t as CONTENT, u as builtInStyles, v as Shape, w as Layer, x as componentsFor, y as Sprite } from "./visual-DUvvPPCt.mjs";
5
+ import { i as servePaused, n as captureFrame, r as serveCaptures, t as cancelCaptures } from "./capture-BX5Gfoty.mjs";
5
6
  import { n as isDev } from "./dev-wKLgOw7c.mjs";
6
7
  import { i as isComponentDefinition, n as flatten, r as defineComponent } from "./flatten-BkXuscAV.mjs";
7
8
  import { envPlugin, logPlugin } from "@moku-labs/common/browser";
@@ -30350,6 +30351,7 @@ function createJsxApi(ctx, modules) {
30350
30351
  state.components.set(definition.name, definition);
30351
30352
  },
30352
30353
  refreshAll: () => {
30354
+ for (const element of state.elements.values()) modules.layout.markMeasured(element);
30353
30355
  for (const root of state.roots.values()) {
30354
30356
  root.dirty = true;
30355
30357
  root.needsSolve = true;
@@ -31077,6 +31079,8 @@ function createGuideHandler(ctx) {
31077
31079
  const DEFAULT_TEXT_STYLE = "body";
31078
31080
  /** The glyph an icon is sized from: one line of the text style it sits in. */
31079
31081
  const LINE_SAMPLE = "M";
31082
+ /** Below this a measured length counts as whole: the tolerance Yoga's own rounding uses. */
31083
+ const WHOLE_EPSILON = 1e-4;
31080
31084
  /**
31081
31085
  * Makes an icon square on the line height of the text next to it.
31082
31086
  *
@@ -31173,16 +31177,21 @@ function shownOf(element, source) {
31173
31177
  }
31174
31178
  /**
31175
31179
  * The size a text or an icon takes before Yoga clamps it: an icon is square on one line of its
31176
- * text style, a bound text measures the string it shows, any other text its content.
31180
+ * text style, a bound text measures the string it shows, any other text its content. The size is
31181
+ * rounded up to whole units. Yoga rounds a node with a measure function outward but sizes its
31182
+ * parent from the unrounded length, so a fractional size would run into the parent's padding.
31177
31183
  *
31178
31184
  * @param element - The text or icon element.
31179
31185
  * @param source - What it is measured through.
31180
- * @returns The width and the height.
31186
+ * @returns The width and the height, in whole units.
31181
31187
  */
31182
31188
  function naturalSize(element, source) {
31183
31189
  const { content, style } = contentOf$1(element);
31184
- if (element.type === "icon") return squareOf(source.measure(LINE_SAMPLE, style));
31185
- return source.measure(shownOf(element, source) ?? content, style);
31190
+ const size = element.type === "icon" ? squareOf(source.measure(LINE_SAMPLE, style)) : source.measure(shownOf(element, source) ?? content, style);
31191
+ return {
31192
+ width: Math.ceil(size.width - WHOLE_EPSILON),
31193
+ height: Math.ceil(size.height - WHOLE_EPSILON)
31194
+ };
31186
31195
  }
31187
31196
  /**
31188
31197
  * Clamps one axis of a measured size to what Yoga offered on that axis.
@@ -31715,6 +31724,7 @@ function createLayoutApi(ctx) {
31715
31724
  markMeasured(state, element);
31716
31725
  },
31717
31726
  remeasure: (element) => remeasureShown(state, element, source),
31727
+ markMeasured: (element) => markMeasured(state, element),
31718
31728
  place: (parent, children) => placeChildren(state, parent, children),
31719
31729
  free: (element) => freeNode(state, element),
31720
31730
  solve: (rootElement, size, lookup) => solveRoot(state, rootElement, size, lookup),
@@ -34228,7 +34238,8 @@ function defineMotion(spec) {
34228
34238
  *
34229
34239
  * | Subpath | What |
34230
34240
  * |---|---|
34231
- * | `@moku-labs/game/testing` | headless play, repro runs, the in-memory save, the fake clock, visual tests |
34241
+ * | `@moku-labs/game/testing` | headless play, repro runs, the in-memory save, the fake clock |
34242
+ * | `@moku-labs/game/visual` | the visual tests, node and bun only |
34232
34243
  * | `@moku-labs/game/assets` | the build-time asset key scanner and the production packer (`packAssets`, `--pack <dir>`), node and bun only |
34233
34244
  * | `@moku-labs/game/inspect` | the editor's read door: `read`, `watch`, `defineSource`, the `sources` catalogue |
34234
34245
  * | `@moku-labs/game/control` | the editor's write door, dev builds only: `run`, `defineCommand`, the `commands` catalogue |
@@ -24,7 +24,7 @@ function checkId(kind, id) {
24
24
  * ```ts
25
25
  * // A game's .dev module: the orders on the board, re-read after every edge.
26
26
  * export const orders = defineSource({
27
- * id: "timber.orders",
27
+ * id: "mini.orders",
28
28
  * title: "Orders",
29
29
  * input: {},
30
30
  * changes: "edge",
@@ -49,7 +49,7 @@ function defineSource(source) {
49
49
  * ```ts
50
50
  * // A game's .dev module: open the shop from anywhere, through the graph.
51
51
  * export const openShop = defineCommand({
52
- * id: "timber.openShop",
52
+ * id: "mini.openShop",
53
53
  * title: "Open the shop",
54
54
  * input: {},
55
55
  * effect: "route",
@@ -1,9 +1,11 @@
1
- import { At as JournalEntry, H as EntitySnapshot, Ot as FlowGraph, R as ComponentSchema, U as FrameDiff, Ut as Api, f as Usage, gt as Position, j as Explained, k as Api$1, m as Api$2, t as Api$3, tn as Snapshot, w as RenderStats, x as Under } from "./types-BMEn0tSC.mjs";
2
- import { a as InputOf, c as Source, d as HeadlessApp, i as InputArguments, l as WatchApp, o as InputSchema, t as CheatEntry, u as WatchInput } from "./types-CYYKjvLn.mjs";
3
- import { c as UiNode } from "./types-BaUUcIaN.mjs";
4
- import { a as SoundEntry, t as AudioApi, v as UiApi } from "./types-CXyipKhE.mjs";
5
- import { n as defineSource } from "./define-BbgXv4cy.mjs";
6
- import { n as EffectsApi, r as EffectsStats } from "./types-ClF8say0.mjs";
1
+ import { F as Api, q as Snapshot, s as Position, x as JournalEntry, y as FlowGraph } from "./types-ERb2Y9U5.mjs";
2
+ import { H as EntitySnapshot, R as ComponentSchema, U as FrameDiff, f as Usage, j as Explained, k as Api$1, m as Api$2, t as Api$3, w as RenderStats, x as Under } from "./types-BWzCSRM2.mjs";
3
+ import { t as HeadlessApp } from "./headless-q98SwADR.mjs";
4
+ import { a as InputOf, c as Source, i as InputArguments, l as WatchApp, o as InputSchema, t as CheatEntry, u as WatchInput } from "./types-CDSKa0vl.mjs";
5
+ import { c as UiNode } from "./types-DgdMLZnV.mjs";
6
+ import { a as SoundEntry, t as AudioApi, v as UiApi } from "./types-DlgsfOmW.mjs";
7
+ import { n as defineSource } from "./define-BPpZcpxW.mjs";
8
+ import { n as EffectsApi, r as EffectsStats } from "./types-DF5kkW-C.mjs";
7
9
 
8
10
  //#region src/plugins/flow/doors/read.d.ts
9
11
  /**
package/dist/inspect.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { c as defineSource } from "./input-CK4nxBO1.mjs";
2
- import { n as read, r as watch, t as sources } from "./sources-DVWJ8nfr.mjs";
1
+ import { c as defineSource } from "./input-z_35Jwfn.mjs";
2
+ import { n as read, r as watch, t as sources } from "./sources-BBUd15vQ.mjs";
3
3
  export { defineSource, read, sources, watch };
@@ -1,2 +1,2 @@
1
- import { a as jsxs, i as jsxDEV, n as JSX, r as jsx, t as Fragment } from "./runtime-HTlmRb81.mjs";
1
+ import { a as jsxs, i as jsxDEV, n as JSX, r as jsx, t as Fragment } from "./runtime-CvDZZg1S.mjs";
2
2
  export { Fragment, type JSX, jsx, jsxDEV, jsxs };
@@ -1,2 +1,2 @@
1
- import { a as jsxs, n as JSX, r as jsx, t as Fragment } from "./runtime-HTlmRb81.mjs";
1
+ import { a as jsxs, n as JSX, r as jsx, t as Fragment } from "./runtime-CvDZZg1S.mjs";
2
2
  export { Fragment, type JSX, jsx, jsxs };