@moku-labs/game 0.0.1 → 0.0.2
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.
- package/README.md +424 -37
- package/dist/assets.d.mts +161 -0
- package/dist/assets.mjs +4729 -0
- package/dist/component-DGg5DqKK.mjs +44 -0
- package/dist/control.d.mts +162 -0
- package/dist/control.mjs +758 -0
- package/dist/define-sFoO3y6X.d.mts +263 -0
- package/dist/headless-CvamCUcR.mjs +186 -0
- package/dist/headless-KaSWcd0s.d.mts +181 -0
- package/dist/index.d.mts +1204 -50
- package/dist/index.mjs +20978 -2010
- package/dist/inspect.d.mts +109 -0
- package/dist/inspect.mjs +519 -0
- package/dist/jsx-dev-runtime.d.mts +2 -0
- package/dist/jsx-dev-runtime.mjs +2 -0
- package/dist/jsx-runtime.d.mts +2 -0
- package/dist/jsx-runtime.mjs +2 -0
- package/dist/memory-CvgdnsQO.mjs +259 -0
- package/dist/registry-DlpRCibU.mjs +384 -0
- package/dist/runtime-DRlwxkIv.mjs +182 -0
- package/dist/runtime-DiOTkDZz.d.mts +77 -0
- package/dist/session-DmAxY6Ll.mjs +145 -0
- package/dist/testing.d.mts +23 -111
- package/dist/testing.mjs +12 -289
- package/dist/types-BxkNNYul.d.mts +2840 -0
- package/dist/types-CTPS9GBu.d.mts +742 -0
- package/dist/types-DD-QrG_z.d.mts +588 -0
- package/dist/types-DWILGrPn.d.mts +622 -0
- package/dist/types-DYLnSgMI.d.mts +570 -0
- package/dist/types-JNc_UQBo.d.mts +2896 -0
- package/dist/types-yg_ywtT-.d.mts +1859 -0
- package/dist/visual-BDUHSRvf.mjs +734 -0
- package/package.json +26 -4
- package/dist/registry-DWV5C0Mf.mjs +0 -666
- package/dist/types-BfsmUzLC.d.mts +0 -1908
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { n as isComponentDefinition } from "./component-DGg5DqKK.mjs";
|
|
2
|
+
//#region src/plugins/ui/jsx/flatten.ts
|
|
3
|
+
/** The type of the node `Fragment` builds. It never reaches a reconcile: `flatten` lifts it. */
|
|
4
|
+
const FRAGMENT = "#fragment";
|
|
5
|
+
/**
|
|
6
|
+
* Builds the node a bare string or number child becomes.
|
|
7
|
+
*
|
|
8
|
+
* @param content - What stood between the tags.
|
|
9
|
+
* @returns A text node carrying it.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* textNode(5); // { type: "text", props: { content: "5" }, children: [] }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
function textNode(content) {
|
|
16
|
+
return {
|
|
17
|
+
type: "text",
|
|
18
|
+
props: { content: String(content) },
|
|
19
|
+
children: []
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Tells a description node from the other things a child may be.
|
|
24
|
+
*
|
|
25
|
+
* @param child - One child of a tag.
|
|
26
|
+
* @returns True when it is a node.
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* isNode({ type: "row", props: {}, children: [] }); // true
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function isNode(child) {
|
|
33
|
+
return typeof child === "object" && child !== null && !Array.isArray(child) && "type" in child;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Turns whatever stood between two tags into the children of that tag.
|
|
37
|
+
*
|
|
38
|
+
* @param child - One child, an array of children, or nothing.
|
|
39
|
+
* @returns The children as nodes, fragments lifted and empty values dropped.
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* flatten(["a", undefined, 2]).map(node => node.props.content); // ["a", "2"]
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
function flatten(child) {
|
|
46
|
+
if (child === null || child === void 0 || typeof child === "boolean") return [];
|
|
47
|
+
if (typeof child === "string" || typeof child === "number") return [textNode(child)];
|
|
48
|
+
if (Array.isArray(child)) {
|
|
49
|
+
const nodes = [];
|
|
50
|
+
for (const item of child) nodes.push(...flatten(item));
|
|
51
|
+
return nodes;
|
|
52
|
+
}
|
|
53
|
+
if (!isNode(child)) return [];
|
|
54
|
+
if (child.type === "#fragment") return [...child.children];
|
|
55
|
+
return [child];
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/plugins/ui/jsx/runtime.ts
|
|
59
|
+
/**
|
|
60
|
+
* @file ui/jsx — the JSX runtime itself: the three factories a TypeScript build calls, the
|
|
61
|
+
* fragment, and the `JSX` namespace `"jsxImportSource": "@moku-labs/game"` resolves to. Pure and
|
|
62
|
+
* reachable only through `src/jsx-runtime.ts` and `src/jsx-dev-runtime.ts` (lint L9).
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* Splits the children out of the props the compiler built, so a node never carries them twice.
|
|
66
|
+
*
|
|
67
|
+
* @param props - What the compiler passed.
|
|
68
|
+
* @returns The props without `children`.
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* withoutChildren({ style: {}, children: "a" }); // { style: {} }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
function withoutChildren(props) {
|
|
75
|
+
const rest = { ...props };
|
|
76
|
+
delete rest.children;
|
|
77
|
+
return rest;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Wraps what a plain function component returned so it can stand where one node is expected.
|
|
81
|
+
*
|
|
82
|
+
* @param produced - What the function returned.
|
|
83
|
+
* @param key - The key the tag carried, if any.
|
|
84
|
+
* @returns One node; several results are lifted by the parent's `flatten`.
|
|
85
|
+
*/
|
|
86
|
+
function asSingleNode(produced, key) {
|
|
87
|
+
const nodes = flatten(produced);
|
|
88
|
+
const single = nodes.length === 1 ? nodes[0] : void 0;
|
|
89
|
+
if (single === void 0) return {
|
|
90
|
+
type: FRAGMENT,
|
|
91
|
+
props: {},
|
|
92
|
+
children: nodes
|
|
93
|
+
};
|
|
94
|
+
if (key === void 0) return single;
|
|
95
|
+
return {
|
|
96
|
+
...single,
|
|
97
|
+
key
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Builds one description node. A string tag becomes an intrinsic, a component definition becomes
|
|
102
|
+
* one node the reconcile expands, and a plain function runs now.
|
|
103
|
+
*
|
|
104
|
+
* @param type - The tag: a string, `Fragment`, a component definition or a plain function.
|
|
105
|
+
* @param props - What the compiler collected, `children` included.
|
|
106
|
+
* @param key - The `key` attribute, passed as the third argument, never inside `props`.
|
|
107
|
+
* @returns The node.
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* jsx("row", { style: { gap: 8 } }, "tabs"); // { type: "row", key: "tabs", props: { style: { gap: 8 } }, children: [] }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function jsx(type, props, key) {
|
|
114
|
+
const rest = withoutChildren(props);
|
|
115
|
+
if (type === Fragment) return {
|
|
116
|
+
type: FRAGMENT,
|
|
117
|
+
props: rest,
|
|
118
|
+
children: flatten(props.children)
|
|
119
|
+
};
|
|
120
|
+
if (isComponentDefinition(type)) {
|
|
121
|
+
const node = {
|
|
122
|
+
type: type.name,
|
|
123
|
+
props,
|
|
124
|
+
children: []
|
|
125
|
+
};
|
|
126
|
+
return key === void 0 ? node : {
|
|
127
|
+
...node,
|
|
128
|
+
key
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (typeof type === "function") return asSingleNode(type(props), key);
|
|
132
|
+
const node = {
|
|
133
|
+
type: String(type),
|
|
134
|
+
props: rest,
|
|
135
|
+
children: flatten(props.children)
|
|
136
|
+
};
|
|
137
|
+
return key === void 0 ? node : {
|
|
138
|
+
...node,
|
|
139
|
+
key
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The factory for a tag with several static children. The same function as `jsx`.
|
|
144
|
+
*/
|
|
145
|
+
const jsxs = jsx;
|
|
146
|
+
/**
|
|
147
|
+
* The development factory. The last three arguments are debug information the runtime drops.
|
|
148
|
+
*
|
|
149
|
+
* @param type - The tag.
|
|
150
|
+
* @param props - What the compiler collected.
|
|
151
|
+
* @param key - The `key` attribute.
|
|
152
|
+
* @param _isStatic - Whether the children list is static. Ignored.
|
|
153
|
+
* @param _source - The file and line of the tag. Ignored.
|
|
154
|
+
* @param _self - The `this` of the enclosing scope. Ignored.
|
|
155
|
+
* @returns The node `jsx` builds.
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* jsxDEV("row", {}, "tabs", false, { fileName: "hud.tsx" }, undefined).key; // "tabs"
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
function jsxDEV(type, props, key, _isStatic, _source, _self) {
|
|
162
|
+
return jsx(type, props, key);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Groups children without a tag of its own. Its children are lifted into the parent.
|
|
166
|
+
*
|
|
167
|
+
* @param props - The children between the two ends of the fragment.
|
|
168
|
+
* @returns The fragment node, which `flatten` lifts.
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* Fragment({ children: ["a"] }).children.length; // 1
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
function Fragment(props) {
|
|
175
|
+
return {
|
|
176
|
+
type: FRAGMENT,
|
|
177
|
+
props: {},
|
|
178
|
+
children: flatten(props.children)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { jsxs as i, jsx as n, jsxDEV as r, Fragment as t };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { a as JsxChild, m as UiIntrinsicElements, t as DescriptionNode } from "./types-yg_ywtT-.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/ui/jsx/runtime.d.ts
|
|
4
|
+
/** What the compiler passes as props: the children plus whatever the tag declared. */
|
|
5
|
+
type JsxProperties = Record<string, unknown> & {
|
|
6
|
+
children?: JsxChild;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Builds one description node. A string tag becomes an intrinsic, a component definition becomes
|
|
10
|
+
* one node the reconcile expands, and a plain function runs now.
|
|
11
|
+
*
|
|
12
|
+
* @param type - The tag: a string, `Fragment`, a component definition or a plain function.
|
|
13
|
+
* @param props - What the compiler collected, `children` included.
|
|
14
|
+
* @param key - The `key` attribute, passed as the third argument, never inside `props`.
|
|
15
|
+
* @returns The node.
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* jsx("row", { style: { gap: 8 } }, "tabs"); // { type: "row", key: "tabs", props: { style: { gap: 8 } }, children: [] }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function jsx(type: unknown, props: JsxProperties, key?: string): DescriptionNode;
|
|
22
|
+
/**
|
|
23
|
+
* The factory for a tag with several static children. The same function as `jsx`.
|
|
24
|
+
*/
|
|
25
|
+
declare const jsxs: typeof jsx;
|
|
26
|
+
/**
|
|
27
|
+
* The development factory. The last three arguments are debug information the runtime drops.
|
|
28
|
+
*
|
|
29
|
+
* @param type - The tag.
|
|
30
|
+
* @param props - What the compiler collected.
|
|
31
|
+
* @param key - The `key` attribute.
|
|
32
|
+
* @param _isStatic - Whether the children list is static. Ignored.
|
|
33
|
+
* @param _source - The file and line of the tag. Ignored.
|
|
34
|
+
* @param _self - The `this` of the enclosing scope. Ignored.
|
|
35
|
+
* @returns The node `jsx` builds.
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* jsxDEV("row", {}, "tabs", false, { fileName: "hud.tsx" }, undefined).key; // "tabs"
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function jsxDEV(type: unknown, props: JsxProperties, key?: string, _isStatic?: boolean, _source?: unknown, _self?: unknown): DescriptionNode;
|
|
42
|
+
/**
|
|
43
|
+
* Groups children without a tag of its own. Its children are lifted into the parent.
|
|
44
|
+
*
|
|
45
|
+
* @param props - The children between the two ends of the fragment.
|
|
46
|
+
* @returns The fragment node, which `flatten` lifts.
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* Fragment({ children: ["a"] }).children.length; // 1
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function Fragment(props: JsxProperties): DescriptionNode;
|
|
53
|
+
/**
|
|
54
|
+
* The namespace TypeScript reads a `.tsx` file against. A game sets `"jsx": "react-jsx"` and
|
|
55
|
+
* `"jsxImportSource": "@moku-labs/game"`, and both entry files export this namespace.
|
|
56
|
+
*/
|
|
57
|
+
declare namespace JSX {
|
|
58
|
+
/** What a tag evaluates to. */
|
|
59
|
+
interface Element extends DescriptionNode {}
|
|
60
|
+
/** The thirteen tags a screen is written with. */
|
|
61
|
+
interface IntrinsicElements extends UiIntrinsicElements {}
|
|
62
|
+
/** The prop children are collected into. */
|
|
63
|
+
interface ElementChildrenAttribute {
|
|
64
|
+
children: unknown;
|
|
65
|
+
}
|
|
66
|
+
/** The attribute every tag takes next to its own props. */
|
|
67
|
+
interface IntrinsicAttributes {
|
|
68
|
+
key?: string;
|
|
69
|
+
}
|
|
70
|
+
/** The same attribute on a component tag. */
|
|
71
|
+
interface IntrinsicClassAttributes<Component> {
|
|
72
|
+
key?: string;
|
|
73
|
+
ref?: Component;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { jsxs as a, jsxDEV as i, JSX as n, jsx as r, Fragment as t };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
//#region src/plugins/flow/doors/define.ts
|
|
2
|
+
/** Lowercase-first camelCase words joined by dots, at least two: `game.position`. */
|
|
3
|
+
const idPattern = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$/;
|
|
4
|
+
/**
|
|
5
|
+
* Refuses an id that is not a dotted name.
|
|
6
|
+
*
|
|
7
|
+
* @param kind - `"source"` or `"command"`, for the message.
|
|
8
|
+
* @param id - The id to check.
|
|
9
|
+
* @throws {Error} When the id is not camelCase words joined by dots.
|
|
10
|
+
*/
|
|
11
|
+
function checkId(kind, id) {
|
|
12
|
+
if (idPattern.test(id)) return;
|
|
13
|
+
throw new Error(`[game] The ${kind} id "${id}" is not a dotted name.\n Name it like "game.position": camelCase words joined by dots.`);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Declares a source: a read-only view the editor lists, reads and watches. The types flow from
|
|
17
|
+
* the object: the input from `input`, the app from the `read` parameter, the output from its
|
|
18
|
+
* result.
|
|
19
|
+
*
|
|
20
|
+
* @param source - The descriptor.
|
|
21
|
+
* @returns The same descriptor, frozen.
|
|
22
|
+
* @throws {Error} When the id is not a dotted name such as `"game.position"`.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // A game's .dev module: the orders on the board, re-read after every edge.
|
|
26
|
+
* export const orders = defineSource({
|
|
27
|
+
* id: "timber.orders",
|
|
28
|
+
* title: "Orders",
|
|
29
|
+
* input: {},
|
|
30
|
+
* changes: "edge",
|
|
31
|
+
* read: app => app.flow.state().path
|
|
32
|
+
* });
|
|
33
|
+
* read(app, orders); // "board/awaitIntent"
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function defineSource(source) {
|
|
37
|
+
checkId("source", source.id);
|
|
38
|
+
Object.freeze(source.input);
|
|
39
|
+
return Object.freeze(source);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Declares a command: a dev-only action the editor lists and runs. Its body starts with the
|
|
43
|
+
* inline dev guard, so a production build drops it.
|
|
44
|
+
*
|
|
45
|
+
* @param command - The descriptor.
|
|
46
|
+
* @returns The same descriptor, frozen.
|
|
47
|
+
* @throws {Error} When the id is not a dotted name such as `"game.answer"`.
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* // A game's .dev module: open the shop from anywhere, through the graph.
|
|
51
|
+
* export const openShop = defineCommand({
|
|
52
|
+
* id: "timber.openShop",
|
|
53
|
+
* title: "Open the shop",
|
|
54
|
+
* input: {},
|
|
55
|
+
* effect: "route",
|
|
56
|
+
* run: app => {
|
|
57
|
+
* if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
58
|
+
* return app.flow.walk([{ at: "home", intent: "shop" }]);
|
|
59
|
+
* }
|
|
60
|
+
* });
|
|
61
|
+
* (await run(app, openShop)).state.path; // "shop"
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
function defineCommand(command) {
|
|
65
|
+
checkId("command", command.id);
|
|
66
|
+
Object.freeze(command.input);
|
|
67
|
+
return Object.freeze(command);
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/plugins/flow/doors/input.ts
|
|
71
|
+
/** The input of a call that passed none. */
|
|
72
|
+
const noInput = Object.freeze({});
|
|
73
|
+
/**
|
|
74
|
+
* Hands the given input on, or an empty one. `InputArguments` and `WatchInput` let a call leave
|
|
75
|
+
* the input out only when every field of the schema is optional, so `{}` is an input of that
|
|
76
|
+
* schema.
|
|
77
|
+
*
|
|
78
|
+
* @param input - The input of the call, if any.
|
|
79
|
+
* @returns The input to pass to the descriptor.
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* inputOrEmpty<{ last: "number?" }>(undefined); // {}
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
function inputOrEmpty(input) {
|
|
86
|
+
return input ?? noInput;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/plugins/flow/doors/session.ts
|
|
90
|
+
/** The journal of an app that ran no cheat. */
|
|
91
|
+
const noCheats = Object.freeze([]);
|
|
92
|
+
const sessions = /*#__PURE__*/ new WeakMap();
|
|
93
|
+
/**
|
|
94
|
+
* Tells whether a cheat or a raw command ran on this app.
|
|
95
|
+
*
|
|
96
|
+
* @param app - The app.
|
|
97
|
+
* @returns True once a `cheat` or `raw` command ran.
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* // After run(app, commands.restore, { repro }): a restore is raw.
|
|
101
|
+
* isTainted(app); // true
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
function isTainted(app) {
|
|
105
|
+
return sessions.get(app)?.tainted ?? false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Reads the journal of cheat and raw commands of this app, oldest first.
|
|
109
|
+
*
|
|
110
|
+
* @param app - The app.
|
|
111
|
+
* @returns The frozen journal, the same list until the next cheat.
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* // After run(app, commands.restore, { bookmark }) on frame 96.
|
|
115
|
+
* cheatsOf(app); // [{ id: "game.restore", input: { bookmark: { path: "home", ... } }, frame: 96 }]
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
function cheatsOf(app) {
|
|
119
|
+
return sessions.get(app)?.cheats ?? noCheats;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Taints the session of this app and journals the command. The input is copied, so the caller
|
|
123
|
+
* cannot rewrite the journal afterwards.
|
|
124
|
+
*
|
|
125
|
+
* @param app - The app.
|
|
126
|
+
* @param id - The command id.
|
|
127
|
+
* @param input - The input the command got.
|
|
128
|
+
* @param frame - The frame it runs on.
|
|
129
|
+
*/
|
|
130
|
+
function recordCheat(app, id, input, frame) {
|
|
131
|
+
const session = sessions.get(app) ?? {
|
|
132
|
+
tainted: true,
|
|
133
|
+
cheats: noCheats
|
|
134
|
+
};
|
|
135
|
+
const entry = Object.freeze({
|
|
136
|
+
id,
|
|
137
|
+
input: structuredClone(input),
|
|
138
|
+
frame
|
|
139
|
+
});
|
|
140
|
+
session.tainted = true;
|
|
141
|
+
session.cheats = Object.freeze([...session.cheats.slice(-499), entry]);
|
|
142
|
+
sessions.set(app, session);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { defineCommand as a, inputOrEmpty as i, isTainted as n, defineSource as o, recordCheat as r, cheatsOf as t };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,125 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as PlayerStateProvider, L as Json, U as ProviderCall, W as SaveDoc, it as FakeClock } from "./types-JNc_UQBo.mjs";
|
|
2
|
+
import { a as createHeadless, i as ReproResult, n as HeadlessGame, o as runRepro, r as Repro, s as stepFrames, t as HeadlessApp } from "./headless-KaSWcd0s.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/plugins/clock/fake.d.ts
|
|
4
5
|
/**
|
|
5
|
-
* Creates a fake source for tests. Timers fire synchronously inside `advance`, in due order.
|
|
6
|
+
* Creates a fake source for tests. Timers fire synchronously inside `advance`, in due order. A
|
|
7
|
+
* negative delay counts as zero and an unknown handle is ignored.
|
|
6
8
|
*
|
|
7
9
|
* @param start - Starting moment in epoch milliseconds. Defaults to 0.
|
|
8
10
|
* @returns A clock source with the test-only controls `advance` and `set`.
|
|
9
11
|
* @example
|
|
10
12
|
* ```ts
|
|
13
|
+
* // An energy point refills 60 s after it was spent. The test moves the clock, not the minute.
|
|
11
14
|
* const clock = fakeClock(1000);
|
|
12
|
-
* clock
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
declare function fakeClock(start?: number): FakeClock;
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/plugins/flow/headless.d.ts
|
|
18
|
-
/**
|
|
19
|
-
* The part of an app the headless helpers use. Structural on purpose: any app created with the
|
|
20
|
-
* default plugins fits.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* const app: HeadlessApp = createApp({ pluginConfigs: { flow: { mainFlow } } });
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
type HeadlessApp = {
|
|
28
|
-
start(): Promise<void>;
|
|
29
|
-
stop(): Promise<void>;
|
|
30
|
-
time: Api;
|
|
31
|
-
flow: Api$1;
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* A game played without a screen: answers go through the gate, effects resolve instantly.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* const game: HeadlessGame = await createHeadless(app);
|
|
39
|
-
* await game.walk([{ at: "home", intent: "play" }]);
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
type HeadlessGame = {
|
|
43
|
-
walk(route: readonly RouteStep[]): Promise<FlowState>;
|
|
44
|
-
answer(answer: Answer): boolean;
|
|
45
|
-
state(): FlowState;
|
|
46
|
-
history(): readonly JournalEntry[];
|
|
47
|
-
stop(): Promise<void>;
|
|
48
|
-
};
|
|
49
|
-
/**
|
|
50
|
-
* A reproducible run: a starting state, an optional checkpoint and the route played from it.
|
|
15
|
+
* const app = createApp({ pluginConfigs: { clock: { source: clock } } });
|
|
16
|
+
* const seen: number[] = [];
|
|
51
17
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
18
|
+
* app.clock.onElapsed(({ now }) => seen.push(now));
|
|
19
|
+
* app.clock.scheduleAt(61_000);
|
|
20
|
+
* clock.advance(60_000); // the timer fires inside advance, nothing waits
|
|
21
|
+
* seen; // [61000]
|
|
55
22
|
* ```
|
|
56
23
|
*/
|
|
57
|
-
|
|
58
|
-
player: Json;
|
|
59
|
-
session?: Json;
|
|
60
|
-
rng?: RngState;
|
|
61
|
-
checkpoint?: string;
|
|
62
|
-
route: RouteStep[];
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* What a repro run ends with: the path where the loop rests and the committed state.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```ts
|
|
69
|
-
* const { path, player }: ReproResult = await runRepro(app, repro);
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
type ReproResult = {
|
|
73
|
-
path: string[];
|
|
74
|
-
player: Json;
|
|
75
|
-
session: Json;
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Starts an app headless: sets flow mode `"fast"`, awaits `app.start()`, starts `flow.run()`
|
|
79
|
-
* unless the app's own `onStart` already did, and waits until the loop rests for the first time.
|
|
80
|
-
* `game.state().path` therefore names the first rest node of the graph as soon as the call
|
|
81
|
-
* resolves. A fatal error of the loop rejects this call, and a later one is re-thrown by `walk`
|
|
82
|
-
* and by `stop`, so a headless test never loses it.
|
|
83
|
-
*
|
|
84
|
-
* @param app - An app that is not started yet.
|
|
85
|
-
* @returns The game: walk it, answer it, read it, stop it.
|
|
86
|
-
* @throws {Error} When the loop failed fatally before it reached its first rest node.
|
|
87
|
-
* @example
|
|
88
|
-
* ```ts
|
|
89
|
-
* const game = await createHeadless(app);
|
|
90
|
-
* expect(game.state().path).toBe("home");
|
|
91
|
-
* await game.walk([{ at: "board/awaitIntent", intent: "merge", payload: { from: "c2", to: "c3" } }]);
|
|
92
|
-
* await game.stop();
|
|
93
|
-
* ```
|
|
94
|
-
*/
|
|
95
|
-
declare function createHeadless(app: HeadlessApp): Promise<HeadlessGame>;
|
|
96
|
-
/**
|
|
97
|
-
* Plays a repro: starts the app headless, restores the bookmark built from its state and
|
|
98
|
-
* checkpoint, then walks its route. The app keeps running, so the caller can read more than the
|
|
99
|
-
* result and stops it itself.
|
|
100
|
-
*
|
|
101
|
-
* @param app - An app that is not started yet.
|
|
102
|
-
* @param repro - Starting state, optional checkpoint and the route.
|
|
103
|
-
* @returns The path the run ended at, and the committed state.
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* const result = await runRepro(app, { player: saved, checkpoint: "home", route });
|
|
107
|
-
* ```
|
|
108
|
-
*/
|
|
109
|
-
declare function runRepro(app: HeadlessApp, repro: Repro): Promise<ReproResult>;
|
|
110
|
-
/**
|
|
111
|
-
* Steps the frame loop by hand: `count` calls of `app.time.step(deltaMs)`. A headless game has no
|
|
112
|
-
* frame source, so this is the only thing that moves the frame phases.
|
|
113
|
-
*
|
|
114
|
-
* @param app - A started app.
|
|
115
|
-
* @param count - Number of frames.
|
|
116
|
-
* @param deltaMs - Milliseconds per frame.
|
|
117
|
-
* @example
|
|
118
|
-
* ```ts
|
|
119
|
-
* stepFrames(app, 60, 16);
|
|
120
|
-
* ```
|
|
121
|
-
*/
|
|
122
|
-
declare function stepFrames(app: HeadlessApp, count: number, deltaMs: number): void;
|
|
24
|
+
declare function fakeClock(start?: number): FakeClock;
|
|
123
25
|
//#endregion
|
|
124
26
|
//#region src/plugins/model/store/providers/memory.d.ts
|
|
125
27
|
/**
|
|
@@ -129,13 +31,21 @@ declare function stepFrames(app: HeadlessApp, count: number, deltaMs: number): v
|
|
|
129
31
|
* long as the provider does, so a second app over the same instance reads what the first one
|
|
130
32
|
* wrote, and nothing survives the process.
|
|
131
33
|
*
|
|
34
|
+
* `load()` reads the fixture, or everything committed since; without either, the player is new.
|
|
35
|
+
* `commitDurable()` resolves at once: there is no disk behind it.
|
|
36
|
+
*
|
|
132
37
|
* @param fixture - The save `load()` starts from. Omitted: a new player.
|
|
133
38
|
* @param fixture.state - The saved document.
|
|
134
39
|
* @param fixture.version - Schema version of the saved document.
|
|
135
40
|
* @returns A provider that keeps its document and records its calls.
|
|
136
41
|
* @example
|
|
137
42
|
* ```ts
|
|
138
|
-
*
|
|
43
|
+
* // A new player is saved at once. One roll reaches the provider at the next rest node.
|
|
44
|
+
* const provider = memory();
|
|
45
|
+
* const game = await createHeadless(createGame(provider));
|
|
46
|
+
*
|
|
47
|
+
* await game.walk([{ at: "home", intent: "roll" }]);
|
|
48
|
+
* provider.calls.map(call => call.method); // ["load", "commit", "commit"]
|
|
139
49
|
* ```
|
|
140
50
|
*/
|
|
141
51
|
declare function memory(fixture?: {
|
|
@@ -153,6 +63,8 @@ declare function memory(fixture?: {
|
|
|
153
63
|
* @returns The save document.
|
|
154
64
|
* @example
|
|
155
65
|
* ```ts
|
|
66
|
+
* // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
|
|
67
|
+
* saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
|
|
156
68
|
* const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
|
|
157
69
|
* ```
|
|
158
70
|
*/
|