picopilot 0.0.0 → 0.1.0

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 (334) hide show
  1. package/LICENSE +661 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +4 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/cli.d.ts +15 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +59 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/audio.d.ts +17 -0
  11. package/dist/commands/audio.d.ts.map +1 -0
  12. package/dist/commands/audio.js +315 -0
  13. package/dist/commands/audio.js.map +1 -0
  14. package/dist/commands/export.d.ts +36 -0
  15. package/dist/commands/export.d.ts.map +1 -0
  16. package/dist/commands/export.js +257 -0
  17. package/dist/commands/export.js.map +1 -0
  18. package/dist/commands/gfx.d.ts +28 -0
  19. package/dist/commands/gfx.d.ts.map +1 -0
  20. package/dist/commands/gfx.js +521 -0
  21. package/dist/commands/gfx.js.map +1 -0
  22. package/dist/commands/init.d.ts +34 -0
  23. package/dist/commands/init.d.ts.map +1 -0
  24. package/dist/commands/init.js +151 -0
  25. package/dist/commands/init.js.map +1 -0
  26. package/dist/commands/lint.d.ts +20 -0
  27. package/dist/commands/lint.d.ts.map +1 -0
  28. package/dist/commands/lint.js +90 -0
  29. package/dist/commands/lint.js.map +1 -0
  30. package/dist/commands/minify.d.ts +20 -0
  31. package/dist/commands/minify.d.ts.map +1 -0
  32. package/dist/commands/minify.js +129 -0
  33. package/dist/commands/minify.js.map +1 -0
  34. package/dist/commands/music.d.ts +13 -0
  35. package/dist/commands/music.d.ts.map +1 -0
  36. package/dist/commands/music.js +241 -0
  37. package/dist/commands/music.js.map +1 -0
  38. package/dist/commands/playtest.d.ts +29 -0
  39. package/dist/commands/playtest.d.ts.map +1 -0
  40. package/dist/commands/playtest.js +631 -0
  41. package/dist/commands/playtest.js.map +1 -0
  42. package/dist/commands/run.d.ts +30 -0
  43. package/dist/commands/run.d.ts.map +1 -0
  44. package/dist/commands/run.js +202 -0
  45. package/dist/commands/run.js.map +1 -0
  46. package/dist/commands/serve.d.ts +50 -0
  47. package/dist/commands/serve.d.ts.map +1 -0
  48. package/dist/commands/serve.js +213 -0
  49. package/dist/commands/serve.js.map +1 -0
  50. package/dist/commands/sfx.d.ts +13 -0
  51. package/dist/commands/sfx.d.ts.map +1 -0
  52. package/dist/commands/sfx.js +254 -0
  53. package/dist/commands/sfx.js.map +1 -0
  54. package/dist/commands/tokens.d.ts +36 -0
  55. package/dist/commands/tokens.d.ts.map +1 -0
  56. package/dist/commands/tokens.js +129 -0
  57. package/dist/commands/tokens.js.map +1 -0
  58. package/dist/commands/verify.d.ts +57 -0
  59. package/dist/commands/verify.d.ts.map +1 -0
  60. package/dist/commands/verify.js +213 -0
  61. package/dist/commands/verify.js.map +1 -0
  62. package/dist/commands/version.d.ts +12 -0
  63. package/dist/commands/version.d.ts.map +1 -0
  64. package/dist/commands/version.js +24 -0
  65. package/dist/commands/version.js.map +1 -0
  66. package/dist/engine/audio/index.d.ts +14 -0
  67. package/dist/engine/audio/index.d.ts.map +1 -0
  68. package/dist/engine/audio/index.js +14 -0
  69. package/dist/engine/audio/index.js.map +1 -0
  70. package/dist/engine/audio/music.d.ts +116 -0
  71. package/dist/engine/audio/music.d.ts.map +1 -0
  72. package/dist/engine/audio/music.js +138 -0
  73. package/dist/engine/audio/music.js.map +1 -0
  74. package/dist/engine/audio/section.d.ts +34 -0
  75. package/dist/engine/audio/section.d.ts.map +1 -0
  76. package/dist/engine/audio/section.js +67 -0
  77. package/dist/engine/audio/section.js.map +1 -0
  78. package/dist/engine/audio/sfx.d.ts +141 -0
  79. package/dist/engine/audio/sfx.d.ts.map +1 -0
  80. package/dist/engine/audio/sfx.js +506 -0
  81. package/dist/engine/audio/sfx.js.map +1 -0
  82. package/dist/engine/cart/cart.d.ts +75 -0
  83. package/dist/engine/cart/cart.d.ts.map +1 -0
  84. package/dist/engine/cart/cart.js +153 -0
  85. package/dist/engine/cart/cart.js.map +1 -0
  86. package/dist/engine/cart/errors.d.ts +29 -0
  87. package/dist/engine/cart/errors.d.ts.map +1 -0
  88. package/dist/engine/cart/errors.js +26 -0
  89. package/dist/engine/cart/errors.js.map +1 -0
  90. package/dist/engine/cart/gfx.d.ts +79 -0
  91. package/dist/engine/cart/gfx.d.ts.map +1 -0
  92. package/dist/engine/cart/gfx.js +171 -0
  93. package/dist/engine/cart/gfx.js.map +1 -0
  94. package/dist/engine/cart/index.d.ts +15 -0
  95. package/dist/engine/cart/index.d.ts.map +1 -0
  96. package/dist/engine/cart/index.js +15 -0
  97. package/dist/engine/cart/index.js.map +1 -0
  98. package/dist/engine/cart/map.d.ts +46 -0
  99. package/dist/engine/cart/map.d.ts.map +1 -0
  100. package/dist/engine/cart/map.js +118 -0
  101. package/dist/engine/cart/map.js.map +1 -0
  102. package/dist/engine/config/config.d.ts +74 -0
  103. package/dist/engine/config/config.d.ts.map +1 -0
  104. package/dist/engine/config/config.js +103 -0
  105. package/dist/engine/config/config.js.map +1 -0
  106. package/dist/engine/config/index.d.ts +7 -0
  107. package/dist/engine/config/index.d.ts.map +1 -0
  108. package/dist/engine/config/index.js +7 -0
  109. package/dist/engine/config/index.js.map +1 -0
  110. package/dist/engine/gfx/grid.d.ts +51 -0
  111. package/dist/engine/gfx/grid.d.ts.map +1 -0
  112. package/dist/engine/gfx/grid.js +116 -0
  113. package/dist/engine/gfx/grid.js.map +1 -0
  114. package/dist/engine/gfx/index.d.ts +17 -0
  115. package/dist/engine/gfx/index.d.ts.map +1 -0
  116. package/dist/engine/gfx/index.js +17 -0
  117. package/dist/engine/gfx/index.js.map +1 -0
  118. package/dist/engine/gfx/label.d.ts +34 -0
  119. package/dist/engine/gfx/label.d.ts.map +1 -0
  120. package/dist/engine/gfx/label.js +190 -0
  121. package/dist/engine/gfx/label.js.map +1 -0
  122. package/dist/engine/gfx/overlap.d.ts +42 -0
  123. package/dist/engine/gfx/overlap.d.ts.map +1 -0
  124. package/dist/engine/gfx/overlap.js +57 -0
  125. package/dist/engine/gfx/overlap.js.map +1 -0
  126. package/dist/engine/gfx/palette.d.ts +29 -0
  127. package/dist/engine/gfx/palette.d.ts.map +1 -0
  128. package/dist/engine/gfx/palette.js +46 -0
  129. package/dist/engine/gfx/palette.js.map +1 -0
  130. package/dist/engine/gfx/png.d.ts +31 -0
  131. package/dist/engine/gfx/png.d.ts.map +1 -0
  132. package/dist/engine/gfx/png.js +165 -0
  133. package/dist/engine/gfx/png.js.map +1 -0
  134. package/dist/engine/gfx/render.d.ts +42 -0
  135. package/dist/engine/gfx/render.d.ts.map +1 -0
  136. package/dist/engine/gfx/render.js +72 -0
  137. package/dist/engine/gfx/render.js.map +1 -0
  138. package/dist/engine/index.d.ts +22 -0
  139. package/dist/engine/index.d.ts.map +1 -0
  140. package/dist/engine/index.js +22 -0
  141. package/dist/engine/index.js.map +1 -0
  142. package/dist/engine/pico8/adapter.d.ts +267 -0
  143. package/dist/engine/pico8/adapter.d.ts.map +1 -0
  144. package/dist/engine/pico8/adapter.js +37 -0
  145. package/dist/engine/pico8/adapter.js.map +1 -0
  146. package/dist/engine/pico8/drive.d.ts +308 -0
  147. package/dist/engine/pico8/drive.d.ts.map +1 -0
  148. package/dist/engine/pico8/drive.js +581 -0
  149. package/dist/engine/pico8/drive.js.map +1 -0
  150. package/dist/engine/pico8/harness.d.ts +146 -0
  151. package/dist/engine/pico8/harness.d.ts.map +1 -0
  152. package/dist/engine/pico8/harness.js +251 -0
  153. package/dist/engine/pico8/harness.js.map +1 -0
  154. package/dist/engine/pico8/index.d.ts +21 -0
  155. package/dist/engine/pico8/index.d.ts.map +1 -0
  156. package/dist/engine/pico8/index.js +21 -0
  157. package/dist/engine/pico8/index.js.map +1 -0
  158. package/dist/engine/pico8/sentinel.d.ts +63 -0
  159. package/dist/engine/pico8/sentinel.d.ts.map +1 -0
  160. package/dist/engine/pico8/sentinel.js +86 -0
  161. package/dist/engine/pico8/sentinel.js.map +1 -0
  162. package/dist/engine/pico8/session-daemon-entry.d.ts +10 -0
  163. package/dist/engine/pico8/session-daemon-entry.d.ts.map +1 -0
  164. package/dist/engine/pico8/session-daemon-entry.js +11 -0
  165. package/dist/engine/pico8/session-daemon-entry.js.map +1 -0
  166. package/dist/engine/pico8/session-daemon-main.d.ts +44 -0
  167. package/dist/engine/pico8/session-daemon-main.d.ts.map +1 -0
  168. package/dist/engine/pico8/session-daemon-main.js +71 -0
  169. package/dist/engine/pico8/session-daemon-main.js.map +1 -0
  170. package/dist/engine/pico8/session-daemon.d.ts +68 -0
  171. package/dist/engine/pico8/session-daemon.d.ts.map +1 -0
  172. package/dist/engine/pico8/session-daemon.js +264 -0
  173. package/dist/engine/pico8/session-daemon.js.map +1 -0
  174. package/dist/engine/pico8/session.d.ts +140 -0
  175. package/dist/engine/pico8/session.d.ts.map +1 -0
  176. package/dist/engine/pico8/session.js +250 -0
  177. package/dist/engine/pico8/session.js.map +1 -0
  178. package/dist/engine/pico8/shell.d.ts +103 -0
  179. package/dist/engine/pico8/shell.d.ts.map +1 -0
  180. package/dist/engine/pico8/shell.js +324 -0
  181. package/dist/engine/pico8/shell.js.map +1 -0
  182. package/dist/engine/pico8/supervisor.d.ts +137 -0
  183. package/dist/engine/pico8/supervisor.d.ts.map +1 -0
  184. package/dist/engine/pico8/supervisor.js +140 -0
  185. package/dist/engine/pico8/supervisor.js.map +1 -0
  186. package/dist/engine/scaffold/index.d.ts +8 -0
  187. package/dist/engine/scaffold/index.d.ts.map +1 -0
  188. package/dist/engine/scaffold/index.js +8 -0
  189. package/dist/engine/scaffold/index.js.map +1 -0
  190. package/dist/engine/scaffold/scaffold.d.ts +49 -0
  191. package/dist/engine/scaffold/scaffold.d.ts.map +1 -0
  192. package/dist/engine/scaffold/scaffold.js +217 -0
  193. package/dist/engine/scaffold/scaffold.js.map +1 -0
  194. package/dist/engine/shrinko/adapter.d.ts +167 -0
  195. package/dist/engine/shrinko/adapter.d.ts.map +1 -0
  196. package/dist/engine/shrinko/adapter.js +107 -0
  197. package/dist/engine/shrinko/adapter.js.map +1 -0
  198. package/dist/engine/shrinko/index.d.ts +13 -0
  199. package/dist/engine/shrinko/index.d.ts.map +1 -0
  200. package/dist/engine/shrinko/index.js +13 -0
  201. package/dist/engine/shrinko/index.js.map +1 -0
  202. package/dist/engine/shrinko/shell.d.ts +69 -0
  203. package/dist/engine/shrinko/shell.d.ts.map +1 -0
  204. package/dist/engine/shrinko/shell.js +156 -0
  205. package/dist/engine/shrinko/shell.js.map +1 -0
  206. package/dist/engine/skills/index.d.ts +8 -0
  207. package/dist/engine/skills/index.d.ts.map +1 -0
  208. package/dist/engine/skills/index.js +8 -0
  209. package/dist/engine/skills/index.js.map +1 -0
  210. package/dist/engine/skills/skills.d.ts +104 -0
  211. package/dist/engine/skills/skills.d.ts.map +1 -0
  212. package/dist/engine/skills/skills.js +149 -0
  213. package/dist/engine/skills/skills.js.map +1 -0
  214. package/dist/index.d.ts +10 -0
  215. package/dist/index.d.ts.map +1 -0
  216. package/dist/index.js +10 -0
  217. package/dist/index.js.map +1 -0
  218. package/dist/version.d.ts +2 -0
  219. package/dist/version.d.ts.map +1 -0
  220. package/dist/version.js +19 -0
  221. package/dist/version.js.map +1 -0
  222. package/package.json +48 -2
  223. package/src/bin.ts +4 -0
  224. package/src/cli.ts +61 -0
  225. package/src/commands/audio.test.ts +280 -0
  226. package/src/commands/audio.ts +410 -0
  227. package/src/commands/export.test.ts +310 -0
  228. package/src/commands/export.ts +316 -0
  229. package/src/commands/gfx-io.test.ts +178 -0
  230. package/src/commands/gfx.test.ts +470 -0
  231. package/src/commands/gfx.ts +645 -0
  232. package/src/commands/init.test.ts +460 -0
  233. package/src/commands/init.ts +193 -0
  234. package/src/commands/lint.test.ts +137 -0
  235. package/src/commands/lint.ts +117 -0
  236. package/src/commands/minify.test.ts +166 -0
  237. package/src/commands/minify.ts +156 -0
  238. package/src/commands/music.test.ts +272 -0
  239. package/src/commands/music.ts +288 -0
  240. package/src/commands/playtest.test.ts +317 -0
  241. package/src/commands/playtest.ts +773 -0
  242. package/src/commands/run.test.ts +238 -0
  243. package/src/commands/run.ts +259 -0
  244. package/src/commands/serve.test.ts +218 -0
  245. package/src/commands/serve.ts +272 -0
  246. package/src/commands/sfx.test.ts +349 -0
  247. package/src/commands/sfx.ts +300 -0
  248. package/src/commands/tokens.test.ts +212 -0
  249. package/src/commands/tokens.ts +168 -0
  250. package/src/commands/verify.test.ts +238 -0
  251. package/src/commands/verify.ts +256 -0
  252. package/src/commands/version.test.ts +70 -0
  253. package/src/commands/version.ts +24 -0
  254. package/src/engine/audio/index.ts +47 -0
  255. package/src/engine/audio/music.test.ts +188 -0
  256. package/src/engine/audio/music.ts +218 -0
  257. package/src/engine/audio/section.test.ts +124 -0
  258. package/src/engine/audio/section.ts +79 -0
  259. package/src/engine/audio/sfx.test.ts +410 -0
  260. package/src/engine/audio/sfx.ts +694 -0
  261. package/src/engine/cart/cart.test.ts +304 -0
  262. package/src/engine/cart/cart.ts +211 -0
  263. package/src/engine/cart/errors.ts +39 -0
  264. package/src/engine/cart/fixtures/crlf.p8 +6 -0
  265. package/src/engine/cart/fixtures/full.p8 +174 -0
  266. package/src/engine/cart/fixtures/header-only.p8 +2 -0
  267. package/src/engine/cart/fixtures/malformed-no-header.p8 +2 -0
  268. package/src/engine/cart/fixtures/minimal.p8 +10 -0
  269. package/src/engine/cart/fixtures/no-trailing-newline.p8 +6 -0
  270. package/src/engine/cart/gfx.ts +195 -0
  271. package/src/engine/cart/index.ts +27 -0
  272. package/src/engine/cart/map.ts +134 -0
  273. package/src/engine/config/config.ts +115 -0
  274. package/src/engine/config/index.ts +12 -0
  275. package/src/engine/gfx/grid.test.ts +128 -0
  276. package/src/engine/gfx/grid.ts +136 -0
  277. package/src/engine/gfx/index.ts +34 -0
  278. package/src/engine/gfx/label.test.ts +105 -0
  279. package/src/engine/gfx/label.ts +229 -0
  280. package/src/engine/gfx/overlap.test.ts +71 -0
  281. package/src/engine/gfx/overlap.ts +66 -0
  282. package/src/engine/gfx/palette.test.ts +35 -0
  283. package/src/engine/gfx/palette.ts +54 -0
  284. package/src/engine/gfx/png.test.ts +111 -0
  285. package/src/engine/gfx/png.ts +196 -0
  286. package/src/engine/gfx/render.test.ts +180 -0
  287. package/src/engine/gfx/render.ts +106 -0
  288. package/src/engine/index.ts +21 -0
  289. package/src/engine/pico8/adapter.ts +303 -0
  290. package/src/engine/pico8/drive.test.ts +415 -0
  291. package/src/engine/pico8/drive.ts +729 -0
  292. package/src/engine/pico8/harness.test.ts +181 -0
  293. package/src/engine/pico8/harness.ts +354 -0
  294. package/src/engine/pico8/index.ts +20 -0
  295. package/src/engine/pico8/sentinel.test.ts +80 -0
  296. package/src/engine/pico8/sentinel.ts +93 -0
  297. package/src/engine/pico8/session-daemon-entry.ts +12 -0
  298. package/src/engine/pico8/session-daemon-main.ts +98 -0
  299. package/src/engine/pico8/session-daemon.test.ts +212 -0
  300. package/src/engine/pico8/session-daemon.ts +306 -0
  301. package/src/engine/pico8/session.test.ts +239 -0
  302. package/src/engine/pico8/session.ts +321 -0
  303. package/src/engine/pico8/shell.test.ts +583 -0
  304. package/src/engine/pico8/shell.ts +412 -0
  305. package/src/engine/pico8/supervisor.test.ts +105 -0
  306. package/src/engine/pico8/supervisor.ts +197 -0
  307. package/src/engine/scaffold/index.ts +15 -0
  308. package/src/engine/scaffold/scaffold.ts +236 -0
  309. package/src/engine/shrinko/adapter.ts +241 -0
  310. package/src/engine/shrinko/fixtures/count-no-compressed.txt +2 -0
  311. package/src/engine/shrinko/fixtures/count-over-budget.txt +3 -0
  312. package/src/engine/shrinko/fixtures/count-under-budget.txt +3 -0
  313. package/src/engine/shrinko/index.ts +33 -0
  314. package/src/engine/shrinko/shell.ts +224 -0
  315. package/src/engine/shrinko/shrinko.test.ts +182 -0
  316. package/src/engine/skills/index.ts +14 -0
  317. package/src/engine/skills/skills.test.ts +376 -0
  318. package/src/engine/skills/skills.ts +205 -0
  319. package/src/index.ts +13 -0
  320. package/src/skills/game-design-reference/SKILL.md +70 -0
  321. package/src/skills/game-jam/SKILL.md +45 -0
  322. package/src/skills/picopilot-art/SKILL.md +62 -0
  323. package/src/skills/picopilot-audio/SKILL.md +194 -0
  324. package/src/skills/picopilot-code/SKILL.md +97 -0
  325. package/src/skills/picopilot-code/reference/README.md +42 -0
  326. package/src/skills/picopilot-code/reference/mode7-racing.md +95 -0
  327. package/src/skills/picopilot-code/reference/platformer.md +119 -0
  328. package/src/skills/picopilot-code/reference/puzzle-grid.md +106 -0
  329. package/src/skills/picopilot-code/reference/rpg-menus-dialog.md +122 -0
  330. package/src/skills/picopilot-code/reference/top-down-adventure.md +117 -0
  331. package/src/skills/picopilot-code/reference/twin-stick-arcade.md +128 -0
  332. package/src/skills/picopilot-debug/SKILL.md +195 -0
  333. package/src/skills/picopilot-overview/SKILL.md +52 -0
  334. 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"}