@moku-labs/game 0.8.0 → 0.10.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 (60) hide show
  1. package/README.md +88 -26
  2. package/bin/moku-game.mjs +4 -0
  3. package/dist/app/page.d.mts +41 -0
  4. package/dist/app/page.mjs +376 -0
  5. package/dist/app/system.d.mts +129 -0
  6. package/dist/app/system.mjs +307 -0
  7. package/dist/app.d.mts +37 -0
  8. package/dist/app.mjs +235 -0
  9. package/dist/assets-BHwTLVta.mjs +7391 -0
  10. package/dist/assets.d.mts +97 -33
  11. package/dist/assets.mjs +1 -7150
  12. package/dist/{capture-BX5Gfoty.mjs → capture-fpd83Mi5.mjs} +384 -1
  13. package/dist/cli.d.mts +70 -0
  14. package/dist/cli.mjs +1650 -0
  15. package/dist/{commands-C1kIfOG5.d.mts → commands-C4QlqijJ.d.mts} +5 -6
  16. package/dist/config-CdARrrhU.mjs +81 -0
  17. package/dist/control.d.mts +11 -10
  18. package/dist/control.mjs +3 -3
  19. package/dist/{define-BPpZcpxW.d.mts → define-CCc0ozKe.d.mts} +2 -2
  20. package/dist/{dev-wKLgOw7c.mjs → dev-Cc50Dk7H.mjs} +1 -1
  21. package/dist/{memory-CvgdnsQO.mjs → drafts-BawpSaVp.mjs} +1 -104
  22. package/dist/fake-MpzwZhIZ.mjs +87 -0
  23. package/dist/{headless-CvamCUcR.mjs → headless-BsNdJs9W.mjs} +1 -1
  24. package/dist/{headless-q98SwADR.d.mts → headless-G4nuZ-xz.d.mts} +1 -1
  25. package/dist/hot.d.mts +8 -8
  26. package/dist/hot.mjs +15 -14
  27. package/dist/index-xxSxKJJW.d.mts +1611 -0
  28. package/dist/index.d.mts +6 -1597
  29. package/dist/index.mjs +4 -34343
  30. package/dist/inspect-la_KkjCS.mjs +1 -0
  31. package/dist/inspect.d.mts +7 -8
  32. package/dist/inspect.mjs +3 -2
  33. package/dist/jsx-dev-runtime.d.mts +1 -1
  34. package/dist/jsx-runtime.d.mts +1 -1
  35. package/dist/lint.d.mts +55 -4
  36. package/dist/lint.mjs +999 -42
  37. package/dist/memory-CWlqhS7l.mjs +136 -0
  38. package/dist/memory-DwDhfFWK.d.mts +50 -0
  39. package/dist/project.d.mts +29 -13
  40. package/dist/project.mjs +1120 -631
  41. package/dist/{run-CUPSFM85.mjs → run-wYty_Lwo.mjs} +4 -4
  42. package/dist/{runtime-CvDZZg1S.d.mts → runtime-DOBYM6g4.d.mts} +1 -1
  43. package/dist/{input-z_35Jwfn.mjs → session-DgukhR_7.mjs} +20 -20
  44. package/dist/{sources-BBUd15vQ.mjs → sources-Duzj6lhV.mjs} +3 -3
  45. package/dist/src-DSl1eNB1.mjs +34366 -0
  46. package/dist/testing.d.mts +115 -34
  47. package/dist/testing.mjs +175 -70
  48. package/dist/{types-DlgsfOmW.d.mts → types-BM4shh8K.d.mts} +300 -263
  49. package/dist/{types-DgdMLZnV.d.mts → types-BUwDHKXm.d.mts} +1372 -1373
  50. package/dist/{types-DF5kkW-C.d.mts → types-CefgYzFt.d.mts} +4 -5
  51. package/dist/{types-CDSKa0vl.d.mts → types-DAdwEJMp.d.mts} +2 -2
  52. package/dist/{types-BWzCSRM2.d.mts → types-ZQLYXo5X.d.mts} +2983 -20
  53. package/dist/types-hQgI_wrq.d.mts +544 -0
  54. package/dist/{visual-DUvvPPCt.mjs → visual-dMW8iqAE.mjs} +8 -4
  55. package/dist/visual.d.mts +10 -11
  56. package/dist/visual.mjs +5 -5
  57. package/llms.txt +170 -57
  58. package/package.json +30 -2
  59. package/dist/registry-DlpRCibU.mjs +0 -384
  60. package/dist/types-ERb2Y9U5.d.mts +0 -2967
@@ -0,0 +1,136 @@
1
+ import { t as applyTo } from "./drafts-BawpSaVp.mjs";
2
+ //#region src/plugins/clock/system.ts
3
+ /**
4
+ * Tells whether a handle was produced by this source.
5
+ *
6
+ * @param handle - Handle given back to `clearTimer`.
7
+ * @returns `true` when the handle carries a platform timer.
8
+ * @example
9
+ * ```ts
10
+ * isSystemTimer({ timer: 7 }); // true
11
+ * isSystemTimer(7); // false: a handle of another source is ignored
12
+ * ```
13
+ */
14
+ function isSystemTimer(handle) {
15
+ return typeof handle === "object" && handle !== null && "timer" in handle;
16
+ }
17
+ /**
18
+ * Creates the system source: epoch milliseconds from the device clock and real timers.
19
+ *
20
+ * @returns A source reading `Date.now` and scheduling with `setTimeout`.
21
+ */
22
+ function systemSource() {
23
+ return {
24
+ now: () => Date.now(),
25
+ setTimer: (callback, delayMs) => ({ timer: setTimeout(callback, delayMs) }),
26
+ clearTimer: (handle) => {
27
+ if (!isSystemTimer(handle)) return;
28
+ clearTimeout(handle.timer);
29
+ }
30
+ };
31
+ }
32
+ //#endregion
33
+ //#region src/plugins/model/store/providers/memory.ts
34
+ /** Seed of a save built by `saveOf` when the caller does not pin one. */
35
+ const defaultSeed = 1;
36
+ /** The base a patch applies to when the provider was never handed a document. */
37
+ const noDocument = {};
38
+ /**
39
+ * Creates an in-memory provider that keeps what it was committed and records every call.
40
+ * It is the default when a game configures no `playerProvider`, and the double every test uses:
41
+ * `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
42
+ * long as the provider does, so a second app over the same instance reads what the first one
43
+ * wrote, and nothing survives the process.
44
+ *
45
+ * `load()` reads the fixture, or everything committed since; without either, the player is new.
46
+ * `commitDurable()` resolves at once: there is no disk behind it.
47
+ *
48
+ * @param fixture - The save `load()` starts from. Omitted: a new player.
49
+ * @param fixture.state - The saved document.
50
+ * @param fixture.version - Schema version of the saved document.
51
+ * @returns A provider that keeps its document and records its calls.
52
+ * @example
53
+ * ```ts
54
+ * // A new player is saved at once. One roll reaches the provider at the next rest node.
55
+ * const provider = memory();
56
+ * const game = await createHeadless(createGame(provider));
57
+ *
58
+ * await game.walk([{ at: "home", intent: "roll" }]);
59
+ * provider.calls.map(call => call.method); // ["load", "commit", "commit"]
60
+ * ```
61
+ */
62
+ function memory(fixture) {
63
+ const calls = [];
64
+ const held = {
65
+ document: fixture?.state,
66
+ version: fixture?.version ?? 0
67
+ };
68
+ /**
69
+ * Applies the patches of one commit to the held document.
70
+ *
71
+ * @param patches - Doc patches since the last commit.
72
+ * @param version - Schema version this build writes.
73
+ * @throws {Error} When a patch names a path the held document does not have.
74
+ */
75
+ const hold = (patches, version) => {
76
+ held.document = applyTo(held.document ?? noDocument, patches);
77
+ held.version = version;
78
+ };
79
+ return {
80
+ calls,
81
+ load: async () => {
82
+ calls.push({ method: "load" });
83
+ const document = held.document;
84
+ if (document === void 0) return null;
85
+ return {
86
+ state: document,
87
+ version: held.version
88
+ };
89
+ },
90
+ commit: (patches, version) => {
91
+ calls.push({
92
+ method: "commit",
93
+ patches,
94
+ version
95
+ });
96
+ hold(patches, version);
97
+ },
98
+ commitDurable: async (patches, txId, version) => {
99
+ calls.push({
100
+ method: "commitDurable",
101
+ patches,
102
+ txId,
103
+ version
104
+ });
105
+ hold(patches, version);
106
+ },
107
+ flush: async () => {
108
+ calls.push({ method: "flush" });
109
+ }
110
+ };
111
+ }
112
+ /**
113
+ * Builds a save document from a player tree, with no rng streams drawn yet.
114
+ * Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
115
+ *
116
+ * @param player - Player tree of the save.
117
+ * @param seed - Rng seed of the save.
118
+ * @returns The save document.
119
+ * @example
120
+ * ```ts
121
+ * // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
122
+ * saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
123
+ * const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
124
+ * ```
125
+ */
126
+ function saveOf(player, seed = defaultSeed) {
127
+ return {
128
+ player,
129
+ rng: {
130
+ seed,
131
+ streams: {}
132
+ }
133
+ };
134
+ }
135
+ //#endregion
136
+ export { saveOf as n, systemSource as r, memory as t };
@@ -0,0 +1,50 @@
1
+ import { Xt as Json, nn as ProviderCall, rn as SaveDoc, tn as PlayerStateProvider } from "./types-ZQLYXo5X.mjs";
2
+
3
+ //#region src/plugins/model/store/providers/memory.d.ts
4
+ /**
5
+ * Creates an in-memory provider that keeps what it was committed and records every call.
6
+ * It is the default when a game configures no `playerProvider`, and the double every test uses:
7
+ * `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
8
+ * long as the provider does, so a second app over the same instance reads what the first one
9
+ * wrote, and nothing survives the process.
10
+ *
11
+ * `load()` reads the fixture, or everything committed since; without either, the player is new.
12
+ * `commitDurable()` resolves at once: there is no disk behind it.
13
+ *
14
+ * @param fixture - The save `load()` starts from. Omitted: a new player.
15
+ * @param fixture.state - The saved document.
16
+ * @param fixture.version - Schema version of the saved document.
17
+ * @returns A provider that keeps its document and records its calls.
18
+ * @example
19
+ * ```ts
20
+ * // A new player is saved at once. One roll reaches the provider at the next rest node.
21
+ * const provider = memory();
22
+ * const game = await createHeadless(createGame(provider));
23
+ *
24
+ * await game.walk([{ at: "home", intent: "roll" }]);
25
+ * provider.calls.map(call => call.method); // ["load", "commit", "commit"]
26
+ * ```
27
+ */
28
+ declare function memory(fixture?: {
29
+ state: SaveDoc;
30
+ version: number;
31
+ }): PlayerStateProvider & {
32
+ calls: ProviderCall[];
33
+ };
34
+ /**
35
+ * Builds a save document from a player tree, with no rng streams drawn yet.
36
+ * Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
37
+ *
38
+ * @param player - Player tree of the save.
39
+ * @param seed - Rng seed of the save.
40
+ * @returns The save document.
41
+ * @example
42
+ * ```ts
43
+ * // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
44
+ * saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
45
+ * const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
46
+ * ```
47
+ */
48
+ declare function saveOf(player: Json, seed?: number): SaveDoc;
49
+ //#endregion
50
+ export { saveOf as n, memory as t };
@@ -4,8 +4,9 @@
4
4
  * it holds, the anchors of a key, the fresh lines `find` answers and the handle itself. The index
5
5
  * maps every engine id of a game (flows, nodes, features, scenes, projections, emitters, text
6
6
  * styles, styles, components and JSX keys) to anchors: a path plus a binding, a key or a component. It never
7
- * holds a line number; `find` reads the line from the file as it is on disk at the call. No type
8
- * here names a `typescript` type, so the shipped declarations need no TypeScript.
7
+ * holds a line number; `find` reads the line from the file as it is on disk at the call. Names are
8
+ * followed through relative imports and the tsconfig `paths` of the game. No type here names a
9
+ * `typescript` type, so the shipped declarations need no TypeScript.
9
10
  */
10
11
  /**
11
12
  * What `openProject` reads. Paths are root-relative POSIX; the root itself is resolved against the
@@ -15,11 +16,18 @@
15
16
  * ```ts
16
17
  * // The editor opens the mini game with the default manifest and a slower debounce.
17
18
  * const options: ProjectOptions = { root: "tests/fixtures/mini-game", debounceMs: 120 };
19
+ * // A v15 game whose aliases live in a second tsconfig.
20
+ * const game: ProjectOptions = { root: "games/orders", tsconfig: "tsconfig.game.json" };
18
21
  * ```
19
22
  */
20
23
  type ProjectOptions = {
21
24
  /** The folder of the game. A path that is not a directory rejects `openProject`. */root: string; /** The asset manifest, root-relative. Default `manifest.json`, the scanner's default. */
22
- manifest?: string; /** The quiet period, in milliseconds, that closes a batch of file events. Default 75. */
25
+ manifest?: string;
26
+ /**
27
+ * The tsconfig whose `paths` the index follows, root-relative. Default `tsconfig.json`. A path
28
+ * that leaves the root or a file that does not parse rejects `openProject`.
29
+ */
30
+ tsconfig?: string; /** The quiet period, in milliseconds, that closes a batch of file events. Default 75. */
23
31
  debounceMs?: number;
24
32
  };
25
33
  /**
@@ -68,12 +76,15 @@ type Anchor = {
68
76
  * ```ts
69
77
  * index.symbols["flow:infoPopup"]; // { def: [{ path: "flows/info.ts", binding: "infoFlow" }] }
70
78
  * index.files["nodes/count.ts"]?.state; // "ok"
79
+ * // A game whose tsconfig.json holds paths, such as tests/fixtures/layout-game.
80
+ * index.tsconfig; // "tsconfig.json"
71
81
  * ```
72
82
  */
73
83
  type ProjectIndex = {
74
84
  /** The shape of this object. A change of shape bumps it. */schemaVersion: 1; /** sha1 hex over the sorted lines `${path}\0${hash}\n` of every indexed file. */
75
85
  revision: string; /** The asset manifest, root-relative, when that file exists. */
76
- manifest?: string;
86
+ manifest?: string; /** The tsconfig, root-relative, when that file exists and holds `paths` the index follows. */
87
+ tsconfig?: string;
77
88
  /**
78
89
  * Every key: where it is defined (`def`), where its binding is used (`uses`, nodes, styles and
79
90
  * components, one level), and `conflict` when a key other than `jsx:` is defined twice.
@@ -150,12 +161,13 @@ type ProjectApi = {
150
161
  */
151
162
  readonly index: ProjectIndex;
152
163
  /**
153
- * Answers where a key is defined, with lines read from the file on disk now. A file whose bytes
154
- * changed since the index was built is parsed again for the answer; the index itself stays as
155
- * it is until the next watch batch. A JSX key the game reports at run time also finds the
156
- * patterns it was built from: exact keys first, then `{id}` and `{amountKey}` patterns filled
157
- * with a literal prop of the same name on their component (both the pattern and the prop come
158
- * back), then `*` patterns. An unknown key answers `[]`.
164
+ * Answers where a key is defined, with lines read from the file on disk now. A name is followed
165
+ * through relative imports and the tsconfig aliases, so `@features/home` reaches the file that
166
+ * declares the node. A file whose bytes changed since the index was built is parsed again for
167
+ * the answer; the index itself stays as it is until the next watch batch. A JSX key the game
168
+ * reports at run time also finds the patterns it was built from: exact keys first, then `{id}`
169
+ * and `{amountKey}` patterns filled with a literal prop of the same name on their component
170
+ * (both the pattern and the prop come back), then `*` patterns. An unknown key answers `[]`.
159
171
  *
160
172
  * @param key - An engine id: `node:infoPopup/count`, `flow:main`, `textStyle:ui.counter`, `jsx:infoPanelSpark`.
161
173
  * @returns One entry per place, in the order above; `broken` when the file does not parse now.
@@ -190,8 +202,9 @@ type ProjectApi = {
190
202
  watch(onIndex: (index: ProjectIndex, change: ProjectChange) => void): () => void;
191
203
  /**
192
204
  * Re-indexes one root-relative path now, for a caller that watches files itself. A deleted
193
- * file drops its entries; a path that is not a `.ts` or `.tsx` source of the index changes
194
- * nothing. The watch callers are not called.
205
+ * file drops its entries. The tsconfig, or a file it extends, reads the aliases again, and the
206
+ * index is rebuilt when names now follow them elsewhere. Any other path that is not a `.ts` or
207
+ * `.tsx` source of the index changes nothing. The watch callers are not called.
195
208
  *
196
209
  * @param path - The root-relative POSIX path of the file.
197
210
  * @returns The index after the file was read.
@@ -201,6 +214,8 @@ type ProjectApi = {
201
214
  * // The editor saved nodes/count.ts through its own file layer.
202
215
  * const index = await project.changed("nodes/count.ts");
203
216
  * index.files["nodes/count.ts"]?.state; // "ok"
217
+ * // It added a paths block to tsconfig.json: the keys behind the aliases join the index.
218
+ * (await project.changed("tsconfig.json")).tsconfig; // "tsconfig.json"
204
219
  * ```
205
220
  */
206
221
  changed(path: string): Promise<ProjectIndex>;
@@ -260,7 +275,8 @@ declare function runCli(argv: readonly string[], ui?: IndexUi): Promise<number>;
260
275
  * path), loads TypeScript, parses every source file and builds the index. Nothing is written to
261
276
  * disk and nothing is inferred from the working directory but the root itself.
262
277
  *
263
- * @param options - The game root, the manifest path and the quiet period of `watch`.
278
+ * @param options - The game root, the manifest path, the tsconfig whose `paths` the index follows
279
+ * (`tsconfig`, root-relative, `tsconfig.json` by default) and the quiet period of `watch`.
264
280
  * @returns The handle; close it when the game closes.
265
281
  * @throws {Error} When the root is missing or not a directory, or the `typescript` package is not
266
282
  * installed: `[game] The project index needs the "typescript" package.`