@moku-labs/game 0.9.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 (53) hide show
  1. package/README.md +81 -22
  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 +1 -1
  11. package/dist/assets.mjs +1 -7390
  12. package/dist/cli.d.mts +70 -0
  13. package/dist/cli.mjs +1650 -0
  14. package/dist/{commands-DuwCxK5t.d.mts → commands-C4QlqijJ.d.mts} +5 -5
  15. package/dist/config-CdARrrhU.mjs +81 -0
  16. package/dist/control.d.mts +11 -10
  17. package/dist/control.mjs +3 -3
  18. package/dist/{define-B6pHpdBi.d.mts → define-CCc0ozKe.d.mts} +2 -2
  19. package/dist/{dev-wKLgOw7c.mjs → dev-Cc50Dk7H.mjs} +1 -1
  20. package/dist/drafts-BawpSaVp.mjs +156 -0
  21. package/dist/fake-MpzwZhIZ.mjs +87 -0
  22. package/dist/{headless-SCQXJxQf.d.mts → headless-G4nuZ-xz.d.mts} +1 -1
  23. package/dist/hot.d.mts +4 -6
  24. package/dist/hot.mjs +8 -9
  25. package/dist/{index-BO9kZzY3.d.mts → index-xxSxKJJW.d.mts} +17 -17
  26. package/dist/index.d.mts +5 -5
  27. package/dist/index.mjs +1 -1
  28. package/dist/inspect-la_KkjCS.mjs +1 -0
  29. package/dist/inspect.d.mts +7 -7
  30. package/dist/inspect.mjs +3 -2
  31. package/dist/jsx-dev-runtime.d.mts +1 -1
  32. package/dist/jsx-runtime.d.mts +1 -1
  33. package/dist/lint.mjs +10 -6
  34. package/dist/memory-CWlqhS7l.mjs +136 -0
  35. package/dist/memory-DwDhfFWK.d.mts +50 -0
  36. package/dist/project.mjs +3 -3
  37. package/dist/{run-CofJEdYx.mjs → run-wYty_Lwo.mjs} +2 -2
  38. package/dist/{runtime-CdNJ0UBm.d.mts → runtime-DOBYM6g4.d.mts} +1 -1
  39. package/dist/{input-z_35Jwfn.mjs → session-DgukhR_7.mjs} +20 -20
  40. package/dist/{sources-DNnzyCYf.mjs → sources-Duzj6lhV.mjs} +2 -2
  41. package/dist/{src-536arXjV.mjs → src-DSl1eNB1.mjs} +14 -292
  42. package/dist/testing.d.mts +4 -50
  43. package/dist/testing.mjs +3 -87
  44. package/dist/{types-VZVWwqCg.d.mts → types-BM4shh8K.d.mts} +259 -259
  45. package/dist/{types-xXfyk56Q.d.mts → types-BUwDHKXm.d.mts} +1377 -1377
  46. package/dist/{types-CQueODzo.d.mts → types-CefgYzFt.d.mts} +2 -2
  47. package/dist/{types-DUMoYPYe.d.mts → types-DAdwEJMp.d.mts} +2 -2
  48. package/dist/{types-B0v5VVa3.d.mts → types-ZQLYXo5X.d.mts} +304 -304
  49. package/dist/types-hQgI_wrq.d.mts +544 -0
  50. package/dist/visual.d.mts +6 -6
  51. package/dist/visual.mjs +5 -5
  52. package/llms.txt +119 -43
  53. package/package.json +30 -2
@@ -0,0 +1,129 @@
1
+ import { tn as PlayerStateProvider } from "../types-ZQLYXo5X.mjs";
2
+ import { ot as PlatformProvider } from "../index-xxSxKJJW.mjs";
3
+ import { _ as SystemShell, d as ResolvedGameConfig, g as SystemName } from "../types-hQgI_wrq.mjs";
4
+ import { Back, Haptics, KeepAwake, Lifecycle, Store } from "@moku-labs/system";
5
+
6
+ //#region src/app/system.d.ts
7
+ /**
8
+ * What the bridge reads of the system app: the four capabilities, each one there only when the
9
+ * game names its plugin. A system app of `lifecyclePlugin`, `backPlugin`, `hapticsPlugin` and
10
+ * `keepAwakePlugin` has all of them.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const slice: SystemSlice = await createSystemApp(["haptics"], "moku-game"); // slice.haptics only
15
+ * ```
16
+ */
17
+ type SystemSlice = {
18
+ /** Pause and resume, from `@moku-labs/system/lifecycle`. */readonly lifecycle?: Lifecycle.LifecycleApi; /** The hardware Back press and the exit, from `@moku-labs/system/back`. */
19
+ readonly back?: Back.BackApi; /** Impact, outcome and selection ticks, from `@moku-labs/system/haptics`. */
20
+ readonly haptics?: Haptics.HapticsApi; /** The screen wake lock, from `@moku-labs/system/keep-awake`. */
21
+ readonly keepAwake?: KeepAwake.KeepAwakeApi;
22
+ };
23
+ /**
24
+ * What the store save reads of the system store: one read and one write.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const system = createApp({ plugins: [storePlugin] }); // createApp of @moku-labs/system
29
+ * const store: StoreSlice = system.store;
30
+ * ```
31
+ */
32
+ type StoreSlice = Pick<Store.StoreApi, "get" | "set">;
33
+ /**
34
+ * The system app of the game shell: the capabilities of the plugins the game names, the store
35
+ * among them, and the start and stop of the app.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const system: SystemApp = await createSystemApp(["lifecycle", "store"], "com.mokulabs.timber");
40
+ * await system.start(); // system.lifecycle and system.store answer from here on
41
+ * ```
42
+ */
43
+ type SystemApp = SystemSlice & {
44
+ /** Key-value persistence, from `@moku-labs/system/store`. */readonly store?: Store.StoreApi; /** Starts the system app: every capability picks its provider. */
45
+ readonly start: () => Promise<void>; /** Stops the system app: every capability lets go of its provider. */
46
+ readonly stop: () => Promise<void>;
47
+ };
48
+ /**
49
+ * Builds the engine's provider over the system app. On iOS `exit` answers `unsupported`, so Leave
50
+ * lands back on Home there, as on the web page. A capability the slice lacks is inert, as the
51
+ * platform plugin is without a provider: its subscriptions remove nothing and its calls do nothing.
52
+ *
53
+ * @param system - The system app, with the capabilities the game names.
54
+ * @returns The provider to pass as the `platform` seam of `game.screen()`.
55
+ * @example
56
+ * ```ts
57
+ * import { createApp as createSystem } from "@moku-labs/system";
58
+ * const system = createSystem({ plugins: [lifecyclePlugin, backPlugin, hapticsPlugin, keepAwakePlugin] });
59
+ * fromSystem(system).haptic("success"); // the shell plays system.haptics.notify("success")
60
+ * fromSystem({}).exit(); // does nothing: the game named no back capability
61
+ * ```
62
+ */
63
+ declare function fromSystem(system: SystemSlice): PlatformProvider;
64
+ /**
65
+ * Creates the `@moku-labs/system` app of the named plugins, in the fixed order `lifecycle`,
66
+ * `back`, `haptics`, `keepAwake`, `store`, each name once. The store is named after `namespace`.
67
+ * Each plugin is loaded by its own `import()`, so a page loads only the plugins its game names.
68
+ * The app is not started.
69
+ *
70
+ * @param names - The system plugins the game names, in any order.
71
+ * @param namespace - The store name: the native identifier, or `"moku-game"`.
72
+ * @returns The system app, not started.
73
+ * @throws {Error} When `@moku-labs/system` is not installed.
74
+ * @example
75
+ * ```ts
76
+ * // A shell of its own: the store named after the app, and the haptics.
77
+ * const system = await createSystemApp(["store", "haptics"], "com.mokulabs.timber");
78
+ * await system.start();
79
+ * await system.haptics?.selection(); // { ok: false, provider: "web", reason: "unsupported" } in iOS Safari
80
+ * ```
81
+ */
82
+ declare function createSystemApp(names: readonly SystemName[], namespace: string): Promise<SystemApp>;
83
+ /**
84
+ * Builds the save provider over the system store: idb on the web, the Tauri store in the native
85
+ * app. It holds the document it loaded and applies each commit's patches to it, then queues one
86
+ * write of the whole value, so the writes stay in commit order. A read the store refuses rejects
87
+ * the load: answering "new player" would overwrite a real save at the first commit. A write the
88
+ * store refuses is reported from `commit` and rejects `commitDurable`; the next write still goes.
89
+ *
90
+ * @param store - The system store.
91
+ * @param key - The store key of the save.
92
+ * @param report - Takes the problem of a write `commit` could not store.
93
+ * @returns The provider to pass as the `provider` seam of `game.screen()`.
94
+ * @example
95
+ * ```ts
96
+ * import { createApp as createSystem } from "@moku-labs/system";
97
+ * const system = createSystem({ plugins: [storePlugin], pluginConfigs: { store: { name: "com.mokulabs.timber" } } });
98
+ * await system.start();
99
+ * const save = storeSave(system.store, "save", problem => problems.push(problem));
100
+ * await save.load(); // null for a new player; rejects when the store cannot be read
101
+ * ```
102
+ */
103
+ declare function storeSave(store: StoreSlice, key: string, report: (problem: string) => void): PlayerStateProvider;
104
+ /**
105
+ * Builds the system shell from the resolved `config.ts`: the `@moku-labs/system` app of the names
106
+ * `config.system` lists, plus the store when `config.save` is `"store"`, in the fixed order. It
107
+ * hands the engine the platform provider over that app and, for a store save, the save provider.
108
+ * The page starts the shell before the game; this function does not start it. The generated
109
+ * `main.ts` passes the function itself to `startPage`, only when `config.ts` names a system
110
+ * plugin or the store save.
111
+ *
112
+ * @param config - The resolved `config.ts` of the game.
113
+ * @param report - Takes a problem the page reports later, such as a write the store refused.
114
+ * @returns The shell, not started.
115
+ * @throws {Error} When `@moku-labs/system` is not installed.
116
+ * @example
117
+ * ```ts
118
+ * // The in-memory .moku/build/main.ts of moku-game build, for a game whose config.ts names system plugins.
119
+ * import { startPage } from "@moku-labs/game/app/page";
120
+ * import { systemShell } from "@moku-labs/game/app/system";
121
+ * import game from "../../index.ts";
122
+ * import config from "../../config.ts";
123
+ *
124
+ * await startPage(game, config, { system: systemShell });
125
+ * ```
126
+ */
127
+ declare function systemShell(config: ResolvedGameConfig, report: (problem: string) => void): Promise<SystemShell>;
128
+ //#endregion
129
+ export { StoreSlice, SystemApp, SystemSlice, createSystemApp, fromSystem, storeSave, systemShell };
@@ -0,0 +1,307 @@
1
+ import { t as applyTo } from "../drafts-BawpSaVp.mjs";
2
+ //#region src/app/system.ts
3
+ /** The system plugins in the order the shell composes them, whatever the order of `config.ts`. */
4
+ const systemOrder = [
5
+ "lifecycle",
6
+ "back",
7
+ "haptics",
8
+ "keepAwake",
9
+ "store"
10
+ ];
11
+ /** One lazy import per capability plugin: the page loads only the plugins the game names. */
12
+ const capabilityLoaders = {
13
+ lifecycle: () => import("@moku-labs/system/lifecycle").then((module) => module.lifecyclePlugin),
14
+ back: () => import("@moku-labs/system/back").then((module) => module.backPlugin),
15
+ haptics: () => import("@moku-labs/system/haptics").then((module) => module.hapticsPlugin),
16
+ keepAwake: () => import("@moku-labs/system/keep-awake").then((module) => module.keepAwakePlugin)
17
+ };
18
+ /**
19
+ * Loads the store plugin, by its own lazy import.
20
+ *
21
+ * @returns The store plugin.
22
+ */
23
+ function loadStore() {
24
+ return import("@moku-labs/system/store").then((module) => module.storePlugin);
25
+ }
26
+ /** The store namespace of a game with no native identifier. */
27
+ const defaultNamespace = "moku-game";
28
+ /** The store key of the save, inside the game's namespace. */
29
+ const saveKey = "save";
30
+ /**
31
+ * Removes nothing: the remover of a subscription the shell cannot make, because the game did not
32
+ * name the capability.
33
+ */
34
+ function removeNothing() {}
35
+ /**
36
+ * Plays one tick of the engine with the capability of its kind: the three impacts, the three
37
+ * outcome patterns, and the selection tick.
38
+ *
39
+ * @param haptics - The haptics capability of the system app.
40
+ * @param kind - One of the seven kinds of `HAPTIC_KINDS`.
41
+ * @returns What the shell answered.
42
+ */
43
+ function play(haptics, kind) {
44
+ switch (kind) {
45
+ case "light":
46
+ case "medium":
47
+ case "heavy": return haptics.impact(kind);
48
+ case "success":
49
+ case "warning":
50
+ case "error": return haptics.notify(kind);
51
+ case "selection": return haptics.selection();
52
+ }
53
+ }
54
+ /**
55
+ * Asks the shell without waiting for the answer. A capability answers with a `SystemResult`, never
56
+ * a throw, and the provider of the engine returns nothing: a tick the shell cannot play (iOS
57
+ * Safari, a desktop) is an honest `unsupported`, not an error of the game. A capability the game
58
+ * did not name is not asked.
59
+ *
60
+ * @param capability - The capability, or `undefined` when the game did not name it.
61
+ * @param ask - The capability call.
62
+ */
63
+ function send(capability, ask) {
64
+ if (capability !== void 0) ask(capability);
65
+ }
66
+ /**
67
+ * Subscribes through a capability, or subscribes nothing when the game did not name it.
68
+ *
69
+ * @param capability - The capability, or `undefined` when the game did not name it.
70
+ * @param listen - The subscription; it returns its remover.
71
+ * @returns The remover of the subscription, or one that removes nothing.
72
+ */
73
+ function subscribe(capability, listen) {
74
+ return capability === void 0 ? removeNothing : listen(capability);
75
+ }
76
+ /**
77
+ * Builds the engine's provider over the system app. On iOS `exit` answers `unsupported`, so Leave
78
+ * lands back on Home there, as on the web page. A capability the slice lacks is inert, as the
79
+ * platform plugin is without a provider: its subscriptions remove nothing and its calls do nothing.
80
+ *
81
+ * @param system - The system app, with the capabilities the game names.
82
+ * @returns The provider to pass as the `platform` seam of `game.screen()`.
83
+ * @example
84
+ * ```ts
85
+ * import { createApp as createSystem } from "@moku-labs/system";
86
+ * const system = createSystem({ plugins: [lifecyclePlugin, backPlugin, hapticsPlugin, keepAwakePlugin] });
87
+ * fromSystem(system).haptic("success"); // the shell plays system.haptics.notify("success")
88
+ * fromSystem({}).exit(); // does nothing: the game named no back capability
89
+ * ```
90
+ */
91
+ function fromSystem(system) {
92
+ return {
93
+ onPause: (fn) => subscribe(system.lifecycle, (lifecycle) => lifecycle.onPause(fn)),
94
+ onResume: (fn) => subscribe(system.lifecycle, (lifecycle) => lifecycle.onResume(fn)),
95
+ onBack: (fn) => subscribe(system.back, (back) => back.onPress(fn)),
96
+ haptic: (kind) => send(system.haptics, (haptics) => play(haptics, kind)),
97
+ keepAwake: (on) => send(system.keepAwake, (keepAwake) => keepAwake.set(on)),
98
+ exit: () => send(system.back, (back) => back.exit())
99
+ };
100
+ }
101
+ /**
102
+ * Loads `@moku-labs/system` and the plugins of the names, all at once.
103
+ *
104
+ * @param names - The system plugins, in the fixed order.
105
+ * @returns The `createApp` of the package, the capability plugins in the order of the names, and
106
+ * the store plugin when the names hold it.
107
+ * @throws {Error} When the package is not installed.
108
+ */
109
+ async function loadSystem(names) {
110
+ const capabilities = names.filter((name) => name !== "store");
111
+ try {
112
+ const [system, store, ...plugins] = await Promise.all([
113
+ import("@moku-labs/system"),
114
+ names.includes("store") ? loadStore() : void 0,
115
+ ...capabilities.map((name) => capabilityLoaders[name]())
116
+ ]);
117
+ return {
118
+ createApp: system.createApp,
119
+ capabilities: plugins,
120
+ store
121
+ };
122
+ } catch (error) {
123
+ throw new Error("[game] config.system needs @moku-labs/system.\n Install it: bun add @moku-labs/system@^0.3.1.", { cause: error });
124
+ }
125
+ }
126
+ /**
127
+ * Creates the `@moku-labs/system` app of the named plugins, in the fixed order `lifecycle`,
128
+ * `back`, `haptics`, `keepAwake`, `store`, each name once. The store is named after `namespace`.
129
+ * Each plugin is loaded by its own `import()`, so a page loads only the plugins its game names.
130
+ * The app is not started.
131
+ *
132
+ * @param names - The system plugins the game names, in any order.
133
+ * @param namespace - The store name: the native identifier, or `"moku-game"`.
134
+ * @returns The system app, not started.
135
+ * @throws {Error} When `@moku-labs/system` is not installed.
136
+ * @example
137
+ * ```ts
138
+ * // A shell of its own: the store named after the app, and the haptics.
139
+ * const system = await createSystemApp(["store", "haptics"], "com.mokulabs.timber");
140
+ * await system.start();
141
+ * await system.haptics?.selection(); // { ok: false, provider: "web", reason: "unsupported" } in iOS Safari
142
+ * ```
143
+ */
144
+ async function createSystemApp(names, namespace) {
145
+ const { createApp, capabilities, store } = await loadSystem(systemOrder.filter((name) => names.includes(name)));
146
+ if (store === void 0) return createApp({ plugins: capabilities });
147
+ return createApp({
148
+ plugins: [...capabilities, store],
149
+ pluginConfigs: { store: { name: namespace } }
150
+ });
151
+ }
152
+ /**
153
+ * Names why the store said no: the reason, and the store's own words when it gave some.
154
+ *
155
+ * @param failure - The failed answer of the store.
156
+ * @returns The reason, for an error message.
157
+ * @example
158
+ * ```ts
159
+ * failureText({ ok: false, provider: "tauri", reason: "denied", message: "not allowed" }); // "denied, not allowed"
160
+ * ```
161
+ */
162
+ function failureText(failure) {
163
+ return failure.message === void 0 ? failure.reason : `${failure.reason}, ${failure.message}`;
164
+ }
165
+ /**
166
+ * Builds the message of a save the store did not take.
167
+ *
168
+ * @param reason - Why the store did not take it.
169
+ * @returns The message.
170
+ * @example
171
+ * ```ts
172
+ * notStored("denied"); // "[game] The save was not stored: denied.\n Check the store permission of the native build; progress since the last stored save is lost on close."
173
+ * ```
174
+ */
175
+ function notStored(reason) {
176
+ return `[game] The save was not stored: ${reason}.\n Check the store permission of the native build; progress since the last stored save is lost on close.`;
177
+ }
178
+ /**
179
+ * Writes the whole save once, after the write before it. It never rejects, so one refused write
180
+ * never stops the writes after it.
181
+ *
182
+ * @param previous - The write before this one, or `undefined` for the first.
183
+ * @param store - The system store.
184
+ * @param key - The store key of the save.
185
+ * @param value - The document and its version.
186
+ * @returns The problem of a write the store did not take, or `undefined` once it is stored.
187
+ */
188
+ async function write(previous, store, key, value) {
189
+ await previous;
190
+ try {
191
+ const result = await store.set(key, value);
192
+ return result.ok ? void 0 : notStored(failureText(result));
193
+ } catch (error) {
194
+ return notStored(error instanceof Error ? error.message : String(error));
195
+ }
196
+ }
197
+ /**
198
+ * Reports the problem of a write nobody waits for, once the write is done.
199
+ *
200
+ * @param written - The queued write.
201
+ * @param report - Takes the problem of a write the store did not take.
202
+ */
203
+ async function reportProblem(written, report) {
204
+ const problem = await written;
205
+ if (problem !== void 0) report(problem);
206
+ }
207
+ /**
208
+ * Builds the save provider over the system store: idb on the web, the Tauri store in the native
209
+ * app. It holds the document it loaded and applies each commit's patches to it, then queues one
210
+ * write of the whole value, so the writes stay in commit order. A read the store refuses rejects
211
+ * the load: answering "new player" would overwrite a real save at the first commit. A write the
212
+ * store refuses is reported from `commit` and rejects `commitDurable`; the next write still goes.
213
+ *
214
+ * @param store - The system store.
215
+ * @param key - The store key of the save.
216
+ * @param report - Takes the problem of a write `commit` could not store.
217
+ * @returns The provider to pass as the `provider` seam of `game.screen()`.
218
+ * @example
219
+ * ```ts
220
+ * import { createApp as createSystem } from "@moku-labs/system";
221
+ * const system = createSystem({ plugins: [storePlugin], pluginConfigs: { store: { name: "com.mokulabs.timber" } } });
222
+ * await system.start();
223
+ * const save = storeSave(system.store, "save", problem => problems.push(problem));
224
+ * await save.load(); // null for a new player; rejects when the store cannot be read
225
+ * ```
226
+ */
227
+ function storeSave(store, key, report) {
228
+ const held = {
229
+ document: void 0,
230
+ written: void 0
231
+ };
232
+ /**
233
+ * Applies the patches of one commit to the held document and queues the write of the whole
234
+ * value after the last one.
235
+ *
236
+ * @param patches - Doc patches since the last commit.
237
+ * @param version - Schema version this build writes.
238
+ * @returns The problem of this write, or `undefined` once it is stored.
239
+ */
240
+ const queue = (patches, version) => {
241
+ const document = applyTo(held.document ?? {}, patches);
242
+ const written = write(held.written, store, key, {
243
+ state: document,
244
+ version
245
+ });
246
+ held.document = document;
247
+ held.written = written;
248
+ return written;
249
+ };
250
+ return {
251
+ load: async () => {
252
+ const read = await store.get(key);
253
+ if (!read.ok) throw new Error(`[game] The save could not be read from the system store: ${failureText(read)}.\n The game does not start over it; check the store permission of the native build.`);
254
+ if (read.value === void 0) return null;
255
+ held.document = read.value.state;
256
+ return {
257
+ state: read.value.state,
258
+ version: read.value.version
259
+ };
260
+ },
261
+ commit: (patches, version) => {
262
+ reportProblem(queue(patches, version), report);
263
+ },
264
+ commitDurable: async (patches, _txId, version) => {
265
+ const problem = await queue(patches, version);
266
+ if (problem !== void 0) throw new Error(problem);
267
+ },
268
+ flush: async () => {
269
+ await held.written;
270
+ }
271
+ };
272
+ }
273
+ /**
274
+ * Builds the system shell from the resolved `config.ts`: the `@moku-labs/system` app of the names
275
+ * `config.system` lists, plus the store when `config.save` is `"store"`, in the fixed order. It
276
+ * hands the engine the platform provider over that app and, for a store save, the save provider.
277
+ * The page starts the shell before the game; this function does not start it. The generated
278
+ * `main.ts` passes the function itself to `startPage`, only when `config.ts` names a system
279
+ * plugin or the store save.
280
+ *
281
+ * @param config - The resolved `config.ts` of the game.
282
+ * @param report - Takes a problem the page reports later, such as a write the store refused.
283
+ * @returns The shell, not started.
284
+ * @throws {Error} When `@moku-labs/system` is not installed.
285
+ * @example
286
+ * ```ts
287
+ * // The in-memory .moku/build/main.ts of moku-game build, for a game whose config.ts names system plugins.
288
+ * import { startPage } from "@moku-labs/game/app/page";
289
+ * import { systemShell } from "@moku-labs/game/app/system";
290
+ * import game from "../../index.ts";
291
+ * import config from "../../config.ts";
292
+ *
293
+ * await startPage(game, config, { system: systemShell });
294
+ * ```
295
+ */
296
+ async function systemShell(config, report) {
297
+ const app = await createSystemApp(config.save === "store" ? [...config.system, "store"] : config.system, config.native?.identifier ?? defaultNamespace);
298
+ return {
299
+ handle: app,
300
+ platform: fromSystem(app),
301
+ save: app.store === void 0 ? void 0 : storeSave(app.store, saveKey, report),
302
+ start: () => app.start(),
303
+ stop: () => app.stop()
304
+ };
305
+ }
306
+ //#endregion
307
+ export { createSystemApp, fromSystem, storeSave, systemShell };
package/dist/app.d.mts ADDED
@@ -0,0 +1,37 @@
1
+ import { Xt as Json } from "./types-ZQLYXo5X.mjs";
2
+ import { a as GameHandle, c as MemoryProvider, f as SaveKind, g as SystemName, i as GameDefinition, l as PageAgent, m as ScreenSeams, n as GameApp, o as GamePluginConfigs, p as Scenario, r as GameConfig, s as HeadlessSeams } from "./types-hQgI_wrq.mjs";
3
+ import { AnyPluginInstance } from "@moku-labs/core";
4
+
5
+ //#region src/app/define.d.ts
6
+ /**
7
+ * The moment the default fake clock starts at, in epoch milliseconds. A test stamps times from it.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * game.headless().clock.now() === startMoment; // true
12
+ * ```
13
+ */
14
+ declare const startMoment = 1000000;
15
+ /**
16
+ * Turns a game's one data object into the game: `game.headless()` for logic tests and
17
+ * `game.screen()` for the page and the screen tests. Every type comes from the object: the plugin
18
+ * tuples, the player and the session. Checks the definition at once, so an error points at
19
+ * `index.ts`.
20
+ *
21
+ * @param definition - The game: its flow, starting state, features, plugins and plugin configs.
22
+ * @returns The game, with `headless()` and `screen()`; no app is created yet.
23
+ * @throws {Error} When `flow` is missing, or an entry of `shared`, `features` or `headless.features` is not a feature.
24
+ * @example
25
+ * ```ts
26
+ * // index.ts of a game: the whole game, no createApp call. Its tests import it as `game`.
27
+ * export default defineGameApp({
28
+ * flow: mainFlow, safeNode: "home", player: startingPlayer, session: startingSession,
29
+ * shared: sharedFeature, features: [homeFeature, boardFeature, rewardFeature], plugins: [loadingPlugin],
30
+ * headless: { features: [rewardFeature] }, pluginConfigs: { ui: { tapTargetPt: 48 } }
31
+ * });
32
+ * game.headless({ seed: 7 }).app.flow.state().running; // false: the test starts it
33
+ * ```
34
+ */
35
+ declare function defineGameApp<const Plugins extends readonly AnyPluginInstance[] = readonly [], const HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json>(definition: GameDefinition<Plugins, HeadlessPlugins, Player, Session>): GameApp<Plugins, HeadlessPlugins, Player, Session>;
36
+ //#endregion
37
+ export { type GameApp, type GameConfig, type GameDefinition, type GameHandle, type GamePluginConfigs, type HeadlessSeams, type MemoryProvider, type PageAgent, type SaveKind, type Scenario, type ScreenSeams, type SystemName, defineGameApp, startMoment };