picopilot 0.0.0 → 0.1.1
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/LICENSE +661 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audio.d.ts +17 -0
- package/dist/commands/audio.d.ts.map +1 -0
- package/dist/commands/audio.js +315 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/export.d.ts +36 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +257 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/gfx.d.ts +28 -0
- package/dist/commands/gfx.d.ts.map +1 -0
- package/dist/commands/gfx.js +521 -0
- package/dist/commands/gfx.js.map +1 -0
- package/dist/commands/init.d.ts +34 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +151 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +20 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +90 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/minify.d.ts +20 -0
- package/dist/commands/minify.d.ts.map +1 -0
- package/dist/commands/minify.js +129 -0
- package/dist/commands/minify.js.map +1 -0
- package/dist/commands/music.d.ts +13 -0
- package/dist/commands/music.d.ts.map +1 -0
- package/dist/commands/music.js +241 -0
- package/dist/commands/music.js.map +1 -0
- package/dist/commands/playtest.d.ts +29 -0
- package/dist/commands/playtest.d.ts.map +1 -0
- package/dist/commands/playtest.js +631 -0
- package/dist/commands/playtest.js.map +1 -0
- package/dist/commands/run.d.ts +30 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +224 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/serve.d.ts +50 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +213 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/sfx.d.ts +13 -0
- package/dist/commands/sfx.d.ts.map +1 -0
- package/dist/commands/sfx.js +254 -0
- package/dist/commands/sfx.js.map +1 -0
- package/dist/commands/tokens.d.ts +36 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +129 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/verify.d.ts +57 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +213 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/version.d.ts +12 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +24 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/engine/audio/index.d.ts +14 -0
- package/dist/engine/audio/index.d.ts.map +1 -0
- package/dist/engine/audio/index.js +14 -0
- package/dist/engine/audio/index.js.map +1 -0
- package/dist/engine/audio/music.d.ts +116 -0
- package/dist/engine/audio/music.d.ts.map +1 -0
- package/dist/engine/audio/music.js +138 -0
- package/dist/engine/audio/music.js.map +1 -0
- package/dist/engine/audio/section.d.ts +34 -0
- package/dist/engine/audio/section.d.ts.map +1 -0
- package/dist/engine/audio/section.js +67 -0
- package/dist/engine/audio/section.js.map +1 -0
- package/dist/engine/audio/sfx.d.ts +141 -0
- package/dist/engine/audio/sfx.d.ts.map +1 -0
- package/dist/engine/audio/sfx.js +506 -0
- package/dist/engine/audio/sfx.js.map +1 -0
- package/dist/engine/cart/cart.d.ts +75 -0
- package/dist/engine/cart/cart.d.ts.map +1 -0
- package/dist/engine/cart/cart.js +153 -0
- package/dist/engine/cart/cart.js.map +1 -0
- package/dist/engine/cart/errors.d.ts +29 -0
- package/dist/engine/cart/errors.d.ts.map +1 -0
- package/dist/engine/cart/errors.js +26 -0
- package/dist/engine/cart/errors.js.map +1 -0
- package/dist/engine/cart/gfx.d.ts +79 -0
- package/dist/engine/cart/gfx.d.ts.map +1 -0
- package/dist/engine/cart/gfx.js +171 -0
- package/dist/engine/cart/gfx.js.map +1 -0
- package/dist/engine/cart/index.d.ts +15 -0
- package/dist/engine/cart/index.d.ts.map +1 -0
- package/dist/engine/cart/index.js +15 -0
- package/dist/engine/cart/index.js.map +1 -0
- package/dist/engine/cart/map.d.ts +46 -0
- package/dist/engine/cart/map.d.ts.map +1 -0
- package/dist/engine/cart/map.js +118 -0
- package/dist/engine/cart/map.js.map +1 -0
- package/dist/engine/config/config.d.ts +74 -0
- package/dist/engine/config/config.d.ts.map +1 -0
- package/dist/engine/config/config.js +103 -0
- package/dist/engine/config/config.js.map +1 -0
- package/dist/engine/config/index.d.ts +7 -0
- package/dist/engine/config/index.d.ts.map +1 -0
- package/dist/engine/config/index.js +7 -0
- package/dist/engine/config/index.js.map +1 -0
- package/dist/engine/gfx/grid.d.ts +51 -0
- package/dist/engine/gfx/grid.d.ts.map +1 -0
- package/dist/engine/gfx/grid.js +116 -0
- package/dist/engine/gfx/grid.js.map +1 -0
- package/dist/engine/gfx/index.d.ts +17 -0
- package/dist/engine/gfx/index.d.ts.map +1 -0
- package/dist/engine/gfx/index.js +17 -0
- package/dist/engine/gfx/index.js.map +1 -0
- package/dist/engine/gfx/label.d.ts +34 -0
- package/dist/engine/gfx/label.d.ts.map +1 -0
- package/dist/engine/gfx/label.js +190 -0
- package/dist/engine/gfx/label.js.map +1 -0
- package/dist/engine/gfx/overlap.d.ts +42 -0
- package/dist/engine/gfx/overlap.d.ts.map +1 -0
- package/dist/engine/gfx/overlap.js +57 -0
- package/dist/engine/gfx/overlap.js.map +1 -0
- package/dist/engine/gfx/palette.d.ts +29 -0
- package/dist/engine/gfx/palette.d.ts.map +1 -0
- package/dist/engine/gfx/palette.js +46 -0
- package/dist/engine/gfx/palette.js.map +1 -0
- package/dist/engine/gfx/png.d.ts +31 -0
- package/dist/engine/gfx/png.d.ts.map +1 -0
- package/dist/engine/gfx/png.js +165 -0
- package/dist/engine/gfx/png.js.map +1 -0
- package/dist/engine/gfx/render.d.ts +42 -0
- package/dist/engine/gfx/render.d.ts.map +1 -0
- package/dist/engine/gfx/render.js +72 -0
- package/dist/engine/gfx/render.js.map +1 -0
- package/dist/engine/index.d.ts +22 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +22 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/pico8/adapter.d.ts +267 -0
- package/dist/engine/pico8/adapter.d.ts.map +1 -0
- package/dist/engine/pico8/adapter.js +37 -0
- package/dist/engine/pico8/adapter.js.map +1 -0
- package/dist/engine/pico8/drive.d.ts +308 -0
- package/dist/engine/pico8/drive.d.ts.map +1 -0
- package/dist/engine/pico8/drive.js +581 -0
- package/dist/engine/pico8/drive.js.map +1 -0
- package/dist/engine/pico8/harness.d.ts +146 -0
- package/dist/engine/pico8/harness.d.ts.map +1 -0
- package/dist/engine/pico8/harness.js +251 -0
- package/dist/engine/pico8/harness.js.map +1 -0
- package/dist/engine/pico8/index.d.ts +21 -0
- package/dist/engine/pico8/index.d.ts.map +1 -0
- package/dist/engine/pico8/index.js +21 -0
- package/dist/engine/pico8/index.js.map +1 -0
- package/dist/engine/pico8/sentinel.d.ts +63 -0
- package/dist/engine/pico8/sentinel.d.ts.map +1 -0
- package/dist/engine/pico8/sentinel.js +86 -0
- package/dist/engine/pico8/sentinel.js.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts +10 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.js +11 -0
- package/dist/engine/pico8/session-daemon-entry.js.map +1 -0
- package/dist/engine/pico8/session-daemon-main.d.ts +44 -0
- package/dist/engine/pico8/session-daemon-main.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-main.js +71 -0
- package/dist/engine/pico8/session-daemon-main.js.map +1 -0
- package/dist/engine/pico8/session-daemon.d.ts +68 -0
- package/dist/engine/pico8/session-daemon.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon.js +264 -0
- package/dist/engine/pico8/session-daemon.js.map +1 -0
- package/dist/engine/pico8/session.d.ts +140 -0
- package/dist/engine/pico8/session.d.ts.map +1 -0
- package/dist/engine/pico8/session.js +250 -0
- package/dist/engine/pico8/session.js.map +1 -0
- package/dist/engine/pico8/shell.d.ts +103 -0
- package/dist/engine/pico8/shell.d.ts.map +1 -0
- package/dist/engine/pico8/shell.js +324 -0
- package/dist/engine/pico8/shell.js.map +1 -0
- package/dist/engine/pico8/supervisor.d.ts +137 -0
- package/dist/engine/pico8/supervisor.d.ts.map +1 -0
- package/dist/engine/pico8/supervisor.js +140 -0
- package/dist/engine/pico8/supervisor.js.map +1 -0
- package/dist/engine/scaffold/index.d.ts +8 -0
- package/dist/engine/scaffold/index.d.ts.map +1 -0
- package/dist/engine/scaffold/index.js +8 -0
- package/dist/engine/scaffold/index.js.map +1 -0
- package/dist/engine/scaffold/scaffold.d.ts +49 -0
- package/dist/engine/scaffold/scaffold.d.ts.map +1 -0
- package/dist/engine/scaffold/scaffold.js +259 -0
- package/dist/engine/scaffold/scaffold.js.map +1 -0
- package/dist/engine/shrinko/adapter.d.ts +167 -0
- package/dist/engine/shrinko/adapter.d.ts.map +1 -0
- package/dist/engine/shrinko/adapter.js +107 -0
- package/dist/engine/shrinko/adapter.js.map +1 -0
- package/dist/engine/shrinko/index.d.ts +13 -0
- package/dist/engine/shrinko/index.d.ts.map +1 -0
- package/dist/engine/shrinko/index.js +13 -0
- package/dist/engine/shrinko/index.js.map +1 -0
- package/dist/engine/shrinko/shell.d.ts +69 -0
- package/dist/engine/shrinko/shell.d.ts.map +1 -0
- package/dist/engine/shrinko/shell.js +156 -0
- package/dist/engine/shrinko/shell.js.map +1 -0
- package/dist/engine/skills/index.d.ts +8 -0
- package/dist/engine/skills/index.d.ts.map +1 -0
- package/dist/engine/skills/index.js +8 -0
- package/dist/engine/skills/index.js.map +1 -0
- package/dist/engine/skills/skills.d.ts +104 -0
- package/dist/engine/skills/skills.d.ts.map +1 -0
- package/dist/engine/skills/skills.js +149 -0
- package/dist/engine/skills/skills.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/package.json +48 -2
- package/src/bin.ts +4 -0
- package/src/cli.ts +61 -0
- package/src/commands/audio.test.ts +280 -0
- package/src/commands/audio.ts +410 -0
- package/src/commands/export.test.ts +310 -0
- package/src/commands/export.ts +316 -0
- package/src/commands/gfx-io.test.ts +178 -0
- package/src/commands/gfx.test.ts +470 -0
- package/src/commands/gfx.ts +645 -0
- package/src/commands/init.test.ts +482 -0
- package/src/commands/init.ts +193 -0
- package/src/commands/lint.test.ts +137 -0
- package/src/commands/lint.ts +117 -0
- package/src/commands/minify.test.ts +166 -0
- package/src/commands/minify.ts +156 -0
- package/src/commands/music.test.ts +272 -0
- package/src/commands/music.ts +288 -0
- package/src/commands/playtest.test.ts +317 -0
- package/src/commands/playtest.ts +773 -0
- package/src/commands/run.test.ts +264 -0
- package/src/commands/run.ts +285 -0
- package/src/commands/serve.test.ts +227 -0
- package/src/commands/serve.ts +272 -0
- package/src/commands/sfx.test.ts +349 -0
- package/src/commands/sfx.ts +300 -0
- package/src/commands/tokens.test.ts +212 -0
- package/src/commands/tokens.ts +168 -0
- package/src/commands/verify.test.ts +238 -0
- package/src/commands/verify.ts +256 -0
- package/src/commands/version.test.ts +70 -0
- package/src/commands/version.ts +24 -0
- package/src/engine/audio/index.ts +47 -0
- package/src/engine/audio/music.test.ts +188 -0
- package/src/engine/audio/music.ts +218 -0
- package/src/engine/audio/section.test.ts +124 -0
- package/src/engine/audio/section.ts +79 -0
- package/src/engine/audio/sfx.test.ts +410 -0
- package/src/engine/audio/sfx.ts +694 -0
- package/src/engine/cart/cart.test.ts +304 -0
- package/src/engine/cart/cart.ts +211 -0
- package/src/engine/cart/errors.ts +39 -0
- package/src/engine/cart/fixtures/crlf.p8 +6 -0
- package/src/engine/cart/fixtures/full.p8 +174 -0
- package/src/engine/cart/fixtures/header-only.p8 +2 -0
- package/src/engine/cart/fixtures/malformed-no-header.p8 +2 -0
- package/src/engine/cart/fixtures/minimal.p8 +10 -0
- package/src/engine/cart/fixtures/no-trailing-newline.p8 +6 -0
- package/src/engine/cart/gfx.ts +195 -0
- package/src/engine/cart/index.ts +27 -0
- package/src/engine/cart/map.ts +134 -0
- package/src/engine/config/config.ts +115 -0
- package/src/engine/config/index.ts +12 -0
- package/src/engine/gfx/grid.test.ts +128 -0
- package/src/engine/gfx/grid.ts +136 -0
- package/src/engine/gfx/index.ts +34 -0
- package/src/engine/gfx/label.test.ts +105 -0
- package/src/engine/gfx/label.ts +229 -0
- package/src/engine/gfx/overlap.test.ts +71 -0
- package/src/engine/gfx/overlap.ts +66 -0
- package/src/engine/gfx/palette.test.ts +35 -0
- package/src/engine/gfx/palette.ts +54 -0
- package/src/engine/gfx/png.test.ts +111 -0
- package/src/engine/gfx/png.ts +196 -0
- package/src/engine/gfx/render.test.ts +180 -0
- package/src/engine/gfx/render.ts +106 -0
- package/src/engine/index.ts +21 -0
- package/src/engine/pico8/adapter.ts +303 -0
- package/src/engine/pico8/drive.test.ts +415 -0
- package/src/engine/pico8/drive.ts +729 -0
- package/src/engine/pico8/harness.test.ts +181 -0
- package/src/engine/pico8/harness.ts +354 -0
- package/src/engine/pico8/index.ts +20 -0
- package/src/engine/pico8/sentinel.test.ts +80 -0
- package/src/engine/pico8/sentinel.ts +93 -0
- package/src/engine/pico8/session-daemon-entry.ts +12 -0
- package/src/engine/pico8/session-daemon-main.ts +98 -0
- package/src/engine/pico8/session-daemon.test.ts +212 -0
- package/src/engine/pico8/session-daemon.ts +306 -0
- package/src/engine/pico8/session.test.ts +239 -0
- package/src/engine/pico8/session.ts +321 -0
- package/src/engine/pico8/shell.test.ts +583 -0
- package/src/engine/pico8/shell.ts +412 -0
- package/src/engine/pico8/supervisor.test.ts +105 -0
- package/src/engine/pico8/supervisor.ts +197 -0
- package/src/engine/scaffold/index.ts +15 -0
- package/src/engine/scaffold/scaffold.ts +278 -0
- package/src/engine/shrinko/adapter.ts +241 -0
- package/src/engine/shrinko/fixtures/count-no-compressed.txt +2 -0
- package/src/engine/shrinko/fixtures/count-over-budget.txt +3 -0
- package/src/engine/shrinko/fixtures/count-under-budget.txt +3 -0
- package/src/engine/shrinko/index.ts +33 -0
- package/src/engine/shrinko/shell.ts +224 -0
- package/src/engine/shrinko/shrinko.test.ts +182 -0
- package/src/engine/skills/index.ts +14 -0
- package/src/engine/skills/skills.test.ts +376 -0
- package/src/engine/skills/skills.ts +205 -0
- package/src/index.ts +13 -0
- package/src/skills/game-design-reference/SKILL.md +70 -0
- package/src/skills/game-jam/SKILL.md +45 -0
- package/src/skills/picopilot-art/SKILL.md +62 -0
- package/src/skills/picopilot-audio/SKILL.md +194 -0
- package/src/skills/picopilot-code/SKILL.md +97 -0
- package/src/skills/picopilot-code/reference/README.md +42 -0
- package/src/skills/picopilot-code/reference/mode7-racing.md +95 -0
- package/src/skills/picopilot-code/reference/platformer.md +119 -0
- package/src/skills/picopilot-code/reference/puzzle-grid.md +106 -0
- package/src/skills/picopilot-code/reference/rpg-menus-dialog.md +122 -0
- package/src/skills/picopilot-code/reference/top-down-adventure.md +117 -0
- package/src/skills/picopilot-code/reference/twin-stick-arcade.md +128 -0
- package/src/skills/picopilot-debug/SKILL.md +195 -0
- package/src/skills/picopilot-overview/SKILL.md +52 -0
- package/src/version.ts +22 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The RESUMABLE playtest SESSION handshake core (ADR-0011, prd US #6): the pure,
|
|
3
|
+
* CI-testable half of the "A" live model. It keeps a single driven PICO-8 process
|
|
4
|
+
* ALIVE and PAUSED between the agent's turns and drives it one command at a time
|
|
5
|
+
* over the verified transport: write a FIXED-SIZE command block, WAIT for the
|
|
6
|
+
* cart's ACK line on stdout, then return. The handshake (not wall-clock spacing)
|
|
7
|
+
* is what makes stepping deterministic and lossless, so the agent always acts on
|
|
8
|
+
* a SETTLED, known frame.
|
|
9
|
+
*
|
|
10
|
+
* This module owns ONLY the send -> wait-ack -> return logic against the
|
|
11
|
+
* {@link Pico8Process} seam (the same seam the one-shot `drive` uses), so every
|
|
12
|
+
* path (a completed step, a lost/late ack, the process dying mid-command) is
|
|
13
|
+
* unit-tested against a FAKE process WITHOUT the paid binary. The daemon that
|
|
14
|
+
* spawns the real process and exposes the session to separate CLI invocations by
|
|
15
|
+
* id lives in `supervisor.ts`; a live multi-turn session is the manual/opt-in
|
|
16
|
+
* tier. The cart-side transform is reused UNCHANGED from `drive.ts`.
|
|
17
|
+
*/
|
|
18
|
+
import { ACK, BLOCK_SIZE, encodeCommand } from './drive.js';
|
|
19
|
+
/** A structured session-command failure (never a raw throw across the seam). */
|
|
20
|
+
export class SessionError extends Error {
|
|
21
|
+
code;
|
|
22
|
+
constructor(code, message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'SessionError';
|
|
25
|
+
this.code = code;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* How long a single command waits for its ACK before it is declared lost (the
|
|
30
|
+
* lost/late-ack path). Generous: a STEP of many frames takes real time at 30fps,
|
|
31
|
+
* plus PICO-8 scheduling jitter, but a hung/dead cart must not wall the session
|
|
32
|
+
* forever. The supervisor's process-death signal (see {@link DriveSession}) ends
|
|
33
|
+
* a command EARLY when the process dies, so this only fires for a truly stuck
|
|
34
|
+
* cart.
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_ACK_TIMEOUT_MS = 15_000;
|
|
37
|
+
/**
|
|
38
|
+
* A stateful line reader over a PICO-8 stdout stream: the cart's `printh` ACKs
|
|
39
|
+
* and the done-sentinel land line-by-line, but the OS delivers stdout in
|
|
40
|
+
* arbitrary chunks. This buffers partial lines and lets a waiter await the NEXT
|
|
41
|
+
* line matching a predicate, so the handshake is exact (an ACK is a whole line,
|
|
42
|
+
* never a substring of unrelated output). Mirrors `SentinelWatcher`'s line
|
|
43
|
+
* discipline; kept separate because the session awaits a SPECIFIC ack, not a
|
|
44
|
+
* single terminal sentinel.
|
|
45
|
+
*/
|
|
46
|
+
class AckLineReader {
|
|
47
|
+
buffer = '';
|
|
48
|
+
pending = [];
|
|
49
|
+
/** Lines seen but not yet consumed by a waiter (an ACK that raced ahead). */
|
|
50
|
+
queued = [];
|
|
51
|
+
/** Feeds a raw stdout chunk; resolves any waiter its lines satisfy. */
|
|
52
|
+
push(chunk) {
|
|
53
|
+
this.buffer += chunk;
|
|
54
|
+
let nl;
|
|
55
|
+
while ((nl = this.buffer.indexOf('\n')) !== -1) {
|
|
56
|
+
let line = this.buffer.slice(0, nl);
|
|
57
|
+
if (line.endsWith('\r'))
|
|
58
|
+
line = line.slice(0, -1);
|
|
59
|
+
this.buffer = this.buffer.slice(nl + 1);
|
|
60
|
+
this.deliver(line);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
deliver(line) {
|
|
64
|
+
// A waiter already parked for this line consumes it; else queue it so a
|
|
65
|
+
// waiter that parks AFTER the cart printed can still find it.
|
|
66
|
+
const idx = this.pending.findIndex((p) => p.match(line));
|
|
67
|
+
if (idx !== -1) {
|
|
68
|
+
const [waiter] = this.pending.splice(idx, 1);
|
|
69
|
+
waiter.resolve(line);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.queued.push(line);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Awaits the next line satisfying `match`. If such a line was already seen
|
|
77
|
+
* (queued) it resolves synchronously-soon; otherwise it parks until one
|
|
78
|
+
* arrives. The caller races this against a timeout / process-death signal.
|
|
79
|
+
*/
|
|
80
|
+
waitFor(match) {
|
|
81
|
+
const idx = this.queued.findIndex(match);
|
|
82
|
+
if (idx !== -1) {
|
|
83
|
+
const [line] = this.queued.splice(idx, 1);
|
|
84
|
+
return Promise.resolve(line);
|
|
85
|
+
}
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
this.pending.push({ match, resolve });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The live-session handshake engine (ADR-0011, US #6). Wraps ONE driven
|
|
93
|
+
* {@link Pico8Process} and exposes the agent's verbs: {@link input} (set the
|
|
94
|
+
* held-buttons byte), {@link step} (advance exactly N frames), {@link shot}
|
|
95
|
+
* (capture the frozen current frame), {@link stop} (tear the session down). Each
|
|
96
|
+
* verb WRITES a single fixed-size command block and AWAITS the cart's ACK before
|
|
97
|
+
* resolving, so the process stays alive + paused between verbs and the caller
|
|
98
|
+
* always acts on a settled frame. Between the ACK and the next verb the game is
|
|
99
|
+
* frozen (its callbacks skipped, a stable framebuffer), so a {@link shot} always
|
|
100
|
+
* captures exactly the intended moment.
|
|
101
|
+
*
|
|
102
|
+
* The engine is transport-agnostic: it talks to the {@link Pico8Process} seam,
|
|
103
|
+
* so the send/wait/return + lost-ack + process-death paths are unit-tested
|
|
104
|
+
* against a FAKE process with no real binary. The supervisor supplies a REAL
|
|
105
|
+
* spawned process for a live session (the manual/opt-in tier).
|
|
106
|
+
*/
|
|
107
|
+
export class DriveSession {
|
|
108
|
+
proc;
|
|
109
|
+
reader = new AckLineReader();
|
|
110
|
+
ackTimeoutMs;
|
|
111
|
+
shotBasename;
|
|
112
|
+
frames = 0;
|
|
113
|
+
shotIndex = 0;
|
|
114
|
+
/** Set once the process dies; every in-flight + future command rejects. */
|
|
115
|
+
dead = false;
|
|
116
|
+
closed = false;
|
|
117
|
+
deathWaiters = [];
|
|
118
|
+
constructor(options) {
|
|
119
|
+
this.proc = options.process;
|
|
120
|
+
this.ackTimeoutMs = options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS;
|
|
121
|
+
this.shotBasename = options.shotBasename;
|
|
122
|
+
this.proc.onStdout((chunk) => this.reader.push(chunk));
|
|
123
|
+
this.proc.onClose(() => this.markDead());
|
|
124
|
+
this.proc.onError(() => this.markDead());
|
|
125
|
+
}
|
|
126
|
+
/** Total frames the session has advanced so far. */
|
|
127
|
+
get frame() {
|
|
128
|
+
return this.frames;
|
|
129
|
+
}
|
|
130
|
+
/** Whether the underlying process has died (crashed/quit). */
|
|
131
|
+
get isDead() {
|
|
132
|
+
return this.dead;
|
|
133
|
+
}
|
|
134
|
+
markDead() {
|
|
135
|
+
if (this.dead)
|
|
136
|
+
return;
|
|
137
|
+
this.dead = true;
|
|
138
|
+
for (const w of this.deathWaiters.splice(0))
|
|
139
|
+
w();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Sets the per-frame HELD-buttons byte for the upcoming steps (US #6: inject
|
|
143
|
+
* input between turns). Writes an INPUT block and awaits its ACK. The held
|
|
144
|
+
* level persists until the next INPUT (the cart holds it), and `btnp` edges
|
|
145
|
+
* are reconstructed cart-side from the held sequence.
|
|
146
|
+
*/
|
|
147
|
+
async input(held) {
|
|
148
|
+
await this.send({ op: 'input', held: held & 0xff }, ACK.input);
|
|
149
|
+
return { frame: this.frames, ack: ACK.input };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Advances EXACTLY `frames` frames then pauses (US #6, US #4). Writes a STEP
|
|
153
|
+
* block and awaits the STEP-DONE ack, which the cart prints only when the
|
|
154
|
+
* frame budget reaches 0, so the returned result means the N frames ACTUALLY
|
|
155
|
+
* ran and the game is now paused on a settled frame. `frames` must fit one
|
|
156
|
+
* byte (the block's arg); larger spans are issued as repeated steps by the
|
|
157
|
+
* caller.
|
|
158
|
+
*/
|
|
159
|
+
async step(frames) {
|
|
160
|
+
if (frames <= 0)
|
|
161
|
+
return { frame: this.frames, ack: ACK.stepDone };
|
|
162
|
+
await this.send({ op: 'step', frames: frames & 0xff }, ACK.stepDone);
|
|
163
|
+
this.frames += frames;
|
|
164
|
+
return { frame: this.frames, ack: ACK.stepDone };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Captures the current (paused, frozen) frame (US #6, US #5). Writes a SHOT
|
|
168
|
+
* block and awaits its ACK; the cart renders the frozen state once then
|
|
169
|
+
* screenshots it, so the capture is exactly the intended moment. Returns the
|
|
170
|
+
* `<basename><n>` the cart named it, which the supervisor collects from the
|
|
171
|
+
* `-desktop` dir.
|
|
172
|
+
*/
|
|
173
|
+
async shot() {
|
|
174
|
+
await this.send({ op: 'shot' }, ACK.shot);
|
|
175
|
+
const shotName = `${this.shotBasename}${this.shotIndex}`;
|
|
176
|
+
this.shotIndex += 1;
|
|
177
|
+
return { frame: this.frames, ack: ACK.shot, shotName };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Tears the session down: writes a QUIT block (the cart prints the sentinel so
|
|
181
|
+
* the launcher kills PICO-8), then kills the process as the backstop. Does NOT
|
|
182
|
+
* await an ACK (QUIT ends the cart), and is idempotent + safe on a dead
|
|
183
|
+
* process. After `stop`, every verb rejects with `playtest-session-closed`.
|
|
184
|
+
*/
|
|
185
|
+
stop() {
|
|
186
|
+
this.closed = true;
|
|
187
|
+
if (!this.dead) {
|
|
188
|
+
try {
|
|
189
|
+
this.proc.writeStdin?.(encodeCommand({ op: 'quit' }));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// A closed stdin (already exiting) is fine; the kill below finishes it.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
this.proc.kill();
|
|
196
|
+
this.markDead();
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The core handshake: write one command block, then race the ACK line against
|
|
200
|
+
* the timeout and the process-death signal. A missing ACK within the deadline
|
|
201
|
+
* is the structured lost/late-ack error; a process that dies mid-command is the
|
|
202
|
+
* structured process-dead error. Never a hang, never a silent success.
|
|
203
|
+
*/
|
|
204
|
+
async send(cmd, ackLine) {
|
|
205
|
+
if (this.closed) {
|
|
206
|
+
throw new SessionError('playtest-session-closed', 'the playtest session is stopped; start a new one');
|
|
207
|
+
}
|
|
208
|
+
if (this.dead) {
|
|
209
|
+
throw new SessionError('playtest-session-process-dead', 'PICO-8 exited; the playtest session is dead (start a new one)');
|
|
210
|
+
}
|
|
211
|
+
const block = encodeCommand(cmd);
|
|
212
|
+
if (block.length !== BLOCK_SIZE) {
|
|
213
|
+
// Defensive: the transport is fixed-size by construction; a mismatch is a
|
|
214
|
+
// codec bug, surfaced loudly rather than sent as a short (coalescing) write.
|
|
215
|
+
throw new SessionError('playtest-session-closed', `encoded command block is ${block.length} bytes, expected ${BLOCK_SIZE}`);
|
|
216
|
+
}
|
|
217
|
+
// Park the ACK waiter BEFORE writing, so an ACK the cart prints promptly is
|
|
218
|
+
// never missed between the write and the await.
|
|
219
|
+
const ackP = this.reader.waitFor((line) => line === ackLine);
|
|
220
|
+
this.proc.writeStdin?.(block);
|
|
221
|
+
let timer;
|
|
222
|
+
const timeoutP = new Promise((resolve) => {
|
|
223
|
+
timer = setTimeout(() => resolve('timeout'), this.ackTimeoutMs);
|
|
224
|
+
});
|
|
225
|
+
const deathP = new Promise((resolve) => {
|
|
226
|
+
if (this.dead)
|
|
227
|
+
resolve('dead');
|
|
228
|
+
else
|
|
229
|
+
this.deathWaiters.push(() => resolve('dead'));
|
|
230
|
+
});
|
|
231
|
+
try {
|
|
232
|
+
const outcome = await Promise.race([
|
|
233
|
+
ackP.then(() => 'ack'),
|
|
234
|
+
timeoutP,
|
|
235
|
+
deathP,
|
|
236
|
+
]);
|
|
237
|
+
if (outcome === 'timeout') {
|
|
238
|
+
throw new SessionError('playtest-session-ack-timeout', `no ACK "${ackLine}" within ${this.ackTimeoutMs}ms (the cart may have hung or faulted)`);
|
|
239
|
+
}
|
|
240
|
+
if (outcome === 'dead') {
|
|
241
|
+
throw new SessionError('playtest-session-process-dead', `PICO-8 exited before ACK "${ackLine}" (the session is dead)`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
if (timer !== undefined)
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/engine/pico8/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type DriveOptions, type ExportOptions, type Pico8Adapter, type Pico8DriveResult, type Pico8ExportResult, type Pico8RecordResult, type Pico8Result, type RecordOptions, type RunOptions } from './adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* A spawned PICO-8 process, abstracted so tests inject a fake. `onStdout`
|
|
4
|
+
* delivers stdout chunks (both `stdout` and `stderr` folded in, since PICO-8's
|
|
5
|
+
* `printh` and its own lines can land on either); `onClose` fires when the
|
|
6
|
+
* process exits on its own; `onError` fires on a spawn failure (ENOENT ⇒ PICO-8
|
|
7
|
+
* absent). `kill` terminates the process tree (the backstop / sentinel kill).
|
|
8
|
+
*/
|
|
9
|
+
export interface Pico8Process {
|
|
10
|
+
onStdout(cb: (chunk: string) => void): void;
|
|
11
|
+
onClose(cb: (code: number | null) => void): void;
|
|
12
|
+
onError(cb: (err: NodeJS.ErrnoException) => void): void;
|
|
13
|
+
kill(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Writes raw bytes to the process's stdin, for the `drive` transport (the
|
|
16
|
+
* fixed-size command blocks). Absent/undefined when stdin was detached
|
|
17
|
+
* (`run`/`record` ignore stdin so the cart never blocks on console input).
|
|
18
|
+
*/
|
|
19
|
+
writeStdin?(bytes: Uint8Array): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The process-spawn seam (mirrors shrinko's `ChildRunner`). The real impl spawns
|
|
23
|
+
* `pico8 -x`; a test injects a fake that emits synthetic stdout and a chosen
|
|
24
|
+
* outcome, so the sentinel-kill / backstop / pico8-absent paths are all testable
|
|
25
|
+
* WITHOUT the paid binary. `env` is handed in (its `PICO8_PATH`/`PATH` are the
|
|
26
|
+
* isolation levers), never read ambiently.
|
|
27
|
+
*/
|
|
28
|
+
export type SpawnRunner = (file: string, args: string[], env: NodeJS.ProcessEnv, options?: {
|
|
29
|
+
stdin?: boolean;
|
|
30
|
+
}) => Pico8Process;
|
|
31
|
+
export declare function pico8HomeDir(): string;
|
|
32
|
+
/**
|
|
33
|
+
* Prepend `-home <isolatedDir>` to a PICO-8 arg list so its config/data never
|
|
34
|
+
* land in the CWD. Applied at every spawn site.
|
|
35
|
+
*/
|
|
36
|
+
export declare function withPico8Home(args: string[]): string[];
|
|
37
|
+
export declare const spawnRunner: SpawnRunner;
|
|
38
|
+
/** The candidate ways to launch PICO-8, tried in order: `PICO8_PATH`, then `pico8` on `PATH`. */
|
|
39
|
+
export declare function pico8Candidates(env: NodeJS.ProcessEnv): string[];
|
|
40
|
+
/** Options for {@link ShellPico8Adapter}. */
|
|
41
|
+
export interface ShellPico8Options {
|
|
42
|
+
/** The environment for the child (its `PICO8_PATH`/`PATH` locate the binary). */
|
|
43
|
+
readonly env: NodeJS.ProcessEnv;
|
|
44
|
+
/** The spawn runner (defaults to {@link spawnRunner}); tests inject a fake. */
|
|
45
|
+
readonly spawn?: SpawnRunner;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The v1 {@link Pico8Adapter}: shells out to the user's native `pico8 -x`
|
|
49
|
+
* (ADR-0006, native path first). It owns the ORCHESTRATION the command should
|
|
50
|
+
* not hand-roll: launch, stream stdout, kill on the done-sentinel, arm the
|
|
51
|
+
* backstop, and collect screenshots + printh + exit reason into one result.
|
|
52
|
+
*
|
|
53
|
+
* PICO-8 absent (the first candidate ENOENTs and there is no fallback) returns
|
|
54
|
+
* the structured {@link pico8NotFound} value, never a throw or a hang. That
|
|
55
|
+
* absent path is the CI-testable one; live runs are a manual/opt-in tier.
|
|
56
|
+
*/
|
|
57
|
+
export declare class ShellPico8Adapter implements Pico8Adapter {
|
|
58
|
+
private readonly env;
|
|
59
|
+
private readonly spawnRun;
|
|
60
|
+
constructor(options: ShellPico8Options);
|
|
61
|
+
run(options: RunOptions): Promise<Pico8Result>;
|
|
62
|
+
/**
|
|
63
|
+
* Records a running cart's audio to a WAV (ADR-0009). Launches PICO-8 in a
|
|
64
|
+
* REAL A/V session (`-run`, NOT the headless `-x` that mixes no audio) and
|
|
65
|
+
* controls the WAV location with `-root_path <wavDir>`, because
|
|
66
|
+
* `extcmd("audio_end", 1)` saves to PICO-8's CURRENT folder, not `-desktop`
|
|
67
|
+
* (the `audio_end(1)` trap). The cooperating cart (a harness for `record`, or
|
|
68
|
+
* the whole render harness) does the `audio_rec`/`audio_end`/sentinel dance; we
|
|
69
|
+
* watch the sentinel, arm the backstop, and collect `<basename>.wav` from the
|
|
70
|
+
* isolated `wavDir`. Live capture is a manual/opt-in tier; the absent path is
|
|
71
|
+
* the CI-testable one.
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* Drives a throwaway cart through scripted input and captures live gameplay
|
|
75
|
+
* (ADR-0011). Launches `-desktop <shotDir> -x <cart>` with a LIVE stdin, writes
|
|
76
|
+
* the whole encoded FIXED-SIZE command block stream up front (the one-shot; the
|
|
77
|
+
* cart drains one block per frame and screenshots at the SHOT points), watches
|
|
78
|
+
* the sentinel, arms the backstop, and collects the SHOT PNGs + printh + exit
|
|
79
|
+
* reason. Returns {@link pico8NotFound} when PICO-8 is absent. Live capture is a
|
|
80
|
+
* manual/opt-in tier; the absent path is the CI-testable one.
|
|
81
|
+
*/
|
|
82
|
+
drive(options: DriveOptions): Promise<Pico8DriveResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Exports the cart as a PICO-8 HTML bundle over the HEADLESS `-x` path (no
|
|
85
|
+
* display needed: `pico8 <cart> -export <outDir>/<name> -x` runs the cart, writes
|
|
86
|
+
* `<name>.html` + `<name>.js`, and exits). PICO-8 derives both file names from
|
|
87
|
+
* the `.html` name, so passing `index.html` yields a directly-serveable pair.
|
|
88
|
+
* A labelless cart prints "please capture a label first" and still exports (a
|
|
89
|
+
* blank splash), surfaced as `labelWarning`. PICO-8 absent -> {@link pico8NotFound}.
|
|
90
|
+
*/
|
|
91
|
+
export(options: ExportOptions): Promise<Pico8ExportResult>;
|
|
92
|
+
record(options: RecordOptions): Promise<Pico8RecordResult>;
|
|
93
|
+
/**
|
|
94
|
+
* The shared launch/sentinel-watch/backstop/collect loop behind both
|
|
95
|
+
* {@link run} and {@link record}. `buildOk` turns the exit reason + captured
|
|
96
|
+
* stdout into the command-specific success value; a spawn ENOENT with no
|
|
97
|
+
* fallback resolves the structured {@link pico8NotFound}. `printh` for `run` is
|
|
98
|
+
* filled by the caller from the run report path (kept out of `buildOk` there so
|
|
99
|
+
* the two report shapes stay distinct).
|
|
100
|
+
*/
|
|
101
|
+
private orchestrate;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/shell.ts"],"names":[],"mappings":"AAUA,OAAO,EACN,KAAK,YAAY,EAGjB,KAAK,aAAa,EAClB,KAAK,YAAY,EAEjB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAEhB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,MAAM,cAAc,CAAC;AAGtB;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,cAAc,KAAK,IAAI,GAAG,IAAI,CAAC;IACxD,IAAI,IAAI,IAAI,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,OAAO,CAAC,EAAE;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAC,KACvB,YAAY,CAAC;AAsBlB,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAItD;AAED,eAAO,MAAM,WAAW,EAAE,WAsCzB,CAAC;AAEF,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,EAAE,CAKhE;AAuDD,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IACjC,iFAAiF;IACjF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;gBAE3B,OAAO,EAAE,iBAAiB;IAKtC,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAoB9C;;;;;;;;;;OAUG;IACH;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmBvD;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA+B1D,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAsB1D;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;CA0DnB"}
|