@vgai/live 0.5.2 → 0.5.4
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/dist/.tsbuildinfo +1 -0
- package/dist/editor.d.ts +78 -55
- package/dist/editor.js +129 -76
- package/dist/game-client/bridge-heartbeat.d.ts +49 -0
- package/dist/game-client/bridge-heartbeat.js +46 -0
- package/dist/game-client/bridge-transport.d.ts +75 -0
- package/dist/game-client/bridge-transport.js +19 -0
- package/dist/game-client/client.d.ts +293 -0
- package/dist/game-client/client.js +706 -0
- package/dist/game-client/errors.d.ts +57 -0
- package/dist/game-client/errors.js +76 -0
- package/dist/game-client/events-matcher.d.ts +41 -0
- package/dist/game-client/events-matcher.js +68 -0
- package/dist/game-client/failure-block.d.ts +93 -0
- package/dist/game-client/failure-block.js +97 -0
- package/dist/game-client/fast-forward.d.ts +125 -0
- package/dist/game-client/fast-forward.js +122 -0
- package/dist/game-client/hidden-recovery.d.ts +85 -0
- package/dist/game-client/hidden-recovery.js +105 -0
- package/dist/game-client/index.d.ts +40 -0
- package/dist/game-client/index.js +26 -0
- package/dist/game-client/perf-sampling.d.ts +56 -0
- package/dist/game-client/perf-sampling.js +85 -0
- package/dist/game-client/relay-transport.d.ts +100 -0
- package/dist/game-client/relay-transport.js +237 -0
- package/dist/game-client/screenshot-target.d.ts +60 -0
- package/dist/game-client/screenshot-target.js +68 -0
- package/dist/game-client/state-cap.d.ts +7 -0
- package/dist/game-client/state-cap.js +21 -0
- package/dist/game-client/types.d.ts +128 -0
- package/dist/game-client/types.js +15 -0
- package/dist/game-client/wait-for.d.ts +155 -0
- package/dist/game-client/wait-for.js +229 -0
- package/dist/game.d.ts +47 -18
- package/dist/game.js +59 -16
- package/dist/index.d.ts +46 -21
- package/dist/index.js +51 -20
- package/dist/session.d.ts +4 -4
- package/dist/session.js +7 -7
- package/dist/tools.d.ts +12 -3
- package/dist/tools.js +15 -6
- package/package.json +10 -5
- package/src/editor.ts +142 -96
- package/src/game-client/bridge-heartbeat.ts +61 -0
- package/src/game-client/bridge-transport.ts +73 -0
- package/src/game-client/client.ts +836 -0
- package/src/game-client/errors.ts +96 -0
- package/src/game-client/events-matcher.ts +106 -0
- package/src/game-client/failure-block.ts +199 -0
- package/src/game-client/fast-forward.ts +175 -0
- package/src/game-client/hidden-recovery.ts +149 -0
- package/src/game-client/index.ts +98 -0
- package/src/game-client/perf-sampling.ts +94 -0
- package/src/game-client/relay-transport.ts +311 -0
- package/src/game-client/screenshot-target.ts +91 -0
- package/src/game-client/state-cap.ts +29 -0
- package/src/game-client/types.ts +137 -0
- package/src/game-client/wait-for.ts +327 -0
- package/src/game.ts +96 -16
- package/src/index.ts +68 -31
- package/src/session.ts +8 -10
- package/src/tools.ts +19 -6
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport seam `GameClient` (`client.ts`) drives (#140). Two
|
|
3
|
+
* implementations answer the identical contract below:
|
|
4
|
+
* - `PageTransport` (`client.ts`) — Playwright's `page.evaluate` against
|
|
5
|
+
* `window.__vgai`, for a standalone game page the caller drives itself.
|
|
6
|
+
* - `RelayTransport` (`relay-transport.ts`) — the editor dev-server's
|
|
7
|
+
* session wire (`POST /__editor/command`, `bridge-call`/
|
|
8
|
+
* `bridge-screenshot` ops), driving the SAME live session a human already
|
|
9
|
+
* has open, with no new browser/window/vite instance.
|
|
10
|
+
*
|
|
11
|
+
* `wait-for.ts`/`events-matcher.ts`/`failure-block.ts`, and every method
|
|
12
|
+
* body on `GameClient` itself, are written against this interface only —
|
|
13
|
+
* none of them may know or care which transport is underneath (load-bearing
|
|
14
|
+
* for a planned default flip to relay-when-a-live-session-exists, and for a
|
|
15
|
+
* future one-shot REPL client reusing the same relay op). Deliberately NO
|
|
16
|
+
* `@playwright/test` import here, nor in `relay-transport.ts` — only
|
|
17
|
+
* `client.ts` is allowed to touch a live `Page` (see its own module doc).
|
|
18
|
+
*/
|
|
19
|
+
/** Result of one generic bridge-method call — thrown page/relay-side errors
|
|
20
|
+
* never cross either transport boundary AS themselves (Node only keeps
|
|
21
|
+
* `.message` across `page.evaluate`; HTTP/JSON strips everything but what
|
|
22
|
+
* the server explicitly serializes), so both transports catch and report
|
|
23
|
+
* `code`/`data` explicitly, and `GameClient.unwrap` reconstructs a
|
|
24
|
+
* `SessionError` from that. */
|
|
25
|
+
export interface BridgeCallOutcome {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
result?: unknown;
|
|
28
|
+
error?: {
|
|
29
|
+
code: string | undefined;
|
|
30
|
+
message: string;
|
|
31
|
+
data?: unknown;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface BridgeTransport {
|
|
35
|
+
/** Sync-style bridge call (state/stateAll/providers/commands/events/
|
|
36
|
+
* snapshot/runTicks/input.*) — "sync-style" describes the PAGE side's own
|
|
37
|
+
* call, not this method, which is always async across either wire. */
|
|
38
|
+
call(method: string, callArgs: unknown[]): Promise<BridgeCallOutcome>;
|
|
39
|
+
/** Async bridge call — `invoke` (debug commands may themselves be async). */
|
|
40
|
+
callAsync(method: string, callArgs: unknown[]): Promise<BridgeCallOutcome>;
|
|
41
|
+
/** Whether the surface showing the game is currently hidden from the user
|
|
42
|
+
* (`document.hidden` on the page transport) — feeds `HiddenRecoveryDriver`. */
|
|
43
|
+
isHidden(): Promise<boolean>;
|
|
44
|
+
/** Bring the surface showing the game to the foreground. */
|
|
45
|
+
bringToFront(): Promise<void>;
|
|
46
|
+
/** Capture a screenshot to `path` (PNG). A transport that cannot support
|
|
47
|
+
* this should reject with a descriptive error rather than write a blank/
|
|
48
|
+
* corrupt file. */
|
|
49
|
+
screenshot(path: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Wave-2 "one dialect, full capability" — runs a UI-automation step
|
|
52
|
+
* written as a literal `async (page) => {...}` (`GameClient.page()`,
|
|
53
|
+
* `client.ts`). `src` is `step.toString()`; `step` is the ORIGINAL
|
|
54
|
+
* function, wrapped so its own parameter type is erased to `unknown` (only
|
|
55
|
+
* `client.ts` — the one file allowed to touch a real `Page` — ever names
|
|
56
|
+
* the `Page` type itself).
|
|
57
|
+
*
|
|
58
|
+
* The two implementations differ ON PURPOSE, and that difference is the
|
|
59
|
+
* load-bearing honesty boundary this method exists to name:
|
|
60
|
+
* - `PageTransport` (`client.ts`) calls `step` DIRECTLY against the real
|
|
61
|
+
* Playwright `Page` — no serialization, so closures over outer Node
|
|
62
|
+
* values work here exactly like any ordinary `page.evaluate` callback.
|
|
63
|
+
* - `RelayTransport` (`relay-transport.ts`) ships `src` over the wire and
|
|
64
|
+
* reconstructs it with `new Function` INSIDE the editor page, against
|
|
65
|
+
* an in-page shim (`packages/editor/src/playwright-shim.ts`) — closure
|
|
66
|
+
* capture over anything outside the step's own body does NOT survive
|
|
67
|
+
* that trip (the same limitation class as Playwright's own `evaluate`
|
|
68
|
+
* serialization).
|
|
69
|
+
*
|
|
70
|
+
* Because the two transports differ this way, specs meant to pass
|
|
71
|
+
* unmodified under both hosts must be written as though ALWAYS
|
|
72
|
+
* serialized — inline every value the step needs.
|
|
73
|
+
*/
|
|
74
|
+
runPageScript(src: string, step: (page: unknown) => unknown): Promise<BridgeCallOutcome>;
|
|
75
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport seam `GameClient` (`client.ts`) drives (#140). Two
|
|
3
|
+
* implementations answer the identical contract below:
|
|
4
|
+
* - `PageTransport` (`client.ts`) — Playwright's `page.evaluate` against
|
|
5
|
+
* `window.__vgai`, for a standalone game page the caller drives itself.
|
|
6
|
+
* - `RelayTransport` (`relay-transport.ts`) — the editor dev-server's
|
|
7
|
+
* session wire (`POST /__editor/command`, `bridge-call`/
|
|
8
|
+
* `bridge-screenshot` ops), driving the SAME live session a human already
|
|
9
|
+
* has open, with no new browser/window/vite instance.
|
|
10
|
+
*
|
|
11
|
+
* `wait-for.ts`/`events-matcher.ts`/`failure-block.ts`, and every method
|
|
12
|
+
* body on `GameClient` itself, are written against this interface only —
|
|
13
|
+
* none of them may know or care which transport is underneath (load-bearing
|
|
14
|
+
* for a planned default flip to relay-when-a-live-session-exists, and for a
|
|
15
|
+
* future one-shot REPL client reusing the same relay op). Deliberately NO
|
|
16
|
+
* `@playwright/test` import here, nor in `relay-transport.ts` — only
|
|
17
|
+
* `client.ts` is allowed to touch a live `Page` (see its own module doc).
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Playwright-page-bound wiring for `game`'s client methods (D17: specs
|
|
3
|
+
* drive `window.__vgai` via `page.evaluate` — the standalone page has no HTTP
|
|
4
|
+
* channel). This module is deliberately the ONLY place that touches `Page`;
|
|
5
|
+
* the budget math (`wait-for.ts`), the events matcher (`events-matcher.ts`),
|
|
6
|
+
* and the failure-block assembler (`failure-block.ts`) are plain modules
|
|
7
|
+
* that never import Playwright, per Task 3.2's architecture requirement.
|
|
8
|
+
*/
|
|
9
|
+
import type { Page } from '@playwright/test';
|
|
10
|
+
import type { BridgeCallOutcome, BridgeTransport } from './bridge-transport.js';
|
|
11
|
+
import { type FastForwardBudget, type FastForwardOptions } from './fast-forward.js';
|
|
12
|
+
import { type TpsStats } from './perf-sampling.js';
|
|
13
|
+
import type { DebugCommandInfo, DebugSnapshot, ProviderInfo, VirtualActionValue } from './types.js';
|
|
14
|
+
import { type WaitForBudget } from './wait-for.js';
|
|
15
|
+
/**
|
|
16
|
+
* #140 — the `BridgeTransport` `page.evaluate` implementation. This is the
|
|
17
|
+
* ONE place a `Page` is ever touched to drive `window.__vgai` (module doc
|
|
18
|
+
* above); `RelayTransport` (`relay-transport.ts`) is the sibling
|
|
19
|
+
* implementation for the live editor session, and imports no Playwright at
|
|
20
|
+
* all. Exported so `fixture.ts` (which already imports `@playwright/test`
|
|
21
|
+
* for its own `page.goto`/error-listener setup) can construct one.
|
|
22
|
+
*/
|
|
23
|
+
export declare class PageTransport implements BridgeTransport {
|
|
24
|
+
private readonly page;
|
|
25
|
+
constructor(page: Page);
|
|
26
|
+
call(method: string, callArgs: unknown[]): Promise<BridgeCallOutcome>;
|
|
27
|
+
callAsync(method: string, callArgs: unknown[]): Promise<BridgeCallOutcome>;
|
|
28
|
+
isHidden(): Promise<boolean>;
|
|
29
|
+
bringToFront(): Promise<void>;
|
|
30
|
+
screenshot(path: string): Promise<void>;
|
|
31
|
+
/** Wave-2: the ONE transport that runs a `game.page()` step against a REAL
|
|
32
|
+
* Playwright `Page` — no serialization, so `step`'s own closures work
|
|
33
|
+
* here (see `bridge-transport.ts`'s `runPageScript` doc comment for the
|
|
34
|
+
* full honesty-boundary contract; `src` is unused on this leg, kept only
|
|
35
|
+
* to satisfy the shared interface). */
|
|
36
|
+
runPageScript(_src: string, step: (page: unknown) => unknown): Promise<BridgeCallOutcome>;
|
|
37
|
+
}
|
|
38
|
+
export interface GameClientOptions {
|
|
39
|
+
/** The bridge transport — `new PageTransport(page)` for a standalone page
|
|
40
|
+
* (`fixture.ts`), `new RelayTransport({ port })` for `--in-editor`
|
|
41
|
+
* (`relay-fixture.ts`). See `bridge-transport.ts`'s module doc: every
|
|
42
|
+
* method below reaches `window.__vgai` (or its relay-side equivalent)
|
|
43
|
+
* ONLY through this seam. */
|
|
44
|
+
transport: BridgeTransport;
|
|
45
|
+
/** Populated by the fixture's `pageerror` listener, installed before goto. */
|
|
46
|
+
pageErrors: string[];
|
|
47
|
+
/** Populated by the fixture's `console` listener (type `'error'`), same timing. */
|
|
48
|
+
consoleErrors: string[];
|
|
49
|
+
/** First snapshot's `time.tick` — used only for `events.expect`'s
|
|
50
|
+
* `withinTicks` timing math (`events-matcher.ts`) and the D5 failure-block
|
|
51
|
+
* deltas below; identity fencing itself is `fenceSeq`'s job (run-4
|
|
52
|
+
* friction #5 — see that field's doc comment). */
|
|
53
|
+
fenceTick: number;
|
|
54
|
+
/** Run-4 friction #5: the fence snapshot's own event ring's last `seq`
|
|
55
|
+
* (`0` if the ring was empty at fence time — nothing emitted yet, so
|
|
56
|
+
* "since seq 0" is correct). `events.expect` fences on THIS, not
|
|
57
|
+
* `fenceTick` — the removed `tick > sinceTick` filter silently dropped any event that
|
|
58
|
+
* shares the fence's own tick (e.g. one emitted from a debug-command
|
|
59
|
+
* handler, which runs between ticks), which is exactly the bug this field
|
|
60
|
+
* closes. See `debug-registry.ts`'s `TickStampedEvent.seq` doc comment
|
|
61
|
+
* for the full mechanism. */
|
|
62
|
+
fenceSeq: number;
|
|
63
|
+
/** D5: first snapshot's `time.simSeconds` — `events.expect`'s failure block
|
|
64
|
+
* computes REAL elapsed sim-time as a delta off this, instead of the
|
|
65
|
+
* absolute (since-boot) `simSeconds` the pre-fix code passed. */
|
|
66
|
+
fenceSimSeconds: number;
|
|
67
|
+
/** D5: `Date.now()` at the same moment as the fence snapshot — the wall
|
|
68
|
+
* side of that same delta, instead of the pre-fix code's hardcoded `0`
|
|
69
|
+
* (which fabricated a "sim speed 0.00x — loop stalled" reading on every
|
|
70
|
+
* single `events.expect` failure, regardless of how the game was
|
|
71
|
+
* actually running). */
|
|
72
|
+
fenceWallMs: number;
|
|
73
|
+
artifactsDir?: string | undefined;
|
|
74
|
+
/** Set by the caller when it reused an already-running game server rather
|
|
75
|
+
* than booting a fresh one for this run. Threaded through so `unwrap` can
|
|
76
|
+
* append the warm-session staleness hint to a
|
|
77
|
+
* `DEBUG_COMMAND_NOT_REGISTERED` failure (see `errors.ts`'s
|
|
78
|
+
* `appendWarmSessionHint`). Defaults to `false` so every caller that never
|
|
79
|
+
* sets it is unaffected. */
|
|
80
|
+
warmSession?: boolean;
|
|
81
|
+
/** `testInfo.title` — names the fixture heartbeat lines `waitFor`/
|
|
82
|
+
* `waitSimTime` print during a long poll (see `wait-for.ts`'s module doc).
|
|
83
|
+
* Defaults to `'test'` so a caller that never sets it (existing direct
|
|
84
|
+
* `GameClient` construction in older tests) still gets a valid, if
|
|
85
|
+
* generic, heartbeat line rather than `undefined` in the output. */
|
|
86
|
+
testTitle?: string;
|
|
87
|
+
}
|
|
88
|
+
export declare class GameInput {
|
|
89
|
+
private readonly client;
|
|
90
|
+
constructor(client: GameClient);
|
|
91
|
+
/**
|
|
92
|
+
* Holds one action, or SIMULTANEOUSLY holds several — `hold('jump')` and
|
|
93
|
+
* `hold(['move_right', 'move_forward'])` (the natural way to drive a
|
|
94
|
+
* diagonal) are both first-class. `action` is deliberately typed as
|
|
95
|
+
* `string | string[]`, never just `string`: a bare JS array silently
|
|
96
|
+
* stringifies to a comma-joined name (`['move_right','move_forward']` →
|
|
97
|
+
* `"move_right,move_forward"`) wherever it crosses a template literal or
|
|
98
|
+
* the bridge's JSON boundary, which used to surface as a baffling
|
|
99
|
+
* `unknown action "move_right,move_forward"` — the array was accepted
|
|
100
|
+
* structurally (nothing rejected it) and then silently mangled instead.
|
|
101
|
+
* Accepting the array for real, instead of merely rejecting it, is the
|
|
102
|
+
* useful behavior: every 2D/3D mover needs "hold two directions at once"
|
|
103
|
+
* as its ordinary case, not an edge case.
|
|
104
|
+
*
|
|
105
|
+
* Single action: one `holdFor` bridge call (`runtime/debug-bridge.ts`)
|
|
106
|
+
* instead of the old set → `waitSimTime`'s 150ms-interval snapshot poll
|
|
107
|
+
* loop → clear (15+ transport round trips over the editor relay for a
|
|
108
|
+
* multi-second hold). The wait uses ordinary host-loop ticks while visible.
|
|
109
|
+
* If the browser has hidden-paused that loop, the bridge drives the held
|
|
110
|
+
* action through the same game phases with deterministic ticks; this
|
|
111
|
+
* collapses transport cost without letting a background tab deadlock it.
|
|
112
|
+
*
|
|
113
|
+
* A relay call has a bounded async-invoke timeout budget (see
|
|
114
|
+
* `relay-transport.ts`'s `INVOKE_TIMEOUT_MS`) — a `simSeconds` long enough
|
|
115
|
+
* to exceed it at worst-case sim speed is NOT silently truncated; the
|
|
116
|
+
* transport timeout is caught and rethrown with a hint to split the hold
|
|
117
|
+
* into multiple shorter `hold()` calls instead.
|
|
118
|
+
*
|
|
119
|
+
* Multiple actions: `holdFor` is a single-action bridge primitive, so this
|
|
120
|
+
* presses every action (`setVirtualAction(action, true)`, in order),
|
|
121
|
+
* waits the shared `simSeconds` once via `waitSimTime`, and releases every
|
|
122
|
+
* action it managed to press — in a `finally`, so a gated press (a
|
|
123
|
+
* `delivered: false` result throws `INPUT_GATED` immediately) or a stalled
|
|
124
|
+
* clock during the wait still leaves no action stuck held.
|
|
125
|
+
*/
|
|
126
|
+
hold(action: string | string[], budget: {
|
|
127
|
+
simSeconds: number;
|
|
128
|
+
}): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Keep one or more honest game actions pressed while `observe` runs, then
|
|
131
|
+
* release every action in a `finally` block. This is the evidence-oriented
|
|
132
|
+
* sibling of {@link hold}: `hold()` resolves after release, which is ideal
|
|
133
|
+
* for asserting distance travelled but cannot capture a moving pose or read
|
|
134
|
+
* transient "currently sprinting" state. `whileHeld()` makes that interval
|
|
135
|
+
* explicit without asking tests to hand-roll unsafe set/clear cleanup.
|
|
136
|
+
*
|
|
137
|
+
* The callback uses the same `GameClient` instance already in the test, so
|
|
138
|
+
* it may call `game.waitFor`, `game.state`, `game.screenshot`, or any other
|
|
139
|
+
* ordinary observation. No state is fabricated and the actions still enter
|
|
140
|
+
* through `input.setVirtualAction`/the project's real input map.
|
|
141
|
+
*/
|
|
142
|
+
whileHeld<T>(action: string | string[], observe: () => T | Promise<T>): Promise<T>;
|
|
143
|
+
/**
|
|
144
|
+
* One honest one-shot press — pressed, carried through AT LEAST ONE FULL
|
|
145
|
+
* FIXED TICK, then released, as a single bridge call.
|
|
146
|
+
*
|
|
147
|
+
* The contract is deliberately "a tick services it", not "the bridge
|
|
148
|
+
* accepted it", because the raw primitive underneath (`InputManager.
|
|
149
|
+
* tapVirtualAction`) only QUEUES: `poll()` promotes the queue to that tick's
|
|
150
|
+
* just-pressed set, and nothing else ever does. On a visible tab a tick
|
|
151
|
+
* lands ~16ms later so the difference is invisible; on a HIDDEN tab the host
|
|
152
|
+
* loop is stopped outright, so a queue-only tap sat unserviced and then died
|
|
153
|
+
* — silently, having already reported `delivered: true` (measured:
|
|
154
|
+
* `input.trace: { ticks: [] }`, no event, while `whileHeld` on the same
|
|
155
|
+
* action worked every time). Routing through the `holdFor` primitive is what
|
|
156
|
+
* makes the press TICK-ALIGNED by construction: that call owns the wait, and
|
|
157
|
+
* its hidden-tab leg drives the tick deterministically
|
|
158
|
+
* (`runtime/debug-bridge.ts`'s `waitForHoldBudget`), so a tap can no longer
|
|
159
|
+
* fall between ticks on any transport or any tab state.
|
|
160
|
+
*
|
|
161
|
+
* A gated action still fails loudly and immediately with `INPUT_GATED`,
|
|
162
|
+
* exactly as {@link hold} does — same `delivered:false` contract, same
|
|
163
|
+
* error.
|
|
164
|
+
*/
|
|
165
|
+
tap(action: string): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Sets one action, or the SAME value on several at once — same
|
|
168
|
+
* `string | string[]` shape as {@link hold}, and for the same reason: a
|
|
169
|
+
* bare array used to silently stringify into a single bogus action name
|
|
170
|
+
* instead of being rejected or honored.
|
|
171
|
+
*/
|
|
172
|
+
set(action: string | string[], value: VirtualActionValue): Promise<void>;
|
|
173
|
+
}
|
|
174
|
+
export declare class GameEvents {
|
|
175
|
+
private readonly client;
|
|
176
|
+
constructor(client: GameClient);
|
|
177
|
+
expect(names: string[], opts?: {
|
|
178
|
+
exact?: boolean;
|
|
179
|
+
withinTicks?: number;
|
|
180
|
+
}): Promise<void>;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The `game` fixture value. Every method reaches `window.__vgai` (or its
|
|
184
|
+
* relay-side equivalent) through `this.#transport` — see `bridge-transport.ts`'s
|
|
185
|
+
* module doc for the seam, and this file's own module doc for why it, alone,
|
|
186
|
+
* is allowed to import Playwright (via `PageTransport`, above).
|
|
187
|
+
*/
|
|
188
|
+
export declare class GameClient {
|
|
189
|
+
#private;
|
|
190
|
+
readonly input: GameInput;
|
|
191
|
+
readonly events: GameEvents;
|
|
192
|
+
readonly fenceTick: number;
|
|
193
|
+
/** Run-4 friction #5: see `GameClientOptions.fenceSeq`. */
|
|
194
|
+
readonly fenceSeq: number;
|
|
195
|
+
/** D5: see `GameClientOptions.fenceSimSeconds`. */
|
|
196
|
+
readonly fenceSimSeconds: number;
|
|
197
|
+
/** D5: see `GameClientOptions.fenceWallMs`. */
|
|
198
|
+
readonly fenceWallMs: number;
|
|
199
|
+
readonly pageErrors: string[];
|
|
200
|
+
readonly consoleErrors: string[];
|
|
201
|
+
/** M13: hidden-tab recovery's own structured notices — kept SEPARATE from
|
|
202
|
+
* `consoleErrors` (see `hidden-recovery.ts`'s hook below and
|
|
203
|
+
* `failure-block.ts`'s `recoveryNotices` member). */
|
|
204
|
+
readonly recoveryNotices: string[];
|
|
205
|
+
/** Where a labelled `screenshot()` lands — project-scoped by `@vgai/live`, cwd-relative otherwise. Public: a caller reading it is asking a fair question, and `screenshot()` returns a path under it anyway. */
|
|
206
|
+
readonly artifactsDir: string;
|
|
207
|
+
constructor(opts: GameClientOptions);
|
|
208
|
+
state(name: string): Promise<unknown>;
|
|
209
|
+
command(name: string, ...args: unknown[]): Promise<unknown>;
|
|
210
|
+
providers(): Promise<ProviderInfo[]>;
|
|
211
|
+
commands(): Promise<DebugCommandInfo[]>;
|
|
212
|
+
snapshot(sinceSeq?: number): Promise<DebugSnapshot>;
|
|
213
|
+
/** Fixture-teardown perf read: p50/p95 effective ticks-per-second over all
|
|
214
|
+
* the snapshot polls this test performed. */
|
|
215
|
+
tpsStats(): TpsStats;
|
|
216
|
+
/** True if the hidden-tab recovery fired (page.bringToFront was called). */
|
|
217
|
+
hiddenRecoveryTriggered(): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* D15/T-D15.4 — synchronously drives `budget` worth of sim time/ticks via
|
|
220
|
+
* the live `Game`'s `runTicks` (through the debug bridge), instead of
|
|
221
|
+
* waiting for real wall-clock time to pass. Doctrine (see
|
|
222
|
+
* `fast-forward.ts`'s module doc, which also documents the two honesty
|
|
223
|
+
* decisions this wraps): SETUP/STAGING traversal — reaching a known
|
|
224
|
+
* late-game state fast — not a substitute for real-input proofs, which
|
|
225
|
+
* still run in real ticks. Returns the final `{time, state, events,
|
|
226
|
+
* pageErrors}` snapshot (a completely ordinary `snapshot()` read, taken
|
|
227
|
+
* AFTER the tps baseline reset below, so it never itself corrupts the tps
|
|
228
|
+
* stats either).
|
|
229
|
+
*/
|
|
230
|
+
fastForward(budget: FastForwardBudget, opts?: FastForwardOptions): Promise<DebugSnapshot>;
|
|
231
|
+
waitFor(pred: (s: (name: string) => unknown) => boolean, budget: WaitForBudget): Promise<void>;
|
|
232
|
+
/** Used by `input.hold` — waits for a sim-time delta to pass with no
|
|
233
|
+
* predicate (a predicate that is never true would misreport as a
|
|
234
|
+
* waitFor failure, so this is its own tiny loop, not `runWaitFor` with a
|
|
235
|
+
* false predicate).
|
|
236
|
+
*
|
|
237
|
+
* D2: this loop has the SAME frozen-clock bug `runWaitFor` had — with no
|
|
238
|
+
* predicate at all, a stalled sim clock would poll forever, since
|
|
239
|
+
* `current.time.simSeconds` would never advance. Carries the identical
|
|
240
|
+
* stall guard (`WAIT_FOR_STALL_POLL_LIMIT` consecutive polls with an
|
|
241
|
+
* unchanged tick — see wait-for.ts's doc comment for the rationale),
|
|
242
|
+
* throwing the same `WaitForTimeoutError` shape so it renders through the
|
|
243
|
+
* same honest failure block as an ordinary `waitFor`/`waitSimTime` budget
|
|
244
|
+
* exhaustion. */
|
|
245
|
+
waitSimTime(budget: {
|
|
246
|
+
simSeconds: number;
|
|
247
|
+
}): Promise<void>;
|
|
248
|
+
/**
|
|
249
|
+
* Capture the running game to disk and return the ABSOLUTE path written.
|
|
250
|
+
*
|
|
251
|
+
* `labelOrPath` is read as a LABEL when it is a bare identifier
|
|
252
|
+
* (`'waitfor-timeout'`) — numbered into the artifacts directory, the
|
|
253
|
+
* long-standing spec behaviour — and as a DESTINATION PATH the moment it
|
|
254
|
+
* carries a separator or a file extension, in which case the bytes land
|
|
255
|
+
* exactly there (relative to `process.cwd()`). See
|
|
256
|
+
* `screenshot-target.ts`'s header for the live incident that made this
|
|
257
|
+
* distinction mandatory: a caller naming a path used to get "success" and
|
|
258
|
+
* an empty path.
|
|
259
|
+
*/
|
|
260
|
+
screenshot(labelOrPath: string): Promise<string>;
|
|
261
|
+
/**
|
|
262
|
+
* Wave-2 "one dialect, full capability" — runs a UI-automation step
|
|
263
|
+
* written as a literal Playwright `async (page) => {...}` (interface
|
|
264
|
+
* doctrine §3.2/§4 rung 4: "the AI should think it is basically just
|
|
265
|
+
* executing Playwright"). Under `PageTransport` this drives the REAL
|
|
266
|
+
* `Page` (real closures, real hit-testing); under `RelayTransport` it
|
|
267
|
+
* posts the step's own `toString()` source to the editor dev server's
|
|
268
|
+
* `page-script` op, which reconstructs it in-page against
|
|
269
|
+
* `playwright-shim.ts`'s in-page shim (`isTrusted: false` synthetic
|
|
270
|
+
* DOM events, no real hit testing). This is the honest input path for a
|
|
271
|
+
* React-only DOM game; canvas gameplay continues to use `game.input.*`.
|
|
272
|
+
* Write specs as if ALWAYS serialized — inline every value the step needs,
|
|
273
|
+
* never close over imported `expect`/helpers/outer variables, and return
|
|
274
|
+
* observations to assert outside the callback. See `bridge-transport.ts`'s
|
|
275
|
+
* `runPageScript` doc comment for the full contract this method wraps.
|
|
276
|
+
*/
|
|
277
|
+
page<T = unknown>(step: (page: Page) => T | Promise<T>): Promise<T>;
|
|
278
|
+
private toSessionFailure;
|
|
279
|
+
/** run-3 friction #2 — called at the top of every bridge dispatch, before
|
|
280
|
+
* the await, so a burst of short calls (e.g. many `hold()`s) keeps
|
|
281
|
+
* printing throttled liveness even while individually near-silent. See
|
|
282
|
+
* bridge-heartbeat.ts's module doc for why this needs no sim-tick
|
|
283
|
+
* awareness the way `wait-for.ts`'s poll-loop heartbeat does. */
|
|
284
|
+
private emitBridgeHeartbeatIfDue;
|
|
285
|
+
/** @internal exposed for GameInput/GameEvents in this module only. */
|
|
286
|
+
callBridge<T>(method: string, ...callArgs: unknown[]): Promise<T>;
|
|
287
|
+
/** @internal like `callBridge`, but awaits the page-side call (for async
|
|
288
|
+
* bridge methods like `invoke`). */
|
|
289
|
+
callBridgeAsync<T>(method: string, ...callArgs: unknown[]): Promise<T>;
|
|
290
|
+
/** @internal void-returning convenience (clearVirtualActions). */
|
|
291
|
+
callBridgeVoid(method: string, ...callArgs: unknown[]): Promise<void>;
|
|
292
|
+
private unwrap;
|
|
293
|
+
}
|