@moku-labs/game 0.0.2 → 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.
Files changed (35) hide show
  1. package/README.md +89 -21
  2. package/dist/assets.d.mts +58 -6
  3. package/dist/assets.mjs +1630 -234
  4. package/dist/commands-BOxqlv7w.d.mts +99 -0
  5. package/dist/control.d.mts +3 -92
  6. package/dist/control.mjs +3 -757
  7. package/dist/define-BMo0pHe_.d.mts +51 -0
  8. package/dist/{registry-DlpRCibU.mjs → dev-CcwFrwok.mjs} +30 -1
  9. package/dist/index.d.mts +217 -29
  10. package/dist/index.mjs +6179 -727
  11. package/dist/inspect.d.mts +10 -8
  12. package/dist/inspect.mjs +2 -518
  13. package/dist/jsx-dev-runtime.d.mts +1 -1
  14. package/dist/jsx-runtime.d.mts +1 -1
  15. package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
  16. package/dist/run-tlcn1w7y.mjs +944 -0
  17. package/dist/{runtime-DiOTkDZz.d.mts → runtime-BstIkrhH.d.mts} +1 -1
  18. package/dist/sources-BooL2MsX.mjs +545 -0
  19. package/dist/testing.d.mts +305 -3
  20. package/dist/testing.mjs +2056 -2
  21. package/dist/types-BTycq8_Z.d.mts +1196 -0
  22. package/dist/{types-yg_ywtT-.d.mts → types-C0SLQsx0.d.mts} +230 -31
  23. package/dist/types-Cjf3ywai.d.mts +5982 -0
  24. package/dist/{define-sFoO3y6X.d.mts → types-D0uYTTC3.d.mts} +180 -51
  25. package/dist/{types-DWILGrPn.d.mts → types-DgwbzDLP.d.mts} +87 -19
  26. package/dist/types-KlE_GL1z.d.mts +1444 -0
  27. package/dist/{visual-BDUHSRvf.mjs → visual-NPrDkvUk.mjs} +19 -13
  28. package/package.json +17 -2
  29. package/dist/headless-CvamCUcR.mjs +0 -186
  30. package/dist/headless-KaSWcd0s.d.mts +0 -181
  31. package/dist/types-BxkNNYul.d.mts +0 -2840
  32. package/dist/types-CTPS9GBu.d.mts +0 -742
  33. package/dist/types-DD-QrG_z.d.mts +0 -588
  34. package/dist/types-DYLnSgMI.d.mts +0 -570
  35. package/dist/types-JNc_UQBo.d.mts +0 -2896
@@ -197,7 +197,7 @@ const Transform = /*#__PURE__*/ component("Transform", transformDefaults);
197
197
  const Sprite = /*#__PURE__*/ component("Sprite", spriteDefaults);
198
198
  /**
199
199
  * A stretchable panel, sized in reference units, with its own alpha and tint. `debug: true` draws
200
- * the slice outline over it.
200
+ * the slice outline over it. `clip: true` masks the children of the entity to the panel box.
201
201
  */
202
202
  const NineSlice = /*#__PURE__*/ component("NineSlice", {
203
203
  texture: "",
@@ -205,7 +205,8 @@ const NineSlice = /*#__PURE__*/ component("NineSlice", {
205
205
  height: 0,
206
206
  alpha: 1,
207
207
  tint: 16777215,
208
- debug: false
208
+ debug: false,
209
+ clip: false
209
210
  });
210
211
  /**
211
212
  * The entity this view moves with. `0` means no parent.
@@ -266,8 +267,9 @@ function componentsFor() {
266
267
  }
267
268
  /**
268
269
  * Words on the screen: a string or a message, the style it is drawn in, and the string `text`
269
- * resolved out of it. A game writes `content`, `style`, `bind` and `anchor`; `resolved` is
270
- * engine-owned and `ui` and the tests read it.
270
+ * resolved out of it. A game writes `content`, `style`, `bind`, `anchor` and `alpha`; `resolved`
271
+ * is engine-owned and `ui` and the tests read it. `alpha` (1 by default) fades the whole block and
272
+ * is patched in place, so a tween on it rebuilds nothing.
271
273
  */
272
274
  const Text = /*#__PURE__*/ component("Text", {
273
275
  content: "",
@@ -277,6 +279,7 @@ const Text = /*#__PURE__*/ component("Text", {
277
279
  x: .5,
278
280
  y: .5
279
281
  },
282
+ alpha: 1,
280
283
  resolved: ""
281
284
  }, { owned: ["resolved"] });
282
285
  /**
@@ -289,7 +292,7 @@ const Text = /*#__PURE__*/ component("Text", {
289
292
  * ```ts
290
293
  * label({ text: "+5", style: "board.float", at: { x: 90, y: 180 } });
291
294
  * // [Text({ content: "+5", style: "board.float", bind: undefined,
292
- * // anchor: { x: 0.5, y: 0.5 }, resolved: "" }),
295
+ * // anchor: { x: 0.5, y: 0.5 }, alpha: 1, resolved: "" }),
293
296
  * // Transform({ x: 90, y: 180, rotation: 0, scale: 1 })]
294
297
  * ```
295
298
  */
@@ -585,10 +588,12 @@ function contentOf(props) {
585
588
  * The visual component of an element: a sprite for an image or an icon, the text for a text, the
586
589
  * nine-slice of its style (outlined when the style sets `debug`), or a rounded rectangle, a
587
590
  * triangle with `shape: "triangle"`, its stroke dashed with `dash`. A clipping element (`scroll`,
588
- * `overflow: "hidden"`) keeps the rectangle, which carries the clip. A style with a stroke and no fill draws
589
- * only the stroke, a ring. A container with no fill and no stroke gets an invisible rectangle:
590
- * the renderer hangs children under the display object of their parent, so every parent needs
591
- * one.
591
+ * `overflow: "hidden"`) keeps the rectangle, which carries the clip; a text field's rectangle
592
+ * clips too, and a nine-slice field draws its slices and clips to them. A style with a stroke and
593
+ * no fill draws only the stroke, a ring. A container with no fill and no stroke gets a rectangle that draws
594
+ * nothing, no fill and no stroke: the renderer hangs children under the display object of their
595
+ * parent, so every parent needs one. It keeps the alpha of its style, because the alpha of an
596
+ * entity fades its children too.
592
597
  *
593
598
  * @param element - The element to draw, with its rect known.
594
599
  * @returns The component values: one visual.
@@ -628,7 +633,8 @@ function visualOf(element) {
628
633
  height: rect.h,
629
634
  alpha,
630
635
  tint,
631
- debug: style.debug ?? false
636
+ debug: style.debug ?? false,
637
+ clip: type === "input"
632
638
  })];
633
639
  const filled = style.fill !== void 0 || style.stroke !== void 0;
634
640
  const invisible = isContainer(type) && !filled;
@@ -638,12 +644,12 @@ function visualOf(element) {
638
644
  h: rect.h,
639
645
  fill: style.fill ?? Shape.defaults.fill,
640
646
  fillAlpha: style.fill === void 0 ? 0 : Shape.defaults.fillAlpha,
641
- alpha: invisible ? 0 : alpha,
647
+ alpha,
642
648
  radius: style.radius ?? 0,
643
649
  stroke: style.stroke ?? Shape.defaults.stroke,
644
- strokeWidth: style.strokeWidth ?? 0,
650
+ strokeWidth: invisible ? 0 : style.strokeWidth ?? 0,
645
651
  dash: style.dash ?? 0,
646
- clip
652
+ clip: clip || type === "input"
647
653
  })];
648
654
  }
649
655
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -64,13 +64,25 @@
64
64
  "test:integration": "vitest run --project integration",
65
65
  "test:coverage": "vitest run --project unit --project integration --coverage",
66
66
  "build": "tsdown",
67
+ "fixture:pack": "bun src/assets.ts --root tests/integration/merge-game --keys tests/integration/merge-game/generated/assets.ts --pack tests/integration/merge-game/dist/assets",
68
+ "fixture:visual": "bun tests/visual/run.ts",
67
69
  "validate": "publint && attw --pack . --profile esm-only",
68
70
  "release:setup": "moku-release setup",
69
71
  "release:doctor": "moku-release doctor",
70
72
  "release": "moku-release"
71
73
  },
72
74
  "peerDependencies": {
73
- "pixi.js": "^8.0.0"
75
+ "pixi.js": "^8.0.0",
76
+ "playwright-core": ">=1.50.0",
77
+ "sharp": "^0.34.0"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "playwright-core": {
81
+ "optional": true
82
+ },
83
+ "sharp": {
84
+ "optional": true
85
+ }
74
86
  },
75
87
  "devDependencies": {
76
88
  "@arethetypeswrong/cli": "0.18.3",
@@ -89,7 +101,9 @@
89
101
  "jiti": "2.6.1",
90
102
  "lefthook": "2.1.1",
91
103
  "pixi.js": "8.21.0",
104
+ "playwright-core": "1.60.0",
92
105
  "publint": "0.3.21",
106
+ "sharp": "0.34.5",
93
107
  "tsdown": "0.22.1",
94
108
  "typescript": "6.0.3",
95
109
  "typescript-eslint": "8.58.0",
@@ -99,6 +113,7 @@
99
113
  "@moku-labs/common": "0.3.3",
100
114
  "@moku-labs/core": "1.7.0",
101
115
  "immer": "11.1.18",
116
+ "maxrects-packer": "2.7.3",
102
117
  "yoga-layout": "3.2.1"
103
118
  },
104
119
  "publishConfig": {
@@ -1,186 +0,0 @@
1
- import { a as graphHash } from "./registry-DlpRCibU.mjs";
2
- //#region src/plugins/flow/headless.ts
3
- const noPayload = null;
4
- /** Seed of a repro that pins no rng state, the seed `saveOf` writes. */
5
- const defaultSeed = 1;
6
- /**
7
- * Turns whatever `run()` rejected with into an error that can be thrown again.
8
- *
9
- * @param value - The rejection value.
10
- * @returns The value itself when it is an error, a wrapped one otherwise.
11
- * @example
12
- * ```ts
13
- * asError("offline").message; // "[game] The headless run failed.\n offline."
14
- * ```
15
- */
16
- function asError(value) {
17
- if (value instanceof Error) return value;
18
- return /* @__PURE__ */ new Error(`[game] The headless run failed.\n ${String(value)}.`);
19
- }
20
- /**
21
- * Starts the one loop unless the app's own `onStart` already did, and keeps a fatal error until
22
- * someone asks for it. `run()` rejects only on a fatal error, and nobody awaits it here.
23
- *
24
- * @param app - The started app.
25
- * @returns The holder of the fatal error.
26
- */
27
- function startLoop(app) {
28
- const fatal = { error: void 0 };
29
- if (app.flow.state().running) return fatal;
30
- app.flow.run().catch((error) => {
31
- fatal.error = error;
32
- });
33
- return fatal;
34
- }
35
- /**
36
- * Waits until the loop rests for the first time, or ends. An empty route walks nowhere: it only
37
- * waits for the gate the loop opens at the node it enters, so a start that is a chain of transit
38
- * nodes is played out before anything reads the game.
39
- *
40
- * @param app - The started app whose loop is running.
41
- * @param fatal - Holder of the fatal error of `run()`.
42
- * @returns Resolves once the loop rests at its first rest node.
43
- * @throws {Error} When the loop failed fatally instead of reaching a rest node.
44
- */
45
- async function settleAtFirstRest(app, fatal) {
46
- await app.flow.walk([]).catch((error) => {
47
- throw asError(fatal.error ?? error);
48
- });
49
- if (fatal.error !== void 0) throw asError(fatal.error);
50
- }
51
- /**
52
- * Starts an app headless: sets flow mode `"fast"`, awaits `app.start()`, starts `flow.run()`
53
- * unless the app's own `onStart` already did, and waits until the loop rests for the first time.
54
- * `game.state().path` therefore names the first rest node of the graph as soon as the call
55
- * resolves. A fatal error of the loop rejects this call, and a later one is re-thrown by `walk`
56
- * and by `stop`, so a headless test never loses it.
57
- *
58
- * @param app - An app that is not started yet.
59
- * @returns The game: walk it, answer it, read it, stop it.
60
- * @throws {Error} When the loop failed fatally before it reached its first rest node.
61
- * @example
62
- * ```ts
63
- * // A test starts the merge game without a screen. The call resolves at the first rest node.
64
- * const game = await createHeadless(app);
65
- *
66
- * game.state().path; // "home": the transit node "boot" was played out
67
- * game.state().mode; // "fast"
68
- * await game.stop();
69
- * ```
70
- */
71
- async function createHeadless(app) {
72
- app.flow.setMode("fast");
73
- await app.start();
74
- const fatal = startLoop(app);
75
- await settleAtFirstRest(app, fatal);
76
- return {
77
- walk: async (route) => {
78
- const state = await app.flow.walk(route).catch((error) => {
79
- throw asError(fatal.error ?? error);
80
- });
81
- if (fatal.error !== void 0) throw asError(fatal.error);
82
- return state;
83
- },
84
- answer: (answer) => app.flow.gate.answer(answer),
85
- state: () => app.flow.state(),
86
- history: () => app.flow.history(),
87
- stop: async () => {
88
- await app.stop();
89
- if (fatal.error !== void 0) throw asError(fatal.error);
90
- }
91
- };
92
- }
93
- /**
94
- * Reads the rest node a repro starts at: its checkpoint, or the start of the main flow.
95
- *
96
- * @param app - The started app.
97
- * @param repro - Starting state, optional checkpoint and the route.
98
- * @returns The path of the node the repro enters.
99
- * @throws {Error} When the graph has no main flow to start from.
100
- */
101
- function reproPath(app, repro) {
102
- if (repro.checkpoint !== void 0) return repro.checkpoint;
103
- const graph = app.flow.describe();
104
- const start = graph.flows[graph.main]?.start;
105
- if (start === void 0) throw new Error(`[game] runRepro() found no flow "${graph.main}" to start from.\n Name the checkpoint the repro was taken at.`);
106
- return start;
107
- }
108
- /**
109
- * Builds the bookmark a repro is entered with: its state, its checkpoint and the hash of the
110
- * graph that runs now, so a checkpoint is accepted and any other rest node is checked. The route
111
- * is not part of it: the caller walks it after the restore.
112
- *
113
- * @param app - The started app.
114
- * @param repro - Starting state, optional checkpoint and the route.
115
- * @returns The bookmark to restore.
116
- * @throws {Error} When the repro names no checkpoint and the graph has no main flow.
117
- * @example
118
- * ```ts
119
- * // The game.restore command loads a bug report taken at the "home" checkpoint.
120
- * const bookmark = reproBookmark(app, { player: { coins: 7 }, checkpoint: "home", route: [] });
121
- * bookmark.path; // "home"
122
- * bookmark.rng; // { seed: 1, streams: {} }: the repro pinned no rng
123
- * await app.flow.restore(bookmark);
124
- * ```
125
- */
126
- function reproBookmark(app, repro) {
127
- return {
128
- path: reproPath(app, repro),
129
- input: noPayload,
130
- player: repro.player,
131
- session: repro.session ?? {},
132
- rng: repro.rng ?? {
133
- seed: defaultSeed,
134
- streams: {}
135
- },
136
- graph: graphHash(app.flow.describe())
137
- };
138
- }
139
- /**
140
- * Plays a repro: starts the app headless, restores the bookmark built from its state and
141
- * checkpoint, then walks its route. The app keeps running, so the caller can read more than the
142
- * result and stops it itself.
143
- *
144
- * @param app - An app that is not started yet.
145
- * @param repro - Starting state, optional checkpoint and the route.
146
- * @returns The path the run ended at, and the committed state.
147
- * @example
148
- * ```ts
149
- * // A bug report on the dice game: "reset keeps my coins". Replay it from the "home" checkpoint.
150
- * const reset = { at: "home", intent: "reset" };
151
- * const result = await runRepro(app, { player: { coins: 7 }, checkpoint: "home", route: [reset] });
152
- *
153
- * result.player; // { coins: 0 }; result.path is ["home"]
154
- * await app.stop(); // the app keeps running after the run
155
- * ```
156
- */
157
- async function runRepro(app, repro) {
158
- const game = await createHeadless(app);
159
- await app.flow.restore(reproBookmark(app, repro));
160
- const state = await game.walk(repro.route);
161
- const ended = app.flow.bookmark();
162
- return {
163
- path: state.stack.map((frame) => frame.node),
164
- player: ended.player,
165
- session: ended.session
166
- };
167
- }
168
- /**
169
- * Steps the frame loop by hand: `count` calls of `app.time.step(deltaMs)`. A headless game has no
170
- * frame source, so this is the only thing that moves the frame phases.
171
- *
172
- * @param app - A started app.
173
- * @param count - Number of frames.
174
- * @param deltaMs - Milliseconds per frame.
175
- * @example
176
- * ```ts
177
- * // A headless game has no frame source: the test moves the six frame phases by hand.
178
- * stepFrames(app, 2, 16);
179
- * app.time.snapshot(); // frame: 2, delta: 16, elapsed: 32
180
- * ```
181
- */
182
- function stepFrames(app, count, deltaMs) {
183
- for (let frame = 0; frame < count; frame += 1) app.time.step(deltaMs);
184
- }
185
- //#endregion
186
- export { stepFrames as i, reproBookmark as n, runRepro as r, createHeadless as t };
@@ -1,181 +0,0 @@
1
- import { L as Json, O as Answer, S as RouteStep, Y as RngState, b as JournalEntry, k as Api, t as Api$1, y as FlowState } from "./types-JNc_UQBo.mjs";
2
-
3
- //#region src/plugins/flow/headless.d.ts
4
- /**
5
- * The part of an app the headless helpers use. Structural on purpose: any app created with the
6
- * default plugins fits.
7
- *
8
- * @example
9
- * ```ts
10
- * const app: HeadlessApp = createApp({ pluginConfigs: { flow: { mainFlow } } });
11
- * ```
12
- */
13
- type HeadlessApp = {
14
- start(): Promise<void>;
15
- stop(): Promise<void>;
16
- time: Api;
17
- flow: Api$1;
18
- };
19
- /**
20
- * A game played without a screen: answers go through the gate, effects resolve instantly.
21
- *
22
- * @example
23
- * ```ts
24
- * // A test of the dice game: two rolls, no screen, no waiting.
25
- * const game = await createHeadless(app);
26
- * const state = await game.walk([{ at: "home", intent: "roll" }, { at: "home", intent: "roll" }]);
27
- *
28
- * state.path; // "home"
29
- * await game.stop();
30
- * ```
31
- */
32
- type HeadlessGame = {
33
- /**
34
- * Walks a route through the running loop.
35
- *
36
- * @param route - The player's answers and substituted sub-flow results, in order.
37
- * @returns The state the walk ended in.
38
- * @throws {Error} When a step is never reached, or the loop failed fatally.
39
- * @example
40
- * ```ts
41
- * // Open the board and tap the generator twice. Every node's logic runs for real.
42
- * const tap = { at: "board/awaitIntent", intent: "tap", payload: { generatorId: "sawmill" } };
43
- * const state = await game.walk([{ at: "home", intent: "play" }, tap, tap]);
44
- *
45
- * state.path; // "board/awaitIntent"
46
- * ```
47
- */
48
- walk(route: readonly RouteStep[]): Promise<FlowState>;
49
- /**
50
- * Gives one answer to the gate, the way a tap does.
51
- *
52
- * @param answer - Intent and optional payload.
53
- * @returns Whether the gate took it.
54
- * @example
55
- * ```ts
56
- * // One tap without a route. The test then waits for the loop by itself.
57
- * game.answer({ intent: "play" }); // true: "home" rests and lists "play"
58
- * ```
59
- */
60
- answer(answer: Answer): boolean;
61
- /**
62
- * Reads where the graph stands.
63
- *
64
- * @returns Whether it runs, the path, the stack, what it waits for and the mode.
65
- * @example
66
- * ```ts
67
- * // Right after createHeadless: the graph rests at its first rest node.
68
- * game.state().path; // "home"
69
- * game.state().pending; // { gate: ["play"] }
70
- * ```
71
- */
72
- state(): FlowState;
73
- /**
74
- * Reads the edges taken since the last checkpoint.
75
- *
76
- * @returns A copy of the journal.
77
- * @example
78
- * ```ts
79
- * // After a merge onto an empty cell: the last edge says why the move was refused.
80
- * const last = game.history().at(-1);
81
- * // last?.path: "board/merge", last?.outcome: "rejected", last?.payload: { reason: "empty" }
82
- * ```
83
- */
84
- history(): readonly JournalEntry[];
85
- /**
86
- * Stops the app, then re-throws a fatal error of the loop.
87
- *
88
- * @throws {Error} When the loop failed fatally.
89
- * @example
90
- * ```ts
91
- * // The last line of a headless test. A fatal error of the loop fails the test here.
92
- * await game.stop();
93
- * ```
94
- */
95
- stop(): Promise<void>;
96
- };
97
- /**
98
- * A reproducible run: a starting state, an optional checkpoint and the route played from it.
99
- *
100
- * @example
101
- * ```ts
102
- * const repro: Repro = { player: { coins: 0 }, checkpoint: "home", route: [{ at: "home", intent: "play" }] };
103
- * ```
104
- */
105
- type Repro = {
106
- player: Json;
107
- session?: Json;
108
- rng?: RngState;
109
- checkpoint?: string;
110
- route: RouteStep[];
111
- };
112
- /**
113
- * What a repro run ends with: the path where the loop rests and the committed state.
114
- *
115
- * @example
116
- * ```ts
117
- * const result: ReproResult = { path: ["home"], player: { coins: 3 }, session: { visits: 1 } };
118
- * ```
119
- */
120
- type ReproResult = {
121
- path: string[];
122
- player: Json;
123
- session: Json;
124
- };
125
- /**
126
- * Starts an app headless: sets flow mode `"fast"`, awaits `app.start()`, starts `flow.run()`
127
- * unless the app's own `onStart` already did, and waits until the loop rests for the first time.
128
- * `game.state().path` therefore names the first rest node of the graph as soon as the call
129
- * resolves. A fatal error of the loop rejects this call, and a later one is re-thrown by `walk`
130
- * and by `stop`, so a headless test never loses it.
131
- *
132
- * @param app - An app that is not started yet.
133
- * @returns The game: walk it, answer it, read it, stop it.
134
- * @throws {Error} When the loop failed fatally before it reached its first rest node.
135
- * @example
136
- * ```ts
137
- * // A test starts the merge game without a screen. The call resolves at the first rest node.
138
- * const game = await createHeadless(app);
139
- *
140
- * game.state().path; // "home": the transit node "boot" was played out
141
- * game.state().mode; // "fast"
142
- * await game.stop();
143
- * ```
144
- */
145
- declare function createHeadless(app: HeadlessApp): Promise<HeadlessGame>;
146
- /**
147
- * Plays a repro: starts the app headless, restores the bookmark built from its state and
148
- * checkpoint, then walks its route. The app keeps running, so the caller can read more than the
149
- * result and stops it itself.
150
- *
151
- * @param app - An app that is not started yet.
152
- * @param repro - Starting state, optional checkpoint and the route.
153
- * @returns The path the run ended at, and the committed state.
154
- * @example
155
- * ```ts
156
- * // A bug report on the dice game: "reset keeps my coins". Replay it from the "home" checkpoint.
157
- * const reset = { at: "home", intent: "reset" };
158
- * const result = await runRepro(app, { player: { coins: 7 }, checkpoint: "home", route: [reset] });
159
- *
160
- * result.player; // { coins: 0 }; result.path is ["home"]
161
- * await app.stop(); // the app keeps running after the run
162
- * ```
163
- */
164
- declare function runRepro(app: HeadlessApp, repro: Repro): Promise<ReproResult>;
165
- /**
166
- * Steps the frame loop by hand: `count` calls of `app.time.step(deltaMs)`. A headless game has no
167
- * frame source, so this is the only thing that moves the frame phases.
168
- *
169
- * @param app - A started app.
170
- * @param count - Number of frames.
171
- * @param deltaMs - Milliseconds per frame.
172
- * @example
173
- * ```ts
174
- * // A headless game has no frame source: the test moves the six frame phases by hand.
175
- * stepFrames(app, 2, 16);
176
- * app.time.snapshot(); // frame: 2, delta: 16, elapsed: 32
177
- * ```
178
- */
179
- declare function stepFrames(app: HeadlessApp, count: number, deltaMs: number): void;
180
- //#endregion
181
- export { createHeadless as a, ReproResult as i, HeadlessGame as n, runRepro as o, Repro as r, stepFrames as s, HeadlessApp as t };