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.
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 +224 -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 +259 -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 +482 -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 +264 -0
  243. package/src/commands/run.ts +285 -0
  244. package/src/commands/serve.test.ts +227 -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 +278 -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 @@
1
+ {"version":3,"file":"session-daemon-entry.js","sourceRoot":"","sources":["../../../src/engine/pico8/session-daemon-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAEpD,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The playtest session DAEMON ENTRYPOINT (ADR-0011, prd US #6): the small
3
+ * detached process `playtest start` spawns to OWN a live driven PICO-8 for a
4
+ * session's lifetime. It is invoked via the hidden `playtest __daemon` verb (so it
5
+ * re-uses the exact same executable/loader the parent ran under, dev `tsx` or
6
+ * built `dist`), reads its config from JSON on argv, spawns PICO-8 on the
7
+ * throwaway driven cart with a LIVE stdin, and hands the process to
8
+ * {@link serveSession} to answer verbs over the session socket.
9
+ *
10
+ * PICO-8 ABSENT: the spawn ENOENTs, so the daemon writes a `pico8-not-found`
11
+ * marker file into the session dir and exits; the `start` client polls for either
12
+ * the socket (ready) or that marker (absent) and maps the marker to the same
13
+ * structured `pico8-not-found` boundary as `run`/`audio render`. This keeps the
14
+ * absent path structured WITHOUT the parent ever launching PICO-8 itself (the
15
+ * `pico8 --help` footgun is never touched).
16
+ */
17
+ import type { SessionPaths } from './supervisor.js';
18
+ /** The marker file the daemon writes into the session dir when PICO-8 is absent. */
19
+ export declare const PICO8_NOT_FOUND_MARKER: "pico8-not-found";
20
+ /** The daemon's launch config, passed as a single JSON argument. */
21
+ export interface DaemonConfig {
22
+ readonly id: string;
23
+ readonly paths: SessionPaths;
24
+ /** Absolute path to the throwaway driven cart to run live. */
25
+ readonly cartPath: string;
26
+ /** The screenshot basename the driven cart names each SHOT. */
27
+ readonly shotBasename: string;
28
+ /** Idle-reap window in ms. */
29
+ readonly idleTimeoutMs: number;
30
+ /** Per-command ACK deadline in ms. */
31
+ readonly ackTimeoutMs: number;
32
+ }
33
+ /**
34
+ * Runs the daemon from its JSON config: spawns PICO-8 live on the driven cart and
35
+ * serves the session, or writes the pico8-not-found marker and exits on ENOENT.
36
+ * Exported so a test can drive it with an injected spawner (no real binary).
37
+ */
38
+ export declare function runDaemon(config: DaemonConfig, env?: NodeJS.ProcessEnv, spawn?: import("./shell.js").SpawnRunner): void;
39
+ /**
40
+ * The daemon process main: reads the JSON config from `argv[0]` and runs. Called
41
+ * by the hidden `playtest __daemon <configJson>` verb in a detached child.
42
+ */
43
+ export declare function daemonMain(argv: readonly string[]): void;
44
+ //# sourceMappingURL=session-daemon-main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-daemon-main.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/session-daemon-main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAElD,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,EAAG,iBAA0B,CAAC;AAEjE,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,8BAA8B;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,sCAAsC;IACtC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACxB,MAAM,EAAE,YAAY,EACpB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,KAAK,mCAAc,GACjB,IAAI,CAiCN;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAQxD"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The playtest session DAEMON ENTRYPOINT (ADR-0011, prd US #6): the small
3
+ * detached process `playtest start` spawns to OWN a live driven PICO-8 for a
4
+ * session's lifetime. It is invoked via the hidden `playtest __daemon` verb (so it
5
+ * re-uses the exact same executable/loader the parent ran under, dev `tsx` or
6
+ * built `dist`), reads its config from JSON on argv, spawns PICO-8 on the
7
+ * throwaway driven cart with a LIVE stdin, and hands the process to
8
+ * {@link serveSession} to answer verbs over the session socket.
9
+ *
10
+ * PICO-8 ABSENT: the spawn ENOENTs, so the daemon writes a `pico8-not-found`
11
+ * marker file into the session dir and exits; the `start` client polls for either
12
+ * the socket (ready) or that marker (absent) and maps the marker to the same
13
+ * structured `pico8-not-found` boundary as `run`/`audio render`. This keeps the
14
+ * absent path structured WITHOUT the parent ever launching PICO-8 itself (the
15
+ * `pico8 --help` footgun is never touched).
16
+ */
17
+ import { writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { pico8NotFound } from './adapter.js';
20
+ import { serveSession } from './session-daemon.js';
21
+ import { spawnRunner, pico8Candidates, withPico8Home } from './shell.js';
22
+ /** The marker file the daemon writes into the session dir when PICO-8 is absent. */
23
+ export const PICO8_NOT_FOUND_MARKER = 'pico8-not-found';
24
+ /**
25
+ * Runs the daemon from its JSON config: spawns PICO-8 live on the driven cart and
26
+ * serves the session, or writes the pico8-not-found marker and exits on ENOENT.
27
+ * Exported so a test can drive it with an injected spawner (no real binary).
28
+ */
29
+ export function runDaemon(config, env = process.env, spawn = spawnRunner) {
30
+ const [file] = pico8Candidates(env);
31
+ // Live session: `-desktop <shotDir>` for SHOT captures, `-x` headless, stdin
32
+ // piped so the daemon can send fixed-size command blocks per verb.
33
+ const args = ['-desktop', config.paths.shotDir, '-x', config.cartPath];
34
+ // `-home <isolated>` so PICO-8's config/data tree lands in a throwaway dir, not
35
+ // the caller's CWD (matches the shell adapter's spawn sites).
36
+ const proc = spawn(file, withPico8Home(args), env, { stdin: true });
37
+ // A spawn ENOENT means PICO-8 is absent (a bad PICO8_PATH surfaces the same
38
+ // way): leave the structured marker the `start` client polls for, then exit.
39
+ // This can only happen at spawn time (a launched binary does not ENOENT later),
40
+ // so it is unambiguously the absent boundary, not a mid-session fault.
41
+ proc.onError((err) => {
42
+ if (err.code === 'ENOENT') {
43
+ writeFileSync(join(config.paths.dir, PICO8_NOT_FOUND_MARKER), JSON.stringify(pico8NotFound()));
44
+ process.exit(0);
45
+ }
46
+ process.exit(1);
47
+ });
48
+ serveSession({
49
+ id: config.id,
50
+ paths: config.paths,
51
+ process: proc,
52
+ shotBasename: config.shotBasename,
53
+ idleTimeoutMs: config.idleTimeoutMs,
54
+ ackTimeoutMs: config.ackTimeoutMs,
55
+ onExit: () => process.exit(0),
56
+ });
57
+ }
58
+ /**
59
+ * The daemon process main: reads the JSON config from `argv[0]` and runs. Called
60
+ * by the hidden `playtest __daemon <configJson>` verb in a detached child.
61
+ */
62
+ export function daemonMain(argv) {
63
+ const raw = argv[0];
64
+ if (raw === undefined) {
65
+ process.stderr.write('playtest daemon: missing config\n');
66
+ process.exit(2);
67
+ }
68
+ const config = JSON.parse(raw);
69
+ runDaemon(config);
70
+ }
71
+ //# sourceMappingURL=session-daemon-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-daemon-main.js","sourceRoot":"","sources":["../../../src/engine/pico8/session-daemon-main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAC,aAAa,EAAC,MAAM,SAAS,CAAC;AACtC,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAC,WAAW,EAAE,eAAe,EAAE,aAAa,EAAC,MAAM,YAAY,CAAC;AAGvE,oFAAoF;AACpF,MAAM,CAAC,MAAM,sBAAsB,GAAG,iBAA0B,CAAC;AAgBjE;;;;GAIG;AACH,MAAM,UAAU,SAAS,CACxB,MAAoB,EACpB,MAAyB,OAAO,CAAC,GAAG,EACpC,KAAK,GAAG,WAAW;IAEnB,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,GAAG,CAA0B,CAAC;IAC7D,6EAA6E;IAC7E,mEAAmE;IACnE,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACvE,gFAAgF;IAChF,8DAA8D;IAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAC,KAAK,EAAE,IAAI,EAAC,CAAC,CAAC;IAElE,4EAA4E;IAC5E,6EAA6E;IAC7E,gFAAgF;IAChF,uEAAuE;IACvE,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACpB,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,aAAa,CACZ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,sBAAsB,CAAC,EAC9C,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC,CAC/B,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,YAAY,CAAC;QACZ,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;KAC7B,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAuB;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IAC/C,SAAS,CAAC,MAAM,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The playtest session DAEMON runtime + CLIENT (ADR-0011, prd US #6). This is the
3
+ * IO half of the supervisor: the daemon LISTENS on a session's Unix domain socket
4
+ * and drives a live {@link DriveSession} on behalf of each verb sub-invocation;
5
+ * the {@link SessionClient} CONNECTS to that socket to send a verb and read back
6
+ * the structured response.
7
+ *
8
+ * The daemon owns the live process the whole session, so the game stays ALIVE +
9
+ * PAUSED between the agent's turns. It self-reaps on an IDLE timeout (the orphan
10
+ * path) and on `stop`. The live daemon end-to-end is the manual/opt-in tier; the
11
+ * pure pieces it composes (the handshake {@link DriveSession}, the registry, the
12
+ * frame codec) are unit-tested without a real socket or binary.
13
+ */
14
+ import type { Pico8Process } from './shell.js';
15
+ import { type SessionPaths, type SessionRequest, type SessionResponse } from './supervisor.js';
16
+ /**
17
+ * A client for a running session daemon: connects to its socket, sends ONE verb
18
+ * request, awaits the single response, and closes. Each `playtest step/input/shot
19
+ * /stop` sub-invocation makes exactly one of these round-trips.
20
+ */
21
+ export declare class SessionClient {
22
+ private readonly socketPath;
23
+ constructor(socketPath: string);
24
+ /**
25
+ * Sends `request` to the daemon and resolves its {@link SessionResponse}. A
26
+ * connection failure (no daemon / dead socket) rejects with a plain Error the
27
+ * command maps to `playtest-session-not-found`.
28
+ */
29
+ send(request: SessionRequest): Promise<SessionResponse>;
30
+ }
31
+ /** Whether a session daemon is reachable (its socket exists). */
32
+ export declare function sessionExists(paths: SessionPaths): boolean;
33
+ /**
34
+ * Spawns the session DAEMON as a DETACHED background process that outlives this
35
+ * (the `start`) invocation, so the driven PICO-8 stays alive across the agent's
36
+ * separate verb turns. It re-invokes the current executable + `execArgv` (so the
37
+ * daemon runs under the SAME loader, dev `tsx` or built node) on the daemon entry
38
+ * file, passing the config as a single JSON arg. `unref` + `detached` cut it free
39
+ * from this process's lifetime; its stdio is ignored (the daemon talks over the
40
+ * session socket, not this process's pipes).
41
+ */
42
+ export declare function spawnSessionDaemon(config: import('./session-daemon-main.js').DaemonConfig): void;
43
+ /** Options for {@link serveSession}. */
44
+ export interface ServeSessionOptions {
45
+ readonly id: string;
46
+ readonly paths: SessionPaths;
47
+ /** The live driven process to own for this session (spawned by the caller). */
48
+ readonly process: Pico8Process;
49
+ /** The screenshot basename the driven cart names each SHOT (matches transform). */
50
+ readonly shotBasename: string;
51
+ /** Idle-reap window (defaults to {@link DEFAULT_IDLE_TIMEOUT_MS}). */
52
+ readonly idleTimeoutMs?: number;
53
+ /** Per-command ACK deadline forwarded to {@link DriveSession}. */
54
+ readonly ackTimeoutMs?: number;
55
+ /** Called when the daemon has fully torn down (for the process to exit). */
56
+ readonly onExit?: () => void;
57
+ }
58
+ /**
59
+ * Runs the session daemon: wraps the live process in a {@link DriveSession},
60
+ * listens on the session socket, and dispatches each incoming verb request to the
61
+ * handshake (send-command -> wait-ack -> respond). Resets an IDLE-reap timer on
62
+ * every verb, so an orphaned session tears down on its own. Returns the net
63
+ * server so the caller (the daemon entrypoint) can await its lifetime.
64
+ */
65
+ export declare function serveSession(options: ServeSessionOptions): {
66
+ close: () => void;
67
+ };
68
+ //# sourceMappingURL=session-daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-daemon.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/session-daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,YAAY,CAAC;AAC7C,OAAO,EAIN,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,MAAM,iBAAiB,CAAC;AAEzB;;;;GAIG;AACH,qBAAa,aAAa;IACb,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,MAAM;IAE/C;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA6BvD;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAE1D;AAWD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,OAAO,0BAA0B,EAAE,YAAY,GACrD,IAAI,CAON;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,kEAAkE;IAClE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG;IAC3D,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB,CA0IA"}
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The playtest session DAEMON runtime + CLIENT (ADR-0011, prd US #6). This is the
3
+ * IO half of the supervisor: the daemon LISTENS on a session's Unix domain socket
4
+ * and drives a live {@link DriveSession} on behalf of each verb sub-invocation;
5
+ * the {@link SessionClient} CONNECTS to that socket to send a verb and read back
6
+ * the structured response.
7
+ *
8
+ * The daemon owns the live process the whole session, so the game stays ALIVE +
9
+ * PAUSED between the agent's turns. It self-reaps on an IDLE timeout (the orphan
10
+ * path) and on `stop`. The live daemon end-to-end is the manual/opt-in tier; the
11
+ * pure pieces it composes (the handshake {@link DriveSession}, the registry, the
12
+ * frame codec) are unit-tested without a real socket or binary.
13
+ */
14
+ import { spawn } from 'node:child_process';
15
+ import { createConnection, createServer } from 'node:net';
16
+ import { existsSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
17
+ import { dirname, join } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { DriveSession, SessionError, } from './session.js';
20
+ import { DEFAULT_IDLE_TIMEOUT_MS, encodeFrame, FrameDecoder, } from './supervisor.js';
21
+ /**
22
+ * A client for a running session daemon: connects to its socket, sends ONE verb
23
+ * request, awaits the single response, and closes. Each `playtest step/input/shot
24
+ * /stop` sub-invocation makes exactly one of these round-trips.
25
+ */
26
+ export class SessionClient {
27
+ socketPath;
28
+ constructor(socketPath) {
29
+ this.socketPath = socketPath;
30
+ }
31
+ /**
32
+ * Sends `request` to the daemon and resolves its {@link SessionResponse}. A
33
+ * connection failure (no daemon / dead socket) rejects with a plain Error the
34
+ * command maps to `playtest-session-not-found`.
35
+ */
36
+ send(request) {
37
+ return new Promise((resolve, reject) => {
38
+ const decoder = new FrameDecoder();
39
+ let settled = false;
40
+ const sock = createConnection(this.socketPath);
41
+ sock.setEncoding('utf8');
42
+ sock.on('connect', () => sock.write(encodeFrame(request)));
43
+ sock.on('data', (chunk) => {
44
+ const [msg] = decoder.push(chunk);
45
+ if (msg !== undefined && !settled) {
46
+ settled = true;
47
+ resolve(msg);
48
+ sock.end();
49
+ }
50
+ });
51
+ sock.on('error', (err) => {
52
+ if (!settled) {
53
+ settled = true;
54
+ reject(err);
55
+ }
56
+ });
57
+ sock.on('close', () => {
58
+ if (!settled) {
59
+ settled = true;
60
+ reject(new Error('daemon closed the connection with no response'));
61
+ }
62
+ });
63
+ });
64
+ }
65
+ }
66
+ /** Whether a session daemon is reachable (its socket exists). */
67
+ export function sessionExists(paths) {
68
+ return existsSync(paths.socket);
69
+ }
70
+ /** Resolves the sibling daemon-entry module (`.ts` in dev, `.js` when built). */
71
+ function daemonEntryPath() {
72
+ const here = dirname(fileURLToPath(import.meta.url));
73
+ // This module and the entry are compiled/loaded together, so the extension
74
+ // here (`.ts` under tsx, `.js` under dist) is the entry's extension too.
75
+ const ext = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
76
+ return join(here, `session-daemon-entry${ext}`);
77
+ }
78
+ /**
79
+ * Spawns the session DAEMON as a DETACHED background process that outlives this
80
+ * (the `start`) invocation, so the driven PICO-8 stays alive across the agent's
81
+ * separate verb turns. It re-invokes the current executable + `execArgv` (so the
82
+ * daemon runs under the SAME loader, dev `tsx` or built node) on the daemon entry
83
+ * file, passing the config as a single JSON arg. `unref` + `detached` cut it free
84
+ * from this process's lifetime; its stdio is ignored (the daemon talks over the
85
+ * session socket, not this process's pipes).
86
+ */
87
+ export function spawnSessionDaemon(config) {
88
+ const child = spawn(process.execPath, [...process.execArgv, daemonEntryPath(), JSON.stringify(config)], { detached: true, stdio: 'ignore' });
89
+ child.unref();
90
+ }
91
+ /**
92
+ * Runs the session daemon: wraps the live process in a {@link DriveSession},
93
+ * listens on the session socket, and dispatches each incoming verb request to the
94
+ * handshake (send-command -> wait-ack -> respond). Resets an IDLE-reap timer on
95
+ * every verb, so an orphaned session tears down on its own. Returns the net
96
+ * server so the caller (the daemon entrypoint) can await its lifetime.
97
+ */
98
+ export function serveSession(options) {
99
+ const session = new DriveSession({
100
+ process: options.process,
101
+ shotBasename: options.shotBasename,
102
+ ackTimeoutMs: options.ackTimeoutMs,
103
+ });
104
+ const idleMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
105
+ let idleTimer;
106
+ let tornDown = false;
107
+ const teardown = () => {
108
+ if (tornDown)
109
+ return;
110
+ tornDown = true;
111
+ if (idleTimer !== undefined)
112
+ clearTimeout(idleTimer);
113
+ try {
114
+ session.stop();
115
+ }
116
+ catch {
117
+ // best-effort; the process kill below is the real backstop
118
+ }
119
+ try {
120
+ server.close();
121
+ }
122
+ catch {
123
+ // ignore
124
+ }
125
+ // Reap the WHOLE session dir (socket + pidfile + driven cart + shots), so an
126
+ // idle/orphan teardown leaves nothing behind, not just the socket. (The CLI
127
+ // `stop` also sweeps the dir; doing it here covers the no-`stop` orphan path.)
128
+ try {
129
+ rmSync(options.paths.dir, { recursive: true, force: true });
130
+ }
131
+ catch {
132
+ // ignore
133
+ }
134
+ options.onExit?.();
135
+ };
136
+ const armIdle = () => {
137
+ if (idleTimer !== undefined)
138
+ clearTimeout(idleTimer);
139
+ idleTimer = setTimeout(teardown, idleMs);
140
+ idleTimer.unref?.();
141
+ };
142
+ const handle = async (req) => {
143
+ armIdle();
144
+ try {
145
+ switch (req.verb) {
146
+ case 'input': {
147
+ const r = await session.input(req.held);
148
+ return ok(options.id, { frame: r.frame, ack: r.ack, alive: true });
149
+ }
150
+ case 'step': {
151
+ // A frames span may exceed one byte; the daemon issues it as repeated
152
+ // single-byte steps, each awaiting its own STEP-DONE ack, so the total
153
+ // is exact and lossless regardless of size.
154
+ let last = { frame: session.frame, ack: '' };
155
+ let remaining = Math.max(0, Math.floor(req.frames));
156
+ while (remaining > 0) {
157
+ const chunk = Math.min(remaining, 0xff);
158
+ last = await session.step(chunk);
159
+ remaining -= chunk;
160
+ }
161
+ return ok(options.id, {
162
+ frame: last.frame,
163
+ ack: last.ack,
164
+ alive: true,
165
+ });
166
+ }
167
+ case 'shot': {
168
+ const r = await session.shot();
169
+ const screenshot = collectShot(options.paths.shotDir, r.shotName);
170
+ return ok(options.id, {
171
+ frame: r.frame,
172
+ ack: r.ack,
173
+ shotName: r.shotName,
174
+ screenshot,
175
+ alive: true,
176
+ });
177
+ }
178
+ case 'status': {
179
+ return ok(options.id, {
180
+ frame: session.frame,
181
+ alive: !session.isDead,
182
+ });
183
+ }
184
+ case 'stop': {
185
+ // The connection handler tears the daemon down AFTER this response has
186
+ // flushed to the client (so `stop` returns cleanly, no close-race).
187
+ return ok(options.id, { frame: session.frame, alive: false });
188
+ }
189
+ }
190
+ }
191
+ catch (e) {
192
+ if (e instanceof SessionError) {
193
+ return { ok: false, code: e.code, message: e.message };
194
+ }
195
+ return {
196
+ ok: false,
197
+ code: 'playtest-session-error',
198
+ message: e instanceof Error ? e.message : String(e),
199
+ };
200
+ }
201
+ };
202
+ const server = createServer((sock) => {
203
+ sock.setEncoding('utf8');
204
+ const decoder = new FrameDecoder();
205
+ sock.on('data', (chunk) => {
206
+ for (const req of decoder.push(chunk)) {
207
+ const isStop = req.verb === 'stop';
208
+ void handle(req).then((res) => {
209
+ try {
210
+ // Flush the response, THEN (for `stop`) end the socket and tear the
211
+ // daemon down, so the client always reads the stop reply first.
212
+ sock.write(encodeFrame(res), () => {
213
+ if (isStop) {
214
+ sock.end();
215
+ teardown();
216
+ }
217
+ });
218
+ }
219
+ catch {
220
+ if (isStop)
221
+ teardown();
222
+ }
223
+ });
224
+ }
225
+ });
226
+ sock.on('error', () => {
227
+ /* a dropped client is not fatal to the daemon */
228
+ });
229
+ });
230
+ // If the driven process dies, the next verb reports it (process-dead); we do
231
+ // NOT auto-teardown here, so the agent can still `stop`/`status` and get the
232
+ // structured error rather than a vanished socket.
233
+ server.listen(options.paths.socket, () => {
234
+ writeFileSync(options.paths.pidfile, String(process.pid));
235
+ armIdle();
236
+ });
237
+ return { close: teardown };
238
+ }
239
+ /** Builds an ok {@link SessionResponse} from a partial value + the id. */
240
+ function ok(id, partial) {
241
+ return { ok: true, value: { id, ...partial } };
242
+ }
243
+ /**
244
+ * Resolves the absolute path of the screenshot a SHOT just produced in the
245
+ * `-desktop` dir. The cart named it `<shotName>` (basename) and PICO-8 appends
246
+ * `.png`; if the exact file is not yet visible (a filesystem race) it falls back
247
+ * to the most-recent PNG, so the caller still gets a path to read.
248
+ */
249
+ function collectShot(shotDir, shotName) {
250
+ const exact = join(shotDir, `${shotName}.png`);
251
+ if (existsSync(exact))
252
+ return exact;
253
+ try {
254
+ const pngs = readdirSync(shotDir)
255
+ .filter((f) => f.toLowerCase().endsWith('.png'))
256
+ .sort();
257
+ const last = pngs.at(-1);
258
+ return last !== undefined ? join(shotDir, last) : undefined;
259
+ }
260
+ catch {
261
+ return undefined;
262
+ }
263
+ }
264
+ //# sourceMappingURL=session-daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-daemon.js","sourceRoot":"","sources":["../../../src/engine/pico8/session-daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
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 type { Pico8Process } from './shell.js';
19
+ /**
20
+ * A structured reason a session command could not complete (a boundary, not a
21
+ * crash). Surfaced by the supervisor/command as incur's `error({code,...})`.
22
+ */
23
+ export type SessionErrorCode =
24
+ /** No ACK arrived within the deadline (the lost/late-ack path). */
25
+ 'playtest-session-ack-timeout'
26
+ /** PICO-8 exited (crashed/quit) while a command was in flight or between them. */
27
+ | 'playtest-session-process-dead'
28
+ /** A command was issued after the session was already stopped/closed. */
29
+ | 'playtest-session-closed';
30
+ /** A structured session-command failure (never a raw throw across the seam). */
31
+ export declare class SessionError extends Error {
32
+ readonly code: SessionErrorCode;
33
+ constructor(code: SessionErrorCode, message: string);
34
+ }
35
+ /**
36
+ * How long a single command waits for its ACK before it is declared lost (the
37
+ * lost/late-ack path). Generous: a STEP of many frames takes real time at 30fps,
38
+ * plus PICO-8 scheduling jitter, but a hung/dead cart must not wall the session
39
+ * forever. The supervisor's process-death signal (see {@link DriveSession}) ends
40
+ * a command EARLY when the process dies, so this only fires for a truly stuck
41
+ * cart.
42
+ */
43
+ export declare const DEFAULT_ACK_TIMEOUT_MS = 15000;
44
+ /** The result of one completed session command (the structured per-verb envelope). */
45
+ export interface StepResult {
46
+ /** The total frames advanced across the session's lifetime so far. */
47
+ readonly frame: number;
48
+ /** The ACK line the cart printed for this command (the settled-frame proof). */
49
+ readonly ack: string;
50
+ }
51
+ /** The result of a `shot` command: the frame + the basename the cart named it. */
52
+ export interface ShotResult extends StepResult {
53
+ /** The screenshot basename the cart queued (`<basename><n>`), for collection. */
54
+ readonly shotName: string;
55
+ }
56
+ /** Options for a {@link DriveSession}. */
57
+ export interface DriveSessionOptions {
58
+ /** The already-spawned live driven process (owned by the caller/supervisor). */
59
+ readonly process: Pico8Process;
60
+ /** Per-command ACK deadline (defaults to {@link DEFAULT_ACK_TIMEOUT_MS}). */
61
+ readonly ackTimeoutMs?: number;
62
+ /**
63
+ * The screenshot basename the driven cart's SHOT names each capture (matches
64
+ * the transform's `shotBasename`); the session reports `<basename><n>`.
65
+ */
66
+ readonly shotBasename: string;
67
+ }
68
+ /**
69
+ * The live-session handshake engine (ADR-0011, US #6). Wraps ONE driven
70
+ * {@link Pico8Process} and exposes the agent's verbs: {@link input} (set the
71
+ * held-buttons byte), {@link step} (advance exactly N frames), {@link shot}
72
+ * (capture the frozen current frame), {@link stop} (tear the session down). Each
73
+ * verb WRITES a single fixed-size command block and AWAITS the cart's ACK before
74
+ * resolving, so the process stays alive + paused between verbs and the caller
75
+ * always acts on a settled frame. Between the ACK and the next verb the game is
76
+ * frozen (its callbacks skipped, a stable framebuffer), so a {@link shot} always
77
+ * captures exactly the intended moment.
78
+ *
79
+ * The engine is transport-agnostic: it talks to the {@link Pico8Process} seam,
80
+ * so the send/wait/return + lost-ack + process-death paths are unit-tested
81
+ * against a FAKE process with no real binary. The supervisor supplies a REAL
82
+ * spawned process for a live session (the manual/opt-in tier).
83
+ */
84
+ export declare class DriveSession {
85
+ private readonly proc;
86
+ private readonly reader;
87
+ private readonly ackTimeoutMs;
88
+ private readonly shotBasename;
89
+ private frames;
90
+ private shotIndex;
91
+ /** Set once the process dies; every in-flight + future command rejects. */
92
+ private dead;
93
+ private closed;
94
+ private readonly deathWaiters;
95
+ constructor(options: DriveSessionOptions);
96
+ /** Total frames the session has advanced so far. */
97
+ get frame(): number;
98
+ /** Whether the underlying process has died (crashed/quit). */
99
+ get isDead(): boolean;
100
+ private markDead;
101
+ /**
102
+ * Sets the per-frame HELD-buttons byte for the upcoming steps (US #6: inject
103
+ * input between turns). Writes an INPUT block and awaits its ACK. The held
104
+ * level persists until the next INPUT (the cart holds it), and `btnp` edges
105
+ * are reconstructed cart-side from the held sequence.
106
+ */
107
+ input(held: number): Promise<StepResult>;
108
+ /**
109
+ * Advances EXACTLY `frames` frames then pauses (US #6, US #4). Writes a STEP
110
+ * block and awaits the STEP-DONE ack, which the cart prints only when the
111
+ * frame budget reaches 0, so the returned result means the N frames ACTUALLY
112
+ * ran and the game is now paused on a settled frame. `frames` must fit one
113
+ * byte (the block's arg); larger spans are issued as repeated steps by the
114
+ * caller.
115
+ */
116
+ step(frames: number): Promise<StepResult>;
117
+ /**
118
+ * Captures the current (paused, frozen) frame (US #6, US #5). Writes a SHOT
119
+ * block and awaits its ACK; the cart renders the frozen state once then
120
+ * screenshots it, so the capture is exactly the intended moment. Returns the
121
+ * `<basename><n>` the cart named it, which the supervisor collects from the
122
+ * `-desktop` dir.
123
+ */
124
+ shot(): Promise<ShotResult>;
125
+ /**
126
+ * Tears the session down: writes a QUIT block (the cart prints the sentinel so
127
+ * the launcher kills PICO-8), then kills the process as the backstop. Does NOT
128
+ * await an ACK (QUIT ends the cart), and is idempotent + safe on a dead
129
+ * process. After `stop`, every verb rejects with `playtest-session-closed`.
130
+ */
131
+ stop(): void;
132
+ /**
133
+ * The core handshake: write one command block, then race the ACK line against
134
+ * the timeout and the process-death signal. A missing ACK within the deadline
135
+ * is the structured lost/late-ack error; a process that dies mid-command is the
136
+ * structured process-dead error. Never a hang, never a silent success.
137
+ */
138
+ private send;
139
+ }
140
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,YAAY,CAAC;AAE7C;;;GAGG;AACH,MAAM,MAAM,gBAAgB;AAC3B,mEAAmE;AACjE,8BAA8B;AAChC,kFAAkF;GAChF,+BAA+B;AACjC,yEAAyE;GACvE,yBAAyB,CAAC;AAE7B,gFAAgF;AAChF,qBAAa,YAAa,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;gBACpB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM;CAKnD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,sFAAsF;AACtF,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACrB;AAED,kFAAkF;AAClF,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC7C,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AA6DD,0CAA0C;AAC1C,MAAM,WAAW,mBAAmB;IACnC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,SAAS,CAAK;IACtB,2EAA2E;IAC3E,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;gBAEvC,OAAO,EAAE,mBAAmB;IASxC,oDAAoD;IACpD,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,8DAA8D;IAC9D,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,OAAO,CAAC,QAAQ;IAMhB;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAK9C;;;;;;;OAOG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAO/C;;;;;;OAMG;IACG,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC;IAOjC;;;;;OAKG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;OAKG;YACW,IAAI;CA+DlB"}