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,45 @@
1
+ ---
2
+ name: game-jam
3
+ description: Build a complete, original, playable PICO-8 game against a deadline and a theme. Use for a game jam, a timed game-build, or a jam benchmark run. Carries the clock discipline; applies the universal design principles via game-design-reference.
4
+ ---
5
+
6
+ # game jam
7
+
8
+ A jam is building a GOOD game (the universal part) UNDER A CLOCK and to a THEME (the jam part). This skill carries only the jam-specific part; the design principles live in `game-design-reference`.
9
+
10
+ **FIRST STEP, before anything else: use the `read` tool to LOAD AND FOLLOW `game-design-reference` (`../game-design-reference/SKILL.md`, resolved against THIS skill's directory).** It is the shared body of universal design principles (fairness / no dead state, human reaction budget, visibility, readability, difficulty curve, and the originality method) that you apply throughout the jam. This skill does not repeat them; it tells you the calls they force when you are racing a clock, so you need the reference open to make those calls. Do not skip this read: the fairness and reaction-budget self-checks live there, and they are exactly what a jam entry gets wrong. Then use the picopilot loop skills as you go: `picopilot-overview` (the `#include` layout + verify loop), `picopilot-code` (the token budget), `picopilot-art` / `picopilot-audio` (assets), and `picopilot-debug` (run + playtest, your eyes on the running game).
11
+
12
+ ## Interpret the theme yourself
13
+
14
+ The theme is a seed, not a brief. YOU interpret it into a game. Run `game-design-reference`'s originality method and self-check on the bare theme and commit to your OWN idea, do not wait to be told what to build, and do not settle for the obvious version. This is where the jam is won or lost: a fresh, well-chosen idea sets your ceiling.
15
+
16
+ ## The clock: slice, deepen, triage
17
+
18
+ Three phases you map onto whatever time budget you are given, not a rigid schedule:
19
+
20
+ - **SLICE (first third).** Get to a PLAYABLE slice fast. A playable slice = it boots, responds to input (the button actually changes what happens), and has a goal with a win/lose, and `verify` is green. Scope tiny: one screen, one mechanic, one goal. A rough playable game scores; a broken ambitious one scores zero.
21
+ - **DEEPEN (middle).** Once the slice works, spend the BULK of your remaining time making the game DEEPER (go somewhere), not just more finished. Reach UP the progression ladder (`game-design-reference`: difficulty ramp -> escalating waves -> levels/stages -> worlds) as far as your budget can FINISH. Polish (juice, audio, extra feedback) is worth some time but is NOT the main use of a long budget: a short game buffed to a shine still under-uses the clock. Deepening and polishing both stay gated behind a green `verify`, playable at every checkpoint.
22
+ - **TRIAGE (last stretch).** Stop adding features. Confirm it boots and is playable, finalise the writeup. But "stop adding features" is NOT "stop improving": if you have found a real design DEFECT (below), fixing it beats holding, and beats gold-plating. Do not idle a proven build if it still has a fairness or playability flaw.
23
+
24
+ ## Scale your ambition to the clock
25
+
26
+ The RIGHT size of game depends on the time you are given, and you know your budget from the brief. Match your ambition to it:
27
+
28
+ - **Short clock** (minutes): one screen, one mechanic; deepen via a difficulty ramp or a few escalating waves. Levels would over-reach and ship broken. Getting a tight, finished, fair slice IS the win.
29
+ - **Long clock** (an hour-plus, a day, more): a playable slice in the first third is just the beginning. Then reach real progression, hand-built levels, unlocking mechanics, an authored difficulty arc, a bigger, deeper game. Idling a proven short game or only polishing it WASTES the budget; more time should buy more GAME.
30
+
31
+ Never over-reach past what you can FINISH: a rough, unfinished big game loses to a finished smaller arc. Pick the highest rung of the ladder you can complete well, and let the clock, not a fixed template, decide which rung that is. YOU choose the form (ramp / waves / levels / worlds) that fits your idea and your time.
32
+
33
+ ## The design calls the clock forces
34
+
35
+ When you find one of these under time pressure, this is the jam-time decision (the principle is in `game-design-reference`):
36
+
37
+ - **A dead state (unfair forced-loss).** You found a spot where the player has no non-losing move. FIX IT even late: a dead state tanks the playability/fairness a judge and a player feel far more than a missing feature costs. This outranks polish.
38
+ - **A superhuman level (frame-perfect tuning).** You tuned a level against your own frame-perfect `--input` scripts and only YOU can clear it. Budget a reaction margin (widen the window, add telegraphing) before you call it done. If a human with ~250ms reaction cannot clear it, it is not done, it is broken.
39
+ - **An invisible entity.** You `spr()` something that is an empty slot, or the player cannot tell player from hazard from goal. Fix visibility/readability before anything else; an unreadable game is unplayable.
40
+
41
+ Do not declare "done, holding" while any of these is unfixed. The plateau trap is stopping because you cannot SEE what to improve, run the `game-design-reference` self-checks (fairness: test hazard-avoidability from every rest state, not just reachability; reaction: could a human clear this) precisely to find the work that remains.
42
+
43
+ ## Finish
44
+
45
+ At the deadline you ship: a playable `main.p8` (boots, responds, has win/lose, `verify` green) and a short `JAM.md` (theme interpretation, the mechanic, controls, and the honest calls you made under the clock). A rough playable game with a fresh idea beats a broken ambitious one, every time.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: picopilot-art
3
+ description: Edit and JUDGE PICO-8 sprites with picopilot. The char-grid edit surface, the rendered-PNG judge surface, the render to look, set to fix, render again loop, and the non-multimodal fallback for models that cannot read images. Also the gfx/map overlap smart-refuse. Use when editing __gfx__ sprites.
4
+ ---
5
+
6
+ # picopilot art
7
+
8
+ Sprites live in the cart as hex blobs you cannot read. picopilot gives you two
9
+ distinct surfaces so you can both EDIT precisely and JUDGE honestly. (See
10
+ `picopilot-overview` for the `#include` discipline; never hand-edit `__gfx__`.)
11
+
12
+ ## Two surfaces: the char grid (edit) and the PNG (judge)
13
+
14
+ - **Char grid** (`picopilot gfx show <sprite>` / `gfx set <sprite>`): a per-pixel
15
+ hex grid, `.` = transparent, `0-F` = the 16 fixed PICO-8 colours. This maps 1:1
16
+ to `__gfx__` and is your EXACT, round-trippable EDIT surface. It is the
17
+ SVG-for-pixels source you change.
18
+ - **Rendered PNG** (`picopilot gfx render <sprite|sheet>`): an upscaled,
19
+ true-16-colour PNG written to a known path. This is the JUDGE surface: it is
20
+ how you tell whether the sprite READS as intended.
21
+
22
+ You need both, because coding models generate structured visual source (a grid,
23
+ like SVG) well but read it back poorly for gestalt and colour. Edit on the grid;
24
+ judge on the PNG.
25
+
26
+ ## The art loop: render → look → set → render
27
+
28
+ 1. `picopilot gfx render <sprite>`: produce the PNG (and get the grid).
29
+ 2. LOOK at it, then
30
+ 3. `picopilot gfx set <sprite>`: fix pixels on the char grid.
31
+ 4. `picopilot gfx render <sprite>`: re-render and look again.
32
+
33
+ These steps are wired as CTAs, so follow the suggested next command.
34
+
35
+ ## The non-multimodal fallback (load-bearing)
36
+
37
+ The tool ALWAYS emits both the PNG path AND the char grid, because whether you
38
+ can read an image is YOUR capability, not something picopilot can detect.
39
+
40
+ - If you CAN read images: open the `gfx render` PNG and judge the pixels there.
41
+ - If you CANNOT read images: do NOT pretend to. Fall back to reasoning over the
42
+ `gfx show` grid, read the hex nibbles row by row, reconstruct the shape and
43
+ colours in your head, and judge from that. The eyes-loop degrades honestly to
44
+ the grid rather than to guessing.
45
+
46
+ ## gfx/map overlap: the smart-refuse
47
+
48
+ Sprites 128..255 (`0x1000`-`0x1fff`) ALIAS `__map__` rows 32..63, the SAME
49
+ memory. Writing such a sprite can clobber real map tiles you cannot see.
50
+
51
+ `picopilot gfx set` inspects the overlapping map region:
52
+
53
+ - overlap empty/all-zero, or overlap authorised: the write succeeds (the result
54
+ notes the aliasing).
55
+ - real map tiles at risk AND nothing authorised the loss: it REFUSES with a
56
+ structured `map-overlap` result and a nonzero exit, and leaves `__map__`
57
+ UNCHANGED. This is not a warning you may ignore; it is a stop.
58
+
59
+ To authorise overlap deliberately, pass `--allow-map-overlap` on the command or
60
+ set `allowMapOverlap: true` in `picopilot.json`. Or just use sprites 0..127,
61
+ which are gfx-only. The one guaranteed invariant: picopilot never SILENTLY
62
+ overwrites existing map tiles.
@@ -0,0 +1,194 @@
1
+ ---
2
+ name: picopilot-audio
3
+ description: Compose PICO-8 sound as text with picopilot. The #1 reframe (no tempo, compose in TRACKER ROWS at one speed), the picopilot-MML grammar (waveform/volume/effect per note + the 5 SFX filters for designed sounds like explosion/engine/pad), structural music assembly from SFX references, the loud refusals, and the compose to hear ears loop. Use when authoring __sfx__ / __music__.
4
+ ---
5
+
6
+ # picopilot audio
7
+
8
+ SFX and music live in the cart as hex blobs you cannot read. picopilot lets you
9
+ compose sound as TEXT you can reason about, then transpiles it into `__sfx__` /
10
+ `__music__`. (See `picopilot-overview` for the `#include` discipline; never
11
+ hand-write the audio hex.) The exact byte layout + the full grammar/mapping
12
+ tables are the canonical reference in
13
+ `work/notes/findings/pico8-audio-sfx-music-layout-and-mml-subset.md` (Part A =
14
+ layout, Part B = the picopilot-MML grammar + mapping). This skill carries what a
15
+ model gets WRONG, not the whole finding.
16
+
17
+ ## Read this first: no tempo, compose in TRACKER ROWS (ADR-0008)
18
+
19
+ The one thing a model gets wrong: it reaches for sheet music. PICO-8 has NO
20
+ per-note duration and NO tempo/BPM. An SFX is a column of up to 32 tracker ROWS,
21
+ and ALL rows share ONE SFX `speed` (ticks/row, set with `s<N>`). So a
22
+ picopilot-MML length is a ROW COUNT, not a musical note value: `c4` = the note C
23
+ held for 4 ROWS (emitted as the note plus 3 tie rows), NOT a quarter note. There
24
+ is no tempo token; a tempo-style input is refused with a clear message, not
25
+ guessed at. Consequences you must design around:
26
+
27
+ - **No true sustain across rows.** A held note is RE-STRUCK each following row
28
+ (PICO-8 has no cross-row sustain bit). For a smooth hold use effect `e1`
29
+ (slide) or a low `s<N>` speed, not a long length. Do not expect a legato tie.
30
+ - **Dotted notes round to whole rows.** `c4.` = 6 rows; a fractional result
31
+ rounds to the nearest whole row and the result WARNS. Rows are integers.
32
+ - **32-row hard cap.** A melody past 32 rows is refused (see below). Splitting it
33
+ into several SFX arranged by a music pattern is a STRUCTURAL choice YOU make,
34
+ not something the transpiler does silently.
35
+
36
+ ## The ears loop: `sfx from-mml` -> `music from-patterns` -> `audio render`
37
+
38
+ This is the audio analogue of the `picopilot-art` render/look/set eyes loop:
39
+
40
+ 1. `picopilot sfx from-mml <slot> "<mml>"`: compose one sound as text.
41
+ 2. `picopilot music from-patterns "<json>"`: arrange SFX into a song.
42
+ 3. `picopilot audio render`: RECORD a WAV and HEAR it, then iterate.
43
+
44
+ These steps are wired as CTAs (a merged SFX or song points you at `verify` then
45
+ `audio render`), so follow the suggested next command.
46
+
47
+ ## `audio render` / `audio record`: get a WAV by RECORDING (ADR-0009)
48
+
49
+ PICO-8 has NO working offline WAV export (it SIGFPEs upstream), so picopilot gets
50
+ audio-to-WAV the only way that works: by RECORDING a running session
51
+ (`extcmd("audio_rec")`/`audio_end`). Two commands, both PICO-8-gated and both
52
+ REAL-TIME recordings that need a real audio+video session (a developer machine,
53
+ NOT headless CI), NOT deterministic offline exports:
54
+
55
+ - **`picopilot audio render`** injects a THROWAWAY play-harness for a chosen
56
+ target and records it, so you write NO playback code. Pick the target with
57
+ `--sfx <n>` (one SFX slot), `--pattern <p>` (one music pattern), or NEITHER
58
+ (the whole song, `music 0`). The record window is derived from an `--sfx`
59
+ target's speed+rows; override any target with `--seconds <n>`. An empty slot or
60
+ out-of-range target is REFUSED loudly (nothing to render), never a silent
61
+ recording of silence.
62
+ - **`picopilot audio record <cart>`** records the cart's OWN playback (it keeps
63
+ the cart's code and appends a cooperative recorder); use it to capture a whole
64
+ run's audio. `picopilot run --record-audio` does the same alongside a normal
65
+ `run` (a WAV next to the screenshots).
66
+
67
+ The WAV goes to an isolated dir (`--wav-dir`, default a temp dir), never
68
+ `~/Desktop` or the carts root (the `audio_end(1)`-saves-to-the-current-folder
69
+ trap; picopilot steers it with PICO-8's `root_path`). PICO-8 absent -> a
70
+ structured `pico8-not-found` (remedy `set PICO8_PATH or install PICO-8`), never a
71
+ crash. On a headless machine the recording succeeds but captures NO audio
72
+ (`captured:false` + a nudge to run on a machine with a display + audio device):
73
+ that is honest, not a failure of the recipe.
74
+
75
+ ## `sfx from-mml`: one MML string -> one SFX slot
76
+
77
+ `picopilot sfx from-mml <slot> "<mml>"` transpiles picopilot-MML into one
78
+ `__sfx__` slot (`0-63`) and merges it (only that slot changes; every other
79
+ section stays byte-identical). Cart is `--cart` (default `main.p8`); pass a
80
+ longer source with `--file` (give EITHER the argument OR `--file`).
81
+
82
+ Write notes AND their waveform/volume/effect in MML: that is exactly the
83
+ information PICO-8 needs and hand-written hex hides. This is why picopilot-MML,
84
+ not ABC: ABC is score-level and cannot name a per-note waveform, volume, or
85
+ effect, so an ABC-authored SFX is timbre-less ("blind audio in a nicer font").
86
+
87
+ ### The picopilot-MML grammar in brief
88
+
89
+ Modal state (sticky until changed): `@<0-7>` waveform (`@8`-`@f` = SFX 0-7 as a
90
+ custom instrument), `v<0-7>` volume, `e<0-7>` effect (0 none, 1 slide, 2 vibrato,
91
+ 3 drop, 4 fade in, 5 fade out, 6 arpeggio fast, 7 arpeggio slow), `o<0-5>` octave
92
+ (default `o2`), `l<N>` default length in rows, `>`/`<` octave shift. A bare note
93
+ (`c d e f g a b`, `+`/`#` sharp, `-` flat) emits one row in the current state.
94
+ `r<N>` = N silent rows; `^<N>` extends the previous note; `s<N>` = SFX speed
95
+ (default `s16`); `{`/`}` = loop markers (a LONE `{` marks the pattern LEN, the
96
+ finding's LEN special case). SFX-level FILTERS (per-SFX, order-free): `!dampen`/
97
+ `!dampen2`, `!reverb`, `!noiz`, `!buzz`, `!detune1`/`!detune2` (see the filters
98
+ section below). Whitespace + `|` bars are cosmetic. See finding Part B / A.7 for
99
+ the full tables.
100
+
101
+ Note `e` is BOTH a note letter AND the effect prefix: `e0`-`e7` is the EFFECT; a
102
+ bare `e` is note E (hold it with `l4 e`, not `e4`). Effects have NO letter
103
+ mnemonics (the finding's `sl`/`vb`/... sugar did NOT ship: each collides with a
104
+ real note-plus-length sequence); use the numeric `e0`-`e7`.
105
+
106
+ Example: `picopilot sfx from-mml 0 "s8 @3 v6 e2 c e g > c"` is a square-wave
107
+ (`@3`) vibrato (`e2`) arpeggio at speed 8.
108
+
109
+ ### SFX FILTERS: how to reach DESIGNED sounds (explosion, engine, pad)
110
+
111
+ Pitch + waveform + volume + effect get you notes; the 5 per-SFX FILTERS are what
112
+ make a sound DESIGNED. They are `!`-prefixed SFX-level directives (per-SFX like
113
+ `s<N>`, place them anywhere in the string, order-free): they set the SFX's first
114
+ header byte and change nothing per-note. Use them when a boom reads as a
115
+ machine-gun rattle, or an engine as a buzz: no amount of pitch/volume MML can
116
+ synthesize the low BODY or resonant TAIL, only these filters can.
117
+
118
+ - **`!dampen` / `!dampen2`** — a low-pass at 2 levels. This is the BODY/weight:
119
+ turns raw `@6` noise from a hiss/rattle into a "boom". `!dampen2` is heavier.
120
+ The single biggest lever for an explosion.
121
+ - **`!reverb`** — an echo/tail. The resonant TAIL that makes a boom ring out (and
122
+ a pad bloom). On/off (a single switch, see the caveat below).
123
+ - **`!noiz`** — pure white-noise texture; applies ONLY to instrument 6 (`@6`), a
124
+ no-op on any other waveform. The noise character under an explosion.
125
+ - **`!buzz`** — a buzzier alteration (all waveforms). With `@6` + `!buzz` and NOIZ
126
+ off you get brown noise.
127
+ - **`!detune1`** (flange, second detuned voice) / **`!detune2`** (a voice tuned up
128
+ ~an octave) — thickness for a lead or pad. `!detune` needs an explicit 1 or 2.
129
+
130
+ The canonical DESIGNED explosion: `!dampen2 !reverb @6` over a descending pitch
131
+ sweep + volume decay, e.g.
132
+ `picopilot sfx from-mml 3 "s6 !dampen2 !reverb @6 o3 v7 g o1 c o0 g v6 e v5 c v4 g v3 c v2 g v1 c"`.
133
+ Strip the two filters and the SAME notes read as a descending zap / rattle: the
134
+ filters are the tool gap the audio dogfood surfaced (they set header byte `e0`).
135
+
136
+ CAVEAT (finding A.7): the filter byte layout was decoded byte-for-byte on PICO-8
137
+ v0.2.7, but the byte has no non-colliding room for a REVERB level 2 under the
138
+ verified layout, so picopilot exposes `!reverb` as a single on/off switch (DAMPEN
139
+ keeps its 2 levels). A bad level (`!dampen3`, `!noiz2`, `!reverb2`, `!detune`
140
+ without 1/2) is a loud `audio-mml-filter-level-out-of-range` refusal, not a clamp.
141
+
142
+ ## `music from-patterns`: structural, NOT a notation
143
+
144
+ `__music__` holds NO melody: melody lives entirely in the referenced SFX. A song
145
+ is an ORDERED LIST of up-to-4 SFX-channel references per pattern.
146
+ `picopilot music from-patterns "<json>"` writes `__music__` (only `__music__`
147
+ changes; `__sfx__` and every other section stay byte-identical). So author each
148
+ voice as an SFX with `sfx from-mml`, THEN arrange the references here; you never
149
+ write music in a melodic notation. Cart is `--cart` (default `main.p8`); longer
150
+ songs via `--file`.
151
+
152
+ ### The pattern-list JSON
153
+
154
+ An array of patterns, each
155
+ `{"channels":[c0,c1,c2,c3], "loopStart"?, "loopBack"?, "stop"?}`. Pattern order =
156
+ song order. A channel is an SFX index `0-63`, OR `null` = OFF (silent this
157
+ pattern). **`null` is NOT `sfx 0`**: `null` sets the channel's off bit; `0` is a
158
+ real, playing SFX. Keep them distinct. The 3 flow flags (optional, default
159
+ false): `loopStart`, `loopBack` (jump back to the loop-start pattern at this
160
+ pattern's end), `stop` (music stops after this pattern); they combine. A
161
+ `loopBack` with NO `loopStart` anywhere is allowed but WARNS (PICO-8 falls back
162
+ to pattern 0).
163
+
164
+ Example: `[{"channels":[0,1,null,3],"loopStart":true},{"channels":[0,1,null,3],"loopBack":true}]`
165
+ is a 2-pattern loop whose channel 2 is silent.
166
+
167
+ **Pattern length is INHERITED from the SFX, not stored here.** PICO-8 ends a
168
+ pattern when its LEFT-MOST non-looping channel's SFX finishes, so pattern timing
169
+ comes from the referenced SFX (its `speed` + LEN). To change how long a pattern
170
+ plays, change the referenced SFX, not the music.
171
+
172
+ ## The two refusals: loud, never clamped
173
+
174
+ Both stop you with a structured error + nonzero exit (no bytes change on a
175
+ refusal) so a refusal is ACTIONABLE, not a dead end:
176
+
177
+ - **`audio-mml-pitch-out-of-range`** (`sfx from-mml`): a note fell below C0 or
178
+ above D#5 (`0-63`). PICO-8 has no notes outside that range. Fix: TRANSPOSE, pick
179
+ an octave (`o0`-`o5`) that keeps every note in range, rather than transposing
180
+ off the keyboard.
181
+ - **`audio-mml-sfx-overflow`** (`sfx from-mml`): more than 32 rows. Fix: SPLIT the
182
+ melody across multiple SFX slots yourself, then arrange them with
183
+ `music from-patterns`.
184
+ - **`audio-mml-filter-level-out-of-range`** (`sfx from-mml`): a filter level PICO-8
185
+ does not have (`!dampen3`, `!reverb2`, `!noiz2`, or `!detune` without a mode).
186
+ Fix: `!dampen` has levels 1-2 (`!dampen`/`!dampen2`); `!reverb`/`!noiz`/`!buzz`
187
+ are on/off; `!detune` needs `!detune1` (flange) or `!detune2` (octave).
188
+
189
+ Related: `music from-patterns` refuses an out-of-range SFX index with
190
+ `audio-music-sfx-out-of-range` (to silence a channel use `null`, not an
191
+ out-of-range number); a malformed JSON payload is `audio-music-parse-error`. And
192
+ `audio render`/`audio record` require PICO-8, so they return a structured
193
+ `pico8-not-found` (remedy `set PICO8_PATH or install PICO-8`) when absent, never
194
+ a crash. The text authoring + transpile steps need neither PICO-8 nor shrinko.
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: picopilot-code
3
+ description: Write low-token PICO-8 Lua and stay under the 8,192-token budget. How to read picopilot tokens output, the PICO-8 shorthands that reclaim budget, and how minify (safe-only) and verify fit the loop. Use when writing or shrinking cart Lua.
4
+ ---
5
+
6
+ # picopilot code
7
+
8
+ The #1 way an agent fails at PICO-8 is token bloat: verbose Lua silently blows
9
+ past the 8,192-token budget and the cart will not load. picopilot's job here is
10
+ to make the budget visible and cheap to fix. (See `picopilot-overview` for the
11
+ `#include` discipline: you edit `main.lua`, never the `.p8` binary sections.)
12
+
13
+ ## Read the token breakdown every iteration
14
+
15
+ Run `picopilot tokens` after each change. It reports:
16
+
17
+ - `tokens` and `pct`: the count and its percentage of the 8,192 budget.
18
+ - `overBudget`: true when you are over 8,192 (the cart will not load).
19
+ - `chars` and `compressed`: size, not the load-bearing limit; tokens are.
20
+
21
+ When you are over budget, the result CTAs you to `picopilot minify`. Treat the
22
+ token count as the number to drive down, not something to check once.
23
+
24
+ If shrinko is not installed, `tokens` returns a structured `shrinko-not-found`
25
+ result (nonzero exit) with the exact remedy `uv pip install shrinko`. That is not
26
+ a crash; install shrinko and re-run.
27
+
28
+ ## Token discipline (write cheap Lua the first time)
29
+
30
+ Models blow the budget by writing verbose Lua. Prefer PICO-8 shorthands:
31
+
32
+ - `?` for `print`.
33
+ - inline if: `if(c) x=1` instead of `if c then x=1 end`.
34
+ - compound assignment: `+=`, `-=`, `*=`, `/=`.
35
+ - `\` for integer divide.
36
+ - reuse locals; avoid needless globals; fold repeated expressions into one local.
37
+
38
+ ## Reclaim budget with minify (safe-only)
39
+
40
+ `picopilot minify` runs SAFE minification by default and reports the before/after
41
+ token delta. Safe-only never changes behaviour, so it is the first move when over
42
+ budget. Aggressive minification is never silent; if it is ever offered it is an
43
+ explicit opt-in.
44
+
45
+ ## Gate it
46
+
47
+ `picopilot verify` is the single static acceptance gate: tokens + integrity, no
48
+ run. A green verify is well-formed, not proven-to-run, so it points you at
49
+ `picopilot run`. With shrinko absent, verify is `gate-incapable` (never green),
50
+ because it cannot check token bloat, the failure this loop exists to catch.
51
+
52
+ ## PICO-8 gotchas that break carts silently
53
+
54
+ These are NOT token issues; they are behaviours that differ from mainstream Lua
55
+ / math and make a cart compile-and-run-but-behave-wrong. The ones models get
56
+ wrong most:
57
+
58
+ - `sin(t)`/`cos(t)` take TURNS (0..1), not radians, and `sin` is INVERTED for
59
+ screen space (+y is down). A full circle is `t: 0->1`. Do not multiply by
60
+ `2*pi`; do not re-negate `sin`.
61
+ - `/` does NOT truncate (`5/2` is `2.5`). Integer divide is `\` (`5\2` is `2`),
62
+ which floors toward negative infinity (`-5\2` is `-3`).
63
+ - `rnd(x)` returns a FLOAT in `[0,x)`. Wrap with `flr` for an integer
64
+ (`flr(rnd(6))` is 0..5). `rnd(table)` returns a random element.
65
+ - Tables are 1-BASED. `del(t,val)` removes by VALUE; `deli(t,i)` removes by
66
+ INDEX. Deleting the current item inside `foreach` is safe; deleting inside an
67
+ ascending `for i=1,#t` loop ERRORS once the list shrinks (iterate descending).
68
+ - Draw state PERSISTS across frames: `camera`, `pal`, `palt`, `clip`, `color`,
69
+ `fillp` stay set until you reset them (no-arg form: `camera()`, `pal()`).
70
+ Reset before drawing the HUD or offsets/palette swaps leak.
71
+ - Variables are GLOBAL unless declared `local`. The `x = cond and a or b` ternary
72
+ returns `b` when `a` is falsy; use an explicit `if` when `a` can be `false`/`nil`.
73
+
74
+ ## Data-as-split-strings (a top token-saver)
75
+
76
+ `split"a,b,c"` turns a comma string into a 1-based array in one cheap token.
77
+ Declare frames, hitboxes, direction lookups, and whole level rows this way
78
+ instead of table literals. `unpack(split"...")` spreads it as a vararg (e.g.
79
+ `poke(addr, unpack(split"..."))` writes a byte blob in one call). Prefer it for
80
+ any homogeneous list.
81
+
82
+ ## Genre code references (load the one you need)
83
+
84
+ The right STRUCTURE differs by game type: a platformer's gravity + per-axis
85
+ collision is nothing like a grid puzzle's transactional tile-step or a shooter's
86
+ entity swarm. When you start building a specific genre, load the matching file
87
+ from this skill's `reference/` folder (see `reference/README.md` for the index):
88
+
89
+ - `reference/platformer.md`: gravity/jump, per-axis tile collision, camera follow.
90
+ - `reference/puzzle-grid.md`: grid state, discrete tile-step moves, rule passes.
91
+ - `reference/twin-stick-arcade.md`: many entities, spawn/despawn at scale, juice.
92
+ - `reference/top-down-adventure.md`: tiled world, 4-dir movement, tile interaction.
93
+ - `reference/mode7-racing.md`: pseudo-3D `tline` floor projection, driving.
94
+ - `reference/rpg-menus-dialog.md`: UI state stack, cursor menus, timed text reveal.
95
+
96
+ Each carries a minimal idiomatic implementation and a genre-pitfalls checklist.
97
+ Adapt them to your game.
@@ -0,0 +1,42 @@
1
+ # PICO-8 game-type code references
2
+
3
+ Per-genre reference implementations for PICO-8 carts. The right structure for a
4
+ platformer is not the right structure for a puzzle game, so pick the file that
5
+ matches what you are building and load it on demand. These are minimal
6
+ implementations written in idiomatic, token-cheap PICO-8 Lua (see the
7
+ `picopilot-code` SKILL for the token discipline they follow). They teach the
8
+ STRUCTURE and the genre-specific pitfalls; adapt them to your game.
9
+
10
+ Load the ONE that matches your game:
11
+
12
+ - `platformer.md`: side-view gravity/jump, per-axis tile collision, camera
13
+ follow.
14
+ - `puzzle-grid.md`: a grid of cells, discrete tile-by-tile movement, rule
15
+ evaluation, no physics.
16
+ - `twin-stick-arcade.md`: many entities at once (bullets, enemies, particles),
17
+ spawn/despawn at scale, screen juice.
18
+ - `top-down-adventure.md`: walk a tiled world, room/map streaming, 4-direction
19
+ movement and facing, interact with tiles.
20
+ - `mode7-racing.md`: pseudo-3D projected floor via `tline`, per-scanline
21
+ perspective, heading-based driving.
22
+ - `rpg-menus-dialog.md`: UI state STACK, cursor menus, timed text reveal,
23
+ turn-based battle menus.
24
+
25
+ Cross-cutting truths that apply to ALL genres live in the always-loaded
26
+ `AGENTS.md` (palette, memory map, the loop) and in this skill's other sections
27
+ (token budget). The genre files assume you already know those.
28
+
29
+ ## Idioms every genre file relies on (quick recap)
30
+
31
+ - `add(list,obj)` / `del(list,obj)` / `deli(list,i)` for entity lists. Deleting
32
+ the CURRENT item inside `foreach` is safe; deleting inside an ASCENDING numeric
33
+ `for i=1,#list` loop is NOT (it errors once the list shrinks). Iterate a
34
+ descending loop or `foreach` when you delete while iterating.
35
+ - `split"a,b,c"` turns a comma string into a 1-based array in one cheap token;
36
+ it is the cheap way to declare data tables (frames, hitboxes, level rows).
37
+ - `sin(t)`/`cos(t)` take TURNS (0..1), not radians, and `sin` is INVERTED for
38
+ screen space (+y is down). A full circle is `t: 0->1`.
39
+ - `mid(lo,x,hi)` is the idiomatic clamp.
40
+ - Draw state (`camera`, `pal`, `palt`, `clip`, `color`, `fillp`) PERSISTS across
41
+ frames and calls. Reset with the no-arg form (`camera()`, `pal()`) before
42
+ drawing the HUD, or it leaks.
@@ -0,0 +1,95 @@
1
+ # Mode-7 / pseudo-3D racing reference (tline floor projection)
2
+
3
+ Structure for a pseudo-3D game with a projected ground plane (arcade racer,
4
+ first-person floor). PICO-8 has no real 3D; you FAKE it by drawing the map as a
5
+ perspective-warped floor with `tline` (textured line), one horizontal scanline at
6
+ a time, each sampling the map further away as it climbs toward the horizon. This
7
+ is structurally unlike every other genre here: there are no entity-vs-tile
8
+ collisions in world pixels, the "world" is a texture you sample.
9
+
10
+ ## The load-bearing rules
11
+
12
+ 1. Draw the floor BOTTOM-UP: for each screen row `py` from the horizon down to the
13
+ bottom, compute how far into the world that row is, and `tline` one row of the
14
+ map at that depth. Rows near the bottom are close (map moves fast); rows near
15
+ the horizon are far (map moves slowly).
16
+ 2. `tline(x0,y0,x1,y1, u,v, du,dv)` draws screen pixels `x0,y0..x1,y1` sampling the
17
+ MAP starting at map-coord `(u,v)` and stepping `(du,dv)` per pixel. The scale
18
+ (how much world per screen pixel) is what creates perspective: bigger step near
19
+ the bottom, smaller near the horizon.
20
+ 3. Rotation uses `sin`/`cos` in TURNS (0..1). The camera angle rotates the `(du,dv)`
21
+ sampling basis; do NOT feed radians.
22
+ 4. This is CPU-heavy. Keep it to the floor; draw sprites (player, opponents) on top
23
+ with normal `spr`, scaled by their distance.
24
+
25
+ ## Minimal floor projection
26
+
27
+ ```lua
28
+ -- camera: world pos (cx,cy), heading angle (ang, in TURNS), height above floor
29
+ cam={x=0,y=0,ang=0,h=16}
30
+ horizon=48 -- screen row where the floor starts (sky above)
31
+
32
+ function draw_floor()
33
+ local ca,sa=cos(cam.ang),sin(cam.ang)
34
+ for py=horizon,127 do
35
+ -- depth: how far this scanline is into the world. Rows just below the horizon
36
+ -- are very far (large scale); rows at the bottom are near. This 1/(py-horizon)
37
+ -- shape is the perspective.
38
+ local d=cam.h/(py-horizon+1) -- world distance for this row
39
+ local scale=d/64 -- world units per screen pixel
40
+ -- the world point at the CENTER of this scanline, then the left/right ends
41
+ local wx=cam.x + d*ca
42
+ local wy=cam.y + d*sa
43
+ -- sampling basis perpendicular to heading, scaled by depth
44
+ local du= sa*scale
45
+ local dv=-ca*scale
46
+ -- start at the left screen edge: back off 64 pixels along the basis
47
+ local u=wx - du*64
48
+ local v=wy - dv*64
49
+ tline(0,py,127,py, u,v, du,dv) -- one textured scanline of the map
50
+ end
51
+ end
52
+ ```
53
+
54
+ Tune `horizon`, `cam.h`, and the `d`/`scale` constants to taste; the exact numbers
55
+ depend on your track's map scale. The SHAPE (1/row depth, perpendicular basis from
56
+ `sin`/`cos`, `tline` per row) is what matters.
57
+
58
+ ## Driving (heading + speed, no tile collision in the usual sense)
59
+
60
+ ```lua
61
+ speed=0
62
+ function update_car()
63
+ if btn(2) then speed=min(speed+0.1,4) end -- accelerate
64
+ if btn(3) then speed=max(speed-0.1,0) end -- brake
65
+ if btn(0) then cam.ang-=0.005*speed end -- steer (turn amount scales w/ speed)
66
+ if btn(1) then cam.ang+=0.005*speed end
67
+ -- move along heading (TURNS: cos/sin)
68
+ cam.x+=cos(cam.ang)*speed
69
+ cam.y+=sin(cam.ang)*speed
70
+ end
71
+ ```
72
+
73
+ Off-track detection reads the map tile under the car (`mget(cam.x\8, cam.y\8)`) and
74
+ slows the car, rather than blocking movement like a platformer wall.
75
+
76
+ ## Draw order
77
+
78
+ ```lua
79
+ function _draw()
80
+ cls(12) -- sky
81
+ draw_floor() -- projected map
82
+ draw_opponents() -- spr, scaled by distance, sorted far-to-near
83
+ draw_hud() -- speed, lap; camera is not offset here (floor is drawn directly)
84
+ end
85
+ ```
86
+
87
+ ## Genre pitfalls checklist
88
+
89
+ - [ ] Floor drawn per-scanline with `tline`, horizon down to row 127.
90
+ - [ ] Depth uses a 1/(row) shape so far rows compress toward the horizon.
91
+ - [ ] Rotation via `sin`/`cos` in TURNS; sampling basis is perpendicular to heading.
92
+ - [ ] It is CPU-heavy: profile with `picopilot run`, keep the floor loop tight,
93
+ consider `_update60`->`_update` (30fps) if it cannot keep up.
94
+ - [ ] Sprites drawn on top, scaled and sorted by distance, NOT via the floor.
95
+ - [ ] Off-track is a tile lookup that slows you, not a solid-wall collision.
@@ -0,0 +1,119 @@
1
+ # Platformer reference (side-view, gravity + tile collision)
2
+
3
+ Structure for a side-scrolling platformer: the player has velocity, gravity
4
+ pulls it down, and collision is resolved AXIS BY AXIS against solid map tiles.
5
+ This is the shape that gets platformers wrong most often, so follow the axis
6
+ order and the corner checks exactly.
7
+
8
+ ## The load-bearing rules (get these wrong and it feels broken)
9
+
10
+ 1. Gravity and input change VELOCITY (`dx`,`dy`), never position directly. A
11
+ fall must ACCELERATE, so `dy += gravity` each frame, not `y += gravity`.
12
+ 2. Move and resolve X FIRST, then Y SEPARATELY. Moving both then testing once
13
+ sticks the player in corners and forbids wall-sliding.
14
+ 3. An 8px sprite occupies pixels `x .. x+7` (NOT `x+8`). Check tile collision at
15
+ all four corners; `x+8` reads the NEXT tile and gives phantom collisions.
16
+ 4. Reset `grounded` to false every frame BEFORE the Y resolve, then set it true
17
+ only when a downward move hits a floor. Otherwise jump never re-arms (or
18
+ arms forever = infinite mid-air jumps).
19
+ 5. `_update` is 30fps. Tune `gravity`/jump for 30 steps/sec, or switch the whole
20
+ cart to `_update60()` and tune for 60. Do not mix.
21
+
22
+ ## Solid-tile test (map flag 0 = solid, by convention)
23
+
24
+ ```lua
25
+ -- pixel (x,y) -> is its map tile solid? flag 0 set = solid.
26
+ function solid(x,y) return fget(mget(x\8,y\8),0) end
27
+
28
+ -- 8x8 actor a: any of its four corners on a solid tile?
29
+ function hits(a)
30
+ return solid(a.x,a.y) or solid(a.x+7,a.y)
31
+ or solid(a.x,a.y+7) or solid(a.x+7,a.y+7)
32
+ end
33
+ ```
34
+
35
+ ## Player update (input -> velocity -> per-axis move+resolve)
36
+
37
+ ```lua
38
+ gravity=0.3
39
+ accel=0.5
40
+ friction=0.85
41
+ jump=-4
42
+
43
+ function make_player(x,y)
44
+ return {x=x,y=y,dx=0,dy=0,grounded=false}
45
+ end
46
+
47
+ function update_player(p)
48
+ -- input into horizontal velocity
49
+ if btn(0) then p.dx-=accel end
50
+ if btn(1) then p.dx+=accel end
51
+ -- jump only when standing
52
+ if btnp(4) and p.grounded then p.dy=jump end
53
+
54
+ -- physics on velocity
55
+ p.dy+=gravity
56
+ p.dx*=friction
57
+
58
+ -- resolve X
59
+ p.x+=p.dx
60
+ if hits(p) then
61
+ -- back out and stop horizontal motion (simple, non-swept)
62
+ p.x-=p.dx p.dx=0
63
+ end
64
+
65
+ -- resolve Y (reset grounded first)
66
+ p.y+=p.dy
67
+ p.grounded=false
68
+ if hits(p) then
69
+ p.y-=p.dy
70
+ if p.dy>0 then p.grounded=true end -- landed on a floor
71
+ p.dy=0
72
+ end
73
+ end
74
+ ```
75
+
76
+ ## Camera follow with level-edge clamp
77
+
78
+ ```lua
79
+ -- level_w = level width in PIXELS. mid() clamps so you never scroll past the map.
80
+ function update_camera(p)
81
+ local cx=mid(0, p.x-64+4, level_w-128) -- +4 centers the 8px sprite
82
+ camera(cx,0)
83
+ end
84
+
85
+ function _draw()
86
+ cls()
87
+ update_camera(player)
88
+ map() -- draw the tilemap in world space
89
+ draw_player(player)
90
+ camera() -- RESET before HUD or the score scrolls off with the world
91
+ print("score "..score,2,2,7)
92
+ end
93
+ ```
94
+
95
+ ## Animation (walk cycle from a timer, flip when facing left)
96
+
97
+ ```lua
98
+ function draw_player(p)
99
+ -- 4-frame walk (sprites 1..4), advancing ~every 8 update frames; flip when moving left
100
+ local s=1+flr(t()*4%4)
101
+ spr(s, flr(p.x), flr(p.y), 1,1, p.dx<0) -- 6th arg = flip_x (boolean)
102
+ end
103
+ ```
104
+
105
+ ## Genre pitfalls checklist
106
+
107
+ - [ ] Gravity accumulates into `dy`, not into `y`.
108
+ - [ ] X resolved, THEN Y resolved (two separate move+test blocks).
109
+ - [ ] Corner checks use `x+7`/`y+7`, not `x+8`/`y+8`.
110
+ - [ ] `grounded` reset to false each frame before the Y resolve.
111
+ - [ ] Constants tuned for the actual framerate (`_update` = 30, `_update60` = 60).
112
+ - [ ] `camera()` reset before drawing the HUD.
113
+
114
+ ## Level fast (high speed) tunneling
115
+
116
+ Backing out the whole move (`p.x-=p.dx`) is fine at low speeds. If an actor can
117
+ move more than ~4-6 px/frame it can tunnel THROUGH a 1-tile wall. When you need
118
+ that, step the move in 1px increments (or sub-steps) and test each step, instead
119
+ of one big move + one test.