@moku-labs/game 0.0.1 → 0.0.3

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.
@@ -0,0 +1,111 @@
1
+ import { $ as Position, Dt as Api, dt as FlowGraph, h as Api$1, l as RenderStats, pt as JournalEntry, t as Api$2, z as EntitySnapshot, zt as Snapshot } from "./types-Cjf3ywai.mjs";
2
+ import { f as Usage, t as Api$3 } from "./types-DgwbzDLP.mjs";
3
+ import { a as InputOf, c as Source, d as HeadlessApp, i as InputArguments, l as WatchApp, o as InputSchema, t as CheatEntry, u as WatchInput } from "./types-D0uYTTC3.mjs";
4
+ import { c as UiNode } from "./types-C0SLQsx0.mjs";
5
+ import { b as UiApi } from "./types-KlE_GL1z.mjs";
6
+ import { n as defineSource } from "./define-BMo0pHe_.mjs";
7
+ import { _ as EffectsApi, a as SoundEntry, t as AudioApi, v as EffectsStats } from "./types-BTycq8_Z.mjs";
8
+
9
+ //#region src/plugins/flow/doors/read.d.ts
10
+ /**
11
+ * Reads a source once.
12
+ *
13
+ * @param app - The app the source reads.
14
+ * @param source - The source.
15
+ * @param input - Its input; left out when every field of its schema is optional.
16
+ * @returns What the source reads.
17
+ * @example
18
+ * ```ts
19
+ * // An e2e script checks where the game rests after a tap on Play.
20
+ * read(app, sources.position); // { path: "board/awaitIntent", flow: "board", node: "awaitIntent", waiting: ["tap", "leave"] }
21
+ * read(app, sources.history, { last: 1 }); // [{ path: "home", outcome: "play", next: "board/awaitIntent", ... }]
22
+ * ```
23
+ */
24
+ declare function read<S extends InputSchema, O, App>(app: App, source: Source<S, O, App>, ...input: InputArguments<S>): O;
25
+ /**
26
+ * Watches a source: reads it on the first frame, then again whenever its change key moved,
27
+ * in the `signals` phase of the frame loop.
28
+ *
29
+ * @param app - The app the source reads.
30
+ * @param source - The source.
31
+ * @param input - Its input, or `undefined` when every field of its schema is optional.
32
+ * @param fn - Called with each value read.
33
+ * @returns Stops watching.
34
+ * @example
35
+ * ```ts
36
+ * // An editor panel follows the position; it redraws only after the graph moved.
37
+ * const stop = watch(app, sources.position, undefined, position => panel.show(position.path));
38
+ * // next frame: panel shows "home"; a tap on Play: panel shows "board/awaitIntent"
39
+ * stop();
40
+ * ```
41
+ */
42
+ declare function watch<S extends InputSchema, O, App extends WatchApp>(app: App, source: Source<S, O, App>, input: WatchInput<S>, fn: (value: O) => void): () => void;
43
+ //#endregion
44
+ //#region src/plugins/flow/doors/sources.d.ts
45
+ /**
46
+ * Every source of the engine, keyed by its short name: what an editor lists as panel data and
47
+ * MCP tools. Frozen.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * // An e2e script checks where the game rests, and the editor registers every source.
52
+ * read(app, sources.position).path; // "board/awaitIntent"
53
+ * for (const source of Object.values(sources)) registry.add(source);
54
+ * ```
55
+ */
56
+ declare const sources: Readonly<{
57
+ graph: Source<{}, FlowGraph, HeadlessApp>;
58
+ position: Source<{}, Position, HeadlessApp>;
59
+ history: Source<{
60
+ last: "number?";
61
+ }, readonly JournalEntry[], HeadlessApp>;
62
+ tainted: Source<{}, boolean, HeadlessApp>;
63
+ cheats: Source<{}, readonly CheatEntry[], HeadlessApp>;
64
+ model: Source<{}, Snapshot, HeadlessApp & {
65
+ readonly model: Api;
66
+ }>;
67
+ entities: Source<{
68
+ owner: "string?";
69
+ component: "string?";
70
+ }, EntitySnapshot[], HeadlessApp & {
71
+ readonly world: Api$1;
72
+ }>;
73
+ projections: Source<{}, Record<string, Record<string, number>>, HeadlessApp & {
74
+ readonly world: Api$1;
75
+ }>;
76
+ ui: Source<{}, UiNode, HeadlessApp & {
77
+ readonly ui: UiApi;
78
+ }>;
79
+ rect: Source<{
80
+ key: "string";
81
+ }, {
82
+ x: number;
83
+ y: number;
84
+ w: number;
85
+ h: number;
86
+ } | undefined, HeadlessApp & {
87
+ readonly ui: UiApi;
88
+ readonly renderer: Api$2;
89
+ }>;
90
+ render: Source<{}, RenderStats, HeadlessApp & {
91
+ readonly renderer: Api$2;
92
+ }>;
93
+ sounds: Source<{
94
+ last: "number?";
95
+ }, readonly SoundEntry[], HeadlessApp & {
96
+ readonly audio: AudioApi;
97
+ }>;
98
+ effects: Source<{}, EffectsStats, HeadlessApp & {
99
+ readonly effects: EffectsApi;
100
+ }>;
101
+ assets: Source<{}, Usage, HeadlessApp & {
102
+ readonly assets: Api$3;
103
+ }>;
104
+ log: Source<{
105
+ level: "string?";
106
+ }, readonly import("@moku-labs/common/browser").LogEntry[], HeadlessApp & {
107
+ readonly log: import("@moku-labs/common/browser").LogApi;
108
+ }>;
109
+ }>;
110
+ //#endregion
111
+ export { type InputOf, type InputSchema, type Source, defineSource, read, sources, watch };
@@ -0,0 +1,3 @@
1
+ import { o as defineSource } from "./session-DmAxY6Ll.mjs";
2
+ import { n as read, r as watch, t as sources } from "./sources-BooL2MsX.mjs";
3
+ export { defineSource, read, sources, watch };
@@ -0,0 +1,2 @@
1
+ import { a as jsxs, i as jsxDEV, n as JSX, r as jsx, t as Fragment } from "./runtime-BstIkrhH.mjs";
2
+ export { Fragment, type JSX, jsx, jsxDEV, jsxs };
@@ -0,0 +1,2 @@
1
+ import { i as jsxs, n as jsx, r as jsxDEV, t as Fragment } from "./runtime-DRlwxkIv.mjs";
2
+ export { Fragment, jsx, jsxDEV, jsxs };
@@ -0,0 +1,2 @@
1
+ import { a as jsxs, n as JSX, r as jsx, t as Fragment } from "./runtime-BstIkrhH.mjs";
2
+ export { Fragment, type JSX, jsx, jsxs };
@@ -0,0 +1,2 @@
1
+ import { i as jsxs, n as jsx, t as Fragment } from "./runtime-DRlwxkIv.mjs";
2
+ export { Fragment, jsx, jsxs };
@@ -0,0 +1,259 @@
1
+ import { applyPatches, createDraft, enablePatches, finishDraft } from "immer";
2
+ //#region src/plugins/model/store/drafts.ts
3
+ /** Root order of a commit result, so two commits never report the same roots in another order. */
4
+ const rootOrder = Object.freeze([
5
+ "player",
6
+ "session",
7
+ "rng"
8
+ ]);
9
+ /**
10
+ * Turns on Immer's patch recording. Idempotent, and called from the state factory rather than at
11
+ * module scope: a module-scope call would run on import, even when no app is created.
12
+ *
13
+ */
14
+ function enableDraftPatches() {
15
+ enablePatches();
16
+ }
17
+ /**
18
+ * Freezes a tree and every branch of it. Immer freezes what it produces; this covers the trees
19
+ * that never went through a draft — the initial trees and the ones handed to `restore()`.
20
+ *
21
+ * @param tree - The tree to freeze, in place.
22
+ * @returns The same reference, now frozen.
23
+ * @example
24
+ * ```ts
25
+ * const tree = deepFreeze({ player: { bag: ["key"] } });
26
+ * Object.isFrozen(tree.player.bag); // true
27
+ * ```
28
+ */
29
+ function deepFreeze(tree) {
30
+ if (!tree || typeof tree !== "object") return tree;
31
+ for (const branch of Object.values(tree)) deepFreeze(branch);
32
+ return Object.freeze(tree);
33
+ }
34
+ /**
35
+ * Maps one Immer patch to our own structural patch, so no Immer type reaches a public type.
36
+ * A removal carries no value, and `exactOptionalPropertyTypes` forbids an explicit `undefined`.
37
+ *
38
+ * @param patch - The Immer patch.
39
+ * @param path - Path of the patch inside its own tree, without the container segment.
40
+ * @returns The structural patch.
41
+ * @example
42
+ * ```ts
43
+ * toPatch({ op: "remove", path: ["doc", "player", "key"] }, ["player", "key"]);
44
+ * // { op: "remove", path: ["player", "key"] }
45
+ * ```
46
+ */
47
+ function toPatch(patch, path) {
48
+ if (patch.op === "remove") return {
49
+ op: patch.op,
50
+ path
51
+ };
52
+ return {
53
+ op: patch.op,
54
+ path,
55
+ value: patch.value
56
+ };
57
+ }
58
+ /**
59
+ * Derives the touched roots from the patches of one commit, in a fixed order.
60
+ *
61
+ * @param docPatches - Patches of the save document.
62
+ * @param sessionPatches - Patches of the session tree.
63
+ * @returns The roots a listener has to reconcile.
64
+ * @example
65
+ * ```ts
66
+ * rootsOf([{ op: "add", path: ["rng", "streams", "dice"], value: 7 }], []); // ["rng"]
67
+ * rootsOf([], [{ op: "replace", path: ["rolls"], value: 1 }]); // ["session"]
68
+ * ```
69
+ */
70
+ function rootsOf(docPatches, sessionPatches) {
71
+ const touched = /* @__PURE__ */ new Set();
72
+ for (const patch of docPatches) {
73
+ const [head] = patch.path;
74
+ if (head === "player" || head === "rng") touched.add(head);
75
+ }
76
+ if (sessionPatches.length > 0) touched.add("session");
77
+ return rootOrder.filter((root) => touched.has(root));
78
+ }
79
+ /**
80
+ * Opens mutable drafts of the frozen save document and session tree.
81
+ * Both live in one draft container, so one `finishDraft` closes the transaction and the patches
82
+ * arrive in one list, tagged by their container key.
83
+ *
84
+ * @param doc - Frozen save document.
85
+ * @param session - Frozen session tree.
86
+ * @returns The open drafts of one transaction.
87
+ * @example
88
+ * ```ts
89
+ * const pair = openDrafts({ player: { coins: 1 }, rng: { seed: 7, streams: {} } }, { rolls: 0 });
90
+ * pair.session; // a mutable draft of { rolls: 0 }: a write never reaches the frozen input
91
+ * ```
92
+ */
93
+ function openDrafts(doc, session) {
94
+ return createDraft({
95
+ doc,
96
+ session
97
+ });
98
+ }
99
+ /**
100
+ * Finishes the drafts: the new frozen trees, the patches per tree and the touched roots.
101
+ * The drafts are revoked, so a draft that leaked into a view throws on the next touch instead of
102
+ * corrupting the committed tree in silence.
103
+ *
104
+ * @param pair - Open drafts of one transaction.
105
+ * @returns The frozen trees, the patches split by tree, and the touched roots.
106
+ */
107
+ function finishDrafts(pair) {
108
+ const collected = [];
109
+ const finished = finishDraft(pair, (patches) => {
110
+ collected.push(...patches);
111
+ });
112
+ const docPatches = [];
113
+ const sessionPatches = [];
114
+ for (const patch of collected) {
115
+ const [container, ...path] = patch.path;
116
+ (container === "doc" ? docPatches : sessionPatches).push(toPatch(patch, path));
117
+ }
118
+ return {
119
+ doc: finished.doc,
120
+ session: finished.session,
121
+ patches: {
122
+ doc: docPatches,
123
+ session: sessionPatches
124
+ },
125
+ roots: rootsOf(docPatches, sessionPatches)
126
+ };
127
+ }
128
+ /**
129
+ * Applies the patches of one commit to a document, the way a save seam stores what it was handed.
130
+ * A whole-document patch carries the path `[]` and replaces the document. The input is left as it
131
+ * is: the result is a new frozen document.
132
+ *
133
+ * @param document - The document the patches apply to.
134
+ * @param patches - The patches of one commit, in order.
135
+ * @returns The document after the patches.
136
+ * @throws {Error} When a patch names a path the document does not have.
137
+ * @example
138
+ * ```ts
139
+ * applyTo({ player: { coins: 0 } }, [{ op: "replace", path: ["player", "coins"], value: 4 }]);
140
+ * // { player: { coins: 4 } }
141
+ * ```
142
+ */
143
+ function applyTo(document, patches) {
144
+ enablePatches();
145
+ return applyPatches(document, patches);
146
+ }
147
+ /**
148
+ * Drops the drafts without a result: the base trees stay as they are and the drafts are revoked.
149
+ *
150
+ * @param pair - Open drafts of one transaction.
151
+ */
152
+ function dropDrafts(pair) {
153
+ finishDraft(pair);
154
+ }
155
+ //#endregion
156
+ //#region src/plugins/model/store/providers/memory.ts
157
+ /** Seed of a save built by `saveOf` when the caller does not pin one. */
158
+ const defaultSeed = 1;
159
+ /** The base a patch applies to when the provider was never handed a document. */
160
+ const noDocument = {};
161
+ /**
162
+ * Creates an in-memory provider that keeps what it was committed and records every call.
163
+ * It is the default when a game configures no `playerProvider`, and the double every test uses:
164
+ * `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
165
+ * long as the provider does, so a second app over the same instance reads what the first one
166
+ * wrote, and nothing survives the process.
167
+ *
168
+ * `load()` reads the fixture, or everything committed since; without either, the player is new.
169
+ * `commitDurable()` resolves at once: there is no disk behind it.
170
+ *
171
+ * @param fixture - The save `load()` starts from. Omitted: a new player.
172
+ * @param fixture.state - The saved document.
173
+ * @param fixture.version - Schema version of the saved document.
174
+ * @returns A provider that keeps its document and records its calls.
175
+ * @example
176
+ * ```ts
177
+ * // A new player is saved at once. One roll reaches the provider at the next rest node.
178
+ * const provider = memory();
179
+ * const game = await createHeadless(createGame(provider));
180
+ *
181
+ * await game.walk([{ at: "home", intent: "roll" }]);
182
+ * provider.calls.map(call => call.method); // ["load", "commit", "commit"]
183
+ * ```
184
+ */
185
+ function memory(fixture) {
186
+ const calls = [];
187
+ const held = {
188
+ document: fixture?.state,
189
+ version: fixture?.version ?? 0
190
+ };
191
+ /**
192
+ * Applies the patches of one commit to the held document.
193
+ *
194
+ * @param patches - Doc patches since the last commit.
195
+ * @param version - Schema version this build writes.
196
+ * @throws {Error} When a patch names a path the held document does not have.
197
+ */
198
+ const hold = (patches, version) => {
199
+ held.document = applyTo(held.document ?? noDocument, patches);
200
+ held.version = version;
201
+ };
202
+ return {
203
+ calls,
204
+ load: async () => {
205
+ calls.push({ method: "load" });
206
+ const document = held.document;
207
+ if (document === void 0) return null;
208
+ return {
209
+ state: document,
210
+ version: held.version
211
+ };
212
+ },
213
+ commit: (patches, version) => {
214
+ calls.push({
215
+ method: "commit",
216
+ patches,
217
+ version
218
+ });
219
+ hold(patches, version);
220
+ },
221
+ commitDurable: async (patches, txId, version) => {
222
+ calls.push({
223
+ method: "commitDurable",
224
+ patches,
225
+ txId,
226
+ version
227
+ });
228
+ hold(patches, version);
229
+ },
230
+ flush: async () => {
231
+ calls.push({ method: "flush" });
232
+ }
233
+ };
234
+ }
235
+ /**
236
+ * Builds a save document from a player tree, with no rng streams drawn yet.
237
+ * Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
238
+ *
239
+ * @param player - Player tree of the save.
240
+ * @param seed - Rng seed of the save.
241
+ * @returns The save document.
242
+ * @example
243
+ * ```ts
244
+ * // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
245
+ * saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
246
+ * const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
247
+ * ```
248
+ */
249
+ function saveOf(player, seed = defaultSeed) {
250
+ return {
251
+ player,
252
+ rng: {
253
+ seed,
254
+ streams: {}
255
+ }
256
+ };
257
+ }
258
+ //#endregion
259
+ export { enableDraftPatches as a, dropDrafts as i, saveOf as n, finishDrafts as o, deepFreeze as r, openDrafts as s, memory as t };
@@ -1,3 +1,4 @@
1
+ import "node:module";
1
2
  //#region \0rolldown/runtime.js
2
3
  var __defProp = Object.defineProperty;
3
4
  var __exportAll = (all, no_symbols) => {