@moku-labs/game 0.7.1 → 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 +15 -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";
@@ -31078,6 +31079,8 @@ function createGuideHandler(ctx) {
31078
31079
  const DEFAULT_TEXT_STYLE = "body";
31079
31080
  /** The glyph an icon is sized from: one line of the text style it sits in. */
31080
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;
31081
31084
  /**
31082
31085
  * Makes an icon square on the line height of the text next to it.
31083
31086
  *
@@ -31174,16 +31177,21 @@ function shownOf(element, source) {
31174
31177
  }
31175
31178
  /**
31176
31179
  * The size a text or an icon takes before Yoga clamps it: an icon is square on one line of its
31177
- * 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.
31178
31183
  *
31179
31184
  * @param element - The text or icon element.
31180
31185
  * @param source - What it is measured through.
31181
- * @returns The width and the height.
31186
+ * @returns The width and the height, in whole units.
31182
31187
  */
31183
31188
  function naturalSize(element, source) {
31184
31189
  const { content, style } = contentOf$1(element);
31185
- if (element.type === "icon") return squareOf(source.measure(LINE_SAMPLE, style));
31186
- 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
+ };
31187
31195
  }
31188
31196
  /**
31189
31197
  * Clamps one axis of a measured size to what Yoga offered on that axis.
@@ -34230,7 +34238,8 @@ function defineMotion(spec) {
34230
34238
  *
34231
34239
  * | Subpath | What |
34232
34240
  * |---|---|
34233
- * | `@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 |
34234
34243
  * | `@moku-labs/game/assets` | the build-time asset key scanner and the production packer (`packAssets`, `--pack <dir>`), node and bun only |
34235
34244
  * | `@moku-labs/game/inspect` | the editor's read door: `read`, `watch`, `defineSource`, the `sources` catalogue |
34236
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 };
@@ -13,8 +13,8 @@
13
13
  *
14
14
  * @example
15
15
  * ```ts
16
- * // The editor opens the fixture game with the default manifest and a slower debounce.
17
- * const options: ProjectOptions = { root: "tests/integration/merge-game", debounceMs: 120 };
16
+ * // The editor opens the mini game with the default manifest and a slower debounce.
17
+ * const options: ProjectOptions = { root: "tests/fixtures/mini-game", debounceMs: 120 };
18
18
  * ```
19
19
  */
20
20
  type ProjectOptions = {
@@ -29,7 +29,7 @@ type ProjectOptions = {
29
29
  *
30
30
  * @example
31
31
  * ```ts
32
- * const kind: JsxKind = "idProp"; // id="settingsBoard" on <Signboard>
32
+ * const kind: JsxKind = "idProp"; // id="infoPanel" on <Panel>
33
33
  * ```
34
34
  */
35
35
  type JsxKind = "literal" | "template" | "idProp" | "ident";
@@ -39,8 +39,8 @@ type JsxKind = "literal" | "template" | "idProp" | "ident";
39
39
  *
40
40
  * @example
41
41
  * ```ts
42
- * // node:board/merge, defined by the const `merge` of nodes/merge.ts.
43
- * const anchor: Anchor = { path: "nodes/merge.ts", binding: "merge" };
42
+ * // node:infoPopup/count, defined by the const `count` of nodes/count.ts.
43
+ * const anchor: Anchor = { path: "nodes/count.ts", binding: "count" };
44
44
  * ```
45
45
  */
46
46
  type Anchor = {
@@ -66,8 +66,8 @@ type Anchor = {
66
66
  *
67
67
  * @example
68
68
  * ```ts
69
- * index.symbols["flow:board"]; // { def: [{ path: "flows/board.ts", binding: "boardFlow" }] }
70
- * index.files["nodes/merge.ts"]?.state; // "ok"
69
+ * index.symbols["flow:infoPopup"]; // { def: [{ path: "flows/info.ts", binding: "infoFlow" }] }
70
+ * index.files["nodes/count.ts"]?.state; // "ok"
71
71
  * ```
72
72
  */
73
73
  type ProjectIndex = {
@@ -99,9 +99,9 @@ type ProjectIndex = {
99
99
  *
100
100
  * @example
101
101
  * ```ts
102
- * // An agent moved nodes/toast.ts to nodes/board/toast.ts and fixed the import of the board flow.
103
- * change.files; // ["flows/board.ts", "nodes/board/toast.ts", "nodes/toast.ts"]
104
- * change.moved; // [{ key: "node:board/toast", from: "nodes/toast.ts", to: "nodes/board/toast.ts" }]
102
+ * // An agent moved nodes/count.ts to nodes/info/count.ts and fixed the import of the info flow.
103
+ * change.files; // ["flows/info.ts", "nodes/count.ts", "nodes/info/count.ts"]
104
+ * change.moved; // [{ key: "node:infoPopup/count", from: "nodes/count.ts", to: "nodes/info/count.ts" }]
105
105
  * change.removed; // []
106
106
  * ```
107
107
  */
@@ -123,9 +123,9 @@ type ProjectChange = {
123
123
  *
124
124
  * @example
125
125
  * ```ts
126
- * // The editor opens the Home scene of the fixture game.
126
+ * // The editor opens the Home scene of the mini game.
127
127
  * const [found] = await project.find("scene:home");
128
- * [found?.path, found?.line, found?.range]; // ["features/home/scene.ts", 8, [8, 1, 13, 4]]
128
+ * [found?.path, found?.line, found?.range]; // ["features/home/scene.ts", 8, [8, 1, 12, 4]]
129
129
  * ```
130
130
  */
131
131
  type Found = Anchor & {
@@ -145,7 +145,7 @@ type ProjectApi = {
145
145
  * @example
146
146
  * ```ts
147
147
  * // The editor checks a node file before it opens it.
148
- * project.index.files["nodes/merge.ts"]?.state; // "ok"
148
+ * project.index.files["nodes/count.ts"]?.state; // "ok"
149
149
  * ```
150
150
  */
151
151
  readonly index: ProjectIndex;
@@ -157,14 +157,14 @@ type ProjectApi = {
157
157
  * with a literal prop of the same name on their component (both the pattern and the prop come
158
158
  * back), then `*` patterns. An unknown key answers `[]`.
159
159
  *
160
- * @param key - An engine id: `node:board/merge`, `flow:board`, `textStyle:ui.title`, `jsx:card2Picture`.
160
+ * @param key - An engine id: `node:infoPopup/count`, `flow:main`, `textStyle:ui.counter`, `jsx:infoPanelSpark`.
161
161
  * @returns One entry per place, in the order above; `broken` when the file does not parse now.
162
162
  * @example
163
163
  * ```ts
164
- * // The editor jumps to the node the flow view clicked, and to the X of the settings board.
165
- * (await project.find("node:board/merge"))[0]?.line; // 17, in "nodes/merge.ts"
166
- * (await project.find("jsx:settingsBoardClose")).map(found => `${found.path}:${found.line}`);
167
- * // ["features/ui/kit.tsx:844", "features/settings/settings.tsx:290"]
164
+ * // The editor jumps to the node the flow view clicked, and to the spark of the info panel.
165
+ * (await project.find("node:infoPopup/count"))[0]?.line; // 7, in "nodes/count.ts"
166
+ * (await project.find("jsx:infoPanelSpark")).map(found => `${found.path}:${found.line}`);
167
+ * // ["features/info/popup.tsx:65", "features/info/popup.tsx:79"]
168
168
  * ```
169
169
  */
170
170
  find(key: string): Promise<Found[]>;
@@ -198,9 +198,9 @@ type ProjectApi = {
198
198
  * @throws {Error} When the path leaves the root, by `..` or by a symlink.
199
199
  * @example
200
200
  * ```ts
201
- * // The editor saved nodes/merge.ts through its own file layer.
202
- * const index = await project.changed("nodes/merge.ts");
203
- * index.files["nodes/merge.ts"]?.state; // "ok"
201
+ * // The editor saved nodes/count.ts through its own file layer.
202
+ * const index = await project.changed("nodes/count.ts");
203
+ * index.files["nodes/count.ts"]?.state; // "ok"
204
204
  * ```
205
205
  */
206
206
  changed(path: string): Promise<ProjectIndex>;
@@ -247,9 +247,9 @@ type IndexUi = {
247
247
  * `--check` that found a broken file or a conflict; else `0`.
248
248
  * @example
249
249
  * ```ts
250
- * // An agent asks where a node of the fixture game lives.
251
- * await runCli(["--root", "tests/integration/merge-game", "where", "node:board/merge"]);
252
- * // 0, and the console printed "nodes/merge.ts:17"
250
+ * // An agent asks where a node of the mini game lives.
251
+ * await runCli(["--root", "tests/fixtures/mini-game", "where", "node:infoPopup/count"]);
252
+ * // 0, and the console printed "nodes/count.ts:7"
253
253
  * ```
254
254
  */
255
255
  declare function runCli(argv: readonly string[], ui?: IndexUi): Promise<number>;
@@ -266,10 +266,10 @@ declare function runCli(argv: readonly string[], ui?: IndexUi): Promise<number>;
266
266
  * installed: `[game] The project index needs the "typescript" package.`
267
267
  * @example
268
268
  * ```ts
269
- * // The editor opens the fixture game and answers where a node and a text style live.
270
- * const project = await openProject({ root: "tests/integration/merge-game" });
271
- * Object.keys(project.index.files).length; // 111
272
- * (await project.find("textStyle:ui.title"))[0]?.line; // 39, in "features/ui/styles.ts"
269
+ * // The editor opens the mini game and answers where a node and a text style live.
270
+ * const project = await openProject({ root: "tests/fixtures/mini-game" });
271
+ * Object.keys(project.index.files).length; // 21
272
+ * (await project.find("textStyle:ui.counter"))[0]?.line; // 16, in "features/home/styles.ts"
273
273
  * project.close();
274
274
  * ```
275
275
  */