@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
package/dist/app.mjs ADDED
@@ -0,0 +1,235 @@
1
+ import { H as platformPlugin, i as screen, jt as audioPlugin, t as createApp, xt as effectsPlugin } from "./src-DSl1eNB1.mjs";
2
+ import { t as memory } from "./memory-CWlqhS7l.mjs";
3
+ import { t as fakeClock } from "./fake-MpzwZhIZ.mjs";
4
+ //#region src/app/define.ts
5
+ /**
6
+ * The moment the default fake clock starts at, in epoch milliseconds. A test stamps times from it.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * game.headless().clock.now() === startMoment; // true
11
+ * ```
12
+ */
13
+ const startMoment = 1e6;
14
+ /** The rng seed of a new save when neither the definition nor the seam names one. */
15
+ const defaultSeed = 42;
16
+ /** The plugins every app has: the engine's core five and the core plugins `log` and `env`. */
17
+ const corePluginNames = [
18
+ "time",
19
+ "lifecycle",
20
+ "model",
21
+ "clock",
22
+ "flow",
23
+ "log",
24
+ "env"
25
+ ];
26
+ /**
27
+ * Builds the error of a list entry that is not a feature.
28
+ *
29
+ * @param path - The list and index, `features[2]`, or `shared`.
30
+ * @returns The error.
31
+ * @example
32
+ * ```ts
33
+ * notAFeature("features[2]").message; // "[game] defineGameApp: features[2] is not a feature.\n Make it with defineFeature(name, description)."
34
+ * ```
35
+ */
36
+ function notAFeature(path) {
37
+ return /* @__PURE__ */ new Error(`[game] defineGameApp: ${path} is not a feature.\n Make it with defineFeature(name, description).`);
38
+ }
39
+ /**
40
+ * Throws for the first entry of a feature list that `defineFeature` did not make. The type already
41
+ * says so; a game in JavaScript, or one that casts, is caught here, at the definition.
42
+ *
43
+ * @param list - `features`, `headless.features`, or `[shared]`.
44
+ * @param pathOf - Names an entry by its index.
45
+ * @throws {Error} When an entry has no `logicOnly`.
46
+ */
47
+ function checkFeatures(list, pathOf) {
48
+ for (const [index, entry] of list.entries()) if (entry?.logicOnly === void 0) throw notAFeature(pathOf(index));
49
+ }
50
+ /**
51
+ * Checks what TypeScript cannot stop: a definition from JavaScript, or one that casts.
52
+ *
53
+ * @param definition - The game's definition.
54
+ * @throws {Error} When `flow` is missing, or a feature entry is not a feature.
55
+ */
56
+ function checkDefinition(definition) {
57
+ if (definition.flow === void 0) throw new Error("[game] defineGameApp needs flow.\n Pass the main flow: defineGameApp({ flow: mainFlow, ... }).");
58
+ if (definition.shared !== void 0) checkFeatures([definition.shared], () => "shared");
59
+ checkFeatures(definition.features ?? [], (index) => `features[${index}]`);
60
+ checkFeatures(definition.headless?.features ?? [], (index) => `headless.features[${index}]`);
61
+ }
62
+ /**
63
+ * Copies the keys of a seam whose value is set, so a seam left out never erases a game value.
64
+ *
65
+ * @param seam - One seam object, or `undefined`.
66
+ * @returns The set keys only.
67
+ * @example
68
+ * ```ts
69
+ * definedKeys({ journal: 200, context: undefined }); // { journal: 200 }
70
+ * ```
71
+ */
72
+ function definedKeys(seam) {
73
+ if (seam === void 0) return {};
74
+ return Object.fromEntries(Object.entries(seam).filter(([, value]) => value !== void 0));
75
+ }
76
+ /**
77
+ * Merges the seam-owned keys over the game's plugin configs, one plugin at a time: a key the game
78
+ * owns stays, a key the shell owns is written. With `screen` the screen keys are written too.
79
+ *
80
+ * @param definition - The game's definition.
81
+ * @param seams - The seams of this call.
82
+ * @param runtime - The clock and the save provider of this call.
83
+ * @param withScreen - Whether the app is the screen app.
84
+ * @returns The plugin configs of the app.
85
+ */
86
+ function mergeConfigs(definition, seams, runtime, withScreen) {
87
+ const game = definition.pluginConfigs ?? {};
88
+ const logic = {
89
+ ...game,
90
+ model: {
91
+ ...game.model,
92
+ playerProvider: runtime.provider,
93
+ initialPlayer: seams.player ?? definition.player,
94
+ initialSession: seams.session ?? definition.session,
95
+ seed: seams.seed ?? definition.seed ?? defaultSeed
96
+ },
97
+ clock: { source: runtime.clock },
98
+ flow: {
99
+ ...game.flow,
100
+ mainFlow: definition.flow,
101
+ safeNode: definition.safeNode
102
+ }
103
+ };
104
+ if (!withScreen) return logic;
105
+ return {
106
+ ...logic,
107
+ platform: {
108
+ provider: seams.platform,
109
+ keepAwake: seams.keepAwake ?? false
110
+ },
111
+ renderer: {
112
+ ...game.renderer,
113
+ ...definedKeys(seams.renderer)
114
+ },
115
+ assets: {
116
+ ...game.assets,
117
+ manifest: seams.manifest,
118
+ io: seams.io
119
+ },
120
+ audio: {
121
+ ...game.audio,
122
+ ...definedKeys(seams.audio)
123
+ }
124
+ };
125
+ }
126
+ /**
127
+ * Keeps the configs of the plugins an app composes. Core takes unknown keys at run time, but the
128
+ * headless app's type would not: a headless app gets no screen or unlisted game plugin config.
129
+ *
130
+ * @param configs - The merged configs.
131
+ * @param plugins - The plugins the app composes beyond the core.
132
+ * @returns The configs of the composed plugins only.
133
+ */
134
+ function composedOnly(configs, plugins) {
135
+ const names = /* @__PURE__ */ new Set([...corePluginNames, ...plugins.map((plugin) => plugin.name)]);
136
+ return Object.fromEntries(Object.entries(configs).filter(([name]) => names.has(name)));
137
+ }
138
+ /**
139
+ * Picks the clock and the save provider of one call: the seams', or a fresh fake clock at
140
+ * `startMoment` and a fresh memory provider.
141
+ *
142
+ * @param seams - The seams of this call.
143
+ * @returns The clock and the provider.
144
+ */
145
+ function runtimeOf(seams) {
146
+ return {
147
+ clock: seams.clock ?? fakeClock(1e6),
148
+ provider: seams.provider ?? memory()
149
+ };
150
+ }
151
+ /**
152
+ * Creates one app over the engine: the plugins in order, the merged configs, and `referenceLong`
153
+ * only when the definition sets it.
154
+ *
155
+ * @param definition - The game's definition.
156
+ * @param plugins - The plugins beyond the core, in order.
157
+ * @param pluginConfigs - The merged configs.
158
+ * @returns The app, not started.
159
+ */
160
+ function appOf(definition, plugins, pluginConfigs) {
161
+ return createApp({
162
+ plugins: [...plugins],
163
+ pluginConfigs,
164
+ ...definition.referenceLong === void 0 ? {} : { config: { referenceLong: definition.referenceLong } }
165
+ });
166
+ }
167
+ /**
168
+ * Makes the headless app of a definition: the logic of `headless.features`, then
169
+ * `headless.plugins`.
170
+ *
171
+ * @param definition - The game's definition.
172
+ * @param seams - The seams of this call.
173
+ * @returns The app, not started, and its clock and provider.
174
+ */
175
+ function headlessOf(definition, seams) {
176
+ const runtime = runtimeOf(seams);
177
+ const plugins = [...(definition.headless?.features ?? []).map((feature) => feature.logicOnly), ...definition.headless?.plugins ?? []];
178
+ return {
179
+ app: appOf(definition, plugins, composedOnly(mergeConfigs(definition, seams, runtime, false), plugins)),
180
+ ...runtime
181
+ };
182
+ }
183
+ /**
184
+ * Makes the screen app of a definition: the screen set, `audio`, `effects`, `platform`, then
185
+ * `shared`, the features and the game's plugins.
186
+ *
187
+ * @param definition - The game's definition.
188
+ * @param seams - The seams of this call.
189
+ * @returns The app, not started, and its clock and provider.
190
+ */
191
+ function screenOf(definition, seams) {
192
+ const runtime = runtimeOf(seams);
193
+ return {
194
+ app: appOf(definition, [
195
+ ...screen,
196
+ audioPlugin,
197
+ effectsPlugin,
198
+ platformPlugin,
199
+ ...definition.shared === void 0 ? [] : [definition.shared],
200
+ ...definition.features ?? [],
201
+ ...definition.plugins ?? []
202
+ ], mergeConfigs(definition, seams, runtime, true)),
203
+ ...runtime
204
+ };
205
+ }
206
+ /**
207
+ * Turns a game's one data object into the game: `game.headless()` for logic tests and
208
+ * `game.screen()` for the page and the screen tests. Every type comes from the object: the plugin
209
+ * tuples, the player and the session. Checks the definition at once, so an error points at
210
+ * `index.ts`.
211
+ *
212
+ * @param definition - The game: its flow, starting state, features, plugins and plugin configs.
213
+ * @returns The game, with `headless()` and `screen()`; no app is created yet.
214
+ * @throws {Error} When `flow` is missing, or an entry of `shared`, `features` or `headless.features` is not a feature.
215
+ * @example
216
+ * ```ts
217
+ * // index.ts of a game: the whole game, no createApp call. Its tests import it as `game`.
218
+ * export default defineGameApp({
219
+ * flow: mainFlow, safeNode: "home", player: startingPlayer, session: startingSession,
220
+ * shared: sharedFeature, features: [homeFeature, boardFeature, rewardFeature], plugins: [loadingPlugin],
221
+ * headless: { features: [rewardFeature] }, pluginConfigs: { ui: { tapTargetPt: 48 } }
222
+ * });
223
+ * game.headless({ seed: 7 }).app.flow.state().running; // false: the test starts it
224
+ * ```
225
+ */
226
+ function defineGameApp(definition) {
227
+ const wide = definition;
228
+ checkDefinition(wide);
229
+ return {
230
+ headless: (seams = {}) => headlessOf(wide, seams),
231
+ screen: (seams = {}) => screenOf(wide, seams)
232
+ };
233
+ }
234
+ //#endregion
235
+ export { defineGameApp, startMoment };