@moku-labs/game 0.13.2 → 0.14.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 (35) hide show
  1. package/README.md +2 -2
  2. package/dist/app/page.d.mts +1 -1
  3. package/dist/app/system.d.mts +3 -3
  4. package/dist/app.d.mts +2 -2
  5. package/dist/app.mjs +1 -1
  6. package/dist/assets.d.mts +1 -1
  7. package/dist/{capture-fpd83Mi5.mjs → capture-D7XvoHdH.mjs} +78 -1
  8. package/dist/{commands-BrgeMP5y.d.mts → commands-Cpn2ga7s.d.mts} +5 -5
  9. package/dist/control.d.mts +4 -4
  10. package/dist/control.mjs +1 -1
  11. package/dist/{define-ZmWbWxon.d.mts → define-Bwp8CNkW.d.mts} +2 -2
  12. package/dist/{headless-BsNdJs9W.mjs → headless-BA6ffzqD.mjs} +30 -9
  13. package/dist/{headless-Dgxvp6Ym.d.mts → headless-IfD6_JMH.d.mts} +1 -1
  14. package/dist/{index-CdbE_jGs.d.mts → index-yUO4Dx2C.d.mts} +4 -4
  15. package/dist/index.d.mts +5 -5
  16. package/dist/index.mjs +1 -1
  17. package/dist/inspect.d.mts +7 -7
  18. package/dist/jsx-dev-runtime.d.mts +1 -1
  19. package/dist/jsx-runtime.d.mts +1 -1
  20. package/dist/{memory-DJ2sPExv.d.mts → memory-D1IKoFr1.d.mts} +1 -1
  21. package/dist/{run-wYty_Lwo.mjs → run-Bf2hKyjZ.mjs} +151 -43
  22. package/dist/{runtime-6qakboTm.d.mts → runtime-CWY6co6m.d.mts} +1 -1
  23. package/dist/{src-CltFFKtc.mjs → src-Dgmdzl8R.mjs} +444 -128
  24. package/dist/testing.d.mts +4 -4
  25. package/dist/testing.mjs +2 -2
  26. package/dist/{types-DGIkfZB7.d.mts → types-B98iRBSG.d.mts} +4 -4
  27. package/dist/{types-CmuwAjkV.d.mts → types-BArxen_M.d.mts} +1 -1
  28. package/dist/{types-C6y8EfEB.d.mts → types-BWDESfx2.d.mts} +94 -25
  29. package/dist/{types-zRGJdFGX.d.mts → types-D7MGz4c-.d.mts} +2 -2
  30. package/dist/{types-D57SntUt.d.mts → types-InZGjvkI.d.mts} +2 -2
  31. package/dist/{types-B3T2X6To.d.mts → types-oQoQEj-y.d.mts} +19 -12
  32. package/dist/visual.d.mts +6 -6
  33. package/dist/visual.mjs +1 -1
  34. package/llms.txt +5 -4
  35. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
- import { Dt as AnyFlow, Ht as ClockSource, Nt as NodeTable, Wt as FakeClock, Xt as Json, ht as FeaturePlugin } from "./types-C6y8EfEB.mjs";
2
- import { t as createApp } from "./index-CdbE_jGs.mjs";
3
- import { n as saveOf, t as memory } from "./memory-DJ2sPExv.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-Dgxvp6Ym.mjs";
1
+ import { Dt as AnyFlow, Ht as ClockSource, Nt as NodeTable, Wt as FakeClock, Xt as Json, ht as FeaturePlugin } from "./types-BWDESfx2.mjs";
2
+ import { t as createApp } from "./index-yUO4Dx2C.mjs";
3
+ import { n as saveOf, t as memory } from "./memory-D1IKoFr1.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-IfD6_JMH.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
7
  //#region src/plugins/clock/fake.d.ts
package/dist/testing.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { Kt as type, Ut as exit, t as createApp } from "./src-CltFFKtc.mjs";
1
+ import { Kt as type, Ut as exit, t as createApp } from "./src-Dgmdzl8R.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
- import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BsNdJs9W.mjs";
4
+ import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BA6ffzqD.mjs";
5
5
  //#region src/testing/isolate.ts
6
6
  const noPayload = null;
7
7
  /** Id of the harness main flow. */
@@ -1,7 +1,7 @@
1
- import { Dt as AnyFlow, Ht as ClockSource, Jt as Config, Wt as FakeClock, Xt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, pt as Config$1, tn as PlayerStateProvider, u as Manifest, v as PixiModule } from "./types-C6y8EfEB.mjs";
2
- import { o as AudioContextLike, s as Config$4 } from "./types-B3T2X6To.mjs";
3
- import { _t as audioPlugin, ct as PlatformProvider, ht as effectsPlugin, i as screen, ot as platformPlugin, t as createApp } from "./index-CdbE_jGs.mjs";
4
- import { t as memory } from "./memory-DJ2sPExv.mjs";
1
+ import { Dt as AnyFlow, Ht as ClockSource, Jt as Config, Wt as FakeClock, Xt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, pt as Config$1, tn as PlayerStateProvider, u as Manifest, v as PixiModule } from "./types-BWDESfx2.mjs";
2
+ import { o as AudioContextLike, s as Config$4 } from "./types-oQoQEj-y.mjs";
3
+ import { _t as audioPlugin, ct as PlatformProvider, ht as effectsPlugin, i as screen, ot as platformPlugin, t as createApp } from "./index-yUO4Dx2C.mjs";
4
+ import { t as memory } from "./memory-D1IKoFr1.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
7
  //#region src/app/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, Sn as Require, V as ComponentValue, Vt as Api$5, Xt as Json, at as NumericFields, dt as Api$1, h as Api$3, hn as Api, it as MotionHandle, k as ViewportSize, ot as ProjectionMotion, rt as Motion, s as Events$1, t as Api$4, ut as ViewHandle, zt as Hint } from "./types-C6y8EfEB.mjs";
1
+ import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, I as AnyComponentType, L as AnyComponentValue, Lt as Descriptor, O as Point$2, Q as ChangeHook, Sn as Require, V as ComponentValue, Vt as Api$5, Xt as Json, at as NumericFields, dt as Api$1, h as Api$3, hn as Api, it as MotionHandle, k as ViewportSize, ot as ProjectionMotion, rt as Motion, s as Events$1, t as Api$4, ut as ViewHandle, zt as Hint } from "./types-BWDESfx2.mjs";
2
2
  import { Log } from "@moku-labs/common/browser";
3
3
  import { PluginCtx } from "@moku-labs/core";
4
4
 
@@ -836,7 +836,9 @@ type StoreState = {
836
836
  /**
837
837
  * store module API, `app.model.store`. Logic changes the trees only inside a transaction, and only
838
838
  * a rest point hands patches to the provider, so a kill between two rest nodes loses a whole
839
- * transition and never half of one.
839
+ * transition and never half of one. One restore differs: `flow.restore` of a bookmark taken at a
840
+ * transit node marks a rest point there, so the provider receives a document from between two rest
841
+ * nodes, and a kill right after it loads that state at the start node.
840
842
  *
841
843
  * @example
842
844
  * ```ts
@@ -2139,20 +2141,21 @@ type RouteStep = {
2139
2141
  };
2140
2142
  };
2141
2143
  /**
2142
- * A rest node plus a state that really existed there. Serialisable. `graph` is the hash of
2143
- * `describe()`.
2144
+ * A node plus a state that really existed there. Serialisable. `path` names a rest node, or a
2145
+ * transit node that waited at the gate for the answer of an effect (a popup); for the second the
2146
+ * state is the one committed when the node was entered. `graph` is the hash of `describe()`.
2144
2147
  *
2145
2148
  * @example
2146
2149
  * ```ts
2150
+ * // Taken while the info popup of the mini game waits for its answer, opened from "home".
2147
2151
  * const bookmark: Bookmark = {
2148
- * path: "board/awaitIntent", input: null,
2149
- * player: { coins: 7 }, session: { taps: 0 },
2150
- * rng: { seed: 42, streams: {} }, graph: "fe4d257a"
2152
+ * path: "info/show", input: null, player: { count: 0 }, session: { opened: 0 },
2153
+ * rng: { seed: 42, streams: {} }, graph: "0e0d2aa8", rest: { path: "home", input: null }
2151
2154
  * };
2152
2155
  * ```
2153
2156
  */
2154
2157
  type Bookmark = {
2155
- path: string;
2158
+ /** Path of a rest node, or of a transit node that waited at the gate. */path: string;
2156
2159
  input: Json;
2157
2160
  player: Json;
2158
2161
  session: Json;
@@ -2163,6 +2166,15 @@ type Bookmark = {
2163
2166
  * `game.restore`; `flow.restore` ignores it.
2164
2167
  */
2165
2168
  scene?: string;
2169
+ /**
2170
+ * The rest point before a transit `path`: the last rest node and its input. `restore` enters
2171
+ * it instead when the graph changed since the bookmark. Absent for a bookmark of a rest node,
2172
+ * and when no rest node came before the transit node.
2173
+ */
2174
+ rest?: {
2175
+ path: string;
2176
+ input: Json;
2177
+ };
2166
2178
  };
2167
2179
  /**
2168
2180
  * What `onEnter` callbacks learn about the node being entered.
@@ -2286,9 +2298,21 @@ type EnterCallback = (node: NodeInfo, ctx: {
2286
2298
  * game just runs: `walk.ts` and `restore` create it through `loopSeam` when they need it.
2287
2299
  */
2288
2300
  type LoopSeam = {
2289
- /** Sub-flow results a walk substitutes, by path. The loop takes each one once. */substitutions: Map<string, Result>; /** Called with the path every time the loop enters a rest node. */
2290
- rest: ((path: string) => void)[]; /** Called every time the loop opens the gate of a rest node. The walk waits on it. */
2291
- gateOpen: (() => void)[]; /** The bookmark the loop enters at the next turn. */
2301
+ /** Sub-flow results a walk substitutes, by path. The loop takes each one once. */substitutions: Map<string, Result>;
2302
+ /**
2303
+ * Called with the path every time the loop enters a rest node, and once when it enters the
2304
+ * node of a bookmark, which may be a transit node. `restore` resolves on the second.
2305
+ */
2306
+ rest: ((path: string) => void)[];
2307
+ /**
2308
+ * Called every time the loop opens a gate: the wait of a rest node, or an effect with answers
2309
+ * inside any node. The walk waits on it, and so does `restore` after it entered a transit node.
2310
+ */
2311
+ gateOpen: (() => void)[];
2312
+ /**
2313
+ * The bookmark the loop enters at the next turn. The loop clears it when it takes it. A loop
2314
+ * that ends first never takes it, so the restore that waits drops its own.
2315
+ */
2292
2316
  restoring: Bookmark | undefined;
2293
2317
  };
2294
2318
  /**
@@ -2298,7 +2322,12 @@ type RunnerState = {
2298
2322
  /** Flush started by a background pause. `onStop` awaits it. */flushing: Promise<void> | undefined; /** Every flow of the graph by id, collected at `run()`. */
2299
2323
  flows: Map<string, AnyFlow>;
2300
2324
  enterCallbacks: Record<Stage, EnterCallback[]>; /** The position: one frame per nesting level. */
2301
- stack: Frame[]; /** The stack of the last rest node: the rollback target. */
2325
+ stack: Frame[];
2326
+ /**
2327
+ * The frames a failed node is retried from, paired with the state a rollback returns to: the
2328
+ * last rest node, or the node the graph started at or a restore entered. The last two may be
2329
+ * transit nodes, so `bookmark()` writes its `rest` only when these frames name a rest node.
2330
+ */
2302
2331
  restFrame: Frame[] | undefined; /** Inside a slot: the contribution the loop just finished. The slot continues after it. */
2303
2332
  slotAfter: string | undefined; /** Edges since the last checkpoint. */
2304
2333
  journal: JournalEntry[];
@@ -2373,7 +2402,10 @@ type RunnerApi = {
2373
2402
  /**
2374
2403
  * Walks a route in fast mode through the running loop: it answers the gate at each step's `at`
2375
2404
  * and substitutes the result of every sub-flow node the route skips. A `from` bookmark is
2376
- * entered through the same check as `restore`. The mode of the caller is put back afterwards.
2405
+ * entered through the same check as `restore`, with the same fallback. A rest node is entered
2406
+ * before the switch to fast mode: its stages see the mode of the caller. A transit node is
2407
+ * entered after the switch and without the wait `restore` adds for it: its popup is not shown,
2408
+ * the route answers it. The mode of the caller is put back afterwards.
2377
2409
  *
2378
2410
  * @param route - The player's answers and substituted sub-flow results, in order.
2379
2411
  * @param options - Walk options.
@@ -2395,32 +2427,62 @@ type RunnerApi = {
2395
2427
  from?: Bookmark;
2396
2428
  }): Promise<FlowState>;
2397
2429
  /**
2398
- * Makes a bookmark of the current rest point: the rest node plus the committed state.
2430
+ * Makes a bookmark of where the graph stands: the last rest node plus the committed state.
2431
+ * While a transit node waits at the gate for the answer of an effect (a `popup`, or any
2432
+ * descriptor with `answers`), the bookmark names that node instead. Its state is the one
2433
+ * committed when the node was entered: what the node wrote before the wait is not in it, the
2434
+ * node writes it again when it is restored. `rest` then names the rest point before the node.
2435
+ * While a transit node awaits anything else (an animation, a request) the bookmark still names
2436
+ * the last rest node.
2399
2437
  *
2400
2438
  * @returns The bookmark, ready for JSON.
2401
2439
  * @throws {Error} When the graph has no position yet.
2402
2440
  * @example
2403
2441
  * ```ts
2404
- * // A devtools button keeps the position while the board rests.
2405
- * const bookmark = app.flow.bookmark();
2406
- * bookmark.path; // "board/awaitIntent"
2407
- * JSON.stringify(bookmark); // plain data: path, input, player, session, rng and the graph hash
2442
+ * // A devtools button keeps the position. The board rests: the bookmark names its rest node.
2443
+ * const atBoard = app.flow.bookmark(); // plain data, ready for JSON.stringify
2444
+ * atBoard.path; // "board/awaitIntent"
2445
+ * // The mini game with its info popup open: the bookmark names the node that waits.
2446
+ * const atPopup = app.flow.bookmark();
2447
+ * atPopup.path; // "info/show"
2448
+ * atPopup.rest; // { path: "home", input: null }: the rest point before it
2408
2449
  * ```
2409
2450
  */
2410
2451
  bookmark(): Bookmark;
2411
2452
  /**
2412
- * Replaces the state with the bookmark's and enters its node. A checkpoint is always
2413
- * accepted; any other rest node only while the graph is unchanged.
2453
+ * Replaces the state with the bookmark's and enters its node. A rest checkpoint is always
2454
+ * accepted; any other node only while the graph is unchanged. A transit node is never taken
2455
+ * for a checkpoint: when the graph changed, such a bookmark is entered at its `rest` point
2456
+ * instead, if that is a checkpoint, and the log gets `flow:restore-fell-back`. A renamed or
2457
+ * removed node is a changed graph too: a bookmark with `rest` whose path names no node takes
2458
+ * the same fallback.
2459
+ *
2460
+ * A rest node: the promise resolves when the node is entered, before its scene stage ran. A
2461
+ * transit node runs again from its first line, live, with the bookmark's input, so its effects
2462
+ * before the gate play again; the promise resolves when its gate is open, or at the rest node
2463
+ * or the end of the loop it reaches without one. A node that does none of these keeps the
2464
+ * promise pending. An open gate is not a drawn popup: the popup shows on the next frame. And
2465
+ * `setMode` still throws there, since the graph waits inside a transit node.
2466
+ *
2467
+ * The promise also resolves when the graph stops before the bookmark was entered: `onStop`
2468
+ * ended the loop, or `run()` rejected. Nothing is entered then and the state is not replaced.
2469
+ * `state()` shows whether it was: its `path` is the node the graph stands at.
2414
2470
  *
2415
2471
  * @param bookmark - The bookmark to enter.
2416
- * @returns A promise that resolves once the graph rests at the bookmark's node.
2417
- * @throws {Error} When the bookmark names no rest node of this graph, when the graph changed
2418
- * since a bookmark of a plain rest node, and before `run()`.
2472
+ * @returns A promise that resolves once a rest node is entered, or a transit node waits at
2473
+ * its gate. It also resolves when the graph stops first, with nothing entered: read
2474
+ * `state()` to tell.
2475
+ * @throws {Error} When the bookmark names no node of this graph and has no `rest`, when the
2476
+ * graph changed since the bookmark and neither it nor its `rest` point is a rest checkpoint,
2477
+ * and before `run()`.
2419
2478
  * @example
2420
2479
  * ```ts
2421
2480
  * // The next session opens where the last one stopped: state and position come back together.
2422
- * await app.flow.restore(bookmark);
2481
+ * await app.flow.restore(atBoard);
2423
2482
  * app.flow.state().path; // "board/awaitIntent"
2483
+ * // A bookmark the mini game took at its info popup: the popup waits again when this resolves.
2484
+ * await app.flow.restore(atPopup);
2485
+ * app.flow.state().pending.gate; // ["ok", "close"], and state().path is "info/show"
2424
2486
  * ```
2425
2487
  */
2426
2488
  restore(bookmark: Bookmark): Promise<void>;
@@ -2468,7 +2530,9 @@ type RunnerApi = {
2468
2530
  */
2469
2531
  history(): readonly JournalEntry[];
2470
2532
  /**
2471
- * Switches between live and fast mode. Legal before `run()` and while the graph rests.
2533
+ * Switches between live and fast mode. Legal before `run()` and while the graph rests. A
2534
+ * transit node that waits at the gate for a popup does not rest: it throws there too, also
2535
+ * right after a `restore` into such a node.
2472
2536
  *
2473
2537
  * @param mode - `"live"` or `"fast"`.
2474
2538
  * @throws {Error} When a transit node is running.
@@ -2773,7 +2837,12 @@ type Events$3 = {
2773
2837
  };
2774
2838
  index: number;
2775
2839
  now: number;
2776
- }; /** The graph reached a rest node. */
2840
+ };
2841
+ /**
2842
+ * The graph reached a rest node, or entered the node of a bookmark. The second also happens
2843
+ * for a transit node that waited at the gate: `path` is then no rest node and `checkpoint` is
2844
+ * false.
2845
+ */
2777
2846
  "flow:rest": {
2778
2847
  path: string;
2779
2848
  checkpoint: boolean;
@@ -1,5 +1,5 @@
1
- import { A as Api$1, B as ComponentType, D as FilterSlot, H as Entity, O as Point, R as ComponentHandle, Sn as Require, _ as PixiFilter, b as PixiTexture, dt as Api, h as Api$2, s as Events, t as Api$3, v as PixiModule, xn as Events$1, y as PixiSprite } from "./types-C6y8EfEB.mjs";
2
- import { x as AnimApi } from "./types-B3T2X6To.mjs";
1
+ import { A as Api$1, B as ComponentType, D as FilterSlot, H as Entity, O as Point, R as ComponentHandle, Sn as Require, _ as PixiFilter, b as PixiTexture, dt as Api, h as Api$2, s as Events, t as Api$3, v as PixiModule, xn as Events$1, y as PixiSprite } from "./types-BWDESfx2.mjs";
2
+ import { x as AnimApi } from "./types-oQoQEj-y.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
  import { PluginCtx } from "@moku-labs/core";
5
5
 
@@ -1,5 +1,5 @@
1
- import { Xt as Json, an as Snapshot } from "./types-C6y8EfEB.mjs";
2
- import { t as HeadlessApp } from "./headless-Dgxvp6Ym.mjs";
1
+ import { Xt as Json, an as Snapshot } from "./types-BWDESfx2.mjs";
2
+ import { t as HeadlessApp } from "./headless-IfD6_JMH.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
 
5
5
  //#region src/plugins/flow/doors/types.d.ts
@@ -1,5 +1,5 @@
1
- import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, It as TypeTag, L as AnyComponentValue, Lt as Descriptor, R as ComponentHandle, Sn as Require, Xt as Json, Yt as Events$2, at as NumericFields, ct as TrackOptions, dn as Events$3, dt as Api$1, et as Ease, h as Api$5, hn as Api, it as MotionHandle, lt as TrackSegment, nt as LayerSpec, q as Owner, qt as Api$4, s as Events$4, t as Api$3, tt as LayerSort, xn as Events$5 } from "./types-C6y8EfEB.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-CmuwAjkV.mjs";
1
+ import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, It as TypeTag, L as AnyComponentValue, Lt as Descriptor, R as ComponentHandle, Sn as Require, Xt as Json, Yt as Events$2, at as NumericFields, ct as TrackOptions, dn as Events$3, dt as Api$1, et as Ease, h as Api$5, hn as Api, it as MotionHandle, lt as TrackSegment, nt as LayerSpec, q as Owner, qt as Api$4, s as Events$4, t as Api$3, tt as LayerSort, xn as Events$5 } from "./types-BWDESfx2.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-BArxen_M.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";
@@ -1297,9 +1297,11 @@ type State$2 = {
1297
1297
  /** Scene id to its declaration, filled in `onStart` from `flow.features`. */scenes: Map<string, SceneDefinition>; /** Id of the scene that is mounted now. */
1298
1298
  current: string | undefined;
1299
1299
  /**
1300
- * The scene the next rest node without its own `scene` mounts: the one a transit node named in
1301
- * fast mode, or the one the restore door expected with `expect`. A rest node clears it; an over
1302
- * rest node takes it only while nothing is mounted.
1300
+ * The scene the next node without its own `scene` mounts, at a rest node or at a transit node
1301
+ * in live mode: the one a transit node named in fast mode, or the one the restore door expected
1302
+ * with `expect`. A rest node clears it; a transit node clears it only when it took it; an over
1303
+ * node takes it and clears it, whatever is mounted, except an over transit node of a fast walk,
1304
+ * which leaves it alone.
1303
1305
  */
1304
1306
  pending: string | undefined;
1305
1307
  owner: SceneOwner; /** The remover of the `onEnter` callback: set in `onStart`, run in `onStop`. */
@@ -1348,19 +1350,24 @@ type ScenesApi = {
1348
1350
  */
1349
1351
  current(): string | undefined;
1350
1352
  /**
1351
- * Names the scene the next rest node without its own `scene` mounts. The restore door calls it
1352
- * with the scene of a bookmark, so a bookmark at a popup comes back with the scene under it.
1353
- * An over rest node takes the expected scene only while nothing is mounted; it never switches a
1354
- * mounted one.
1353
+ * Names the scene the next node without its own `scene` mounts. A rest node reads it, and so
1354
+ * does a transit node in live mode; a transit node of a fast walk leaves it for the rest point.
1355
+ * The restore door calls it with the scene of a bookmark, so a bookmark at a popup comes back
1356
+ * with the scene under it, also when the popup is a transit node that waits for its answer:
1357
+ * the scene of such a popup is mounted before `flow.restore` resolves.
1358
+ * An over node takes the expected scene too, whatever is mounted, so a popup bookmark restored
1359
+ * on a page that shows another scene switches to the scene of the bookmark. With nothing
1360
+ * expected an over node never switches. An over transit node of a fast walk mounts nothing and
1361
+ * keeps the record.
1355
1362
  *
1356
1363
  * @param id - Id of a scene a feature declared.
1357
1364
  * @throws {Error} For an id no feature declared, also before `onStart` filled the registry.
1358
1365
  * @example
1359
1366
  * ```ts
1360
- * // A fresh page restores a bookmark taken at the Settings popup over Home.
1367
+ * // A fresh page restores a bookmark taken at the Settings popup over Home, a transit node
1368
+ * // that waits for its answer.
1361
1369
  * app.scenes.expect("home");
1362
- * await app.flow.restore(bookmark);
1363
- * await app.flow.walk([]); // the scene stage runs after restore resolves
1370
+ * await app.flow.restore(bookmark); // resolves when the popup's gate is open
1364
1371
  * app.scenes.current(); // "home"
1365
1372
  * ```
1366
1373
  */
package/dist/visual.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { A as Api$1, h as Api, ln as Api$3, qt as Api$2 } from "./types-C6y8EfEB.mjs";
2
- import { r as UiApi, x as AnimApi } from "./types-B3T2X6To.mjs";
3
- import { A as InputApi } from "./types-CmuwAjkV.mjs";
4
- import { r as Repro } from "./headless-Dgxvp6Ym.mjs";
5
- import { a as InputOf, r as ControlApp } from "./types-D57SntUt.mjs";
6
- import { t as commands } from "./commands-BrgeMP5y.mjs";
1
+ import { A as Api$1, h as Api, ln as Api$3, qt as Api$2 } from "./types-BWDESfx2.mjs";
2
+ import { r as UiApi, x as AnimApi } from "./types-oQoQEj-y.mjs";
3
+ import { A as InputApi } from "./types-BArxen_M.mjs";
4
+ import { r as Repro } from "./headless-IfD6_JMH.mjs";
5
+ import { a as InputOf, r as ControlApp } from "./types-InZGjvkI.mjs";
6
+ import { t as commands } from "./commands-Cpn2ga7s.mjs";
7
7
 
8
8
  //#region src/visual/types.d.ts
9
9
  /** The base commands of the `/control` door, keyed by their short names. */
package/dist/visual.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as read, t as sources } from "./sources-Duzj6lhV.mjs";
2
- import { n as commands, t as run } from "./run-wYty_Lwo.mjs";
2
+ import { n as commands, t as run } from "./run-Bf2hKyjZ.mjs";
3
3
  import path from "node:path";
4
4
  import { createBrandConsole } from "@moku-labs/common/cli";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
package/llms.txt CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 2D puzzle game engine on TypeScript, built as a Layer-2 framework on @moku-labs/core. The game is a deterministic graph of business logic. State commits only on edges. Position is data. Player input and world events are answers a node waits for. The screen is a projection of committed state. V1 is the logic plugins `time`, `lifecycle`, `model`, `clock`, `flow` and a headless testing entry; V2 adds the screen plugins `world`, `renderer`, `input`, `assets`, `scenes`, a Pixi v8 renderer loaded lazily, and the `@moku-labs/game/assets` entry that generates typed asset keys. V3 adds the interface plugins `anim`, `i18n`, `text`, `ui` (`screen` lists all nine) and the opt-in `audio`: choreographies as data, strings as data, text from MSDF fonts, screens in JSX laid out by Yoga, sound as an effect a node awaits. V4 adds two doors for the editor: `@moku-labs/game/inspect` reads a running game, `@moku-labs/game/control` drives a dev build. V5 adds the opt-in plugin `effects`: particles and WGSL/GLSL filters as components. V6 adds the opt-in plugin `platform`: the Back button, haptics and keep-awake through a provider the game passes. The game shell makes a game a folder of data: `@moku-labs/game/app` (`defineGameApp`, `config.ts` as `GameConfig`), the page `@moku-labs/game/app/page`, the system shell `@moku-labs/game/app/system` and the bin `moku-game` that serves, builds, packages and packs the folder. The package is published on npm as `@moku-labs/game`, and this file ships inside it.
4
4
 
5
- Facts: Bun only. ESM only. `"sideEffects"` lists the root entry `./dist/index.mjs` only, every other entry has none. `pixi.js` `^8.0.0` is a peer dependency, no V1 code imports it. `playwright-core` (visual tests), `sharp` (the asset pack), `typescript` (the project index), `@moku-labs/system` (the system shell and the store save) and `@moku-labs/native` (`moku-game native`) are optional peers. Node >= 24, Bun >= 1.3.14. TypeScript strict with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`. The engine ships no genre rules: no merge, no match-3, no physics. A game brings its own pure rule functions.
5
+ Facts: Bun only. ESM only. `"sideEffects"` lists the root entry `./dist/index.mjs` only, every other entry has none. `pixi.js` `^8.0.0` is a peer dependency, no V1 code imports it. `playwright-core` (visual tests), `sharp` (the asset pack), `typescript` (the project index), `@moku-labs/system` (the system shell and the store save) and `@moku-labs/native` (`moku-game native`) are optional peers. Node >= 24, Bun >= 1.4.2. TypeScript strict with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`. The engine ships no genre rules: no merge, no match-3, no physics. A game brings its own pure rule functions.
6
6
 
7
7
  ## Entry points
8
8
 
@@ -474,14 +474,15 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
474
474
  - `fx.handle(kind, handler, options?: { runInFast?: boolean }): () => void` registers the single handler of one effect kind. `fx.dispatch(descriptor)` is fire-and-forget. An awaited effect without a handler resolves `undefined` at once.
475
475
  - `features.register(name, description)`, `features.all()`, `features.contributions(slotName)`.
476
476
  - `FlowState` is `{ running, path, stack, pending: { fx?, gate? }, mode }`.
477
- - `Bookmark` is `{ path, input, player, session, rng, graph, scene? }`. Serialisable. `scene` is written by the `game.bookmark` door and read by `game.restore`; `flow.restore` ignores it. `restore` needs a running graph and the path of a rest node. A checkpoint is always accepted. Any other rest node is accepted only when the graph did not change since the bookmark.
477
+ - `Bookmark` is `{ path, input, player, session, rng, graph, scene?, rest? }`. Serialisable. `scene` is written by the `game.bookmark` door and read by `game.restore`; `flow.restore` ignores it. `path` names a rest node, or the transit node that waits at the gate for an effect that takes answers (a `popup`, or a hand-written descriptor with `answers`); `rest: { path, input }` then names the rest point before it (left out when the graph started at that node or a restore entered it), and the state is the one committed when that node was entered. `restore` needs a running graph. A rest checkpoint is always accepted. Any other node is accepted only when the graph did not change since the bookmark; with another graph a transit bookmark is entered at `rest` instead when that is a checkpoint, and the log gets `flow:restore-fell-back`.
478
+ - `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.
478
479
  - Events: `flow:edge` `{ flow, node, outcome, payload, next, patches: { doc, session }, index, now }` after the commit of an edge. `flow:rest` `{ path, checkpoint }`. `flow:error` `{ path, error, rolledBackTo, retry }`.
479
480
  - Config: `mainFlow` default `undefined` and required before `run()`, `safeNode: string | undefined` default `undefined` which means the main flow's `start`, `retries` default `1`, `settleTimeoutMs` default `2000`, `journalLimit` default `500`.
480
481
 
481
482
  ### Screen plugins (V2, built)
482
483
 
483
484
  `world`, `renderer`, `input`, `assets`, `scenes`. Each has a README at `src/plugins/<name>/README.md`; the root README lists their APIs, events and config. Rendering: WebGPU preferred, with Pixi's WebGL fallback.
484
- - `app.scenes`: `current()` the mounted scene id, `expect(id)` the scene the next rest node without its own `scene` mounts (the restore door calls it; an unknown id throws).
485
+ - `app.scenes`: `current()` the mounted scene id, `expect(id)` the scene the next node without its own `scene` mounts, a rest node or a transit node in live mode (the restore door calls it; an unknown id throws). An `over` node takes it the same way, also over another mounted scene.
485
486
  - `resource(name, defaults)` clones plain JSON defaults into every world. `resource(name, () => value)` calls the factory once per world on first read, so the value may hold a `Map` or a function: `const Looks = resource("boardLooks", () => ({ shown: new Map<World.Entity, { look: string }>() }))`. A system keeps its own state there; `snapshot()` leaves a value that is not JSON out.
486
487
  - `app.world.projection.replace(spec)` swaps the spec of a registered projection; a mounted one runs its new `view` on the next frame. An unknown name throws. The caller is the dev hot swap.
487
488
 
@@ -592,7 +593,7 @@ Two subpaths, neither on the root (V4). An editor, MCP tools and e2e scripts rea
592
593
  - `@moku-labs/game/inspect`: `read(app, source, input?)` reads once; `watch(app, source, input, fn)` reads on the `signals` phase when the source changes and returns the stop function; `defineSource`; the frozen catalogue `sources`; the types `Source`, `InputSchema`, `InputOf`. Every source only reads, so the door is safe in a production build. Keys of `sources`: `graph`, `position`, `history`, `tainted`, `cheats`, `model`, `entities`, `projections`, `ui`, `locate`, `render`, `sounds`, `audioMuted`, `effects`, `assets`, `log`, `explain`, `diff`, `schema`, `at`; their ids are `game.<key>`. `locate` takes `{ key }` (a ui element) or `{ target }` (a view by projection key) and answers its box in CSS px, the numbers Playwright clicks. `at` takes `{ x, y }` in CSS px and lists the views under that point, topmost first. `explain` takes `{ entity }` and answers that entity in full, `undefined` for a stale id. `diff` takes `{ from, to }` frames and answers what changed per entity; it throws in a production build and for a frame outside the last 120. `schema` lists every component and tag type the world met, with the JSON kind of each field. `audioMuted` answers whether the master bus is muted. `sounds` throws `[game] The source game.sounds needs audioPlugin.`, `audioMuted` throws `[game] The source game.audioMuted needs audioPlugin.` and `effects` throws `[game] The source game.effects needs effectsPlugin.` when that plugin is not installed; the editor lists such a source as not installed.
593
594
  - `@moku-labs/game/control`: `run(app, command, input?)` resolves `{ value, state }`, `state` being `{ path, frame, tainted }`; `defineCommand`; `controlRefused`; the frozen catalogue `commands`; the types `Command`, `Ran`. Keys of `commands`: `answer`, `tap`, `drag`, `key`, `fill`, `walk`, `bookmark`, `restore`, `step`, `pause`, `resume`, `capture`, `debug`, `reducedMotion`, `timeScale`, `mute`, `trace`. `capture` resolves `{ png }`, and `{ png, legend }` with `legend: true`; `undefined` while the renderer is inert. `timeScale` takes `{ scale }`; 0 freezes game time while the frames keep running. `mute` takes `{ muted }`, mutes the master bus and answers the flag. Without `audioPlugin` it throws `[game] The command game.mute needs audioPlugin.` `trace` takes `{ path }`, a list of views by projection key, and answers whether the gate took the answer.
594
595
  - Commands run in dev builds only. Only `__MOKU_GAME_DEV__ === true` turns the dev build on: `moku-game dev` defines it `true` and sets the global in `.moku/dev.ts`, `moku-game build` defines it `false`; a game without the shell uses a bundler `define`; a test uses `vi.stubGlobal("__MOKU_GAME_DEV__", true)`. Otherwise `run` throws `[game] Control commands run in dev builds only.`
595
- - A command's effect is `read`, `route`, `cosmetic`, `cheat` or `raw`. A `cheat` or `raw` command taints the session and is journaled; read it with `sources.tainted` and `sources.cheats`. Of the base commands only `restore` is `raw`. `bookmark` answers `flow.bookmark()` plus `scene`, the mounted scene; `restore` calls `scenes.expect(scene)` before `flow.restore`, so a bookmark at a popup restores the scene under it.
596
+ - A command's effect is `read`, `route`, `cosmetic`, `cheat` or `raw`. A `cheat` or `raw` command taints the session and is journaled; read it with `sources.tainted` and `sources.cheats`. Of the base commands only `restore` is `raw`. `bookmark` answers `flow.bookmark()` plus `scene`, the mounted scene; `restore` calls `scenes.expect(scene)` before `flow.restore`, so a bookmark at a popup restores the scene under it, also on a page that shows another scene. A bookmark taken while an effect that takes answers waits names the waiting transit node (`info/show` in the mini game) and carries `rest`; `restore` of it answers once that node's gate is open, with `pending.gate` in its value, and the popup shows on the next frame.
596
597
  - A game's own: `defineSource({ id, title, input, changes, read })` with `changes` `"frame"`, `"commit"` or `"edge"`, and `defineCommand({ id, title, input, effect, run })`. The id is camelCase words joined by dots, at least two: `dice.rolls`. An input schema maps field names to `"string"`, `"number"`, `"boolean"` or `"json"`; a trailing `?` makes one optional. A command body starts with `if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();`. Keep them in `.dev` modules that only the dev page and the tests import: the editor's dev page (`preparePage` with agents) imports every `**/*.dev.ts`, `moku-game build` none.
597
598
 
598
599
  ## Project index
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -95,7 +95,7 @@
95
95
  },
96
96
  "engines": {
97
97
  "node": ">=24.0.0",
98
- "bun": ">=1.3.14"
98
+ "bun": ">=1.4.2"
99
99
  },
100
100
  "scripts": {
101
101
  "lint": "biome check . && eslint .",
@@ -160,7 +160,7 @@
160
160
  "@moku-labs/core": "1.7.1",
161
161
  "@moku-labs/native": "0.3.2",
162
162
  "@moku-labs/system": "0.3.1",
163
- "@types/bun": "1.3.14",
163
+ "@types/bun": "1.4.2",
164
164
  "@vitest/coverage-istanbul": "4.0.18",
165
165
  "eslint": "9.39.3",
166
166
  "eslint-config-biome": "2.1.3",