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,67 @@
1
+ import { SFX_ROW_LENGTH } from './sfx.js';
2
+ /**
3
+ * The number of SFX slots a cart holds (finding: 64 SFX, indices 0..63).
4
+ */
5
+ export const SFX_SLOT_COUNT = 64;
6
+ /** An all-zero `__sfx__` row (168 chars): the canonical "empty slot". */
7
+ const EMPTY_ROW = '0'.repeat(SFX_ROW_LENGTH);
8
+ /**
9
+ * Merges one transpiled `__sfx__` row into slot `slot` (0..63) of a cart's
10
+ * `__sfx__` section, leaving every other slot BYTE-IDENTICAL, and commits it
11
+ * back through the cart model (so all other sections stay untouched).
12
+ *
13
+ * PICO-8 trims trailing all-zero SFX rows on save, so a cart's `__sfx__` body
14
+ * may have FEWER than 64 lines (or be absent). This pads with empty rows up to
15
+ * the target slot as needed, replaces the target, and drops trailing empty rows
16
+ * again so the result matches what PICO-8 would write (a stable round-trip).
17
+ *
18
+ * The `__sfx__` body's newline style + trailing-newline are preserved from the
19
+ * existing section (defaulting to `\n` with a terminating newline, matching what
20
+ * PICO-8 writes).
21
+ */
22
+ export function mergeSfxRow(cart, slot, row) {
23
+ if (!Number.isInteger(slot) || slot < 0 || slot >= SFX_SLOT_COUNT) {
24
+ throw new RangeError(`SFX slot ${slot} out of range 0..${SFX_SLOT_COUNT - 1}`);
25
+ }
26
+ if (row.length !== SFX_ROW_LENGTH) {
27
+ throw new RangeError(`SFX row must be ${SFX_ROW_LENGTH} hex chars, got ${row.length}`);
28
+ }
29
+ const body = cart.getSection('sfx');
30
+ const newline = body !== undefined && /\r\n/.test(body) ? '\r\n' : '\n';
31
+ // Parse existing rows (ignoring blank/trailing lines the split produces).
32
+ const existing = body === undefined
33
+ ? []
34
+ : body.split(/\r?\n/).filter((line) => line.length > 0);
35
+ // Grow to include the target slot, padding intermediate slots with empties.
36
+ const rows = existing.slice();
37
+ while (rows.length <= slot)
38
+ rows.push(EMPTY_ROW);
39
+ rows[slot] = row;
40
+ // Trim trailing all-zero rows (what PICO-8 does on save) so the body is the
41
+ // canonical minimal form; a cart with only slot 0 authored stays one line.
42
+ while (rows.length > 0 && rows[rows.length - 1] === EMPTY_ROW)
43
+ rows.pop();
44
+ const newBody = rows.length === 0 ? '' : rows.join(newline) + newline;
45
+ cart.setSection('sfx', newBody);
46
+ }
47
+ /**
48
+ * Replaces a cart's `__music__` section with a transpiled body, leaving every
49
+ * OTHER section (including `__sfx__`) BYTE-IDENTICAL, and commits it back through
50
+ * the cart model.
51
+ *
52
+ * Unlike `__sfx__` (where {@link mergeSfxRow} writes ONE slot), a `music
53
+ * from-patterns` call authors the WHOLE song at once (an ordered pattern list is
54
+ * the whole `__music__`), so this REPLACES the section wholesale rather than
55
+ * merging a single row. The body is the codec's output (one `FF CCCCCCCC` row per
56
+ * pattern); this only normalises its newline style to match the cart's existing
57
+ * `__music__`/dominant newline so the round-trip stays stable.
58
+ */
59
+ export function setMusicSection(cart, body) {
60
+ const existing = cart.getSection('music');
61
+ // Preserve the file's newline style: CRLF if the existing music section (or,
62
+ // absent, the codec body would already be \n) uses it. The codec emits \n.
63
+ const crlf = existing !== undefined && /\r\n/.test(existing);
64
+ const normalised = crlf ? body.replace(/\n/g, '\r\n') : body;
65
+ cart.setSection('music', normalised);
66
+ }
67
+ //# sourceMappingURL=section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.js","sourceRoot":"","sources":["../../../src/engine/audio/section.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAC;AAExC;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC,yEAAyE;AACzE,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,IAAU,EAAE,IAAY,EAAE,GAAW;IAChE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,cAAc,EAAE,CAAC;QACnE,MAAM,IAAI,UAAU,CACnB,YAAY,IAAI,oBAAoB,cAAc,GAAG,CAAC,EAAE,CACxD,CAAC;IACH,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;QACnC,MAAM,IAAI,UAAU,CACnB,mBAAmB,cAAc,mBAAmB,GAAG,CAAC,MAAM,EAAE,CAChE,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAExE,0EAA0E;IAC1E,MAAM,QAAQ,GACb,IAAI,KAAK,SAAS;QACjB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE1D,4EAA4E;IAC5E,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC9B,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjD,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAEjB,4EAA4E;IAC5E,2EAA2E;IAC3E,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,SAAS;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC;IAE1E,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACtE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAAU,EAAE,IAAY;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1C,6EAA6E;IAC7E,2EAA2E;IAC3E,MAAM,IAAI,GAAG,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * `engine/audio` — the shrinko-free picopilot-MML to `__sfx__` codec.
3
+ *
4
+ * This module OWNS the picopilot-MML dialect and its transpilation into ONE
5
+ * `__sfx__` text row (the friendly text surface PICO-8 serializes, NOT the
6
+ * scattered RAM bit-packing). It is the greenfield/algorithmic seam of the audio
7
+ * path; the `sfx from-mml` command is thin wiring over {@link mmlToSfxRow}.
8
+ *
9
+ * The mapping is the CLOSED spec from the ADR-0005 spike, verified byte-for-byte
10
+ * against PICO-8 v0.2.7:
11
+ * `work/notes/findings/pico8-audio-sfx-music-layout-and-mml-subset.md` (Part A =
12
+ * the `__sfx__` text layout; Part B = the picopilot-MML grammar + mapping).
13
+ *
14
+ * The KEY reframe (finding B.4 / B.7 #1): PICO-8 has NO per-note duration or
15
+ * tempo. Every note occupies exactly one tracker ROW; all rows share ONE
16
+ * SFX-level `speed`. A picopilot-MML length is therefore a ROW COUNT, not a
17
+ * musical fraction, and there is no tempo/BPM token. Holding a note for M rows
18
+ * re-emits the same note in the following (M-1) rows (PICO-8 has no cross-row
19
+ * sustain bit).
20
+ *
21
+ * Out-of-range pitch and >32 rows are STRUCTURED ERRORS ({@link AudioMmlError}),
22
+ * never silent clamps: a silent clamp produces wrong-sounding music the author
23
+ * cannot see (finding B.2 / B.4, matching picopilot's smart-refuse philosophy).
24
+ *
25
+ * SFX-level FILTER directives (finding A.7): `!noiz !buzz !detune1/!detune2
26
+ * !reverb[1|2] !dampen[1|2]` set PICO-8's 5 per-SFX filters (the first header
27
+ * byte), reaching the class of DESIGNED sounds (explosion = DAMPEN body + REVERB
28
+ * tail, engine hum, pad) that no per-note pitch/waveform/volume MML can
29
+ * synthesize. `!`-prefixed keywords never collide with the note grammar (unlike
30
+ * the rejected bare-letter effect mnemonics, ADR-0008). A bad DAMPEN/REVERB level
31
+ * is a structured refusal, never a clamp.
32
+ */
33
+ /** A `__sfx__` row is exactly this many hex chars: 8 header + 32 notes x 5. */
34
+ export declare const SFX_ROW_LENGTH = 168;
35
+ /** The number of tracker rows (notes) in one SFX. A hard PICO-8 limit. */
36
+ export declare const SFX_MAX_ROWS = 32;
37
+ /** The lowest representable pitch: C0 = 0 (finding A.2 / B.2). */
38
+ export declare const PITCH_MIN = 0;
39
+ /** The highest representable pitch: D#5 = 63 (finding A.2 / B.2). */
40
+ export declare const PITCH_MAX = 63;
41
+ /** The default octave when none is set (`o2`, pitch 24 = C2; finding B.2). */
42
+ export declare const DEFAULT_OCTAVE = 2;
43
+ /** The default SFX speed (ticks/row) when `s<N>` is omitted (finding B.5). */
44
+ export declare const DEFAULT_SPEED = 16;
45
+ /** The default note length in tracker rows (one row; finding B.4). */
46
+ export declare const DEFAULT_LENGTH = 1;
47
+ /**
48
+ * The per-SFX FILTER switches (finding A.7). They live in the FIRST header byte
49
+ * of the `__sfx__` row (the `[0:2]` hex pair, the byte A.1 called "editor mode"):
50
+ * an 8-bit bitfield whose LOW bit is the cosmetic pitch/tracker mode flag and
51
+ * whose upper 7 bits are the filters. picopilot emits mode 0 (pitch mode), so the
52
+ * byte is exactly the OR of the selected filter values below.
53
+ *
54
+ * Decoded byte-for-byte on PICO-8 v0.2.7 by poke-and-readback + single-bit audio
55
+ * diff (finding A.7). NOIZ applies only to instrument 6 (`@6`, manual);
56
+ * DAMPEN/REVERB have 2 levels each (`!dampen`/`!dampen2`, `!reverb`/`!reverb2`).
57
+ */
58
+ export declare const FILTER_NOIZ = 2;
59
+ export declare const FILTER_BUZZ = 4;
60
+ export declare const FILTER_DETUNE_1 = 8;
61
+ export declare const FILTER_DETUNE_2 = 16;
62
+ /** REVERB (echo) switch (`0x20`); see the level note below (finding A.7). */
63
+ export declare const FILTER_REVERB = 32;
64
+ /**
65
+ * DAMPEN (low-pass) level 1 (`0x80`) and level 2 (`0xc0`); 0 = off (finding
66
+ * A.7). Verified progressive: `0xc0` low-passes strictly more than `0x80`.
67
+ */
68
+ export declare const FILTER_DAMPEN_LEVEL: readonly [0, 128, 192];
69
+ /** The highest level DAMPEN (`!dampen<N>`) accepts (finding A.7). */
70
+ export declare const FILTER_MAX_LEVEL = 2;
71
+ /** Machine-readable reason a picopilot-MML string could not be transpiled. */
72
+ export type AudioMmlErrorCode =
73
+ /** A pitch fell outside C0..D#5 (0..63) (finding B.2). */
74
+ 'audio-mml-pitch-out-of-range'
75
+ /** The accumulated tracker rows exceeded the 32-row SFX cap (finding B.4). */
76
+ | 'audio-mml-sfx-overflow'
77
+ /** A `!dampen<N>`/`!reverb<N>` level fell outside 1..2 (finding A.7). */
78
+ | 'audio-mml-filter-level-out-of-range'
79
+ /** The MML text is syntactically malformed (a token we cannot parse). */
80
+ | 'audio-mml-parse-error';
81
+ /**
82
+ * A structured picopilot-MML transpile failure. Carries a machine-readable
83
+ * `code` (surfaced by the command as incur's `error({code,...})` envelope) plus
84
+ * a human-readable, actionable `message`. Never thrown for a case the spec
85
+ * defines a clamp/round for; only the genuine refusals + parse errors.
86
+ */
87
+ export declare class AudioMmlError extends Error {
88
+ readonly code: AudioMmlErrorCode;
89
+ constructor(code: AudioMmlErrorCode, message: string);
90
+ }
91
+ /** A rounding note surfaced to the caller (a dotted length that was not integral). */
92
+ export interface RowRoundingWarning {
93
+ /** The picopilot-MML token whose row count was rounded, e.g. `c4.`. */
94
+ token: string;
95
+ /** The exact (fractional) row count the length + dots computed. */
96
+ exactRows: number;
97
+ /** The integer row count actually emitted (nearest, min 1). */
98
+ roundedRows: number;
99
+ }
100
+ /** The successful result of transpiling picopilot-MML to one `__sfx__` row. */
101
+ export interface SfxTranspileResult {
102
+ /** The `__sfx__` text row: exactly {@link SFX_ROW_LENGTH} hex chars. */
103
+ row: string;
104
+ /** The number of tracker rows the MML produced (0..32). */
105
+ rows: number;
106
+ /** The SFX speed (ticks/row) written into the header (1..255). */
107
+ speed: number;
108
+ /** The header loop-start value (row index, or a LEN; finding A.1 / B.5). */
109
+ loopStart: number;
110
+ /** The header loop-end value (0 when off or in the LEN special case). */
111
+ loopEnd: number;
112
+ /**
113
+ * The per-SFX FILTER byte (the first header byte, finding A.7): the OR of the
114
+ * selected filter values, with the low mode bit 0. 0 = no filters.
115
+ */
116
+ filters: number;
117
+ /** Any dotted-length rows that had to be rounded to a whole row. */
118
+ roundingWarnings: RowRoundingWarning[];
119
+ }
120
+ /**
121
+ * Transpiles a picopilot-MML string into ONE `__sfx__` text row (finding Part
122
+ * A/B). Emits the friendly text nibbles directly.
123
+ *
124
+ * Modal state model (finding B.1): `@<hex>` waveform, `v<0-7>` volume,
125
+ * `e<0-7>`/mnemonic effect, `o<0-5>` octave, `l<N>` default length, `>`/`<`
126
+ * octave shift, all sticky. A bare note emits one row using the current state.
127
+ *
128
+ * SFX-level directives (finding B.5): `s<N>` speed (default 16), `{`/`}` loop
129
+ * markers (single `{` = the LEN special case).
130
+ *
131
+ * Durations are TRACKER ROWS (finding B.4): a length `N` = N rows, emitted as
132
+ * the note then `(N-1)` tie rows re-striking it; `.` multiplies by 1.5 (rounded
133
+ * to the nearest whole row, warned); `r` = silent rows; `^` extends the previous
134
+ * note.
135
+ *
136
+ * @throws {AudioMmlError} `audio-mml-pitch-out-of-range` (a note below C0 / above
137
+ * D#5), `audio-mml-sfx-overflow` (>32 rows), or `audio-mml-parse-error` (a
138
+ * token that cannot be parsed, incl. a tempo-style input).
139
+ */
140
+ export declare function mmlToSfxRow(mml: string): SfxTranspileResult;
141
+ //# sourceMappingURL=sfx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sfx.d.ts","sourceRoot":"","sources":["../../../src/engine/audio/sfx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,0EAA0E;AAC1E,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B,kEAAkE;AAClE,eAAO,MAAM,SAAS,IAAI,CAAC;AAE3B,qEAAqE;AACrE,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B,8EAA8E;AAC9E,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,8EAA8E;AAC9E,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,sEAAsE;AACtE,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,IAAO,CAAC;AAChC,eAAO,MAAM,WAAW,IAAO,CAAC;AAChC,eAAO,MAAM,eAAe,IAAO,CAAC;AACpC,eAAO,MAAM,eAAe,KAAO,CAAC;AACpC,6EAA6E;AAC7E,eAAO,MAAM,aAAa,KAAO,CAAC;AAClC;;;GAGG;AACH,eAAO,MAAM,mBAAmB,wBAA8B,CAAC;AAE/D,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAyBlC,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB;AAC5B,0DAA0D;AACxD,8BAA8B;AAChC,8EAA8E;GAC5E,wBAAwB;AAC1B,yEAAyE;GACvE,qCAAqC;AACvC,yEAAyE;GACvE,uBAAuB,CAAC;AAE3B;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;gBAErB,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM;CAKpD;AAcD,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IAClC,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IAClC,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,gBAAgB,EAAE,kBAAkB,EAAE,CAAC;CACvC;AAWD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAkP3D"}