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,146 @@
1
+ /**
2
+ * The `audio render` harness builder (ADR-0009 Part 3): the PURE, CI-testable
3
+ * core that turns a user's cart + a chosen render TARGET into a THROWAWAY cart
4
+ * whose `_update` plays the target, records it via `audio_rec`/`audio_end(1)`,
5
+ * and prints the done-sentinel. This is the audio analogue of the `run`
6
+ * screenshot harness (the `picopilot-debug` recipe): the author writes NO
7
+ * playback code and still gets "render sfx 5 -> a WAV".
8
+ *
9
+ * Kept pure (cart-text in, cart-text + a record window out) so the harness
10
+ * construction + the record-duration heuristic are unit-testable WITHOUT the
11
+ * paid PICO-8 binary; only the actual capture needs a live A/V session (the
12
+ * manual/opt-in tier, ADR-0009).
13
+ */
14
+ import { Cart } from '../cart/index.js';
15
+ /**
16
+ * A render target: a single SFX slot, a single music PATTERN, or the whole song
17
+ * (pattern 0 onward). This mirrors the audio authoring vocabulary (`sfx from-mml`
18
+ * writes a slot; `music from-patterns` writes an ordered pattern list), so an
19
+ * author renders exactly what they composed.
20
+ */
21
+ export type RenderTarget = {
22
+ readonly kind: 'sfx';
23
+ readonly index: number;
24
+ } | {
25
+ readonly kind: 'pattern';
26
+ readonly index: number;
27
+ } | {
28
+ readonly kind: 'song';
29
+ };
30
+ /** The lowest/highest SFX slot a render target may name (finding: 64 slots). */
31
+ export declare const SFX_TARGET_MIN = 0;
32
+ export declare const SFX_TARGET_MAX = 63;
33
+ /** The lowest/highest music PATTERN a render target may name (finding: 64 patterns). */
34
+ export declare const PATTERN_TARGET_MIN = 0;
35
+ export declare const PATTERN_TARGET_MAX = 63;
36
+ /**
37
+ * The default record window (seconds) for a target whose exact play length
38
+ * cannot be derived cheaply (a music pattern / whole song: PICO-8 does not store
39
+ * a per-pattern length, and following loops/flags statically is out of scope for
40
+ * a render harness). A generous window that captures a typical loop; the author
41
+ * overrides it with an explicit duration (ADR-0009: render is a real-time
42
+ * recording, so a fixed window is honest, not a promise of exact length).
43
+ */
44
+ export declare const DEFAULT_RECORD_SECONDS = 8;
45
+ /** A small tail margin (seconds) appended so the target's final note is not clipped. */
46
+ export declare const RECORD_TAIL_SECONDS = 0.5;
47
+ /**
48
+ * A structured reason a render harness could not be built. Surfaced by the
49
+ * command as incur's `error({code,...})` envelope; never a silent guess.
50
+ */
51
+ export type HarnessErrorCode =
52
+ /** The target named an SFX/pattern outside the valid 0..63 range. */
53
+ 'audio-render-target-out-of-range'
54
+ /** The target SFX slot is empty (all-zero) in the cart: nothing to render. */
55
+ | 'audio-render-target-empty'
56
+ /** The cart could not be parsed (not a valid .p8). */
57
+ | 'audio-render-cart-parse-error';
58
+ /** A structured harness-build failure (a target refusal, not a crash). */
59
+ export declare class HarnessError extends Error {
60
+ readonly code: HarnessErrorCode;
61
+ constructor(code: HarnessErrorCode, message: string);
62
+ }
63
+ /**
64
+ * The decoded timing of one authored `__sfx__` row: its `speed` (ticks/row) and
65
+ * the number of NON-EMPTY leading rows (its play length in rows). Both come from
66
+ * the friendly text row (finding Part A): header bytes 2..3 = speed, then 32
67
+ * five-nibble notes; a note is silent when its volume nibble is 0.
68
+ */
69
+ interface SfxTiming {
70
+ readonly speed: number;
71
+ readonly rows: number;
72
+ }
73
+ /**
74
+ * Decodes a `__sfx__` text row into its {@link SfxTiming}. `speed` is header
75
+ * byte [2:4]; `rows` is the count of tracker rows up to and including the last
76
+ * one with a non-zero volume nibble (a trailing silent tail does not extend the
77
+ * audible length). Returns `rows = 0` for an all-silent row.
78
+ */
79
+ export declare function decodeSfxTiming(row: string): SfxTiming;
80
+ /**
81
+ * The record window (seconds) for a render target, given the cart. For an SFX
82
+ * target the window is derived from that slot's speed + audible row count
83
+ * (`rows * speed` ticks / {@link PICO8_TICKS_PER_SECOND}) plus a tail margin, so
84
+ * the recording covers exactly the target's play length without guessing. For a
85
+ * pattern / whole-song target the length is not cheaply derivable, so the
86
+ * {@link DEFAULT_RECORD_SECONDS} window is used (overridable by the caller).
87
+ *
88
+ * An explicit `override` (the command's `--seconds`) always wins.
89
+ */
90
+ export declare function recordSeconds(cart: Cart, target: RenderTarget, override?: number): number;
91
+ /** A one-line human description of the target (for the render result + help). */
92
+ export declare function describeTarget(target: RenderTarget): string;
93
+ /** The result of building a render harness: the throwaway cart + its record window. */
94
+ export interface RenderHarness {
95
+ /** The throwaway `.p8` cart text (same `__sfx__`/`__music__`, injected `__lua__`). */
96
+ readonly cartText: string;
97
+ /** The record window in seconds the adapter should let the capture run for. */
98
+ readonly seconds: number;
99
+ /** The number of `_update` frames the harness records for (= seconds * 30fps). */
100
+ readonly frames: number;
101
+ /** The base filename the harness passes to `extcmd("set_filename")`. */
102
+ readonly wavBasename: string;
103
+ /** A human description of what is being rendered (for the result envelope). */
104
+ readonly description: string;
105
+ }
106
+ /**
107
+ * Builds the throwaway render harness (ADR-0009 Part 3). Takes the user's cart
108
+ * text + a render TARGET (+ an optional record-window override) and produces a
109
+ * new cart with the SAME authored `__sfx__`/`__music__` but a REPLACED `__lua__`
110
+ * whose `_update`:
111
+ * - at frame 1: `extcmd("set_filename", <base>)`, `extcmd("audio_rec")`, plays
112
+ * the target (`sfx(N)` / `music(P)` / `music(0)`);
113
+ * - at frame `frames`: `extcmd("audio_end", 1)` (save to the current folder,
114
+ * which the adapter points at an isolated temp dir);
115
+ * - at frame `frames + 1`: `printh` the done-sentinel so the launcher kills
116
+ * PICO-8 promptly (a cart cannot self-quit, ADR-0006).
117
+ *
118
+ * @throws {HarnessError} an out-of-range/empty target or an unparseable cart.
119
+ */
120
+ export declare function buildRenderHarness(cartText: string, target: RenderTarget, options?: {
121
+ seconds?: number;
122
+ sentinel?: string;
123
+ wavBasename?: string;
124
+ }): RenderHarness;
125
+ /**
126
+ * Builds the `audio record <cart>` harness: unlike `render`, this captures the
127
+ * cart's OWN playback, so it keeps the cart's `__lua__` and only APPENDS a
128
+ * cooperative recorder tab that starts recording immediately and stops after the
129
+ * window. The recorder runs as an extra code tab so it does not disturb the
130
+ * cart's own `_update`/`_draw` (PICO-8 runs all tabs; the appended top-level code
131
+ * arms the recorder and a hooked `_update` advances the timer).
132
+ *
133
+ * Because a general cart may define its own `_update`, the recorder wraps it: it
134
+ * saves the cart's `_update` and installs one that ticks the record timer then
135
+ * calls the original. This is the record analogue of the debug skill's "add
136
+ * capture lines, run, revert" recipe, done on a THROWAWAY copy.
137
+ *
138
+ * @throws {HarnessError} an unparseable cart.
139
+ */
140
+ export declare function buildRecordHarness(cartText: string, options?: {
141
+ seconds?: number;
142
+ sentinel?: string;
143
+ wavBasename?: string;
144
+ }): RenderHarness;
145
+ export {};
146
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAC;AAStC;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACrB;IAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAC9C;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAClD;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC;AAE3B,gFAAgF;AAChF,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;;GAGG;AACH,MAAM,MAAM,gBAAgB;AAC3B,qEAAqE;AACnE,kCAAkC;AACpC,8EAA8E;GAC5E,2BAA2B;AAC7B,sDAAsD;GACpD,+BAA+B,CAAC;AAEnC,0EAA0E;AAC1E,qBAAa,YAAa,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;gBACpB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM;CAKnD;AAKD;;;;;GAKG;AACH,UAAU,SAAS;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAoBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAUtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC5B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,YAAY,EACpB,QAAQ,CAAC,EAAE,MAAM,GACf,MAAM,CAaR;AAcD,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAS3D;AAED,uFAAuF;AACvF,MAAM,WAAW,aAAa;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAM,GACvE,aAAa,CA4Cf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAM,GACvE,aAAa,CA6Cf"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * The `audio render` harness builder (ADR-0009 Part 3): the PURE, CI-testable
3
+ * core that turns a user's cart + a chosen render TARGET into a THROWAWAY cart
4
+ * whose `_update` plays the target, records it via `audio_rec`/`audio_end(1)`,
5
+ * and prints the done-sentinel. This is the audio analogue of the `run`
6
+ * screenshot harness (the `picopilot-debug` recipe): the author writes NO
7
+ * playback code and still gets "render sfx 5 -> a WAV".
8
+ *
9
+ * Kept pure (cart-text in, cart-text + a record window out) so the harness
10
+ * construction + the record-duration heuristic are unit-testable WITHOUT the
11
+ * paid PICO-8 binary; only the actual capture needs a live A/V session (the
12
+ * manual/opt-in tier, ADR-0009).
13
+ */
14
+ import { Cart } from '../cart/index.js';
15
+ import { DONE_SENTINEL, PICO8_FRAMES_PER_SECOND, PICO8_TICKS_PER_SECOND, RECORD_WAV_BASENAME, } from './adapter.js';
16
+ import { SFX_ROW_LENGTH } from '../audio/sfx.js';
17
+ /** The lowest/highest SFX slot a render target may name (finding: 64 slots). */
18
+ export const SFX_TARGET_MIN = 0;
19
+ export const SFX_TARGET_MAX = 63;
20
+ /** The lowest/highest music PATTERN a render target may name (finding: 64 patterns). */
21
+ export const PATTERN_TARGET_MIN = 0;
22
+ export const PATTERN_TARGET_MAX = 63;
23
+ /**
24
+ * The default record window (seconds) for a target whose exact play length
25
+ * cannot be derived cheaply (a music pattern / whole song: PICO-8 does not store
26
+ * a per-pattern length, and following loops/flags statically is out of scope for
27
+ * a render harness). A generous window that captures a typical loop; the author
28
+ * overrides it with an explicit duration (ADR-0009: render is a real-time
29
+ * recording, so a fixed window is honest, not a promise of exact length).
30
+ */
31
+ export const DEFAULT_RECORD_SECONDS = 8;
32
+ /** A small tail margin (seconds) appended so the target's final note is not clipped. */
33
+ export const RECORD_TAIL_SECONDS = 0.5;
34
+ /** A structured harness-build failure (a target refusal, not a crash). */
35
+ export class HarnessError extends Error {
36
+ code;
37
+ constructor(code, message) {
38
+ super(message);
39
+ this.name = 'HarnessError';
40
+ this.code = code;
41
+ }
42
+ }
43
+ /** An all-zero `__sfx__` row: the canonical "empty slot" (mirrors section.ts). */
44
+ const EMPTY_SFX_ROW = '0'.repeat(SFX_ROW_LENGTH);
45
+ /** Reads slot `index`'s row from a `__sfx__` body, or undefined if absent/empty. */
46
+ function sfxRow(sfxBody, index) {
47
+ if (sfxBody === undefined)
48
+ return undefined;
49
+ const rows = sfxBody.split(/\r?\n/).filter((l) => l.length > 0);
50
+ const row = rows[index];
51
+ if (row === undefined ||
52
+ row.length !== SFX_ROW_LENGTH ||
53
+ row === EMPTY_SFX_ROW) {
54
+ return undefined;
55
+ }
56
+ return row;
57
+ }
58
+ /**
59
+ * Decodes a `__sfx__` text row into its {@link SfxTiming}. `speed` is header
60
+ * byte [2:4]; `rows` is the count of tracker rows up to and including the last
61
+ * one with a non-zero volume nibble (a trailing silent tail does not extend the
62
+ * audible length). Returns `rows = 0` for an all-silent row.
63
+ */
64
+ export function decodeSfxTiming(row) {
65
+ const speed = Number.parseInt(row.slice(2, 4), 16) || 1;
66
+ let lastAudible = 0;
67
+ for (let r = 0; r < 32; r++) {
68
+ const noteStart = 8 + r * 5;
69
+ // Note layout: PP (pitch, 2) W (waveform, 1) V (volume, 1) E (effect, 1).
70
+ const vol = Number.parseInt(row.charAt(noteStart + 3), 16);
71
+ if (!Number.isNaN(vol) && vol > 0)
72
+ lastAudible = r + 1;
73
+ }
74
+ return { speed, rows: lastAudible };
75
+ }
76
+ /**
77
+ * The record window (seconds) for a render target, given the cart. For an SFX
78
+ * target the window is derived from that slot's speed + audible row count
79
+ * (`rows * speed` ticks / {@link PICO8_TICKS_PER_SECOND}) plus a tail margin, so
80
+ * the recording covers exactly the target's play length without guessing. For a
81
+ * pattern / whole-song target the length is not cheaply derivable, so the
82
+ * {@link DEFAULT_RECORD_SECONDS} window is used (overridable by the caller).
83
+ *
84
+ * An explicit `override` (the command's `--seconds`) always wins.
85
+ */
86
+ export function recordSeconds(cart, target, override) {
87
+ if (override !== undefined)
88
+ return override;
89
+ if (target.kind === 'sfx') {
90
+ const row = sfxRow(cart.getSection('sfx'), target.index);
91
+ if (row !== undefined) {
92
+ const { speed, rows } = decodeSfxTiming(row);
93
+ const ticks = rows * speed;
94
+ const seconds = ticks / PICO8_TICKS_PER_SECOND + RECORD_TAIL_SECONDS;
95
+ // A degenerate (0-row) slot still records a short window rather than 0s.
96
+ return Math.max(seconds, RECORD_TAIL_SECONDS + 0.5);
97
+ }
98
+ }
99
+ return DEFAULT_RECORD_SECONDS;
100
+ }
101
+ /** The Lua expression that PLAYS the target once, at harness start. */
102
+ function playCall(target) {
103
+ switch (target.kind) {
104
+ case 'sfx':
105
+ return `sfx(${target.index})`;
106
+ case 'pattern':
107
+ return `music(${target.index})`;
108
+ case 'song':
109
+ return 'music(0)';
110
+ }
111
+ }
112
+ /** A one-line human description of the target (for the render result + help). */
113
+ export function describeTarget(target) {
114
+ switch (target.kind) {
115
+ case 'sfx':
116
+ return `sfx ${target.index}`;
117
+ case 'pattern':
118
+ return `music pattern ${target.index}`;
119
+ case 'song':
120
+ return 'the whole song (music 0)';
121
+ }
122
+ }
123
+ /**
124
+ * Validates a render target against the cart's authored content, refusing an
125
+ * out-of-range index or an empty SFX slot (nothing to render) with a structured
126
+ * {@link HarnessError} rather than recording silence.
127
+ */
128
+ function validateTarget(cart, target) {
129
+ if (target.kind === 'sfx') {
130
+ if (target.index < SFX_TARGET_MIN || target.index > SFX_TARGET_MAX) {
131
+ throw new HarnessError('audio-render-target-out-of-range', `SFX target ${target.index} is out of range ${SFX_TARGET_MIN}..${SFX_TARGET_MAX}.`);
132
+ }
133
+ if (sfxRow(cart.getSection('sfx'), target.index) === undefined) {
134
+ throw new HarnessError('audio-render-target-empty', `SFX slot ${target.index} is empty in this cart, there is nothing to render. Author it first with \`sfx from-mml ${target.index} "<mml>"\`.`);
135
+ }
136
+ }
137
+ else if (target.kind === 'pattern') {
138
+ if (target.index < PATTERN_TARGET_MIN ||
139
+ target.index > PATTERN_TARGET_MAX) {
140
+ throw new HarnessError('audio-render-target-out-of-range', `music pattern target ${target.index} is out of range ${PATTERN_TARGET_MIN}..${PATTERN_TARGET_MAX}.`);
141
+ }
142
+ }
143
+ }
144
+ /**
145
+ * Builds the throwaway render harness (ADR-0009 Part 3). Takes the user's cart
146
+ * text + a render TARGET (+ an optional record-window override) and produces a
147
+ * new cart with the SAME authored `__sfx__`/`__music__` but a REPLACED `__lua__`
148
+ * whose `_update`:
149
+ * - at frame 1: `extcmd("set_filename", <base>)`, `extcmd("audio_rec")`, plays
150
+ * the target (`sfx(N)` / `music(P)` / `music(0)`);
151
+ * - at frame `frames`: `extcmd("audio_end", 1)` (save to the current folder,
152
+ * which the adapter points at an isolated temp dir);
153
+ * - at frame `frames + 1`: `printh` the done-sentinel so the launcher kills
154
+ * PICO-8 promptly (a cart cannot self-quit, ADR-0006).
155
+ *
156
+ * @throws {HarnessError} an out-of-range/empty target or an unparseable cart.
157
+ */
158
+ export function buildRenderHarness(cartText, target, options = {}) {
159
+ let cart;
160
+ try {
161
+ cart = Cart.parse(cartText);
162
+ }
163
+ catch (e) {
164
+ throw new HarnessError('audio-render-cart-parse-error', `cart does not parse: ${e instanceof Error ? e.message : String(e)}`);
165
+ }
166
+ validateTarget(cart, target);
167
+ const seconds = recordSeconds(cart, target, options.seconds);
168
+ const frames = Math.max(1, Math.round(seconds * PICO8_FRAMES_PER_SECOND));
169
+ const sentinel = options.sentinel ?? DONE_SENTINEL;
170
+ const wavBasename = options.wavBasename ?? RECORD_WAV_BASENAME;
171
+ const play = playCall(target);
172
+ // Replace __lua__ with the record harness; keep every OTHER section (crucially
173
+ // __sfx__/__music__) byte-identical so the target plays exactly as authored.
174
+ const lua = [
175
+ '-- picopilot audio render harness (throwaway; ADR-0009).',
176
+ '-- Real-time recording of playback, NOT an offline export.',
177
+ 't=0',
178
+ 'function _update()',
179
+ ' t+=1',
180
+ ` if t==1 then extcmd("set_filename","${wavBasename}") extcmd("audio_rec") ${play} end`,
181
+ ` if t==${frames} then extcmd("audio_end",1) end`,
182
+ ` if t==${frames + 1} then printh("${sentinel}") end`,
183
+ 'end',
184
+ 'function _draw() cls() print("rendering "..t,0,0,7) end',
185
+ '',
186
+ ].join('\n');
187
+ cart.setSection('lua', lua);
188
+ return {
189
+ cartText: cart.serialize(),
190
+ seconds,
191
+ frames,
192
+ wavBasename,
193
+ description: describeTarget(target),
194
+ };
195
+ }
196
+ /**
197
+ * Builds the `audio record <cart>` harness: unlike `render`, this captures the
198
+ * cart's OWN playback, so it keeps the cart's `__lua__` and only APPENDS a
199
+ * cooperative recorder tab that starts recording immediately and stops after the
200
+ * window. The recorder runs as an extra code tab so it does not disturb the
201
+ * cart's own `_update`/`_draw` (PICO-8 runs all tabs; the appended top-level code
202
+ * arms the recorder and a hooked `_update` advances the timer).
203
+ *
204
+ * Because a general cart may define its own `_update`, the recorder wraps it: it
205
+ * saves the cart's `_update` and installs one that ticks the record timer then
206
+ * calls the original. This is the record analogue of the debug skill's "add
207
+ * capture lines, run, revert" recipe, done on a THROWAWAY copy.
208
+ *
209
+ * @throws {HarnessError} an unparseable cart.
210
+ */
211
+ export function buildRecordHarness(cartText, options = {}) {
212
+ let cart;
213
+ try {
214
+ cart = Cart.parse(cartText);
215
+ }
216
+ catch (e) {
217
+ throw new HarnessError('audio-render-cart-parse-error', `cart does not parse: ${e instanceof Error ? e.message : String(e)}`);
218
+ }
219
+ const seconds = options.seconds ?? DEFAULT_RECORD_SECONDS;
220
+ const frames = Math.max(1, Math.round(seconds * PICO8_FRAMES_PER_SECOND));
221
+ const sentinel = options.sentinel ?? DONE_SENTINEL;
222
+ const wavBasename = options.wavBasename ?? RECORD_WAV_BASENAME;
223
+ const existing = cart.getSection('lua') ?? '';
224
+ // Append the recorder AFTER the cart's own code. It wraps _update so it works
225
+ // whether or not the cart defines one, and never edits the cart's own logic.
226
+ const recorder = [
227
+ '',
228
+ '-->8',
229
+ '-- picopilot audio record harness (throwaway; ADR-0009).',
230
+ "-- Records the cart's own audio; real-time capture, NOT an offline export.",
231
+ '__pp_rec_update=_update',
232
+ '__pp_rec_t=0',
233
+ 'function _update()',
234
+ ' __pp_rec_t+=1',
235
+ ` if __pp_rec_t==1 then extcmd("set_filename","${wavBasename}") extcmd("audio_rec") end`,
236
+ ` if __pp_rec_t==${frames} then extcmd("audio_end",1) end`,
237
+ ` if __pp_rec_t==${frames + 1} then printh("${sentinel}") end`,
238
+ ' if __pp_rec_update then __pp_rec_update() end',
239
+ 'end',
240
+ '',
241
+ ].join('\n');
242
+ cart.setSection('lua', existing + recorder);
243
+ return {
244
+ cartText: cart.serialize(),
245
+ seconds,
246
+ frames,
247
+ wavBasename,
248
+ description: 'the running cart',
249
+ };
250
+ }
251
+ //# sourceMappingURL=harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../../src/engine/pico8/harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The PICO-8 engine seam: launch + capture over the user's PICO-8 binary
3
+ * (ADR-0006, native `pico8 -x` path). Barrel re-export so the command layer and
4
+ * later PICO-8-gated features (`audio render`, `export`) import from one place.
5
+ *
6
+ * - `adapter`: the `Pico8Adapter` interface, `Pico8Result`, the structured
7
+ * `pico8-not-found` value, and the run-report shapes.
8
+ * - `sentinel`: the pure sentinel-watch core (CI-testable without the binary).
9
+ * - `shell`: the v1 adapter shelling out to native `pico8 -x`, with an
10
+ * injectable spawn seam for isolation-testing.
11
+ */
12
+ export * from './adapter.js';
13
+ export * from './drive.js';
14
+ export * from './harness.js';
15
+ export * from './sentinel.js';
16
+ export * from './session.js';
17
+ export * from './session-daemon.js';
18
+ export * from './session-daemon-main.js';
19
+ export * from './shell.js';
20
+ export * from './supervisor.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The PICO-8 engine seam: launch + capture over the user's PICO-8 binary
3
+ * (ADR-0006, native `pico8 -x` path). Barrel re-export so the command layer and
4
+ * later PICO-8-gated features (`audio render`, `export`) import from one place.
5
+ *
6
+ * - `adapter`: the `Pico8Adapter` interface, `Pico8Result`, the structured
7
+ * `pico8-not-found` value, and the run-report shapes.
8
+ * - `sentinel`: the pure sentinel-watch core (CI-testable without the binary).
9
+ * - `shell`: the v1 adapter shelling out to native `pico8 -x`, with an
10
+ * injectable spawn seam for isolation-testing.
11
+ */
12
+ export * from './adapter.js';
13
+ export * from './drive.js';
14
+ export * from './harness.js';
15
+ export * from './sentinel.js';
16
+ export * from './session.js';
17
+ export * from './session-daemon.js';
18
+ export * from './session-daemon-main.js';
19
+ export * from './shell.js';
20
+ export * from './supervisor.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/pico8/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The pure sentinel-watch core (ADR-0006): scan a stdout stream line by line and
3
+ * decide WHEN to kill PICO-8. A cart cannot self-quit the app, so it signals
4
+ * "done" by printing a sentinel line via `printh`; the launcher kills the moment
5
+ * it matches. This is strictly better than a blind timeout (no guessing the
6
+ * duration), which stays only as a backstop in the shell layer.
7
+ *
8
+ * Kept PURE and stream-shaped on purpose: it is the load-bearing logic and the
9
+ * one piece testable in CI WITHOUT the paid PICO-8 binary. The shell layer feeds
10
+ * it real child stdout; a test feeds it a fake array of lines.
11
+ */
12
+ /**
13
+ * Splits a raw stdout chunk into complete lines plus a trailing remainder,
14
+ * so sentinel matching works on whole lines even when the OS delivers stdout in
15
+ * arbitrary chunks. The remainder (an unterminated final line) is carried to the
16
+ * next chunk by the caller.
17
+ *
18
+ * Handles both `\n` and `\r\n`; the `\r` is stripped so a sentinel matches
19
+ * regardless of platform line endings.
20
+ */
21
+ export declare function splitLines(chunk: string): {
22
+ lines: string[];
23
+ rest: string;
24
+ };
25
+ /**
26
+ * Scans `lines` for the `sentinel` and reports whether it appeared and at which
27
+ * index. A match means the caller should kill PICO-8 now (exit reason
28
+ * `sentinel`); no match means keep streaming until the backstop or a natural
29
+ * exit. The comparison is an EXACT line match (the cart prints the sentinel on
30
+ * its own line), so incidental substrings in other output never trip it.
31
+ */
32
+ export declare function findSentinel(lines: readonly string[], sentinel?: string): {
33
+ matched: boolean;
34
+ index: number;
35
+ };
36
+ /**
37
+ * A stateful line accumulator for a streaming run: feed it raw stdout chunks;
38
+ * it buffers partial lines, exposes the complete lines seen so far, and reports
39
+ * the moment the sentinel is matched. This is the exact glue the shell adapter
40
+ * drives (feed chunks, stop on `sentinelSeen`), and a test drives it with
41
+ * synthetic chunks to assert prompt-kill vs. no-match without a real process.
42
+ */
43
+ export declare class SentinelWatcher {
44
+ private readonly sentinel;
45
+ private buffer;
46
+ private readonly linesSeen;
47
+ private matched;
48
+ constructor(sentinel?: string);
49
+ /**
50
+ * Feeds one raw stdout chunk. Returns `true` the first time the sentinel is
51
+ * matched (the caller should kill PICO-8 then); once matched it stays matched.
52
+ */
53
+ push(chunk: string): boolean;
54
+ /** Whether the sentinel has been seen on a complete line so far. */
55
+ get sentinelSeen(): boolean;
56
+ /**
57
+ * The full captured stdout as text (complete lines plus any unterminated
58
+ * trailing buffer), for the run report's `printh`. The sentinel line itself
59
+ * is KEPT (it is real cart output); the command decides whether to show it.
60
+ */
61
+ get text(): string;
62
+ }
63
+ //# sourceMappingURL=sentinel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/sentinel.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAOzE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,GAAE,MAAsB,GAC9B;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAGnC;AAED;;;;;;GAMG;AACH,qBAAa,eAAe;IAKf,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJrC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,OAAO,CAAS;gBAEK,QAAQ,GAAE,MAAsB;IAE7D;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAU5B,oEAAoE;IACpE,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAMjB;CACD"}
@@ -0,0 +1,86 @@
1
+ import { DONE_SENTINEL } from './adapter.js';
2
+ /**
3
+ * The pure sentinel-watch core (ADR-0006): scan a stdout stream line by line and
4
+ * decide WHEN to kill PICO-8. A cart cannot self-quit the app, so it signals
5
+ * "done" by printing a sentinel line via `printh`; the launcher kills the moment
6
+ * it matches. This is strictly better than a blind timeout (no guessing the
7
+ * duration), which stays only as a backstop in the shell layer.
8
+ *
9
+ * Kept PURE and stream-shaped on purpose: it is the load-bearing logic and the
10
+ * one piece testable in CI WITHOUT the paid PICO-8 binary. The shell layer feeds
11
+ * it real child stdout; a test feeds it a fake array of lines.
12
+ */
13
+ /**
14
+ * Splits a raw stdout chunk into complete lines plus a trailing remainder,
15
+ * so sentinel matching works on whole lines even when the OS delivers stdout in
16
+ * arbitrary chunks. The remainder (an unterminated final line) is carried to the
17
+ * next chunk by the caller.
18
+ *
19
+ * Handles both `\n` and `\r\n`; the `\r` is stripped so a sentinel matches
20
+ * regardless of platform line endings.
21
+ */
22
+ export function splitLines(chunk) {
23
+ const parts = chunk.split('\n');
24
+ const rest = parts.pop() ?? '';
25
+ return {
26
+ lines: parts.map((l) => (l.endsWith('\r') ? l.slice(0, -1) : l)),
27
+ rest,
28
+ };
29
+ }
30
+ /**
31
+ * Scans `lines` for the `sentinel` and reports whether it appeared and at which
32
+ * index. A match means the caller should kill PICO-8 now (exit reason
33
+ * `sentinel`); no match means keep streaming until the backstop or a natural
34
+ * exit. The comparison is an EXACT line match (the cart prints the sentinel on
35
+ * its own line), so incidental substrings in other output never trip it.
36
+ */
37
+ export function findSentinel(lines, sentinel = DONE_SENTINEL) {
38
+ const index = lines.findIndex((l) => l === sentinel);
39
+ return { matched: index !== -1, index };
40
+ }
41
+ /**
42
+ * A stateful line accumulator for a streaming run: feed it raw stdout chunks;
43
+ * it buffers partial lines, exposes the complete lines seen so far, and reports
44
+ * the moment the sentinel is matched. This is the exact glue the shell adapter
45
+ * drives (feed chunks, stop on `sentinelSeen`), and a test drives it with
46
+ * synthetic chunks to assert prompt-kill vs. no-match without a real process.
47
+ */
48
+ export class SentinelWatcher {
49
+ sentinel;
50
+ buffer = '';
51
+ linesSeen = [];
52
+ matched = false;
53
+ constructor(sentinel = DONE_SENTINEL) {
54
+ this.sentinel = sentinel;
55
+ }
56
+ /**
57
+ * Feeds one raw stdout chunk. Returns `true` the first time the sentinel is
58
+ * matched (the caller should kill PICO-8 then); once matched it stays matched.
59
+ */
60
+ push(chunk) {
61
+ const { lines, rest } = splitLines(this.buffer + chunk);
62
+ this.buffer = rest;
63
+ for (const line of lines) {
64
+ this.linesSeen.push(line);
65
+ if (line === this.sentinel)
66
+ this.matched = true;
67
+ }
68
+ return this.matched;
69
+ }
70
+ /** Whether the sentinel has been seen on a complete line so far. */
71
+ get sentinelSeen() {
72
+ return this.matched;
73
+ }
74
+ /**
75
+ * The full captured stdout as text (complete lines plus any unterminated
76
+ * trailing buffer), for the run report's `printh`. The sentinel line itself
77
+ * is KEPT (it is real cart output); the command decides whether to show it.
78
+ */
79
+ get text() {
80
+ const all = this.buffer.length > 0
81
+ ? [...this.linesSeen, this.buffer]
82
+ : this.linesSeen;
83
+ return all.join('\n');
84
+ }
85
+ }
86
+ //# sourceMappingURL=sentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../../../src/engine/pico8/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAE3C;;;;;;;;;;GAUG;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IAC/B,OAAO;QACN,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,IAAI;KACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC3B,KAAwB,EACxB,WAAmB,aAAa;IAEhC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IACrD,OAAO,EAAC,OAAO,EAAE,KAAK,KAAK,CAAC,CAAC,EAAE,KAAK,EAAC,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,eAAe;IAKE;IAJrB,MAAM,GAAG,EAAE,CAAC;IACH,SAAS,GAAa,EAAE,CAAC;IAClC,OAAO,GAAG,KAAK,CAAC;IAExB,YAA6B,WAAmB,aAAa;QAAhC,aAAQ,GAAR,QAAQ,CAAwB;IAAG,CAAC;IAEjE;;;OAGG;IACH,IAAI,CAAC,KAAa;QACjB,MAAM,EAAC,KAAK,EAAE,IAAI,EAAC,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ;gBAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,oEAAoE;IACpE,IAAI,YAAY;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACH,IAAI,IAAI;QACP,MAAM,GAAG,GACR,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;YACrB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC;YAClC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;QACnB,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;CACD"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The detached daemon process ENTRY (ADR-0011, prd US #6). `playtest start`
3
+ * spawns this file (via `process.execPath [+ execArgv] <this> <configJson>`, so
4
+ * it re-uses the exact loader the parent ran under, dev `tsx` or built `dist`) as
5
+ * a DETACHED background process that owns the live driven PICO-8 for the session's
6
+ * lifetime. It just decodes its JSON config and hands off to {@link daemonMain};
7
+ * all the logic lives in `session-daemon-main.ts` (so it stays unit-testable).
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=session-daemon-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-daemon-entry.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/session-daemon-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The detached daemon process ENTRY (ADR-0011, prd US #6). `playtest start`
3
+ * spawns this file (via `process.execPath [+ execArgv] <this> <configJson>`, so
4
+ * it re-uses the exact loader the parent ran under, dev `tsx` or built `dist`) as
5
+ * a DETACHED background process that owns the live driven PICO-8 for the session's
6
+ * lifetime. It just decodes its JSON config and hands off to {@link daemonMain};
7
+ * all the logic lives in `session-daemon-main.ts` (so it stays unit-testable).
8
+ */
9
+ import { daemonMain } from './session-daemon-main.js';
10
+ daemonMain(process.argv.slice(2));
11
+ //# sourceMappingURL=session-daemon-entry.js.map