c64-kb 0.17.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 (970) hide show
  1. package/CHANGELOG.md +2082 -0
  2. package/LICENSE +29 -0
  3. package/README.md +559 -0
  4. package/VERSION +25 -0
  5. package/dist/cli/setup.js +66 -0
  6. package/dist/cli/setup.js.map +1 -0
  7. package/dist/cli-memorize-audit.js +97 -0
  8. package/dist/cli-memorize-audit.js.map +1 -0
  9. package/dist/cli.js +335 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.js +83 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/context.js +53 -0
  14. package/dist/context.js.map +1 -0
  15. package/dist/domain/budget-notes.js +104 -0
  16. package/dist/domain/budget-notes.js.map +1 -0
  17. package/dist/domain/budget.js +417 -0
  18. package/dist/domain/budget.js.map +1 -0
  19. package/dist/domain/game-design.js +77 -0
  20. package/dist/domain/game-design.js.map +1 -0
  21. package/dist/domain/timing.js +39 -0
  22. package/dist/domain/timing.js.map +1 -0
  23. package/dist/graph/apply.js +132 -0
  24. package/dist/graph/apply.js.map +1 -0
  25. package/dist/graph/claims.js +231 -0
  26. package/dist/graph/claims.js.map +1 -0
  27. package/dist/graph/extract/archetype.js +118 -0
  28. package/dist/graph/extract/archetype.js.map +1 -0
  29. package/dist/graph/extract/common.js +69 -0
  30. package/dist/graph/extract/common.js.map +1 -0
  31. package/dist/graph/extract/failure.js +37 -0
  32. package/dist/graph/extract/failure.js.map +1 -0
  33. package/dist/graph/extract/game-design.js +189 -0
  34. package/dist/graph/extract/game-design.js.map +1 -0
  35. package/dist/graph/extract/hardware.js +124 -0
  36. package/dist/graph/extract/hardware.js.map +1 -0
  37. package/dist/graph/extract/pitfall.js +62 -0
  38. package/dist/graph/extract/pitfall.js.map +1 -0
  39. package/dist/graph/extract/raster-band.js +56 -0
  40. package/dist/graph/extract/raster-band.js.map +1 -0
  41. package/dist/graph/extract/recipe.js +71 -0
  42. package/dist/graph/extract/recipe.js.map +1 -0
  43. package/dist/graph/extract/technique-entities.js +180 -0
  44. package/dist/graph/extract/technique-entities.js.map +1 -0
  45. package/dist/graph/extract/technique.js +179 -0
  46. package/dist/graph/extract/technique.js.map +1 -0
  47. package/dist/graph/extract/toolchain.js +67 -0
  48. package/dist/graph/extract/toolchain.js.map +1 -0
  49. package/dist/graph/extract/types.js +3 -0
  50. package/dist/graph/extract/types.js.map +1 -0
  51. package/dist/graph/extract/vocabulary.js +65 -0
  52. package/dist/graph/extract/vocabulary.js.map +1 -0
  53. package/dist/graph/extract.js +43 -0
  54. package/dist/graph/extract.js.map +1 -0
  55. package/dist/graph/kernal-clobbers.js +64 -0
  56. package/dist/graph/kernal-clobbers.js.map +1 -0
  57. package/dist/graph/machine-variants.js +208 -0
  58. package/dist/graph/machine-variants.js.map +1 -0
  59. package/dist/ingest/files.js +99 -0
  60. package/dist/ingest/files.js.map +1 -0
  61. package/dist/ingest/passes.js +104 -0
  62. package/dist/ingest/passes.js.map +1 -0
  63. package/dist/ingest/points.js +50 -0
  64. package/dist/ingest/points.js.map +1 -0
  65. package/dist/ingest/report.js +140 -0
  66. package/dist/ingest/report.js.map +1 -0
  67. package/dist/ingest/tally.js +81 -0
  68. package/dist/ingest/tally.js.map +1 -0
  69. package/dist/ingest/verified-on.js +43 -0
  70. package/dist/ingest/verified-on.js.map +1 -0
  71. package/dist/ingest.js +135 -0
  72. package/dist/ingest.js.map +1 -0
  73. package/dist/schemas/cost-basis.js +5 -0
  74. package/dist/schemas/cost-basis.js.map +1 -0
  75. package/dist/schemas/machine-variant.js +25 -0
  76. package/dist/schemas/machine-variant.js.map +1 -0
  77. package/dist/schemas/plan-budget.js +121 -0
  78. package/dist/schemas/plan-budget.js.map +1 -0
  79. package/dist/schemas/tool-outputs.js +526 -0
  80. package/dist/schemas/tool-outputs.js.map +1 -0
  81. package/dist/server/define-tool.js +52 -0
  82. package/dist/server/define-tool.js.map +1 -0
  83. package/dist/server/defined-only.js +9 -0
  84. package/dist/server/defined-only.js.map +1 -0
  85. package/dist/server/tools-briefings.js +70 -0
  86. package/dist/server/tools-briefings.js.map +1 -0
  87. package/dist/server/tools-lookup.js +180 -0
  88. package/dist/server/tools-lookup.js.map +1 -0
  89. package/dist/server/tools-maintenance.js +116 -0
  90. package/dist/server/tools-maintenance.js.map +1 -0
  91. package/dist/server/tools-pitfalls.js +89 -0
  92. package/dist/server/tools-pitfalls.js.map +1 -0
  93. package/dist/server/tools-recipes.js +296 -0
  94. package/dist/server/tools-recipes.js.map +1 -0
  95. package/dist/server/tools-runtime.js +48 -0
  96. package/dist/server/tools-runtime.js.map +1 -0
  97. package/dist/server/tools.js +43 -0
  98. package/dist/server/tools.js.map +1 -0
  99. package/dist/server.js +95 -0
  100. package/dist/server.js.map +1 -0
  101. package/dist/services/analytics/schema.js +78 -0
  102. package/dist/services/analytics/schema.js.map +1 -0
  103. package/dist/services/analytics/statements.js +99 -0
  104. package/dist/services/analytics/statements.js.map +1 -0
  105. package/dist/services/analytics.js +195 -0
  106. package/dist/services/analytics.js.map +1 -0
  107. package/dist/services/bm25.js +158 -0
  108. package/dist/services/bm25.js.map +1 -0
  109. package/dist/services/chunker.js +81 -0
  110. package/dist/services/chunker.js.map +1 -0
  111. package/dist/services/embeddings.js +58 -0
  112. package/dist/services/embeddings.js.map +1 -0
  113. package/dist/services/falkor/base.js +85 -0
  114. package/dist/services/falkor/base.js.map +1 -0
  115. package/dist/services/falkor/links.js +361 -0
  116. package/dist/services/falkor/links.js.map +1 -0
  117. package/dist/services/falkor/nodes.js +183 -0
  118. package/dist/services/falkor/nodes.js.map +1 -0
  119. package/dist/services/falkor/params.js +45 -0
  120. package/dist/services/falkor/params.js.map +1 -0
  121. package/dist/services/falkor/schema.js +77 -0
  122. package/dist/services/falkor/schema.js.map +1 -0
  123. package/dist/services/falkor.js +100 -0
  124. package/dist/services/falkor.js.map +1 -0
  125. package/dist/services/qdrant.js +238 -0
  126. package/dist/services/qdrant.js.map +1 -0
  127. package/dist/services/versions.js +73 -0
  128. package/dist/services/versions.js.map +1 -0
  129. package/dist/services/vice-bin.js +51 -0
  130. package/dist/services/vice-bin.js.map +1 -0
  131. package/dist/tools/briefings/archetype.js +174 -0
  132. package/dist/tools/briefings/archetype.js.map +1 -0
  133. package/dist/tools/briefings/budget.js +177 -0
  134. package/dist/tools/briefings/budget.js.map +1 -0
  135. package/dist/tools/briefings/build-order.js +135 -0
  136. package/dist/tools/briefings/build-order.js.map +1 -0
  137. package/dist/tools/briefings/build.js +185 -0
  138. package/dist/tools/briefings/build.js.map +1 -0
  139. package/dist/tools/briefings/discovery.js +290 -0
  140. package/dist/tools/briefings/discovery.js.map +1 -0
  141. package/dist/tools/briefings/plan-pitfalls.js +69 -0
  142. package/dist/tools/briefings/plan-pitfalls.js.map +1 -0
  143. package/dist/tools/briefings/render.js +151 -0
  144. package/dist/tools/briefings/render.js.map +1 -0
  145. package/dist/tools/briefings/rows.js +41 -0
  146. package/dist/tools/briefings/rows.js.map +1 -0
  147. package/dist/tools/briefings/toolchain.js +63 -0
  148. package/dist/tools/briefings/toolchain.js.map +1 -0
  149. package/dist/tools/briefings/why-proposed.js +69 -0
  150. package/dist/tools/briefings/why-proposed.js.map +1 -0
  151. package/dist/tools/briefings.js +44 -0
  152. package/dist/tools/briefings.js.map +1 -0
  153. package/dist/tools/feedback.js +97 -0
  154. package/dist/tools/feedback.js.map +1 -0
  155. package/dist/tools/hydrate.js +75 -0
  156. package/dist/tools/hydrate.js.map +1 -0
  157. package/dist/tools/intelligence.js +67 -0
  158. package/dist/tools/intelligence.js.map +1 -0
  159. package/dist/tools/lint/asm-decimal.js +86 -0
  160. package/dist/tools/lint/asm-decimal.js.map +1 -0
  161. package/dist/tools/lint/asm-rules.js +221 -0
  162. package/dist/tools/lint/asm-rules.js.map +1 -0
  163. package/dist/tools/lint/asm-shared.js +3 -0
  164. package/dist/tools/lint/asm-shared.js.map +1 -0
  165. package/dist/tools/lint/c-rules.js +215 -0
  166. package/dist/tools/lint/c-rules.js.map +1 -0
  167. package/dist/tools/lint/text.js +122 -0
  168. package/dist/tools/lint/text.js.map +1 -0
  169. package/dist/tools/lint/types.js +40 -0
  170. package/dist/tools/lint/types.js.map +1 -0
  171. package/dist/tools/lint.js +76 -0
  172. package/dist/tools/lint.js.map +1 -0
  173. package/dist/tools/memorization-mcp.js +111 -0
  174. package/dist/tools/memorization-mcp.js.map +1 -0
  175. package/dist/tools/pitfalls/bm25.js +53 -0
  176. package/dist/tools/pitfalls/bm25.js.map +1 -0
  177. package/dist/tools/pitfalls/format.js +58 -0
  178. package/dist/tools/pitfalls/format.js.map +1 -0
  179. package/dist/tools/pitfalls/graph.js +124 -0
  180. package/dist/tools/pitfalls/graph.js.map +1 -0
  181. package/dist/tools/pitfalls.js +134 -0
  182. package/dist/tools/pitfalls.js.map +1 -0
  183. package/dist/tools/prompts.js +81 -0
  184. package/dist/tools/prompts.js.map +1 -0
  185. package/dist/tools/query/compatibility/closure.js +62 -0
  186. package/dist/tools/query/compatibility/closure.js.map +1 -0
  187. package/dist/tools/query/compatibility/facts.js +28 -0
  188. package/dist/tools/query/compatibility/facts.js.map +1 -0
  189. package/dist/tools/query/compatibility/fetch.js +164 -0
  190. package/dist/tools/query/compatibility/fetch.js.map +1 -0
  191. package/dist/tools/query/compatibility/hard-rules.js +159 -0
  192. package/dist/tools/query/compatibility/hard-rules.js.map +1 -0
  193. package/dist/tools/query/compatibility/index.js +27 -0
  194. package/dist/tools/query/compatibility/index.js.map +1 -0
  195. package/dist/tools/query/compatibility/kernal-zp-rule.js +90 -0
  196. package/dist/tools/query/compatibility/kernal-zp-rule.js.map +1 -0
  197. package/dist/tools/query/compatibility/render.js +106 -0
  198. package/dist/tools/query/compatibility/render.js.map +1 -0
  199. package/dist/tools/query/compatibility/rules.js +337 -0
  200. package/dist/tools/query/compatibility/rules.js.map +1 -0
  201. package/dist/tools/query/compatibility/unit-rules.js +253 -0
  202. package/dist/tools/query/compatibility/unit-rules.js.map +1 -0
  203. package/dist/tools/query/game-design.js +83 -0
  204. package/dist/tools/query/game-design.js.map +1 -0
  205. package/dist/tools/query/lookups.js +220 -0
  206. package/dist/tools/query/lookups.js.map +1 -0
  207. package/dist/tools/query/plan-budget.js +266 -0
  208. package/dist/tools/query/plan-budget.js.map +1 -0
  209. package/dist/tools/query/recipes.js +214 -0
  210. package/dist/tools/query/recipes.js.map +1 -0
  211. package/dist/tools/query/retrieval.js +128 -0
  212. package/dist/tools/query/retrieval.js.map +1 -0
  213. package/dist/tools/query/shared.js +121 -0
  214. package/dist/tools/query/shared.js.map +1 -0
  215. package/dist/tools/query/techniques.js +331 -0
  216. package/dist/tools/query/techniques.js.map +1 -0
  217. package/dist/tools/query/timing.js +106 -0
  218. package/dist/tools/query/timing.js.map +1 -0
  219. package/dist/tools/query/types.js +11 -0
  220. package/dist/tools/query/types.js.map +1 -0
  221. package/dist/tools/query.js +21 -0
  222. package/dist/tools/query.js.map +1 -0
  223. package/dist/tools/resources.js +186 -0
  224. package/dist/tools/resources.js.map +1 -0
  225. package/dist/tools/run-game.js +249 -0
  226. package/dist/tools/run-game.js.map +1 -0
  227. package/dist/tools/selfimprovement.js +367 -0
  228. package/dist/tools/selfimprovement.js.map +1 -0
  229. package/docker-compose.yml +35 -0
  230. package/docs/ARCHITECTURE.md +164 -0
  231. package/docs/CONVENTIONS-archetypes.md +158 -0
  232. package/docs/CONVENTIONS-failures.md +46 -0
  233. package/docs/CONVENTIONS-game-designs.md +75 -0
  234. package/docs/CONVENTIONS-hardware-reference.md +184 -0
  235. package/docs/CONVENTIONS-pitfalls.md +91 -0
  236. package/docs/CONVENTIONS-recipes.md +106 -0
  237. package/docs/CONVENTIONS-techniques.md +309 -0
  238. package/docs/CONVENTIONS-toolchain-reference.md +89 -0
  239. package/docs/ONTOLOGY.md +655 -0
  240. package/docs/art/art-production-reference.md +157 -0
  241. package/docs/art/asset-pipelines.md +586 -0
  242. package/docs/c64-failure-patterns.md +693 -0
  243. package/docs/demo-design/demo-composition.md +349 -0
  244. package/docs/demo-design/demo-design-philosophy.md +320 -0
  245. package/docs/demo-design/intro-cracktro-patterns.md +435 -0
  246. package/docs/figures/ctm-spd-embed-probe.png +0 -0
  247. package/docs/figures/dot-flag-mid-5000000.png +0 -0
  248. package/docs/figures/dot-flag-no-erase-12000000.png +0 -0
  249. package/docs/figures/edges.json +1 -0
  250. package/docs/figures/fig6-architecture.png +0 -0
  251. package/docs/figures/fig7-ontology.png +0 -0
  252. package/docs/figures/screen-dissolve-mid-frame25.png +0 -0
  253. package/docs/figures/screen-dissolve-wipe-frame25.png +0 -0
  254. package/docs/figures/spindle-two-parts-part1.png +0 -0
  255. package/docs/figures/spindle-two-parts-part2.png +0 -0
  256. package/docs/figures/starters/action-puzzle.png +0 -0
  257. package/docs/figures/starters/adventure.png +0 -0
  258. package/docs/figures/starters/beat-em-up.png +0 -0
  259. package/docs/figures/starters/demo.png +0 -0
  260. package/docs/figures/starters/platformer.png +0 -0
  261. package/docs/figures/starters/shmup-vertical.png +0 -0
  262. package/docs/figures/tape-kernal-master-ntsc.png +0 -0
  263. package/docs/figures/tape-kernal-master-pal.png +0 -0
  264. package/docs/figures/tape-turbo-loader-ntsc.png +0 -0
  265. package/docs/figures/tape-turbo-loader-pal.png +0 -0
  266. package/docs/figures/tape-turbo-master-ntsc.png +0 -0
  267. package/docs/figures/tape-turbo-master-pal.png +0 -0
  268. package/docs/figures/tech-tech-pad2-12000000.png +0 -0
  269. package/docs/figures/tech-tech-pad4-12000000.png +0 -0
  270. package/docs/figures/tech-tech-seven-12000000.png +0 -0
  271. package/docs/figures/twister-mid-5000000.png +0 -0
  272. package/docs/figures/twister-straight-12000000.png +0 -0
  273. package/docs/figures/vector-balls-nosort-ntsc.png +0 -0
  274. package/docs/figures/vector-balls-nosort.png +0 -0
  275. package/docs/formats/c64-file-formats.md +1212 -0
  276. package/docs/formats/iec-disk-reference.md +754 -0
  277. package/docs/game-design/c64-game-archetypes.md +273 -0
  278. package/docs/game-design/designs/falling-blocks.md +54 -0
  279. package/docs/game-design/designs/platformer-scaffold.md +48 -0
  280. package/docs/game-design/designs/simple-shmup.md +27 -0
  281. package/docs/game-design/enemy-behaviour-and-difficulty.md +349 -0
  282. package/docs/game-design/game-design-patterns.md +1189 -0
  283. package/docs/game-design/game-structure.md +354 -0
  284. package/docs/game-design/production-planning.md +349 -0
  285. package/docs/game-design/reference-game-sources.md +90 -0
  286. package/docs/hardware/6502-illegal-opcodes.md +1353 -0
  287. package/docs/hardware/6510-cpu-reference.md +2112 -0
  288. package/docs/hardware/c64-memory-map.md +3184 -0
  289. package/docs/hardware/c64-registers-reference.md +996 -0
  290. package/docs/hardware/cia-reference.md +1281 -0
  291. package/docs/hardware/kernal-routines-reference.md +1836 -0
  292. package/docs/hardware/pal-ntsc-reference.md +813 -0
  293. package/docs/hardware/sid-reference.md +1137 -0
  294. package/docs/hardware/vic-ii-reference.md +1714 -0
  295. package/docs/music/music-production-reference.md +403 -0
  296. package/docs/pitfalls/banking.md +1067 -0
  297. package/docs/pitfalls/cia.md +1147 -0
  298. package/docs/pitfalls/cpu.md +874 -0
  299. package/docs/pitfalls/input.md +282 -0
  300. package/docs/pitfalls/kernal-and-io.md +1615 -0
  301. package/docs/pitfalls/loader.md +1191 -0
  302. package/docs/pitfalls/logic.md +94 -0
  303. package/docs/pitfalls/maths.md +187 -0
  304. package/docs/pitfalls/raster-and-badline.md +721 -0
  305. package/docs/pitfalls/region-timing.md +660 -0
  306. package/docs/pitfalls/scroll.md +207 -0
  307. package/docs/pitfalls/sid.md +738 -0
  308. package/docs/pitfalls/sprite.md +979 -0
  309. package/docs/pitfalls/text-mode-render.md +1172 -0
  310. package/docs/recipes/cc65/cartridge-8k.md +398 -0
  311. package/docs/recipes/cc65/hello-world-conio.md +76 -0
  312. package/docs/recipes/cc65/memory-layout.md +312 -0
  313. package/docs/recipes/cc65/screenshots/README.md +14 -0
  314. package/docs/recipes/cc65/screenshots/cartridge-8k-ntsc.png +0 -0
  315. package/docs/recipes/cc65/screenshots/cartridge-8k.png +0 -0
  316. package/docs/recipes/cc65/screenshots/hello-world-conio.png +0 -0
  317. package/docs/recipes/cc65/screenshots/memory-layout-ntsc.png +0 -0
  318. package/docs/recipes/cc65/screenshots/memory-layout.png +0 -0
  319. package/docs/recipes/kickassembler/basic-float-calls.md +578 -0
  320. package/docs/recipes/kickassembler/basic-wedge.md +398 -0
  321. package/docs/recipes/kickassembler/big-font-scroller.md +620 -0
  322. package/docs/recipes/kickassembler/charset-copy-rom-to-ram.md +623 -0
  323. package/docs/recipes/kickassembler/cia-revision-detect.md +366 -0
  324. package/docs/recipes/kickassembler/colour-cycling.md +343 -0
  325. package/docs/recipes/kickassembler/colour-fade.md +303 -0
  326. package/docs/recipes/kickassembler/compare-16bit-signed.md +1007 -0
  327. package/docs/recipes/kickassembler/cracktro-template.md +526 -0
  328. package/docs/recipes/kickassembler/crt-banked.md +276 -0
  329. package/docs/recipes/kickassembler/dos-error-codes.md +466 -0
  330. package/docs/recipes/kickassembler/dot-flag.md +576 -0
  331. package/docs/recipes/kickassembler/drive-job-queue.md +667 -0
  332. package/docs/recipes/kickassembler/drive-via-probe.md +690 -0
  333. package/docs/recipes/kickassembler/dycp-scroller.md +568 -0
  334. package/docs/recipes/kickassembler/dypp-sprite-scroller.md +681 -0
  335. package/docs/recipes/kickassembler/dysp.md +670 -0
  336. package/docs/recipes/kickassembler/easyflash-save.md +800 -0
  337. package/docs/recipes/kickassembler/eight-way-scroll.md +1484 -0
  338. package/docs/recipes/kickassembler/file-io-roundtrip.md +546 -0
  339. package/docs/recipes/kickassembler/fire-effect.md +420 -0
  340. package/docs/recipes/kickassembler/fld.md +414 -0
  341. package/docs/recipes/kickassembler/fli-image.md +336 -0
  342. package/docs/recipes/kickassembler/four-player-read.md +672 -0
  343. package/docs/recipes/kickassembler/headless-verify.md +228 -0
  344. package/docs/recipes/kickassembler/hello-world.md +98 -0
  345. package/docs/recipes/kickassembler/high-score-insert.md +660 -0
  346. package/docs/recipes/kickassembler/hires-plot-line.md +723 -0
  347. package/docs/recipes/kickassembler/irq-chain.md +316 -0
  348. package/docs/recipes/kickassembler/irq-owns-port.md +668 -0
  349. package/docs/recipes/kickassembler/isometric-room.md +1145 -0
  350. package/docs/recipes/kickassembler/light-pen-read.md +926 -0
  351. package/docs/recipes/kickassembler/logic-rate-decoupling.md +670 -0
  352. package/docs/recipes/kickassembler/mci-interlace.md +543 -0
  353. package/docs/recipes/kickassembler/memory-layout.md +277 -0
  354. package/docs/recipes/kickassembler/mouse-1351-read.md +801 -0
  355. package/docs/recipes/kickassembler/nmi-timer-tick.md +562 -0
  356. package/docs/recipes/kickassembler/own-keyscan.md +881 -0
  357. package/docs/recipes/kickassembler/paddle-read.md +751 -0
  358. package/docs/recipes/kickassembler/palette-cells.md +219 -0
  359. package/docs/recipes/kickassembler/pseudo-3d-road.md +930 -0
  360. package/docs/recipes/kickassembler/pwm-digi.md +463 -0
  361. package/docs/recipes/kickassembler/raster-bars.md +264 -0
  362. package/docs/recipes/kickassembler/reu-dma.md +1019 -0
  363. package/docs/recipes/kickassembler/runtime-relocation.md +743 -0
  364. package/docs/recipes/kickassembler/screen-dissolve.md +545 -0
  365. package/docs/recipes/kickassembler/screen-wipe.md +408 -0
  366. package/docs/recipes/kickassembler/screenshots/README.md +72 -0
  367. package/docs/recipes/kickassembler/screenshots/basic-float-calls-ntsc.png +0 -0
  368. package/docs/recipes/kickassembler/screenshots/basic-float-calls.png +0 -0
  369. package/docs/recipes/kickassembler/screenshots/basic-wedge-ntsc.png +0 -0
  370. package/docs/recipes/kickassembler/screenshots/basic-wedge.png +0 -0
  371. package/docs/recipes/kickassembler/screenshots/big-font-scroller-ntsc.png +0 -0
  372. package/docs/recipes/kickassembler/screenshots/big-font-scroller.png +0 -0
  373. package/docs/recipes/kickassembler/screenshots/charset-copy-rom-to-ram-ntsc.png +0 -0
  374. package/docs/recipes/kickassembler/screenshots/charset-copy-rom-to-ram.png +0 -0
  375. package/docs/recipes/kickassembler/screenshots/cia-revision-detect-ntsc.png +0 -0
  376. package/docs/recipes/kickassembler/screenshots/cia-revision-detect.png +0 -0
  377. package/docs/recipes/kickassembler/screenshots/colour-cycling-ntsc.png +0 -0
  378. package/docs/recipes/kickassembler/screenshots/colour-cycling.png +0 -0
  379. package/docs/recipes/kickassembler/screenshots/colour-fade-ntsc.png +0 -0
  380. package/docs/recipes/kickassembler/screenshots/colour-fade.png +0 -0
  381. package/docs/recipes/kickassembler/screenshots/compare-16bit-signed-ntsc.png +0 -0
  382. package/docs/recipes/kickassembler/screenshots/compare-16bit-signed.png +0 -0
  383. package/docs/recipes/kickassembler/screenshots/cracktro-template.png +0 -0
  384. package/docs/recipes/kickassembler/screenshots/crt-banked-ntsc.png +0 -0
  385. package/docs/recipes/kickassembler/screenshots/crt-banked.png +0 -0
  386. package/docs/recipes/kickassembler/screenshots/dos-error-codes-ntsc.png +0 -0
  387. package/docs/recipes/kickassembler/screenshots/dos-error-codes.png +0 -0
  388. package/docs/recipes/kickassembler/screenshots/dot-flag-ntsc.png +0 -0
  389. package/docs/recipes/kickassembler/screenshots/dot-flag.png +0 -0
  390. package/docs/recipes/kickassembler/screenshots/drive-job-queue-ntsc.png +0 -0
  391. package/docs/recipes/kickassembler/screenshots/drive-job-queue.png +0 -0
  392. package/docs/recipes/kickassembler/screenshots/drive-via-probe-ntsc.png +0 -0
  393. package/docs/recipes/kickassembler/screenshots/drive-via-probe.png +0 -0
  394. package/docs/recipes/kickassembler/screenshots/dycp-scroller-ntsc.png +0 -0
  395. package/docs/recipes/kickassembler/screenshots/dycp-scroller.png +0 -0
  396. package/docs/recipes/kickassembler/screenshots/dypp-sprite-scroller-ntsc.png +0 -0
  397. package/docs/recipes/kickassembler/screenshots/dypp-sprite-scroller.png +0 -0
  398. package/docs/recipes/kickassembler/screenshots/dysp-ntsc.png +0 -0
  399. package/docs/recipes/kickassembler/screenshots/dysp.png +0 -0
  400. package/docs/recipes/kickassembler/screenshots/easyflash-save-ntsc-run2.png +0 -0
  401. package/docs/recipes/kickassembler/screenshots/easyflash-save-ntsc.png +0 -0
  402. package/docs/recipes/kickassembler/screenshots/easyflash-save-run2.png +0 -0
  403. package/docs/recipes/kickassembler/screenshots/easyflash-save.png +0 -0
  404. package/docs/recipes/kickassembler/screenshots/eight-way-scroll-ntsc.png +0 -0
  405. package/docs/recipes/kickassembler/screenshots/eight-way-scroll.png +0 -0
  406. package/docs/recipes/kickassembler/screenshots/file-io-roundtrip-ntsc.png +0 -0
  407. package/docs/recipes/kickassembler/screenshots/file-io-roundtrip.png +0 -0
  408. package/docs/recipes/kickassembler/screenshots/fire-effect-ntsc.png +0 -0
  409. package/docs/recipes/kickassembler/screenshots/fire-effect.png +0 -0
  410. package/docs/recipes/kickassembler/screenshots/fld-ntsc.png +0 -0
  411. package/docs/recipes/kickassembler/screenshots/fld.png +0 -0
  412. package/docs/recipes/kickassembler/screenshots/fli-image.png +0 -0
  413. package/docs/recipes/kickassembler/screenshots/four-player-read-ntsc.png +0 -0
  414. package/docs/recipes/kickassembler/screenshots/four-player-read.png +0 -0
  415. package/docs/recipes/kickassembler/screenshots/headless-verify-ntsc.png +0 -0
  416. package/docs/recipes/kickassembler/screenshots/headless-verify.png +0 -0
  417. package/docs/recipes/kickassembler/screenshots/hello-world.png +0 -0
  418. package/docs/recipes/kickassembler/screenshots/high-score-insert-ntsc.png +0 -0
  419. package/docs/recipes/kickassembler/screenshots/high-score-insert.png +0 -0
  420. package/docs/recipes/kickassembler/screenshots/hires-plot-line-ntsc.png +0 -0
  421. package/docs/recipes/kickassembler/screenshots/hires-plot-line.png +0 -0
  422. package/docs/recipes/kickassembler/screenshots/irq-chain-ntsc.png +0 -0
  423. package/docs/recipes/kickassembler/screenshots/irq-chain.png +0 -0
  424. package/docs/recipes/kickassembler/screenshots/irq-owns-port-ntsc.png +0 -0
  425. package/docs/recipes/kickassembler/screenshots/irq-owns-port.png +0 -0
  426. package/docs/recipes/kickassembler/screenshots/isometric-room-ntsc.png +0 -0
  427. package/docs/recipes/kickassembler/screenshots/isometric-room.png +0 -0
  428. package/docs/recipes/kickassembler/screenshots/light-pen-read-ntsc.png +0 -0
  429. package/docs/recipes/kickassembler/screenshots/light-pen-read.png +0 -0
  430. package/docs/recipes/kickassembler/screenshots/logic-rate-decoupling-ntsc.png +0 -0
  431. package/docs/recipes/kickassembler/screenshots/logic-rate-decoupling.png +0 -0
  432. package/docs/recipes/kickassembler/screenshots/mci-interlace-average.png +0 -0
  433. package/docs/recipes/kickassembler/screenshots/mci-interlace-frame-b.png +0 -0
  434. package/docs/recipes/kickassembler/screenshots/mci-interlace-ntsc.png +0 -0
  435. package/docs/recipes/kickassembler/screenshots/mci-interlace.png +0 -0
  436. package/docs/recipes/kickassembler/screenshots/memory-layout-ntsc.png +0 -0
  437. package/docs/recipes/kickassembler/screenshots/memory-layout.png +0 -0
  438. package/docs/recipes/kickassembler/screenshots/mouse-1351-read-ntsc.png +0 -0
  439. package/docs/recipes/kickassembler/screenshots/mouse-1351-read.png +0 -0
  440. package/docs/recipes/kickassembler/screenshots/nmi-timer-tick-ntsc.png +0 -0
  441. package/docs/recipes/kickassembler/screenshots/nmi-timer-tick.png +0 -0
  442. package/docs/recipes/kickassembler/screenshots/own-keyscan-ntsc.png +0 -0
  443. package/docs/recipes/kickassembler/screenshots/own-keyscan.png +0 -0
  444. package/docs/recipes/kickassembler/screenshots/paddle-read-ntsc.png +0 -0
  445. package/docs/recipes/kickassembler/screenshots/paddle-read.png +0 -0
  446. package/docs/recipes/kickassembler/screenshots/palette-cells-ntsc.png +0 -0
  447. package/docs/recipes/kickassembler/screenshots/palette-cells.png +0 -0
  448. package/docs/recipes/kickassembler/screenshots/pseudo-3d-road-ntsc.png +0 -0
  449. package/docs/recipes/kickassembler/screenshots/pseudo-3d-road.png +0 -0
  450. package/docs/recipes/kickassembler/screenshots/pwm-digi-ntsc.png +0 -0
  451. package/docs/recipes/kickassembler/screenshots/pwm-digi.png +0 -0
  452. package/docs/recipes/kickassembler/screenshots/raster-bars.png +0 -0
  453. package/docs/recipes/kickassembler/screenshots/reu-dma-ntsc.png +0 -0
  454. package/docs/recipes/kickassembler/screenshots/reu-dma.png +0 -0
  455. package/docs/recipes/kickassembler/screenshots/runtime-relocation-ntsc.png +0 -0
  456. package/docs/recipes/kickassembler/screenshots/runtime-relocation.png +0 -0
  457. package/docs/recipes/kickassembler/screenshots/screen-dissolve-ntsc.png +0 -0
  458. package/docs/recipes/kickassembler/screenshots/screen-dissolve.png +0 -0
  459. package/docs/recipes/kickassembler/screenshots/screen-wipe-ntsc.png +0 -0
  460. package/docs/recipes/kickassembler/screenshots/screen-wipe.png +0 -0
  461. package/docs/recipes/kickassembler/screenshots/scroll-panel-split-ntsc.png +0 -0
  462. package/docs/recipes/kickassembler/screenshots/scroll-panel-split.png +0 -0
  463. package/docs/recipes/kickassembler/screenshots/sfx-in-player-ntsc.png +0 -0
  464. package/docs/recipes/kickassembler/screenshots/sfx-in-player.png +0 -0
  465. package/docs/recipes/kickassembler/screenshots/shadebobs-ntsc.png +0 -0
  466. package/docs/recipes/kickassembler/screenshots/shadebobs.png +0 -0
  467. package/docs/recipes/kickassembler/screenshots/sideborder-open.png +0 -0
  468. package/docs/recipes/kickassembler/screenshots/sine-scroller.png +0 -0
  469. package/docs/recipes/kickassembler/screenshots/sine-table-runtime-ntsc.png +0 -0
  470. package/docs/recipes/kickassembler/screenshots/sine-table-runtime.png +0 -0
  471. package/docs/recipes/kickassembler/screenshots/software-sprite-preshifted-ntsc.png +0 -0
  472. package/docs/recipes/kickassembler/screenshots/software-sprite-preshifted.png +0 -0
  473. package/docs/recipes/kickassembler/screenshots/speedcode-generator-ntsc.png +0 -0
  474. package/docs/recipes/kickassembler/screenshots/speedcode-generator.png +0 -0
  475. package/docs/recipes/kickassembler/screenshots/sprite-border-scroller-ntsc.png +0 -0
  476. package/docs/recipes/kickassembler/screenshots/sprite-border-scroller.png +0 -0
  477. package/docs/recipes/kickassembler/screenshots/sprite-cache-flip-ntsc.png +0 -0
  478. package/docs/recipes/kickassembler/screenshots/sprite-cache-flip.png +0 -0
  479. package/docs/recipes/kickassembler/screenshots/sprite-multiplex-24.png +0 -0
  480. package/docs/recipes/kickassembler/screenshots/sprite-multiplex-game-ntsc.png +0 -0
  481. package/docs/recipes/kickassembler/screenshots/sprite-multiplex-game.png +0 -0
  482. package/docs/recipes/kickassembler/screenshots/sprite-priority-classes-ntsc.png +0 -0
  483. package/docs/recipes/kickassembler/screenshots/sprite-priority-classes.png +0 -0
  484. package/docs/recipes/kickassembler/screenshots/sprite-sine-chain-ntsc.png +0 -0
  485. package/docs/recipes/kickassembler/screenshots/sprite-sine-chain.png +0 -0
  486. package/docs/recipes/kickassembler/screenshots/sprite-stretcher-ntsc.png +0 -0
  487. package/docs/recipes/kickassembler/screenshots/sprite-stretcher.png +0 -0
  488. package/docs/recipes/kickassembler/screenshots/sprites-only-screen-ntsc.png +0 -0
  489. package/docs/recipes/kickassembler/screenshots/sprites-only-screen.png +0 -0
  490. package/docs/recipes/kickassembler/screenshots/sqrt-atan2-ntsc.png +0 -0
  491. package/docs/recipes/kickassembler/screenshots/sqrt-atan2.png +0 -0
  492. package/docs/recipes/kickassembler/screenshots/stable-raster-irq-control.png +0 -0
  493. package/docs/recipes/kickassembler/screenshots/stable-raster-irq.png +0 -0
  494. package/docs/recipes/kickassembler/screenshots/tech-tech-ntsc.png +0 -0
  495. package/docs/recipes/kickassembler/screenshots/tech-tech.png +0 -0
  496. package/docs/recipes/kickassembler/screenshots/tod-alarm-ntsc.png +0 -0
  497. package/docs/recipes/kickassembler/screenshots/tod-alarm.png +0 -0
  498. package/docs/recipes/kickassembler/screenshots/topbottom-border-open.png +0 -0
  499. package/docs/recipes/kickassembler/screenshots/twister-ntsc.png +0 -0
  500. package/docs/recipes/kickassembler/screenshots/twister.png +0 -0
  501. package/docs/recipes/kickassembler/screenshots/unit-test-driver-ntsc.png +0 -0
  502. package/docs/recipes/kickassembler/screenshots/unit-test-driver.png +0 -0
  503. package/docs/recipes/kickassembler/screenshots/vector-balls-ntsc.png +0 -0
  504. package/docs/recipes/kickassembler/screenshots/vector-balls.png +0 -0
  505. package/docs/recipes/kickassembler/screenshots/wireframe-ships-ntsc.png +0 -0
  506. package/docs/recipes/kickassembler/screenshots/wireframe-ships.png +0 -0
  507. package/docs/recipes/kickassembler/scroll-panel-split.md +381 -0
  508. package/docs/recipes/kickassembler/sfx-in-player.md +907 -0
  509. package/docs/recipes/kickassembler/shadebobs.md +522 -0
  510. package/docs/recipes/kickassembler/sideborder-open.md +338 -0
  511. package/docs/recipes/kickassembler/sine-scroller.md +321 -0
  512. package/docs/recipes/kickassembler/sine-table-runtime.md +815 -0
  513. package/docs/recipes/kickassembler/software-sprite-preshifted.md +725 -0
  514. package/docs/recipes/kickassembler/speedcode-generator.md +606 -0
  515. package/docs/recipes/kickassembler/sprite-border-scroller.md +696 -0
  516. package/docs/recipes/kickassembler/sprite-cache-flip.md +1071 -0
  517. package/docs/recipes/kickassembler/sprite-multiplex-24.md +326 -0
  518. package/docs/recipes/kickassembler/sprite-multiplex-game.md +986 -0
  519. package/docs/recipes/kickassembler/sprite-priority-classes.md +463 -0
  520. package/docs/recipes/kickassembler/sprite-sine-chain.md +338 -0
  521. package/docs/recipes/kickassembler/sprite-stretcher.md +501 -0
  522. package/docs/recipes/kickassembler/sprites-only-screen.md +550 -0
  523. package/docs/recipes/kickassembler/sqrt-atan2.md +1014 -0
  524. package/docs/recipes/kickassembler/stable-raster-irq.md +360 -0
  525. package/docs/recipes/kickassembler/tape-turbo-loader.md +875 -0
  526. package/docs/recipes/kickassembler/tech-tech.md +679 -0
  527. package/docs/recipes/kickassembler/tod-alarm.md +754 -0
  528. package/docs/recipes/kickassembler/topbottom-border-open.md +407 -0
  529. package/docs/recipes/kickassembler/twister.md +518 -0
  530. package/docs/recipes/kickassembler/unit-test-driver.md +314 -0
  531. package/docs/recipes/kickassembler/vector-balls.md +545 -0
  532. package/docs/recipes/kickassembler/wireframe-ships.md +2324 -0
  533. package/docs/recipes/oscar64/actor-activation-window.md +807 -0
  534. package/docs/recipes/oscar64/adventure-engine.md +1094 -0
  535. package/docs/recipes/oscar64/attract-replay.md +465 -0
  536. package/docs/recipes/oscar64/beat-em-up-lanes.md +563 -0
  537. package/docs/recipes/oscar64/bfs-distance-map.md +723 -0
  538. package/docs/recipes/oscar64/bitmap-koala-viewer.md +355 -0
  539. package/docs/recipes/oscar64/car-contact.md +950 -0
  540. package/docs/recipes/oscar64/cave-scan.md +609 -0
  541. package/docs/recipes/oscar64/char-bullets.md +477 -0
  542. package/docs/recipes/oscar64/charset-animation.md +383 -0
  543. package/docs/recipes/oscar64/charset-parallax.md +401 -0
  544. package/docs/recipes/oscar64/destructible-terrain.md +1099 -0
  545. package/docs/recipes/oscar64/difficulty-tables.md +654 -0
  546. package/docs/recipes/oscar64/dig-and-guards.md +975 -0
  547. package/docs/recipes/oscar64/directory-reader.md +535 -0
  548. package/docs/recipes/oscar64/divide-check.md +581 -0
  549. package/docs/recipes/oscar64/double-buffer-nomirror.md +205 -0
  550. package/docs/recipes/oscar64/double-buffer.md +255 -0
  551. package/docs/recipes/oscar64/falling-blocks.md +874 -0
  552. package/docs/recipes/oscar64/fixed-point-jump-velocity.md +289 -0
  553. package/docs/recipes/oscar64/fixed-point-jump.md +210 -0
  554. package/docs/recipes/oscar64/flip-screen-rooms.md +793 -0
  555. package/docs/recipes/oscar64/frame-sync-loop-overrun.md +214 -0
  556. package/docs/recipes/oscar64/frame-sync-loop.md +254 -0
  557. package/docs/recipes/oscar64/game-tree-search.md +743 -0
  558. package/docs/recipes/oscar64/ghost-targeting.md +867 -0
  559. package/docs/recipes/oscar64/headless-verify.md +345 -0
  560. package/docs/recipes/oscar64/hello-world.md +83 -0
  561. package/docs/recipes/oscar64/high-score-persist.md +772 -0
  562. package/docs/recipes/oscar64/iffl-kernal-skip.md +421 -0
  563. package/docs/recipes/oscar64/joystick-input.md +358 -0
  564. package/docs/recipes/oscar64/lane-pursuit.md +1028 -0
  565. package/docs/recipes/oscar64/level-rle-decoder.md +620 -0
  566. package/docs/recipes/oscar64/lfsr-random-seed2.md +338 -0
  567. package/docs/recipes/oscar64/lfsr-random.md +403 -0
  568. package/docs/recipes/oscar64/load-asset-runtime.md +492 -0
  569. package/docs/recipes/oscar64/memory-layout.md +232 -0
  570. package/docs/recipes/oscar64/mixed-fighters.md +781 -0
  571. package/docs/recipes/oscar64/multi-sprite-object.md +533 -0
  572. package/docs/recipes/oscar64/nav-area-pathfinding.md +881 -0
  573. package/docs/recipes/oscar64/object-pool.md +572 -0
  574. package/docs/recipes/oscar64/pal-ntsc-detect.md +257 -0
  575. package/docs/recipes/oscar64/password-state.md +532 -0
  576. package/docs/recipes/oscar64/per-frame-hitbox.md +651 -0
  577. package/docs/recipes/oscar64/petscii-screen-codes.md +428 -0
  578. package/docs/recipes/oscar64/platformer-scaffold.md +1467 -0
  579. package/docs/recipes/oscar64/print-number.md +367 -0
  580. package/docs/recipes/oscar64/raster-bars.md +239 -0
  581. package/docs/recipes/oscar64/raster-profile-bars.md +404 -0
  582. package/docs/recipes/oscar64/rel-side-sectors.md +391 -0
  583. package/docs/recipes/oscar64/relative-file-records.md +378 -0
  584. package/docs/recipes/oscar64/save-load-seq-file.md +365 -0
  585. package/docs/recipes/oscar64/screenshots/README.md +99 -0
  586. package/docs/recipes/oscar64/screenshots/actor-activation-window-ntsc.png +0 -0
  587. package/docs/recipes/oscar64/screenshots/actor-activation-window.png +0 -0
  588. package/docs/recipes/oscar64/screenshots/adventure-engine-ntsc.png +0 -0
  589. package/docs/recipes/oscar64/screenshots/adventure-engine.png +0 -0
  590. package/docs/recipes/oscar64/screenshots/attract-replay-ntsc.png +0 -0
  591. package/docs/recipes/oscar64/screenshots/attract-replay.png +0 -0
  592. package/docs/recipes/oscar64/screenshots/beat-em-up-lanes-ntsc.png +0 -0
  593. package/docs/recipes/oscar64/screenshots/beat-em-up-lanes.png +0 -0
  594. package/docs/recipes/oscar64/screenshots/bfs-distance-map-ntsc.png +0 -0
  595. package/docs/recipes/oscar64/screenshots/bfs-distance-map.png +0 -0
  596. package/docs/recipes/oscar64/screenshots/bitmap-koala-viewer.png +0 -0
  597. package/docs/recipes/oscar64/screenshots/car-contact-ntsc.png +0 -0
  598. package/docs/recipes/oscar64/screenshots/car-contact.png +0 -0
  599. package/docs/recipes/oscar64/screenshots/cave-scan-ntsc.png +0 -0
  600. package/docs/recipes/oscar64/screenshots/cave-scan.png +0 -0
  601. package/docs/recipes/oscar64/screenshots/char-bullets-ntsc.png +0 -0
  602. package/docs/recipes/oscar64/screenshots/char-bullets.png +0 -0
  603. package/docs/recipes/oscar64/screenshots/charset-animation-ntsc.png +0 -0
  604. package/docs/recipes/oscar64/screenshots/charset-animation.png +0 -0
  605. package/docs/recipes/oscar64/screenshots/charset-parallax-ntsc.png +0 -0
  606. package/docs/recipes/oscar64/screenshots/charset-parallax.png +0 -0
  607. package/docs/recipes/oscar64/screenshots/destructible-terrain-ntsc.png +0 -0
  608. package/docs/recipes/oscar64/screenshots/destructible-terrain.png +0 -0
  609. package/docs/recipes/oscar64/screenshots/difficulty-tables-ntsc.png +0 -0
  610. package/docs/recipes/oscar64/screenshots/difficulty-tables.png +0 -0
  611. package/docs/recipes/oscar64/screenshots/dig-and-guards-ntsc.png +0 -0
  612. package/docs/recipes/oscar64/screenshots/dig-and-guards.png +0 -0
  613. package/docs/recipes/oscar64/screenshots/directory-reader-ntsc.png +0 -0
  614. package/docs/recipes/oscar64/screenshots/directory-reader.png +0 -0
  615. package/docs/recipes/oscar64/screenshots/divide-check-ntsc.png +0 -0
  616. package/docs/recipes/oscar64/screenshots/divide-check.png +0 -0
  617. package/docs/recipes/oscar64/screenshots/double-buffer-nomirror.png +0 -0
  618. package/docs/recipes/oscar64/screenshots/double-buffer-ntsc.png +0 -0
  619. package/docs/recipes/oscar64/screenshots/double-buffer.png +0 -0
  620. package/docs/recipes/oscar64/screenshots/falling-blocks-ntsc.png +0 -0
  621. package/docs/recipes/oscar64/screenshots/falling-blocks.png +0 -0
  622. package/docs/recipes/oscar64/screenshots/fixed-point-jump-ntsc.png +0 -0
  623. package/docs/recipes/oscar64/screenshots/fixed-point-jump-velocity-ntsc.png +0 -0
  624. package/docs/recipes/oscar64/screenshots/fixed-point-jump-velocity.png +0 -0
  625. package/docs/recipes/oscar64/screenshots/fixed-point-jump.png +0 -0
  626. package/docs/recipes/oscar64/screenshots/flip-screen-rooms-ntsc.png +0 -0
  627. package/docs/recipes/oscar64/screenshots/flip-screen-rooms.png +0 -0
  628. package/docs/recipes/oscar64/screenshots/frame-sync-loop-ntsc.png +0 -0
  629. package/docs/recipes/oscar64/screenshots/frame-sync-loop-overrun.png +0 -0
  630. package/docs/recipes/oscar64/screenshots/frame-sync-loop.png +0 -0
  631. package/docs/recipes/oscar64/screenshots/game-tree-search-ntsc.png +0 -0
  632. package/docs/recipes/oscar64/screenshots/game-tree-search.png +0 -0
  633. package/docs/recipes/oscar64/screenshots/ghost-targeting-ntsc.png +0 -0
  634. package/docs/recipes/oscar64/screenshots/ghost-targeting.png +0 -0
  635. package/docs/recipes/oscar64/screenshots/headless-verify-ntsc.png +0 -0
  636. package/docs/recipes/oscar64/screenshots/headless-verify.png +0 -0
  637. package/docs/recipes/oscar64/screenshots/hello-world.png +0 -0
  638. package/docs/recipes/oscar64/screenshots/high-score-persist-ntsc.png +0 -0
  639. package/docs/recipes/oscar64/screenshots/high-score-persist.png +0 -0
  640. package/docs/recipes/oscar64/screenshots/iffl-kernal-skip-ntsc.png +0 -0
  641. package/docs/recipes/oscar64/screenshots/iffl-kernal-skip.png +0 -0
  642. package/docs/recipes/oscar64/screenshots/joystick-input-ntsc.png +0 -0
  643. package/docs/recipes/oscar64/screenshots/joystick-input.png +0 -0
  644. package/docs/recipes/oscar64/screenshots/lane-pursuit-ntsc.png +0 -0
  645. package/docs/recipes/oscar64/screenshots/lane-pursuit.png +0 -0
  646. package/docs/recipes/oscar64/screenshots/level-rle-decoder-ntsc.png +0 -0
  647. package/docs/recipes/oscar64/screenshots/level-rle-decoder.png +0 -0
  648. package/docs/recipes/oscar64/screenshots/lfsr-random-ntsc.png +0 -0
  649. package/docs/recipes/oscar64/screenshots/lfsr-random-seed2.png +0 -0
  650. package/docs/recipes/oscar64/screenshots/lfsr-random.png +0 -0
  651. package/docs/recipes/oscar64/screenshots/load-asset-runtime-ntsc.png +0 -0
  652. package/docs/recipes/oscar64/screenshots/load-asset-runtime.png +0 -0
  653. package/docs/recipes/oscar64/screenshots/memory-layout-ntsc.png +0 -0
  654. package/docs/recipes/oscar64/screenshots/memory-layout.png +0 -0
  655. package/docs/recipes/oscar64/screenshots/mixed-fighters-ntsc.png +0 -0
  656. package/docs/recipes/oscar64/screenshots/mixed-fighters.png +0 -0
  657. package/docs/recipes/oscar64/screenshots/multi-sprite-object-ntsc.png +0 -0
  658. package/docs/recipes/oscar64/screenshots/multi-sprite-object.png +0 -0
  659. package/docs/recipes/oscar64/screenshots/nav-area-pathfinding-ntsc.png +0 -0
  660. package/docs/recipes/oscar64/screenshots/nav-area-pathfinding.png +0 -0
  661. package/docs/recipes/oscar64/screenshots/object-pool-ntsc.png +0 -0
  662. package/docs/recipes/oscar64/screenshots/object-pool.png +0 -0
  663. package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-ntsc.png +0 -0
  664. package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-oldntsc.png +0 -0
  665. package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-pal.png +0 -0
  666. package/docs/recipes/oscar64/screenshots/password-state-ntsc.png +0 -0
  667. package/docs/recipes/oscar64/screenshots/password-state.png +0 -0
  668. package/docs/recipes/oscar64/screenshots/per-frame-hitbox-ntsc.png +0 -0
  669. package/docs/recipes/oscar64/screenshots/per-frame-hitbox.png +0 -0
  670. package/docs/recipes/oscar64/screenshots/petscii-screen-codes-ntsc.png +0 -0
  671. package/docs/recipes/oscar64/screenshots/petscii-screen-codes.png +0 -0
  672. package/docs/recipes/oscar64/screenshots/platformer-scaffold-d64-ntsc.png +0 -0
  673. package/docs/recipes/oscar64/screenshots/platformer-scaffold-d64.png +0 -0
  674. package/docs/recipes/oscar64/screenshots/platformer-scaffold-ntsc.png +0 -0
  675. package/docs/recipes/oscar64/screenshots/platformer-scaffold.png +0 -0
  676. package/docs/recipes/oscar64/screenshots/print-number-ntsc.png +0 -0
  677. package/docs/recipes/oscar64/screenshots/print-number.png +0 -0
  678. package/docs/recipes/oscar64/screenshots/raster-bars.png +0 -0
  679. package/docs/recipes/oscar64/screenshots/raster-profile-bars-ntsc.png +0 -0
  680. package/docs/recipes/oscar64/screenshots/raster-profile-bars.png +0 -0
  681. package/docs/recipes/oscar64/screenshots/rel-side-sectors-ntsc.png +0 -0
  682. package/docs/recipes/oscar64/screenshots/rel-side-sectors.png +0 -0
  683. package/docs/recipes/oscar64/screenshots/relative-file-records-ntsc.png +0 -0
  684. package/docs/recipes/oscar64/screenshots/relative-file-records.png +0 -0
  685. package/docs/recipes/oscar64/screenshots/save-load-seq-file-ntsc.png +0 -0
  686. package/docs/recipes/oscar64/screenshots/save-load-seq-file.png +0 -0
  687. package/docs/recipes/oscar64/screenshots/seeded-level-fill-ntsc.png +0 -0
  688. package/docs/recipes/oscar64/screenshots/seeded-level-fill.png +0 -0
  689. package/docs/recipes/oscar64/screenshots/sfx-engine-ntsc.png +0 -0
  690. package/docs/recipes/oscar64/screenshots/sfx-engine.png +0 -0
  691. package/docs/recipes/oscar64/screenshots/sid-music-player.png +0 -0
  692. package/docs/recipes/oscar64/screenshots/simple-shmup.png +0 -0
  693. package/docs/recipes/oscar64/screenshots/slope-collision-ntsc.png +0 -0
  694. package/docs/recipes/oscar64/screenshots/slope-collision.png +0 -0
  695. package/docs/recipes/oscar64/screenshots/soft-scroll-h.png +0 -0
  696. package/docs/recipes/oscar64/screenshots/sprite-animation-table-ntsc.png +0 -0
  697. package/docs/recipes/oscar64/screenshots/sprite-animation-table.png +0 -0
  698. package/docs/recipes/oscar64/screenshots/sprite-multiplex-8.png +0 -0
  699. package/docs/recipes/oscar64/screenshots/stable-raster-irq.png +0 -0
  700. package/docs/recipes/oscar64/screenshots/text-input-ntsc.png +0 -0
  701. package/docs/recipes/oscar64/screenshots/text-input.png +0 -0
  702. package/docs/recipes/oscar64/screenshots/text-overlay-playfield-gameover.png +0 -0
  703. package/docs/recipes/oscar64/screenshots/text-overlay-playfield-notrail-control.png +0 -0
  704. package/docs/recipes/oscar64/screenshots/text-overlay-playfield-ntsc.png +0 -0
  705. package/docs/recipes/oscar64/screenshots/text-overlay-playfield-ungated-control.png +0 -0
  706. package/docs/recipes/oscar64/screenshots/text-overlay-playfield.png +0 -0
  707. package/docs/recipes/oscar64/screenshots/text-window-menu-ntsc.png +0 -0
  708. package/docs/recipes/oscar64/screenshots/text-window-menu.png +0 -0
  709. package/docs/recipes/oscar64/screenshots/tile-grid-collision-ntsc.png +0 -0
  710. package/docs/recipes/oscar64/screenshots/tile-grid-collision.png +0 -0
  711. package/docs/recipes/oscar64/screenshots/tile-map-render-ntsc.png +0 -0
  712. package/docs/recipes/oscar64/screenshots/tile-map-render.png +0 -0
  713. package/docs/recipes/oscar64/screenshots/two-player-ntsc.png +0 -0
  714. package/docs/recipes/oscar64/screenshots/two-player.png +0 -0
  715. package/docs/recipes/oscar64/screenshots/two-word-parser-ntsc.png +0 -0
  716. package/docs/recipes/oscar64/screenshots/two-word-parser.png +0 -0
  717. package/docs/recipes/oscar64/screenshots/vehicle-control-ntsc.png +0 -0
  718. package/docs/recipes/oscar64/screenshots/vehicle-control.png +0 -0
  719. package/docs/recipes/oscar64/screenshots/wave-director-ntsc.png +0 -0
  720. package/docs/recipes/oscar64/screenshots/wave-director.png +0 -0
  721. package/docs/recipes/oscar64/seeded-level-fill.md +364 -0
  722. package/docs/recipes/oscar64/sfx-engine.md +514 -0
  723. package/docs/recipes/oscar64/sid-music-player.md +441 -0
  724. package/docs/recipes/oscar64/simple-shmup.md +1000 -0
  725. package/docs/recipes/oscar64/slope-collision.md +610 -0
  726. package/docs/recipes/oscar64/soft-scroll-h.md +261 -0
  727. package/docs/recipes/oscar64/sprite-animation-table.md +671 -0
  728. package/docs/recipes/oscar64/sprite-multiplex-8.md +314 -0
  729. package/docs/recipes/oscar64/stable-raster-irq.md +236 -0
  730. package/docs/recipes/oscar64/text-input.md +274 -0
  731. package/docs/recipes/oscar64/text-overlay-playfield.md +617 -0
  732. package/docs/recipes/oscar64/text-window-menu.md +551 -0
  733. package/docs/recipes/oscar64/tile-grid-collision.md +563 -0
  734. package/docs/recipes/oscar64/tile-map-render.md +395 -0
  735. package/docs/recipes/oscar64/two-player.md +528 -0
  736. package/docs/recipes/oscar64/two-word-parser.md +510 -0
  737. package/docs/recipes/oscar64/vehicle-control.md +771 -0
  738. package/docs/recipes/oscar64/wave-director.md +787 -0
  739. package/docs/recipes/runs.json +1606 -0
  740. package/docs/runtime/sim6502-reference.md +466 -0
  741. package/docs/runtime/vice-mcp-reference.md +351 -0
  742. package/docs/runtime/vice-reference.md +1393 -0
  743. package/docs/techniques/bitmap-modes.md +685 -0
  744. package/docs/techniques/cpu-cycle-tricks.md +1570 -0
  745. package/docs/techniques/effects-vector-3d.md +1431 -0
  746. package/docs/techniques/file-io.md +1427 -0
  747. package/docs/techniques/input.md +1072 -0
  748. package/docs/techniques/loaders-packers.md +768 -0
  749. package/docs/techniques/logic.md +3091 -0
  750. package/docs/techniques/maths.md +1524 -0
  751. package/docs/techniques/memory-banking.md +1686 -0
  752. package/docs/techniques/music-sid.md +1144 -0
  753. package/docs/techniques/raster.md +1543 -0
  754. package/docs/techniques/scroll.md +1292 -0
  755. package/docs/techniques/sprite.md +2566 -0
  756. package/docs/techniques/text-mode-render.md +717 -0
  757. package/docs/techniques/text.md +1095 -0
  758. package/docs/techniques/transitions.md +405 -0
  759. package/docs/toolchains/cartconv-reference.md +262 -0
  760. package/docs/toolchains/cc1541-reference.md +316 -0
  761. package/docs/toolchains/cc65-reference.md +667 -0
  762. package/docs/toolchains/error-sources/cc65/README.md +1 -0
  763. package/docs/toolchains/error-sources/cc65/missing-setcpu.s +4 -0
  764. package/docs/toolchains/error-sources/cc65/range-error.s +4 -0
  765. package/docs/toolchains/error-sources/cc65/segment-overflow.c +6 -0
  766. package/docs/toolchains/error-sources/cc65/unresolved-external.c +6 -0
  767. package/docs/toolchains/error-sources/kickassembler/README.md +1 -0
  768. package/docs/toolchains/error-sources/kickassembler/branch-out-of-range.asm +4 -0
  769. package/docs/toolchains/error-sources/kickassembler/failed-assert.asm +3 -0
  770. package/docs/toolchains/error-sources/kickassembler/flat-lib.asm +4 -0
  771. package/docs/toolchains/error-sources/kickassembler/import-source-no-namespace.asm +5 -0
  772. package/docs/toolchains/error-sources/kickassembler/macro-without-parentheses.asm +6 -0
  773. package/docs/toolchains/error-sources/kickassembler/memory-block-overlap.asm +4 -0
  774. package/docs/toolchains/error-sources/kickassembler/symbol-clash-lib.asm +5 -0
  775. package/docs/toolchains/error-sources/kickassembler/symbol-clash-main.asm +6 -0
  776. package/docs/toolchains/error-sources/kickassembler/unknown-symbol.asm +4 -0
  777. package/docs/toolchains/error-sources/oscar64/README.md +1 -0
  778. package/docs/toolchains/error-sources/oscar64/asm-addressing-mode.c +7 -0
  779. package/docs/toolchains/error-sources/oscar64/region-overflow.c +6 -0
  780. package/docs/toolchains/error-sources/oscar64/undefined-extern-var.c +5 -0
  781. package/docs/toolchains/error-sources/oscar64/undefined-extern.c +6 -0
  782. package/docs/toolchains/error-sources/oscar64/unknown-vic-field.c +6 -0
  783. package/docs/toolchains/error-sources/oscar64/void-main.c +3 -0
  784. package/docs/toolchains/kickassembler-reference.md +1020 -0
  785. package/docs/toolchains/memory-layout-planning.md +362 -0
  786. package/docs/toolchains/oscar64-headers-reference.md +583 -0
  787. package/docs/toolchains/oscar64-reference.md +892 -0
  788. package/docs/toolchains/petcat-reference.md +290 -0
  789. package/docs/toolchains/png2prg.md +188 -0
  790. package/docs/toolchains/release-disk.md +314 -0
  791. package/docs/toolchains/sidreloc.md +236 -0
  792. package/docs/toolchains/spindle-reference.md +487 -0
  793. package/docs/toolchains/tape-mastering.md +592 -0
  794. package/docs/toolchains/unit-testing-6502.md +281 -0
  795. package/docs/workflow/agent-harness.md +478 -0
  796. package/package.json +105 -0
  797. package/templates/_harness/CLAUDE.md.template +91 -0
  798. package/templates/_harness/PLAN.md.template +72 -0
  799. package/templates/_harness/check.py +442 -0
  800. package/templates/_harness/claude/settings.json +17 -0
  801. package/templates/_harness/gen-asm-header.py +96 -0
  802. package/templates/_harness/harness.mk +273 -0
  803. package/templates/_harness/hooks/plan-gate.py +185 -0
  804. package/templates/_harness/meter/frame_meter.asm +311 -0
  805. package/templates/_harness/meter/frame_meter.c +155 -0
  806. package/templates/_harness/meter/frame_meter.h +80 -0
  807. package/templates/_harness/zp-used.py +104 -0
  808. package/templates/action-puzzle/.claude/settings.json +17 -0
  809. package/templates/action-puzzle/.mcp.json +10 -0
  810. package/templates/action-puzzle/CLAUDE.md +120 -0
  811. package/templates/action-puzzle/Makefile +125 -0
  812. package/templates/action-puzzle/PLAN.md +388 -0
  813. package/templates/action-puzzle/README.md +240 -0
  814. package/templates/action-puzzle/expect.json +25 -0
  815. package/templates/action-puzzle/src/cave.c +280 -0
  816. package/templates/action-puzzle/src/cave.h +80 -0
  817. package/templates/action-puzzle/src/gen_autopilot.h +20 -0
  818. package/templates/action-puzzle/src/gen_caves.h +59 -0
  819. package/templates/action-puzzle/src/gen_notes.h +16 -0
  820. package/templates/action-puzzle/src/hiscore.c +152 -0
  821. package/templates/action-puzzle/src/hiscore.h +41 -0
  822. package/templates/action-puzzle/src/level.c +64 -0
  823. package/templates/action-puzzle/src/level.h +24 -0
  824. package/templates/action-puzzle/src/main.c +599 -0
  825. package/templates/action-puzzle/src/render.c +239 -0
  826. package/templates/action-puzzle/src/render.h +31 -0
  827. package/templates/action-puzzle/src/sound.c +158 -0
  828. package/templates/action-puzzle/src/sound.h +22 -0
  829. package/templates/action-puzzle/tools/disk_check.py +77 -0
  830. package/templates/action-puzzle/tools/drive.py +106 -0
  831. package/templates/action-puzzle/tools/gen.py +454 -0
  832. package/templates/action-puzzle/tools/model_check.py +49 -0
  833. package/templates/action-puzzle/tools/verdict_codes.py +24 -0
  834. package/templates/adventure/.claude/settings.json +17 -0
  835. package/templates/adventure/.mcp.json +10 -0
  836. package/templates/adventure/CLAUDE.md +118 -0
  837. package/templates/adventure/Makefile +113 -0
  838. package/templates/adventure/PLAN.md +396 -0
  839. package/templates/adventure/README.md +277 -0
  840. package/templates/adventure/expect.json +25 -0
  841. package/templates/adventure/src/engine.c +543 -0
  842. package/templates/adventure/src/engine.h +48 -0
  843. package/templates/adventure/src/gen_script.h +141 -0
  844. package/templates/adventure/src/gen_world.h +511 -0
  845. package/templates/adventure/src/main.c +439 -0
  846. package/templates/adventure/src/picture.c +83 -0
  847. package/templates/adventure/src/picture.h +16 -0
  848. package/templates/adventure/src/save.c +159 -0
  849. package/templates/adventure/src/save.h +25 -0
  850. package/templates/adventure/src/sound.c +71 -0
  851. package/templates/adventure/src/sound.h +16 -0
  852. package/templates/adventure/src/text.c +349 -0
  853. package/templates/adventure/src/text.h +40 -0
  854. package/templates/adventure/tools/disk_check.py +128 -0
  855. package/templates/adventure/tools/drive.py +119 -0
  856. package/templates/adventure/tools/gen.py +1033 -0
  857. package/templates/adventure/tools/world.py +356 -0
  858. package/templates/beat-em-up/.claude/settings.json +17 -0
  859. package/templates/beat-em-up/.mcp.json +10 -0
  860. package/templates/beat-em-up/CLAUDE.md +136 -0
  861. package/templates/beat-em-up/Makefile +96 -0
  862. package/templates/beat-em-up/PLAN.md +502 -0
  863. package/templates/beat-em-up/README.md +315 -0
  864. package/templates/beat-em-up/expect-gameover.json +8 -0
  865. package/templates/beat-em-up/expect.json +29 -0
  866. package/templates/beat-em-up/src/anim.c +70 -0
  867. package/templates/beat-em-up/src/art.c +344 -0
  868. package/templates/beat-em-up/src/autopilot.h +154 -0
  869. package/templates/beat-em-up/src/brute.c +273 -0
  870. package/templates/beat-em-up/src/enemy.c +281 -0
  871. package/templates/beat-em-up/src/engine.asm +512 -0
  872. package/templates/beat-em-up/src/fighter.c +384 -0
  873. package/templates/beat-em-up/src/game.h +269 -0
  874. package/templates/beat-em-up/src/hud.c +135 -0
  875. package/templates/beat-em-up/src/main.c +341 -0
  876. package/templates/beat-em-up/src/sound.c +44 -0
  877. package/templates/beat-em-up/src/street.c +93 -0
  878. package/templates/beat-em-up/src/verdict.h +523 -0
  879. package/templates/beat-em-up/src/view.c +362 -0
  880. package/templates/beat-em-up/tools/drive.py +105 -0
  881. package/templates/beat-em-up/tools/flickercheck.py +273 -0
  882. package/templates/demo/.claude/settings.json +17 -0
  883. package/templates/demo/.mcp.json +10 -0
  884. package/templates/demo/CLAUDE.md +129 -0
  885. package/templates/demo/Makefile +66 -0
  886. package/templates/demo/PLAN.md +401 -0
  887. package/templates/demo/README.md +322 -0
  888. package/templates/demo/expect.json +945 -0
  889. package/templates/demo/src/config.asm +80 -0
  890. package/templates/demo/src/framework.asm +390 -0
  891. package/templates/demo/src/main.asm +139 -0
  892. package/templates/demo/src/music.asm +197 -0
  893. package/templates/demo/src/part_main.asm +279 -0
  894. package/templates/demo/src/part_title.asm +79 -0
  895. package/templates/demo/src/parts.asm +43 -0
  896. package/templates/demo/src/tables.asm +73 -0
  897. package/templates/demo/src/verdict.asm +179 -0
  898. package/templates/demo/tools/audio.py +80 -0
  899. package/templates/demo/tools/gen_expect.py +167 -0
  900. package/templates/demo/tools/probe.py +95 -0
  901. package/templates/hello/.claude/settings.json +17 -0
  902. package/templates/hello/.mcp.json +10 -0
  903. package/templates/hello/CLAUDE.md +100 -0
  904. package/templates/hello/Makefile +38 -0
  905. package/templates/hello/PLAN.md +200 -0
  906. package/templates/hello/expect.json +20 -0
  907. package/templates/hello/src/main.c +161 -0
  908. package/templates/hello/src/sprite.asm +27 -0
  909. package/templates/hello-kick/.claude/settings.json +17 -0
  910. package/templates/hello-kick/.mcp.json +10 -0
  911. package/templates/hello-kick/CLAUDE.md +100 -0
  912. package/templates/hello-kick/Makefile +17 -0
  913. package/templates/hello-kick/PLAN.md +199 -0
  914. package/templates/hello-kick/expect.json +20 -0
  915. package/templates/hello-kick/src/main.asm +233 -0
  916. package/templates/platformer/.claude/settings.json +17 -0
  917. package/templates/platformer/.mcp.json +10 -0
  918. package/templates/platformer/CLAUDE.md +125 -0
  919. package/templates/platformer/Makefile +86 -0
  920. package/templates/platformer/PLAN.md +360 -0
  921. package/templates/platformer/README.md +277 -0
  922. package/templates/platformer/expect.json +32 -0
  923. package/templates/platformer/src/actors.c +289 -0
  924. package/templates/platformer/src/anim.c +51 -0
  925. package/templates/platformer/src/art.c +212 -0
  926. package/templates/platformer/src/autopilot.h +30 -0
  927. package/templates/platformer/src/engine.asm +253 -0
  928. package/templates/platformer/src/game.h +248 -0
  929. package/templates/platformer/src/hud.c +90 -0
  930. package/templates/platformer/src/level.c +184 -0
  931. package/templates/platformer/src/main.c +306 -0
  932. package/templates/platformer/src/player.c +268 -0
  933. package/templates/platformer/src/sound.c +42 -0
  934. package/templates/platformer/src/verdict.h +206 -0
  935. package/templates/platformer/src/view.c +331 -0
  936. package/templates/platformer/stage-expect.json +6 -0
  937. package/templates/platformer/tools/drive.py +112 -0
  938. package/templates/platformer/tools/tearcheck.py +229 -0
  939. package/templates/shmup-vertical/.claude/settings.json +17 -0
  940. package/templates/shmup-vertical/.mcp.json +10 -0
  941. package/templates/shmup-vertical/CLAUDE.md +137 -0
  942. package/templates/shmup-vertical/Makefile +131 -0
  943. package/templates/shmup-vertical/PLAN.md +569 -0
  944. package/templates/shmup-vertical/README.md +189 -0
  945. package/templates/shmup-vertical/expect.json +38 -0
  946. package/templates/shmup-vertical/src/bullets.c +158 -0
  947. package/templates/shmup-vertical/src/bullets.h +35 -0
  948. package/templates/shmup-vertical/src/display.c +239 -0
  949. package/templates/shmup-vertical/src/display.h +40 -0
  950. package/templates/shmup-vertical/src/game.h +85 -0
  951. package/templates/shmup-vertical/src/glyph.asm +358 -0
  952. package/templates/shmup-vertical/src/hiscore.c +141 -0
  953. package/templates/shmup-vertical/src/hiscore.h +16 -0
  954. package/templates/shmup-vertical/src/hit.asm +180 -0
  955. package/templates/shmup-vertical/src/hitbox.c +59 -0
  956. package/templates/shmup-vertical/src/hitbox.h +25 -0
  957. package/templates/shmup-vertical/src/kernel.asm +330 -0
  958. package/templates/shmup-vertical/src/level.c +174 -0
  959. package/templates/shmup-vertical/src/level.h +27 -0
  960. package/templates/shmup-vertical/src/main.c +824 -0
  961. package/templates/shmup-vertical/src/mux.asm +321 -0
  962. package/templates/shmup-vertical/src/sound.asm +255 -0
  963. package/templates/shmup-vertical/src/step.asm +169 -0
  964. package/templates/shmup-vertical/src/waves.c +195 -0
  965. package/templates/shmup-vertical/src/waves.h +33 -0
  966. package/templates/shmup-vertical/stage-expect.json +6 -0
  967. package/templates/shmup-vertical/tools/drive.py +152 -0
  968. package/templates/shmup-vertical/tools/joytest.py +159 -0
  969. package/templates/shmup-vertical/tools/meter.py +15 -0
  970. package/templates/shmup-vertical/tools/phases.py +73 -0
@@ -0,0 +1,1467 @@
1
+ ---
2
+ recipe: platformer-scaffold
3
+ toolchain: oscar64
4
+ output_format: PRG
5
+ region: both
6
+ techniques: [tile_map_render, tile_grid_collision, fixed_point_8_8, jump_arc_table, joystick_edge_detect, joystick_autorepeat, object_pool, lfsr_random, frame_sync_loop, decimal_print, sid_play_routine_pattern, sfx_engine_beside_music, kernal_file_write_seq, kernal_file_read_seq, error_channel_check]
7
+ file_formats: [PRG]
8
+ uses_registers: [D000, D001, D010, D011, D012, D015, D019, D01A, D020, D021, D027, D028, D029, D02A, D02B, D02C, D02D, D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414, D418, D41B, DC00, DC02, DC03, DC06, DC07, DC0F]
9
+ uses_kernal: [SETLFS, SETNAM, OPEN, CLOSE, CHKIN, CHKOUT, CLRCHN, CHRIN, CHROUT, READST]
10
+ scaffolds: [single_screen_platformer]
11
+ ---
12
+
13
+ <!-- doc-type: recipe -->
14
+
15
+ # Oscar64 Platformer Scaffold
16
+
17
+ ## Synopsis
18
+
19
+ A single-screen platformer in one C file, meant to be copied as the
20
+ starting point of a new game rather than read as a demonstration. It has
21
+ the parts every platformer needs and nothing else: a tile map decoded from
22
+ RLE with row checksums, a player with 8.8 fixed-point motion and a jump
23
+ arc from a table, tile-grid collision at the body's corners, ladders, a
24
+ joystick reader with press events and a stated auto-repeat rule, a pool of
25
+ six enemies fed by a wave table and a Galois LFSR, a raster-synced frame
26
+ loop with a CIA timer over the whole loop body and a budget bar in the
27
+ border, a decimal HUD, a jump effect beside a stub three-voice tune, and
28
+ a high score written to disk at game over and read back at the next
29
+ start. A result byte at `$02FF` and the border colour report the verdict
30
+ so a headless run needs no human.
31
+
32
+ The `techniques` list did not name `sid_play_routine_pattern` until #22
33
+ step 3, although `tune_play` plays a three-voice stub every frame (row 8
34
+ below); a budget built from the list left the tune out.
35
+
36
+ It is the game the graph arm of the KB build test wrote from this
37
+ knowledge base, made self-contained and given a fixed layout. Every part
38
+ below names the recipe it was lifted from; an agent that has read those
39
+ pages will recognise the listings. `AUTOPILOT` is on by default so the
40
+ pinned run plays itself; set it to 0 for a build that reads control port 2.
41
+
42
+ ## Memory plan
43
+
44
+ Nothing is placed by pragma. Oscar64's default `c64` target lays the
45
+ program out from `$0801` and this page reads the addresses off the
46
+ `.map` file the compiler wrote, so they are the figures for this build
47
+ and move when the code does.
48
+
49
+ | What | Where | Why there |
50
+ |---|---|---|
51
+ | BASIC stub and start-up | `$0801` to `$0853` | Oscar64's default; `RUN` or `-autostart` enters here |
52
+ | Code | `$0880` to `$11FE` | default code section; 6,473 bytes of PRG in all, including the two-byte load address |
53
+ | Constant data (RLE map stream, checksums, jump table, wave table, sprite rows) | `$11FE` to `$2148` | default data section, straight after the code |
54
+ | Variables (`bss`) | `$2148` to `$25D5` | the decoded map is first at `$2148` (880 bytes, 40 by 22); the high-score record, its read-back buffer and the 40-byte drive reply follow |
55
+ | Software stack | grows down from `$9FFE` | Oscar64's default under BASIC ROM |
56
+ | Sprite shapes | `$0340` to `$03FF` | the cassette buffer, blocks 13, 14 and 15 of VIC bank 0, so no bank switch and no region pragma; three shapes is all a scaffold needs and the buffer holds exactly three |
57
+ | Screen | `$0400`, colour at `$D800` | the KERNAL's own screen; rows 0 and 1 are the HUD, rows 2 to 23 the map, row 24 the drive reply |
58
+ | Character set | ROM upper-case set | no custom charset, so nothing to place; the tiles are `$A0` (solid block) and `H` |
59
+
60
+ When a game outgrows this, the first thing to move is the sprite shapes:
61
+ the KERNAL's tape routines own the cassette buffer, and a game with more
62
+ than three shapes wants a `#pragma region` block of its own, which
63
+ `docs/recipes/oscar64/memory-layout.md` shows. The map array is the
64
+ second: a scroller wants it under the VIC's bank or in a second buffer.
65
+
66
+ ## Module skeleton
67
+
68
+ Build in this order. Each step is one function or one group, each names
69
+ the brief requirement it owns, and each was runnable on its own in the
70
+ original build before the next was added.
71
+
72
+ | Order | Function or group | Owns | Lifted from |
73
+ |---|---|---|---|
74
+ | 1 | `fault`, `ASSERT`, `RESULT` | the verdict: `$02FF` = 1 pass, 2 fail; border green or red | `headless-verify.md` |
75
+ | 2 | `put_str`, `put_dec`, `put_petscii` | screen-code text, decimal without division, PETSCII from the drive | `print-number.md` (the subtract-powers-of-ten loop; the original build divided and the HUD ate the frame) |
76
+ | 3 | `rnd` | 16-bit Galois LFSR, taps `$B400`, seeded from SID voice 3 | `lfsr-random.md` |
77
+ | 4 | `rle_row`, `fold`, `map_decode_and_draw`, `tile_at`, `solid_at` | the map: RLE rows, row checksums, the decoded grid, the tile lookups | `tile-map-render.md` for the format and decoder; `tile-grid-collision.md` for the lookups |
78
+ | 5 | `player_reset`, `player_update`, `player_draw` | 8.8 position, the jump table, gravity after the table, corner probes, landing snap, head bump, ladders | `fixed-point-jump.md` and `tile-grid-collision.md` |
79
+ | 6 | `joy_edge`, `repeat_step`, `autopilot_port` | press events by previous-frame comparison, auto-repeat after 20 frames then every 4, a synthetic port byte when `AUTOPILOT` is on | `joystick-input.md`; the autopilot is this page's own |
80
+ | 7 | `pool_alloc`, `spawn_enemy`, `wave_step`, `update_enemies`, `hit_enemy` | six slots in parallel arrays, a wave table on a 450-frame cycle, despawn off screen, a software box test | `object-pool.md` |
81
+ | 8 | `tune_play`, `sfx_jump`, `sfx_update` | a stub tune on three voices every frame, the effect borrowing voice 2 after it | `sfx-engine.md` (nobody has listened to either) |
82
+ | 9 | `drive_reply`, `hs_load`, `hs_save`, `hs_start`, `hs_game_over` | a five-byte SEQ record, first run gets 62 and writes, later runs load, replace is scratch then write, the no-device guard, the error channel on the bottom row | `high-score-persist.md` |
83
+ | 10 | `on_frame`, `timer_start`, `timer_stop`, `hud_draw`, `main` | a raster IRQ at row 250 bumping a tick byte, the loop waiting on it, CIA1 timer B across the loop body, dropped-frame count, the budget bar under `BUDGET_BAR` | `frame-sync-loop.md`, with the timer harness from `tile-map-render.md` |
84
+
85
+ The tables in the listing were generated. The generator is at the end
86
+ of this page in a text fence; run it when the map or the jump changes and
87
+ paste its output over the block marked "Generated tables".
88
+
89
+ ## Source
90
+
91
+ ```c
92
+ // platformer-scaffold.c -- a single-screen platformer for the stock C64,
93
+ // Oscar64, in one file. Copy it, keep the skeleton, replace the art and
94
+ // the map. Every part names the KB recipe it was lifted from:
95
+ //
96
+ // tile map recipes/oscar64/tile-map-render.md (RLE rows, no offset table)
97
+ // collision recipes/oscar64/tile-grid-collision.md (probe the body corners)
98
+ // jump / 8.8 recipes/oscar64/fixed-point-jump.md (gravity first, then position)
99
+ // joystick recipes/oscar64/joystick-input.md (joy_edge, repeat_step)
100
+ // object pool recipes/oscar64/object-pool.md (parallel arrays, scan alloc)
101
+ // LFSR recipes/oscar64/lfsr-random.md (16-bit Galois, taps $B400)
102
+ // frame loop recipes/oscar64/frame-sync-loop.md (rirq at row 250, tick byte, budget bar)
103
+ // decimal HUD recipes/oscar64/print-number.md (subtract powers of ten)
104
+ // sfx + tune stub recipes/oscar64/sfx-engine.md (voice 2 borrowed from the tune)
105
+ // high score recipes/oscar64/high-score-persist.md (kernalio.h, scratch then write)
106
+ // result byte recipes/oscar64/headless-verify.md ($02FF, border 5/2)
107
+ //
108
+ // Build: oscar64 -tm=c64 -O2 -o=platformer-scaffold.prg platformer-scaffold.c
109
+ // Defines: AUTOPILOT=1 (default) drives the joystick from the LFSR so a
110
+ // headless run plays itself; 0 reads control port 2.
111
+ // BUDGET_BAR=1 (default) paints the border while the loop works.
112
+ // DISK_WAIT_FRAMES=50 (default) frames to wait before the first OPEN.
113
+
114
+ #include <c64/vic.h>
115
+ #include <c64/cia.h>
116
+ #include <c64/sid.h>
117
+ #include <c64/sprites.h>
118
+ #include <c64/rasterirq.h>
119
+ #include <c64/kernalio.h>
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Generated tables. These were produced by the gen.py script quoted on the
123
+ // recipe page; regenerate them there when you change the map or the jump.
124
+ #define MAP_W 40
125
+ #define MAP_H 22
126
+ #define MAP_STREAM_LEN 244
127
+ static const char map_rle[] = {
128
+ 0x01, 0x01, 0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01,
129
+ 0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x82, 0x00,
130
+ 0x89, 0x02, 0x8c, 0x00, 0x8b, 0x02, 0x84, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8a, 0x00, 0x01,
131
+ 0x03, 0x9b, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8a, 0x00, 0x01, 0x03, 0x9b, 0x00, 0x01, 0x01,
132
+ 0x00, 0x01, 0x01, 0x8a, 0x00, 0x01, 0x03, 0x9b, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x84, 0x00,
133
+ 0x8a, 0x02, 0x01, 0x03, 0x89, 0x02, 0x85, 0x00, 0x87, 0x02, 0x82, 0x00, 0x01, 0x01, 0x00, 0x01,
134
+ 0x01, 0x8e, 0x00, 0x01, 0x03, 0x97, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8e, 0x00, 0x01, 0x03,
135
+ 0x97, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8e, 0x00, 0x01, 0x03, 0x97, 0x00, 0x01, 0x01, 0x00,
136
+ 0x01, 0x01, 0x89, 0x02, 0x85, 0x00, 0x01, 0x03, 0x84, 0x00, 0x8b, 0x02, 0x88, 0x00, 0x01, 0x01,
137
+ 0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8e, 0x00, 0x82, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00,
138
+ 0x01, 0x03, 0x8f, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01,
139
+ 0x01, 0x00, 0x01, 0x01, 0x83, 0x00, 0x8e, 0x02, 0x85, 0x00, 0x01, 0x03, 0x84, 0x00, 0x89, 0x02,
140
+ 0x82, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01, 0x01, 0x00,
141
+ 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01,
142
+ 0x03, 0x8f, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01, 0x01,
143
+ 0x00, 0xa8, 0x01, 0x00,
144
+ };
145
+ static const unsigned map_row_chk[MAP_H] = {
146
+ 0x72be, 0x72be, 0x72be, 0x72be, 0x739e, 0xadb3, 0xadb3, 0xadb3, 0xf563, 0x5f23, 0x5f23, 0x5f23, 0x57ff, 0xff26, 0xff03, 0xff03, 0x3d61, 0xff03, 0xff03, 0xff03, 0xff03, 0xf010
147
+ };
148
+ #define JUMP_FRAMES 45
149
+ static const int jump_vy[JUMP_FRAMES] = {
150
+ -856, -816, -776, -736, -696, -656, -616, -576,
151
+ -536, -496, -456, -416, -376, -336, -296, -256,
152
+ -216, -176, -136, -96, -56, -16, 24, 64,
153
+ 104, 144, 184, 224, 264, 304, 344, 384,
154
+ 424, 464, 504, 544, 584, 624, 664, 704,
155
+ 744, 784, 824, 864, 904,
156
+ };
157
+ #define N_PLATFORM_ROWS 5
158
+ static const char platform_rows[N_PLATFORM_ROWS] = { 4, 8, 12, 16, 21 };
159
+
160
+ #ifndef AUTOPILOT
161
+ #define AUTOPILOT 1
162
+ #endif
163
+ #ifndef BUDGET_BAR
164
+ #define BUDGET_BAR 1
165
+ #endif
166
+
167
+ #define SCREEN ((char *)0x0400)
168
+ #define COLOUR ((char *)0xd800)
169
+ #define SPR_PLAYER ((char *)0x0340) // block 13, cassette buffer
170
+ #define SPR_ENEMY_A ((char *)0x0380) // block 14
171
+ #define SPR_ENEMY_B ((char *)0x03c0) // block 15
172
+ #define RESULT (*(volatile char *)0x02ff)
173
+
174
+ #define CODE_PASS 0x01
175
+ #define CODE_FAIL 0x02
176
+
177
+ #define MAP_ROW 2 // first screen row of the map
178
+ #define HUD_ROW 0
179
+ #define CYC_ROW 1
180
+ #define DRIVE_ROW 24
181
+
182
+ #define SYNC_ROW 250 // rirq row; IRQ lands on line 251
183
+
184
+ #define PASS_FRAME 600 // autopilot: frames without fault
185
+ #define FORCE_OVER 800 // autopilot: force game over here
186
+
187
+ // Tiles
188
+ #define T_EMPTY 0
189
+ #define T_BRICK 1
190
+ #define T_PLAT 2
191
+ #define T_LADDER 3
192
+
193
+ // Joystick bits (active low on the port)
194
+ #define JOY_UP 0x01
195
+ #define JOY_DOWN 0x02
196
+ #define JOY_LEFT 0x04
197
+ #define JOY_RIGHT 0x08
198
+ #define JOY_FIRE 0x10
199
+ #define JOY_MASK 0x1f
200
+ #define REPEAT_DELAY 20
201
+ #define REPEAT_RATE 4
202
+
203
+ // Physics (8.8)
204
+ #define WALK_SPEED 0x0140 // 1.25 px/frame
205
+ #define GRAVITY 0x0028 // the G of the table generator
206
+ #define MAX_FALL 0x0400 // 4 px/frame
207
+ #define CLIMB_SPEED 1 // px/frame on a ladder
208
+
209
+ // Player body inside the 24x21 sprite: x 8..15, y 5..20
210
+ #define BODY_L 8
211
+ #define BODY_R 15
212
+ #define BODY_T 5
213
+ #define BODY_B 20
214
+ #define PLAYER_START_X 32 // world px (sprite x - 24)
215
+ #define PLAYER_START_Y (21*8 - 21) // feet on the floor row (tile row 21)
216
+
217
+ // Object pool
218
+ #define MAX_OBJ 6
219
+ #define OBJ_FREE 0
220
+ #define OBJ_ALIVE 1
221
+ #define NO_SLOT 0xff
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // Fault reporting: the result-byte contract from headless-verify.md
225
+ static char fault_code; // 0 none, else a fault number
226
+
227
+ static void fault(char n)
228
+ {
229
+ if (fault_code == 0)
230
+ {
231
+ fault_code = n;
232
+ RESULT = CODE_FAIL;
233
+ vic.color_border = 2;
234
+ }
235
+ }
236
+ #define ASSERT(c, n) do { if (!(c)) fault(n); } while (0)
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // Text (screen codes, no KERNAL)
240
+ static void put_str(char row, char col, const char *s)
241
+ {
242
+ char *p = SCREEN + 40 * row + col;
243
+ while (*s)
244
+ {
245
+ char c = *s++;
246
+ *p++ = (c >= 'a' && c <= 'z') ? c - 'a' + 1 : c;
247
+ }
248
+ }
249
+
250
+ // Decimal without division: subtract powers of ten. Worst case 45
251
+ // 16-bit compare-and-subtracts for five digits, far cheaper than five
252
+ // 16-bit divisions (the first build divided and the HUD ate the frame:
253
+ // the budget bar was white end to end while the timed region said 4,000).
254
+ static const unsigned pow10[4] = { 10000, 1000, 100, 10 };
255
+ static void put_dec(char row, char col, unsigned v, char width)
256
+ {
257
+ char *p = SCREEN + 40 * row + col;
258
+ char k = 4 - (width - 1); // first power for this width
259
+ for (; k < 4; k++)
260
+ {
261
+ unsigned d = pow10[k];
262
+ char n = '0';
263
+ while (v >= d) { v -= d; n++; }
264
+ *p++ = n;
265
+ }
266
+ *p = '0' + (char)v;
267
+ }
268
+
269
+ // PETSCII from the drive to screen codes: digits and punctuation map to
270
+ // themselves, upper-case letters $41..$5A become 1..26.
271
+ static void put_petscii(char row, char col, const char *s, char max)
272
+ {
273
+ char *p = SCREEN + 40 * row + col;
274
+ char i = 0;
275
+ bool ended = false;
276
+ while (i < max)
277
+ {
278
+ char c = ended ? 0 : s[i];
279
+ if (c == 0) { ended = true; c = ' '; }
280
+ else if (c >= 0x41 && c <= 0x5a) c -= 0x40;
281
+ else if (c >= 0x61 && c <= 0x7a) c -= 0x60;
282
+ else if (c < 0x20 || c > 0x5f) c = '?';
283
+ p[i++] = c;
284
+ }
285
+ }
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // LFSR (lfsr-random.md): 16-bit Galois, right shift, taps $B400.
289
+ static unsigned rng;
290
+
291
+ static char rnd(void)
292
+ {
293
+ unsigned s = rng;
294
+ char carry = s & 1;
295
+ s >>= 1;
296
+ if (carry) s ^= 0xb400;
297
+ rng = s;
298
+ return (char)s;
299
+ }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Tile map (tile-map-render.md RLE, one stream per row)
303
+ static char map[MAP_W * MAP_H];
304
+ static const char tile_code[4] = { 0x20, 0xa0, 0xa0, 0x08 }; // ' ', block, block, 'H'
305
+ static const char tile_colour[4] = { 0, 9, 12, 7 }; // -, brown, grey, yellow
306
+
307
+ static const char *rle_row(const char *src, char *dst, char *len)
308
+ {
309
+ char n = 0;
310
+ for (;;)
311
+ {
312
+ char c = *src++;
313
+ if (c == 0)
314
+ break;
315
+ if (c & 0x80)
316
+ {
317
+ char v = *src++;
318
+ char k = c & 0x7f;
319
+ do { dst[n++] = v; } while (--k);
320
+ }
321
+ else
322
+ {
323
+ do { dst[n++] = *src++; } while (--c);
324
+ }
325
+ }
326
+ *len = n;
327
+ return src;
328
+ }
329
+
330
+ static unsigned fold(unsigned chk, unsigned v)
331
+ {
332
+ return (chk ^ v) * 5 + 1;
333
+ }
334
+
335
+ static void map_decode_and_draw(void)
336
+ {
337
+ const char *src = map_rle;
338
+ for (char y = 0; y < MAP_H; y++)
339
+ {
340
+ char len;
341
+ char *row = map + y * MAP_W;
342
+ src = rle_row(src, row, &len);
343
+ ASSERT(len == MAP_W, 1);
344
+ unsigned chk = 0;
345
+ for (char x = 0; x < MAP_W; x++)
346
+ chk = fold(chk, row[x]);
347
+ ASSERT(chk == map_row_chk[y], 2);
348
+ char *s = SCREEN + (MAP_ROW + y) * 40;
349
+ char *k = COLOUR + (MAP_ROW + y) * 40;
350
+ for (char x = 0; x < MAP_W; x++)
351
+ {
352
+ char t = row[x];
353
+ s[x] = tile_code[t];
354
+ k[x] = tile_colour[t];
355
+ }
356
+ }
357
+ ASSERT(src == map_rle + MAP_STREAM_LEN, 3);
358
+ }
359
+
360
+ // Tile at world pixel (wx 0..319, wy 0..199). Outside the map is empty
361
+ // above and solid below/sideways so nothing escapes.
362
+ static char tile_at(int wx, int wy)
363
+ {
364
+ if (wx < 0 || wx >= 320) return T_BRICK;
365
+ int ty = (wy >> 3) - MAP_ROW;
366
+ if (ty < 0) return T_EMPTY;
367
+ if (ty >= MAP_H) return T_BRICK;
368
+ return map[ty * MAP_W + (wx >> 3)];
369
+ }
370
+
371
+ static bool solid_at(int wx, int wy)
372
+ {
373
+ char t = tile_at(wx, wy);
374
+ return t == T_BRICK || t == T_PLAT;
375
+ }
376
+
377
+ // ---------------------------------------------------------------------------
378
+ // Player
379
+ // Position: Y is 8.8 in an UNSIGNED 16-bit word (max 200*256 = 51200; a
380
+ // signed int wraps at 128 px, which the fixed-point-jump page warns about).
381
+ // X needs 9 bits of pixel, so it is an int of pixels plus an 8-bit fraction:
382
+ // the walk adds WALK_SPEED to the fraction and carries into the pixel.
383
+ static int px_i; // world px of the sprite's left edge
384
+ static char px_sub; // fraction of a pixel (1/256)
385
+ static unsigned py_fp; // 8.8 world py of the sprite's top
386
+ static int vy_fp; // 8.8, + is down
387
+ static char jump_n; // 0 = not in the table part of a jump
388
+ static bool on_ground, on_ladder;
389
+ static char facing; // 0 right, 1 left
390
+ static char invuln; // frames of invulnerability left
391
+ static char lives;
392
+ static unsigned score, hiscore;
393
+ static bool game_over;
394
+ static char blocked_x; // set when a wall stopped the walk
395
+
396
+ static void player_reset(void)
397
+ {
398
+ px_i = PLAYER_START_X;
399
+ px_sub = 0;
400
+ py_fp = (unsigned)PLAYER_START_Y << 8;
401
+ vy_fp = 0;
402
+ jump_n = 0;
403
+ on_ground = true;
404
+ on_ladder = false;
405
+ facing = 0;
406
+ }
407
+
408
+ // ---------------------------------------------------------------------------
409
+ // Sound (sfx-engine.md): the tune stub writes all three voices every frame;
410
+ // the jump effect borrows voice 2 and re-pokes it after the tune each frame.
411
+ // Nobody has listened to this; it is register-level only.
412
+ struct SfxFrame { unsigned freq; char ctrl; };
413
+ #define SFX_END 0xff
414
+ static const struct SfxFrame jump_frames[] = {
415
+ { 0x1000, SID_CTRL_TEST | SID_CTRL_GATE },
416
+ { 0x1200, SID_CTRL_TRI | SID_CTRL_GATE },
417
+ { 0x1600, SID_CTRL_TRI | SID_CTRL_GATE },
418
+ { 0x1c00, SID_CTRL_TRI | SID_CTRL_GATE },
419
+ { 0x2400, SID_CTRL_TRI | SID_CTRL_GATE },
420
+ { 0x2c00, SID_CTRL_TRI | SID_CTRL_GATE },
421
+ { 0x3000, SID_CTRL_TRI },
422
+ { 0x2800, SID_CTRL_TRI },
423
+ { 0x0000, SFX_END }
424
+ };
425
+ static const struct SfxFrame *sfx_pos; // 0 = voice 2 belongs to the tune
426
+ static unsigned sfx_started;
427
+ static unsigned tune_frame;
428
+ static const unsigned tune_base[3] = { 0x0800, 0x0c00, 0x1200 };
429
+
430
+ static void tune_play(void)
431
+ {
432
+ char step = tune_frame & 31;
433
+ char gate = (tune_frame & 8) ? SID_CTRL_GATE : 0;
434
+ volatile char *r = (volatile char *)0xd400;
435
+ for (char v = 0; v < 3; v++)
436
+ {
437
+ unsigned f = tune_base[v] + (unsigned)step * 16;
438
+ r[0] = (char)f;
439
+ r[1] = f >> 8;
440
+ r[2] = 0x00;
441
+ r[3] = 0x04;
442
+ r[4] = SID_CTRL_TRI | gate;
443
+ r[5] = SID_ATK_8 | SID_DKY_168;
444
+ r[6] = 0x80 | SID_DKY_300;
445
+ r += 7;
446
+ }
447
+ sid.fmodevol = 0x0f;
448
+ tune_frame++;
449
+ }
450
+
451
+ static void sfx_jump(void)
452
+ {
453
+ sfx_pos = jump_frames;
454
+ sfx_started++;
455
+ }
456
+
457
+ static void sfx_update(void)
458
+ {
459
+ if (!sfx_pos) return;
460
+ if (sfx_pos->ctrl == SFX_END) { sfx_pos = nullptr; return; }
461
+ volatile char *r = (volatile char *)0xd407; // voice 2
462
+ r[5] = SID_ATK_2 | SID_DKY_48;
463
+ r[6] = 0xa0 | SID_DKY_204;
464
+ r[0] = (char)sfx_pos->freq;
465
+ r[1] = sfx_pos->freq >> 8;
466
+ r[2] = 0x00;
467
+ r[3] = 0x08;
468
+ r[4] = sfx_pos->ctrl;
469
+ sfx_pos++;
470
+ }
471
+
472
+ // ---------------------------------------------------------------------------
473
+ // Object pool (object-pool.md): parallel arrays, scan allocator.
474
+ static char obj_state[MAX_OBJ];
475
+ static char obj_type[MAX_OBJ];
476
+ static int obj_x[MAX_OBJ]; // world px, may run past either edge
477
+ static char obj_y[MAX_OBJ]; // world py (sprite top)
478
+ static signed char obj_dx[MAX_OBJ];
479
+ static char obj_anim[MAX_OBJ];
480
+ static unsigned spawned, despawned, spawn_refused;
481
+
482
+ // Wave table: frame offset within a cycle, type (1 slow, 2 fast), count.
483
+ // Rows and directions come from the LFSR so no two cycles look alike.
484
+ struct Wave { unsigned frame; char type; char count; };
485
+ static const struct Wave wave_table[] = {
486
+ { 30, 1, 1 },
487
+ { 90, 1, 2 },
488
+ { 150, 2, 1 },
489
+ { 210, 1, 2 },
490
+ { 270, 2, 2 },
491
+ { 330, 1, 3 },
492
+ { 390, 2, 2 },
493
+ { 0xffff, 0, 0 }
494
+ };
495
+ #define WAVE_CYCLE 450
496
+ static char wave_pos;
497
+ static unsigned wave_base;
498
+
499
+ static char pool_alloc(void)
500
+ {
501
+ for (char i = 0; i < MAX_OBJ; i++)
502
+ if (obj_state[i] == OBJ_FREE)
503
+ return i;
504
+ return NO_SLOT;
505
+ }
506
+
507
+ static void spawn_enemy(char type)
508
+ {
509
+ char s = pool_alloc();
510
+ if (s == NO_SLOT) { spawn_refused++; return; }
511
+ char r = rnd();
512
+ char row = platform_rows[r % N_PLATFORM_ROWS];
513
+ char speed = (type == 2) ? 2 : 1;
514
+ obj_type[s] = type;
515
+ obj_y[s] = (MAP_ROW + row) * 8 - 21; // feet on the tile top
516
+ if (r & 0x80) { obj_x[s] = -24; obj_dx[s] = speed; }
517
+ else { obj_x[s] = 320; obj_dx[s] = -speed; }
518
+ obj_anim[s] = 0;
519
+ obj_state[s] = OBJ_ALIVE;
520
+ spawned++;
521
+ }
522
+
523
+ static void wave_step(unsigned frame)
524
+ {
525
+ unsigned rel = frame - wave_base;
526
+ if (rel >= WAVE_CYCLE)
527
+ {
528
+ wave_base = frame;
529
+ wave_pos = 0;
530
+ rel = 0;
531
+ }
532
+ while (wave_table[wave_pos].frame == rel)
533
+ {
534
+ char n = wave_table[wave_pos].count;
535
+ for (char k = 0; k < n; k++)
536
+ spawn_enemy(wave_table[wave_pos].type);
537
+ wave_pos++;
538
+ ASSERT(wave_pos < 8, 4);
539
+ }
540
+ }
541
+
542
+ static void update_enemies(void)
543
+ {
544
+ for (char i = 0; i < MAX_OBJ; i++)
545
+ {
546
+ if (obj_state[i] == OBJ_FREE)
547
+ {
548
+ spr_show(1 + i, false);
549
+ continue;
550
+ }
551
+ ASSERT(obj_state[i] == OBJ_ALIVE, 5);
552
+ int x = obj_x[i] + obj_dx[i];
553
+ obj_x[i] = x;
554
+ if (x <= -24 || x >= 320)
555
+ {
556
+ obj_state[i] = OBJ_FREE; // left the screen: despawn
557
+ despawned++;
558
+ score += 10; // dodged
559
+ spr_show(1 + i, false);
560
+ continue;
561
+ }
562
+ obj_anim[i]++;
563
+ spr_image(1 + i, (obj_anim[i] & 8) ? 14 : 15);
564
+ spr_color(1 + i, obj_type[i] == 2 ? VCOL_LT_RED : VCOL_CYAN);
565
+ spr_move(1 + i, x + 24, obj_y[i] + 50);
566
+ spr_show(1 + i, true);
567
+ }
568
+ }
569
+
570
+ // ---------------------------------------------------------------------------
571
+ // Input (joystick-input.md): press events by previous-frame comparison,
572
+ // delayed auto-repeat on the horizontal lines.
573
+ struct JoyEvents { char newp, held, released; };
574
+
575
+ static void joy_edge(char prev, char cur, struct JoyEvents *e)
576
+ {
577
+ char pressed = ~cur & JOY_MASK;
578
+ e->newp = pressed & prev;
579
+ e->held = pressed & ~prev & JOY_MASK;
580
+ e->released = cur & ~prev & JOY_MASK;
581
+ }
582
+
583
+ // Auto-repeat rule: fires on the first frame of a press, again after
584
+ // REPEAT_DELAY frames, then every REPEAT_RATE frames while held. It is
585
+ // used for the ladder step so a held UP climbs one tile per repeat burst
586
+ // rather than sliding; left/right are level-driven (held moves).
587
+ static char repeat_step(char *age, bool pressed)
588
+ {
589
+ if (!pressed) { *age = 0; return 0; }
590
+ char a = *age + 1;
591
+ if (a == REPEAT_DELAY + REPEAT_RATE) a = REPEAT_DELAY;
592
+ *age = a;
593
+ return (a == 1 || a == REPEAT_DELAY) ? 1 : 0;
594
+ }
595
+
596
+ #if AUTOPILOT
597
+ // The autopilot: a synthetic active-low port byte from the game's LFSR.
598
+ // It walks in one direction for a random spell, turns round at walls,
599
+ // jumps at random and when an enemy is near on its own row, and climbs
600
+ // when standing at a ladder. Fire is a one-frame pulse so the edge
601
+ // detector sees a press event exactly as a real stick would give one.
602
+ static char ap_dir; // JOY_LEFT or JOY_RIGHT
603
+ static char ap_spell; // frames left in this spell
604
+ static char ap_climb; // frames left climbing
605
+
606
+ static char autopilot_port(void)
607
+ {
608
+ char out = 0xff;
609
+ if (ap_spell == 0 || blocked_x)
610
+ {
611
+ char r = rnd();
612
+ ap_dir = (r & 1) ? JOY_LEFT : JOY_RIGHT;
613
+ if (blocked_x) ap_dir = (ap_dir == JOY_LEFT) ? JOY_RIGHT : JOY_LEFT;
614
+ ap_spell = 30 + (r >> 2);
615
+ }
616
+ ap_spell--;
617
+ int px = px_i;
618
+ int py = (int)(py_fp >> 8);
619
+ bool near = false;
620
+ for (char i = 0; i < MAX_OBJ; i++)
621
+ {
622
+ if (obj_state[i] != OBJ_ALIVE) continue;
623
+ if (obj_y[i] != py) continue;
624
+ int d = obj_x[i] - px;
625
+ if (d < 0) d = -d;
626
+ if (d < 48 && d > 12) near = true;
627
+ }
628
+ char r = rnd();
629
+ if (on_ladder || ap_climb)
630
+ {
631
+ if (ap_climb == 0) ap_climb = 40 + (r & 31);
632
+ ap_climb--;
633
+ out &= ~JOY_UP;
634
+ return out;
635
+ }
636
+ if (tile_at(px + 12, py + BODY_B + 1) == T_LADDER && (r & 3) == 0)
637
+ {
638
+ ap_climb = 40;
639
+ out &= ~JOY_UP;
640
+ return out;
641
+ }
642
+ out &= ~ap_dir;
643
+ if (on_ground && (near || (r & 31) == 0))
644
+ out &= ~JOY_FIRE;
645
+ return out;
646
+ }
647
+ #endif
648
+
649
+ // ---------------------------------------------------------------------------
650
+ // Player physics with tile-grid collision (tile-grid-collision.md): the
651
+ // probe points are the body's corners in world pixels.
652
+ static void player_update(char cur, struct JoyEvents *ev, char repeat_fire)
653
+ {
654
+ int px = px_i;
655
+ int py = (int)(py_fp >> 8);
656
+ blocked_x = 0;
657
+
658
+ // Horizontal: level-driven, blocked by a solid at either body corner.
659
+ // Step = WALK_SPEED>>8 whole pixels plus a carry out of the fraction.
660
+ if (!(cur & JOY_LEFT) || !(cur & JOY_RIGHT))
661
+ {
662
+ unsigned t = px_sub + (WALK_SPEED & 0xff);
663
+ int step = (WALK_SPEED >> 8) + (t >> 8);
664
+ int nx = px;
665
+ if (!(cur & JOY_LEFT)) { nx = px - step; facing = 1; }
666
+ else { nx = px + step; facing = 0; }
667
+ int probe = (nx > px) ? nx + BODY_R : nx + BODY_L;
668
+ if (solid_at(probe, py + BODY_T) || solid_at(probe, py + BODY_B))
669
+ blocked_x = 1;
670
+ else
671
+ {
672
+ px_sub = (char)t;
673
+ px_i = nx;
674
+ px = nx;
675
+ }
676
+ }
677
+
678
+ // Ladder: centre column over a ladder tile at mid-body or under the feet.
679
+ int cx = px + 12;
680
+ char mid = tile_at(cx, py + 13);
681
+ char below = tile_at(cx, py + BODY_B + 1);
682
+ on_ladder = (mid == T_LADDER) || (below == T_LADDER && !(cur & JOY_DOWN));
683
+
684
+ if (on_ladder && (!(cur & JOY_UP) || !(cur & JOY_DOWN)))
685
+ {
686
+ vy_fp = 0;
687
+ jump_n = 0;
688
+ if (!(cur & JOY_UP))
689
+ {
690
+ if (tile_at(cx, py + 13 - CLIMB_SPEED) == T_LADDER ||
691
+ tile_at(cx, py + BODY_B) == T_LADDER)
692
+ py_fp -= CLIMB_SPEED << 8;
693
+ }
694
+ else
695
+ {
696
+ if (!solid_at(cx, py + BODY_B + 1 + CLIMB_SPEED))
697
+ py_fp += CLIMB_SPEED << 8;
698
+ }
699
+ py = (int)(py_fp >> 8);
700
+ }
701
+ else
702
+ {
703
+ on_ground = solid_at(px + BODY_L, py + BODY_B + 1) ||
704
+ solid_at(px + BODY_R, py + BODY_B + 1);
705
+ if (on_ladder && mid == T_LADDER) on_ground = true; // hanging still
706
+
707
+ if (on_ground && jump_n == 0 && (ev->newp & JOY_FIRE))
708
+ {
709
+ jump_n = 1;
710
+ vy_fp = jump_vy[0];
711
+ sfx_jump();
712
+ }
713
+ else if (jump_n)
714
+ {
715
+ if (jump_n < JUMP_FRAMES) vy_fp = jump_vy[jump_n++];
716
+ else { vy_fp += GRAVITY; if (vy_fp > MAX_FALL) vy_fp = MAX_FALL; }
717
+ }
718
+ else if (!on_ground)
719
+ {
720
+ vy_fp += GRAVITY;
721
+ if (vy_fp > MAX_FALL) vy_fp = MAX_FALL;
722
+ }
723
+ else
724
+ vy_fp = 0;
725
+
726
+ if (vy_fp)
727
+ {
728
+ py_fp += (unsigned)vy_fp;
729
+ int ny = (int)(py_fp >> 8);
730
+ if (vy_fp > 0)
731
+ {
732
+ // landing: feet crossed into a solid tile -> snap to its top
733
+ if (solid_at(px + BODY_L, ny + BODY_B) || solid_at(px + BODY_R, ny + BODY_B))
734
+ {
735
+ int top = ((ny + BODY_B) & ~7);
736
+ ny = top - BODY_B - 1;
737
+ py_fp = (unsigned)ny << 8;
738
+ vy_fp = 0;
739
+ jump_n = 0;
740
+ on_ground = true;
741
+ }
742
+ }
743
+ else
744
+ {
745
+ // head bump
746
+ if (solid_at(px + BODY_L, ny + BODY_T) || solid_at(px + BODY_R, ny + BODY_T))
747
+ {
748
+ int bottom = ((ny + BODY_T) | 7) + 1;
749
+ ny = bottom - BODY_T;
750
+ py_fp = (unsigned)ny << 8;
751
+ vy_fp = 0;
752
+ jump_n = JUMP_FRAMES; // fall from here
753
+ }
754
+ }
755
+ py = ny;
756
+ }
757
+ }
758
+
759
+ ASSERT(py >= 0 && py < 200, 6);
760
+ ASSERT(px >= 0 && px < 320, 7);
761
+
762
+ (void)repeat_fire;
763
+ }
764
+
765
+ static void player_draw(unsigned frame)
766
+ {
767
+ int px = px_i;
768
+ int py = (int)(py_fp >> 8);
769
+ bool show = true;
770
+ if (invuln && (frame & 4)) show = false;
771
+ spr_move(0, px + 24, py + 50);
772
+ spr_color(0, facing ? VCOL_YELLOW : VCOL_WHITE);
773
+ spr_show(0, show);
774
+ }
775
+
776
+ static bool hit_enemy(void)
777
+ {
778
+ int px = px_i;
779
+ int py = (int)(py_fp >> 8);
780
+ for (char i = 0; i < MAX_OBJ; i++)
781
+ {
782
+ if (obj_state[i] != OBJ_ALIVE) continue;
783
+ int dx = obj_x[i] - px; if (dx < 0) dx = -dx;
784
+ int dy = (int)obj_y[i] - py; if (dy < 0) dy = -dy;
785
+ if (dx < 8 && dy < 16) return true;
786
+ }
787
+ return false;
788
+ }
789
+
790
+ // ---------------------------------------------------------------------------
791
+ // High score on disk (high-score-persist.md): 5-byte SEQ record, first run
792
+ // gets 62 FILE NOT FOUND and writes; later runs load; replace = scratch+write.
793
+ #define DRIVE 8
794
+ static char hs_rec[5];
795
+ static char hs_back[16];
796
+ static char reply[40];
797
+ static char drive_code;
798
+ static bool drive_ok;
799
+ static bool io_frame; // disk I/O ran this frame: timer B is not ours
800
+ static char disk_state; // screen letter: L loaded, F first, W written, X off
801
+
802
+ static void drive_reply(const char *cmd)
803
+ {
804
+ reply[0] = 0;
805
+ drive_code = 99;
806
+ krnio_setnam(cmd);
807
+ if (krnio_open(15, DRIVE, 15))
808
+ {
809
+ int n = krnio_gets(15, reply, sizeof(reply));
810
+ if (n > 0 && reply[n - 1] == 13) reply[n - 1] = 0;
811
+ if (n >= 2) drive_code = (reply[0] - '0') * 10 + (reply[1] - '0');
812
+ krnio_close(15);
813
+ }
814
+ put_str(DRIVE_ROW, 0, "drive:");
815
+ put_petscii(DRIVE_ROW, 7, reply, 33);
816
+ }
817
+
818
+ static int hs_load(void)
819
+ {
820
+ krnio_setnam("HISCORE,S,R");
821
+ bool ok = krnio_open(2, DRIVE, 2);
822
+ if (!ok && (krnio_status() & KRNIO_NODEVICE))
823
+ {
824
+ krnio_close(2);
825
+ put_str(DRIVE_ROW, 0, "drive: no device");
826
+ drive_code = 99;
827
+ drive_ok = false;
828
+ return 0;
829
+ }
830
+ int n = 0;
831
+ if (ok) n = krnio_read(2, hs_back, sizeof(hs_back));
832
+ krnio_close(2);
833
+ drive_reply("");
834
+ return n;
835
+ }
836
+
837
+ static bool hs_save(void)
838
+ {
839
+ hs_rec[0] = 'K'; hs_rec[1] = 'B'; hs_rec[2] = 1;
840
+ hs_rec[3] = (char)hiscore; hs_rec[4] = hiscore >> 8;
841
+ krnio_setnam("HISCORE,S,W");
842
+ bool ok = krnio_open(2, DRIVE, 2);
843
+ if (ok) krnio_write(2, hs_rec, 5);
844
+ krnio_close(2);
845
+ drive_reply("");
846
+ return ok && drive_code == 0;
847
+ }
848
+
849
+ static void hs_start(void)
850
+ {
851
+ drive_ok = true;
852
+ hiscore = 0;
853
+ int n = hs_load();
854
+ if (drive_code == 0 && n == 5 && hs_back[0] == 'K' && hs_back[1] == 'B' && hs_back[2] == 1)
855
+ {
856
+ hiscore = hs_back[3] | ((unsigned)hs_back[4] << 8);
857
+ disk_state = 'l' - 'a' + 1; // L: loaded
858
+ }
859
+ else if (drive_code == 62)
860
+ {
861
+ disk_state = 'f' - 'a' + 1; // F: first run
862
+ }
863
+ else
864
+ {
865
+ drive_ok = false;
866
+ disk_state = 'x' - 'a' + 1; // X: saving off
867
+ }
868
+ }
869
+
870
+ // Game over: replace the file (scratch, then write), read it back, show it.
871
+ static unsigned hs_readback;
872
+ static void hs_game_over(void)
873
+ {
874
+ if (!drive_ok) return;
875
+ drive_reply("S0:HISCORE");
876
+ if (hs_save())
877
+ {
878
+ int n = hs_load();
879
+ if (n == 5) hs_readback = hs_back[3] | ((unsigned)hs_back[4] << 8);
880
+ disk_state = 'w' - 'a' + 1; // W: written and read back
881
+ put_str(CYC_ROW, 34, "b"); // read-back, after ERR
882
+ put_dec(CYC_ROW, 35, hs_readback, 5);
883
+ }
884
+ else
885
+ disk_state = 'e' - 'a' + 1; // E: write failed
886
+ }
887
+
888
+ // ---------------------------------------------------------------------------
889
+ // Sprites: a small figure for the player, two enemy frames.
890
+ static void make_sprites(void)
891
+ {
892
+ for (char i = 0; i < 64; i++) { SPR_PLAYER[i] = 0; SPR_ENEMY_A[i] = 0; SPR_ENEMY_B[i] = 0; }
893
+ // Player: rows 5..20, byte 1 of each row (x 8..15)
894
+ static const char body[16] = {
895
+ 0x3c, 0x7e, 0x5a, 0x7e, 0x3c, 0x18, 0x7e, 0xdb,
896
+ 0x99, 0x18, 0x18, 0x3c, 0x24, 0x24, 0x66, 0x66 };
897
+ for (char r = 0; r < 16; r++) SPR_PLAYER[(BODY_T + r) * 3 + 1] = body[r];
898
+ // Enemy A / B: a crab-ish blob, two frames
899
+ static const char ea[16] = {
900
+ 0x18, 0x3c, 0x7e, 0xff, 0xdb, 0xff, 0x7e, 0x3c,
901
+ 0x5a, 0x99, 0x18, 0x24, 0x42, 0x81, 0x00, 0x00 };
902
+ static const char eb[16] = {
903
+ 0x18, 0x3c, 0x7e, 0xff, 0xdb, 0xff, 0x7e, 0x3c,
904
+ 0x5a, 0x24, 0x24, 0x42, 0x42, 0x00, 0x00, 0x00 };
905
+ for (char r = 0; r < 16; r++)
906
+ {
907
+ SPR_ENEMY_A[(BODY_T + r) * 3 + 1] = ea[r];
908
+ SPR_ENEMY_B[(BODY_T + r) * 3 + 1] = eb[r];
909
+ }
910
+ }
911
+
912
+ // ---------------------------------------------------------------------------
913
+ // Frame sync (frame-sync-loop.md)
914
+ RIRQCode frame_irq;
915
+ volatile char irq_ticks;
916
+
917
+ __interrupt void on_frame(void)
918
+ {
919
+ irq_ticks++;
920
+ }
921
+
922
+ // CIA1 timer B harness (tile-map-render.md): the KERNAL owns timer A.
923
+ static inline void timer_start(void)
924
+ {
925
+ cia1.crb = 0x00;
926
+ cia1.tb = 0xffff;
927
+ cia1.crb = 0x11;
928
+ }
929
+ static inline unsigned timer_stop(void)
930
+ {
931
+ cia1.crb = 0x00;
932
+ return 0xffff - cia1.tb;
933
+ }
934
+
935
+ // Only fields that changed are rewritten; frame and cyc change every frame.
936
+ static unsigned hud_score = 0xffff, hud_hi = 0xffff, hud_max = 0xffff, hud_drop = 0xffff;
937
+ static char hud_lives = 0xff;
938
+ static void hud_draw(unsigned frame, unsigned cyc, unsigned cyc_max, unsigned dropped)
939
+ {
940
+ if (score != hud_score) { hud_score = score; put_dec(HUD_ROW, 6, score, 5); }
941
+ if (lives != hud_lives) { hud_lives = lives; put_dec(HUD_ROW, 18, lives, 1); }
942
+ if (hiscore != hud_hi) { hud_hi = hiscore; put_dec(HUD_ROW, 23, hiscore, 5); }
943
+ put_dec(HUD_ROW, 31, frame, 5);
944
+ SCREEN[HUD_ROW * 40 + 38] = disk_state;
945
+ put_dec(CYC_ROW, 4, cyc, 5);
946
+ if (cyc_max != hud_max) { hud_max = cyc_max; put_dec(CYC_ROW, 14, cyc_max, 5); }
947
+ if (dropped != hud_drop) { hud_drop = dropped; put_dec(CYC_ROW, 25, dropped, 2); }
948
+ SCREEN[CYC_ROW * 40 + 32] = '0' + fault_code;
949
+ }
950
+
951
+ // The map draw leaves colour RAM black under an empty tile, so text put
952
+ // over the map needs its own colour: an earlier build drew this black on
953
+ // black and the picture showed nothing.
954
+ #define GAME_OVER_ROW 12
955
+ static void show_game_over(void)
956
+ {
957
+ put_str(GAME_OVER_ROW, 20, "game over");
958
+ for (char i = 0; i < 9; i++) COLOUR[GAME_OVER_ROW * 40 + 20 + i] = VCOL_WHITE;
959
+ }
960
+
961
+ static void game_reset(void)
962
+ {
963
+ lives = 3;
964
+ score = 0;
965
+ invuln = 60;
966
+ game_over = false;
967
+ for (char i = 0; i < MAX_OBJ; i++) obj_state[i] = OBJ_FREE;
968
+ player_reset();
969
+ }
970
+
971
+ int main(void)
972
+ {
973
+ for (unsigned i = 0; i < 1000; i++) { SCREEN[i] = 0x20; COLOUR[i] = VCOL_WHITE; }
974
+ vic.color_back = VCOL_BLACK;
975
+ vic.color_border = VCOL_BLACK;
976
+
977
+ put_str(HUD_ROW, 0, "score 00000 lives 3 hi 00000 f 00000 d");
978
+ put_str(CYC_ROW, 0, "cyc 00000 max 00000 drop 00 err 0");
979
+
980
+ map_decode_and_draw();
981
+ make_sprites();
982
+
983
+ // Seed the LFSR from SID voice 3 noise (lfsr-random.md); voice 3 is
984
+ // muted by bit 7 of $D418 so the tune's volume nibble is unaffected.
985
+ sid.voices[2].freq = 0xffff;
986
+ sid.voices[2].ctrl = SID_CTRL_NOISE | SID_CTRL_GATE;
987
+ sid.fmodevol = SID_FMODE_3_OFF | 15;
988
+ rng = sid.random;
989
+ for (char i = 0; i < 50; i++) rnd();
990
+ rng = (rng << 8) | sid.random;
991
+ if (rng == 0) rng = 0xace1;
992
+
993
+ // High score from disk, before the raster IRQ owns the frame. The wait
994
+ // is a workaround: with no wait the first OPEN hung on PAL in VICE and
995
+ // never on NTSC. high-score-persist.md, "A start-up hang seen in VICE,
996
+ // located but not explained", has the monitor trace; fifty frames was
997
+ // enough in every run tried and the smallest wait was not measured.
998
+ #ifndef DISK_WAIT_FRAMES
999
+ #define DISK_WAIT_FRAMES 50
1000
+ #endif
1001
+ for (char i = 0; i < DISK_WAIT_FRAMES; i++) vic_waitFrame();
1002
+ hs_start();
1003
+
1004
+ spr_init(SCREEN);
1005
+ spr_set(0, true, PLAYER_START_X + 24, PLAYER_START_Y + 50, 13, VCOL_WHITE, false, false, false);
1006
+ for (char i = 0; i < MAX_OBJ; i++)
1007
+ spr_set(1 + i, false, 0, 0, 14, VCOL_CYAN, false, false, false);
1008
+
1009
+ game_reset();
1010
+
1011
+ rirq_init(true);
1012
+ rirq_build(&frame_irq, 1);
1013
+ rirq_call(&frame_irq, 0, on_frame);
1014
+ rirq_set(0, SYNC_ROW, &frame_irq);
1015
+ rirq_sort();
1016
+ rirq_start();
1017
+
1018
+ unsigned frame = 0, dropped = 0;
1019
+ unsigned cyc = 0, cyc_max = 0;
1020
+ char seen = irq_ticks;
1021
+ char prev = 0xff;
1022
+ char up_age = 0;
1023
+ bool passed = false, halted = false;
1024
+ struct JoyEvents ev;
1025
+
1026
+ for (;;)
1027
+ {
1028
+ while (irq_ticks == seen)
1029
+ ;
1030
+ char delta = irq_ticks - seen;
1031
+ seen += delta;
1032
+ if (delta > 1) dropped += delta - 1;
1033
+
1034
+ #if BUDGET_BAR
1035
+ if (!passed && !fault_code) vic.color_border = VCOL_WHITE;
1036
+ #endif
1037
+ timer_start();
1038
+
1039
+ if (!halted)
1040
+ {
1041
+ // --- input ---
1042
+ #if AUTOPILOT
1043
+ char cur = autopilot_port();
1044
+ #else
1045
+ cia1.ddra = 0xff; cia1.ddrb = 0x00; cia1.pra = 0xff;
1046
+ char cur = cia1.pra; // port 2
1047
+ #endif
1048
+ joy_edge(prev, cur, &ev);
1049
+ prev = cur;
1050
+ char rep = repeat_step(&up_age, !(cur & JOY_UP));
1051
+
1052
+ // --- simulation ---
1053
+ wave_step(frame);
1054
+ update_enemies();
1055
+ player_update(cur, &ev, rep);
1056
+ if (invuln) invuln--;
1057
+ else if (hit_enemy())
1058
+ {
1059
+ lives--;
1060
+ invuln = 90;
1061
+ player_reset();
1062
+ if (lives == 0) game_over = true;
1063
+ }
1064
+ if ((frame & 7) == 0) score++;
1065
+ if (score > hiscore) hiscore = score;
1066
+
1067
+ // --- sound: tune first, then the effect re-pokes voice 2 ---
1068
+ tune_play();
1069
+ sfx_update();
1070
+
1071
+ // --- draw ---
1072
+ player_draw(frame);
1073
+
1074
+ #if AUTOPILOT
1075
+ if (frame == FORCE_OVER && !game_over) { lives = 0; game_over = true; }
1076
+ #endif
1077
+ if (game_over)
1078
+ {
1079
+ for (char i = 0; i < MAX_OBJ; i++) { obj_state[i] = OBJ_FREE; spr_show(1 + i, false); }
1080
+ sid.fmodevol = SID_FMODE_3_OFF; // silence: the tune stops
1081
+ rirq_stop(); // the KERNAL serial code gets the CPU
1082
+ io_frame = true;
1083
+ hs_game_over();
1084
+ rirq_start();
1085
+ seen = irq_ticks;
1086
+ #if AUTOPILOT
1087
+ if (frame >= FORCE_OVER) { halted = true; show_game_over(); }
1088
+ else game_reset();
1089
+ #else
1090
+ game_reset();
1091
+ #endif
1092
+ }
1093
+ frame++;
1094
+ }
1095
+
1096
+ // The HUD is inside the timed region: cyc is the whole loop body,
1097
+ // shown one frame late (the value on screen is the previous frame's).
1098
+ hud_draw(frame, cyc, cyc_max, dropped);
1099
+ cyc = timer_stop();
1100
+ if (io_frame) io_frame = false; // KERNAL used timer B: discard
1101
+ else if (cyc > cyc_max && frame > 2) cyc_max = cyc;
1102
+
1103
+ #if AUTOPILOT
1104
+ if (frame == PASS_FRAME && !passed && !fault_code)
1105
+ {
1106
+ passed = true;
1107
+ RESULT = CODE_PASS;
1108
+ vic.color_border = 5;
1109
+ }
1110
+ #endif
1111
+ #if BUDGET_BAR
1112
+ if (!passed && !fault_code) vic.color_border = VCOL_BLACK;
1113
+ #endif
1114
+ }
1115
+ return 0;
1116
+ }
1117
+ ```
1118
+
1119
+ ## Build
1120
+
1121
+ ```bash
1122
+ oscar64 -tm=c64 -O2 -o=platformer-scaffold.prg platformer-scaffold.c
1123
+ ```
1124
+
1125
+ Outputs `platformer-scaffold.prg` (6,473 bytes), `.map`, `.asm`, `.lbl`.
1126
+ Add `-dAUTOPILOT=0` for a build that reads control port 2 and
1127
+ `-dBUDGET_BAR=0` to leave the border alone.
1128
+
1129
+ Headless, with a fresh disk so the high score has somewhere to go:
1130
+
1131
+ ```bash
1132
+ c1541 -format "TEST,01" d64 disk.d64
1133
+ x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 \
1134
+ -limitcycles 18000000 -8 disk.d64 -drive8wobbleamplitude 0 -drive8wobblefrequency 0 \
1135
+ -exitscreenshot platformer-scaffold.png -autostart platformer-scaffold.prg
1136
+ ```
1137
+
1138
+ To ship this PRG on a bootable D64 and prove the image boots headless, with
1139
+ the score file landing on the release disk, see
1140
+ [release-disk](../../toolchains/release-disk.md).
1141
+
1142
+ ## Expected output
1143
+
1144
+ Black screen. Row 0 reads `SCORE 00000 LIVES 3 HI 00000 F 00000 D`
1145
+ followed by one letter for the disk state (`F` first run, `L` loaded,
1146
+ `W` written and read back, `X` saving off, `E` write failed). Row 1 reads
1147
+ `CYC 00000 MAX 00000 DROP 00 ERR 0`: the CIA timer B count for the
1148
+ previous frame's loop body, the largest so far, frames dropped, and the
1149
+ fault number. Rows 2 to 23 are the map: brown brick walls and floor, grey
1150
+ platforms, yellow ladders. Row 24 is the drive's reply to the start-up
1151
+ read: `DRIVE: 62, FILE NOT FOUND,00,00` on a fresh disk, `00, OK,00,00`
1152
+ once a score is on it. The player is an 8 by 16 pixel figure, white
1153
+ facing right and yellow facing left; enemies are cyan (slow) or light
1154
+ red (fast) and walk a platform row from one edge to the other.
1155
+
1156
+ With `AUTOPILOT` on the game plays itself from the LFSR, sets `$02FF` to
1157
+ `01` and the border green at frame 600 if no assert has fired, forces a
1158
+ game over at frame 800, scratches and rewrites `HISCORE` on the disk,
1159
+ reads it back, prints `GAME OVER` in white over the map and `B` plus the
1160
+ read-back value at the end of row 1, and halts.
1161
+
1162
+ Measured in VICE x64sc 3.10 headless at the pinned 18,000,000 cycles on a
1163
+ fresh disk, decoded with the char ROM and PIL. The same command run twice
1164
+ gave byte-identical PNGs on each model.
1165
+
1166
+ | | PAL | NTSC |
1167
+ |---|---|---|
1168
+ | Frame on the HUD | 622 | 711 |
1169
+ | `CYC` (previous frame) | 4,966 | 6,628 |
1170
+ | `MAX` so far | 8,606 | 10,287 |
1171
+ | `DROP` | 00 | 00 |
1172
+ | Score, lives | 148, 3 | 169, 1 |
1173
+ | Drive row | `62, FILE NOT FOUND,00,00` | same |
1174
+ | Border (2, 100) | (98, 213, 50), green | (114, 189, 103), green |
1175
+ | `$02FF` from the `trace store` log | `01` | `01` |
1176
+ | Player, PNG pixels | white, x 212 to 219, y 166 to 181 | yellow (facing left), x 55 to 62, y 191 to 206 |
1177
+ | Cyan enemy pixels | 172, x 68 to 165, y 67 to 176 | 232, x 188 to 315, y 55 to 203 |
1178
+ | Light red enemy | x 316 to 323, y 203 to 215 | x 240 to 247, y 55 to 67 |
1179
+
1180
+ The PNGs are `screenshots/platformer-scaffold.png` and
1181
+ `screenshots/platformer-scaffold-ntsc.png`. Frame 622 is past the
1182
+ frame-600 verdict, so the budget bar is off and the border is green in
1183
+ both pictures. The frame counts differ because the two models run a
1184
+ different number of frames in 18,000,000 cycles, and the scores and lives
1185
+ differ because the LFSR is seeded from SID noise at a cycle that depends
1186
+ on the load.
1187
+
1188
+ At 40,000,000 cycles on the same fresh disk, both models: `F 00801`,
1189
+ `LIVES 0`, `HI 00191`, state `W`, `GAME OVER` on row 12, `B00191` at the
1190
+ end of row 1, `DRIVE: 00, OK,00,00`, `MAX` 8,693 (PAL) and 10,287 (NTSC),
1191
+ `DROP 00`, `$02FF` still `01`. The HUD alone, in the halted state, costs
1192
+ 1,605 cycles (PAL) and 1,671 (NTSC). So the loop body runs about 5,000 to
1193
+ 7,500 cycles a frame in play with peaks near 10,300 out of 19,656 (PAL)
1194
+ or 17,095 (NTSC), and no frame was dropped in 800.
1195
+
1196
+ Two defects of the build this was promoted from are fixed here and worth
1197
+ knowing about. `GAME OVER` was written over the map with `put_str`, which
1198
+ writes screen codes only; the map draw had left colour RAM black under
1199
+ empty tiles, so the text was black on black and no picture showed it.
1200
+ `show_game_over` sets the colour cells too. And the read-back value was
1201
+ printed at column 27 of row 1, over the `ERR` field; it now follows it.
1202
+
1203
+ ## Why this works
1204
+
1205
+ ### Tile map and collision
1206
+
1207
+ `map_decode_and_draw` expands one RLE stream per row into `map[]` and
1208
+ writes the screen and colour cells in the same pass. Three asserts guard
1209
+ the data: each row must decode to 40 tiles, must fold to the checksum the
1210
+ generator wrote, and the stream must end where the generator said. That
1211
+ is `tile_map_render` from `docs/techniques/scroll.md`, format and decoder
1212
+ from `docs/recipes/oscar64/tile-map-render.md`.
1213
+
1214
+ Collision reads `map[]`, never screen RAM. `tile_at` takes world pixels,
1215
+ returns empty above the map and solid beyond its other three edges, and
1216
+ `solid_at` treats brick and platform as solid and the ladder as not.
1217
+ `player_update` probes the body's corners: the leading edge's top and
1218
+ bottom before a horizontal step, both feet before a fall, both head
1219
+ corners before a rise. A landing snaps the feet to the top of the tile
1220
+ they entered; a head bump snaps to the tile's bottom and turns the jump
1221
+ into a fall. That is `tile_grid_collision` from `docs/techniques/logic.md`
1222
+ with the listing in `docs/recipes/oscar64/tile-grid-collision.md`.
1223
+
1224
+ ### Fixed point and the jump table
1225
+
1226
+ Y is 8.8 in an unsigned 16-bit word. A signed int wraps at 128 pixels,
1227
+ which `docs/recipes/oscar64/fixed-point-jump.md` warns about and the first
1228
+ build hit as fault 6. X needs nine bits of pixel, so it is an int of
1229
+ pixels plus a fraction byte, and the walk adds `WALK_SPEED` to the
1230
+ fraction and carries. The jump is semi-implicit Euler with gravity applied
1231
+ before position, precomputed into `jump_vy[]` by the generator from
1232
+ `V0 = -3.5` px/frame and `G = 0.15625` px/frame²; after the table ends
1233
+ gravity continues so walking off an edge falls. `fixed_point_8_8` and
1234
+ `jump_arc_table` are in `docs/techniques/maths.md`.
1235
+
1236
+ ### Joystick events and auto-repeat
1237
+
1238
+ `joy_edge` compares this frame's active-low port byte with the previous
1239
+ one and yields new, held and released masks. `repeat_step` fires on the
1240
+ first frame of a press, again at 20 frames held, then every 4. Left and
1241
+ right are level-driven; the repeat runs on UP and is available for a
1242
+ ladder step. `autopilot_port` synthesises the port byte from the LFSR and
1243
+ feeds the same `joy_edge`, so no code path differs after the read. The
1244
+ listings are from `docs/recipes/oscar64/joystick-input.md`;
1245
+ `joystick_edge_detect` and `joystick_autorepeat` are in
1246
+ `docs/techniques/input.md`.
1247
+
1248
+ ### Object pool and LFSR
1249
+
1250
+ Six enemy slots in parallel arrays, a scan allocator that refuses when
1251
+ full and counts the refusal, a wave table walked once per frame against
1252
+ a 450-frame cycle, and despawn when an enemy leaves either edge. Rows and
1253
+ directions come from `rnd`, a 16-bit Galois LFSR with taps `$B400` seeded
1254
+ from SID voice 3 noise with voice 3 muted by bit 7 of `$D418`. That is
1255
+ `docs/recipes/oscar64/object-pool.md` and
1256
+ `docs/recipes/oscar64/lfsr-random.md`; `object_pool` is in
1257
+ `docs/techniques/logic.md` and `lfsr_random` in `docs/techniques/maths.md`.
1258
+
1259
+ ### Frame loop, timer and budget bar
1260
+
1261
+ A raster IRQ at row 250 increments a tick byte; the loop waits for it to
1262
+ change and counts any change greater than one as dropped frames. CIA1
1263
+ timer B, not A, brackets the whole loop body including the HUD, because
1264
+ the KERNAL owns timer A for its jiffy clock. A frame in which the KERNAL
1265
+ did disk I/O is discarded from `MAX`: the KERNAL's serial routines use
1266
+ timer B while a file is open, and before the exclusion went in the
1267
+ original build showed `MAX 64256` after the first read. The border is white from the tick to the end of the
1268
+ body while `BUDGET_BAR` is on, so the picture shows the cost as raster
1269
+ lines beside the timer's number. `frame_sync_loop` is in
1270
+ `docs/techniques/raster.md`, the listing in
1271
+ `docs/recipes/oscar64/frame-sync-loop.md`, and the timer harness in
1272
+ `docs/recipes/oscar64/tile-map-render.md`.
1273
+
1274
+ ### Decimal HUD
1275
+
1276
+ `put_dec` subtracts powers of ten, at most 45 sixteen-bit compares for
1277
+ five digits, and only fields that changed are rewritten. The first build
1278
+ used five divisions per field and thirty per frame, and the budget bar
1279
+ was white end to end while the timed region said 4,000 cycles, which is
1280
+ what the bar is for. `decimal_print` is in `docs/techniques/text.md` and
1281
+ `docs/recipes/oscar64/print-number.md`.
1282
+
1283
+ ### Sound
1284
+
1285
+ `tune_play` writes all three voices every frame from a 32-step pattern;
1286
+ `sfx_update` runs after it and re-pokes voice 2 while the jump effect is
1287
+ playing, so the tune never sees the effect and the effect always wins.
1288
+ Nobody has listened to either; the work is register-level, and the tune
1289
+ runs at frame rate so it is a fifth faster on NTSC. That is
1290
+ `sfx_engine_beside_music` in `docs/techniques/music-sid.md` and
1291
+ `docs/recipes/oscar64/sfx-engine.md`.
1292
+
1293
+ ### High score on disk
1294
+
1295
+ At start `hs_load` opens `HISCORE,S,R`, reads five bytes, closes, and
1296
+ reads the error channel; 62 means a first run, 00 with a valid record
1297
+ means a loaded score, anything else turns saving off. If OPEN fails with
1298
+ `KRNIO_NODEVICE` the row says `no device` and nothing else touches the
1299
+ bus. At game over `hs_game_over` scratches, writes, and reads back, with
1300
+ `rirq_stop` around the I/O so the KERNAL's serial timing is undisturbed.
1301
+ The three `file-io` techniques `kernal_file_write_seq`,
1302
+ `kernal_file_read_seq` and `error_channel_check` are in
1303
+ `docs/techniques/file-io.md`; the listing is
1304
+ `docs/recipes/oscar64/high-score-persist.md`.
1305
+
1306
+ `DISK_WAIT_FRAMES` is a workaround and the page says so. The original
1307
+ build with no wait before the first OPEN hung on PAL under `-autostart`
1308
+ with a true drive, never on NTSC; fifty `vic_waitFrame` calls removed it,
1309
+ and so did an unrelated change of code size. The monitor trace is in
1310
+ `docs/recipes/oscar64/high-score-persist.md`, section "A start-up hang
1311
+ seen in VICE, located but not explained": the C64 spins at `$EDD6`
1312
+ waiting for the drive to become the talker after TKSA, and the drive
1313
+ stayed a listener. Why it misses the release is not measured, the smallest
1314
+ wait that suffices is not measured, and no real 1541 was tried. Keep the
1315
+ wait until one of those is.
1316
+
1317
+ ### The generator
1318
+
1319
+ The RLE stream, the row checksums, the jump table and the platform row
1320
+ list in the listing came from this script. It is not built by the
1321
+ listing gate. Run it, then paste its output over the block marked
1322
+ "Generated tables" in the source.
1323
+
1324
+ ```text
1325
+ #!/usr/bin/env python3
1326
+ """Generate map.h: the RLE-compressed tile map, its per-row checksums, the
1327
+ jump velocity table, and the wave/row tables. Run: python3 gen.py > map.h
1328
+
1329
+ RLE format is the KB tile-map-render recipe's: control byte c, c&0x80 = run of
1330
+ (c&0x7f) copies of the next byte, c<0x80 = c literal bytes follow, 0 = end of
1331
+ row. One stream per row, no offset table."""
1332
+
1333
+ # 40 x 22 tiles on screen rows 2..23. '.' empty, '#' brick (solid),
1334
+ # '=' platform (solid), 'H' ladder (climbable, not solid).
1335
+ ART = [
1336
+ "#......................................#", # 0 (screen row 2)
1337
+ "#......................................#", # 1
1338
+ "#......................................#", # 2
1339
+ "#......................................#", # 3
1340
+ "#..=========............===========....#", # 4
1341
+ "#..........H...........................#", # 5
1342
+ "#..........H...........................#", # 6
1343
+ "#..........H...........................#", # 7
1344
+ "#....==========H=========.....=======..#", # 8
1345
+ "#..............H.......................#", # 9
1346
+ "#..............H.......................#", # 10
1347
+ "#..............H.......................#", # 11
1348
+ "#=========.....H....===========........#", # 12
1349
+ "#......................H..............##", # 13
1350
+ "#......................H...............#", # 14
1351
+ "#......................H...............#", # 15
1352
+ "#...==============.....H....=========..#", # 16
1353
+ "#......................H...............#", # 17
1354
+ "#......................H...............#", # 18
1355
+ "#......................H...............#", # 19
1356
+ "#......................H...............#", # 20
1357
+ "########################################", # 21
1358
+ ]
1359
+ CODE = {'.': 0, '#': 1, '=': 2, 'H': 3}
1360
+ W, H = 40, len(ART)
1361
+ assert all(len(r) == W for r in ART), "every row must be 40 wide"
1362
+ assert H == 22
1363
+
1364
+
1365
+ def rle_row(row):
1366
+ out = []
1367
+ i = 0
1368
+ while i < len(row):
1369
+ j = i
1370
+ while j < len(row) and row[j] == row[i] and j - i < 127:
1371
+ j += 1
1372
+ n = j - i
1373
+ if n >= 2:
1374
+ out += [0x80 | n, row[i]]
1375
+ i = j
1376
+ else:
1377
+ # literal run: collect until a run of >= 2 starts
1378
+ k = i
1379
+ while k < len(row) and (k + 1 >= len(row) or row[k + 1] != row[k]) and k - i < 127:
1380
+ k += 1
1381
+ if k == i:
1382
+ k = i + 1
1383
+ out += [k - i] + row[i:k]
1384
+ i = k
1385
+ out.append(0)
1386
+ return out
1387
+
1388
+
1389
+ def fold(chk, v):
1390
+ return ((chk ^ v) * 5 + 1) & 0xffff
1391
+
1392
+
1393
+ rows = [[CODE[c] for c in r] for r in ART]
1394
+ stream = []
1395
+ checks = []
1396
+ for r in rows:
1397
+ s = rle_row(r)
1398
+ # verify decode
1399
+ dec = []
1400
+ i = 0
1401
+ while True:
1402
+ c = s[i]; i += 1
1403
+ if c == 0:
1404
+ break
1405
+ if c & 0x80:
1406
+ dec += [s[i]] * (c & 0x7f); i += 1
1407
+ else:
1408
+ dec += s[i:i + c]; i += c
1409
+ assert dec == r, (dec, r)
1410
+ chk = 0
1411
+ for v in r:
1412
+ chk = fold(chk, v)
1413
+ checks.append(chk)
1414
+ stream += s
1415
+
1416
+ # Jump table: 8.8 velocity per jump frame, semi-implicit Euler like the KB
1417
+ # fixed-point-jump recipe (gravity first, then position).
1418
+ V0 = -0x0380 # -3.5 px/frame
1419
+ G = 0x0028 # +0.15625 px/frame^2
1420
+ vy = V0
1421
+ table = []
1422
+ while True:
1423
+ vy += G
1424
+ table.append(vy)
1425
+ if vy >= -V0:
1426
+ break
1427
+
1428
+ # Wave table: frame offset (within a cycle), type, count, row-choice list index.
1429
+ # Enemy rows are the platform rows an enemy walks along (feet on the tile top).
1430
+ PLATFORM_ROWS = [4, 8, 12, 16, 21]
1431
+
1432
+ print("// Generated by gen.py — do not edit by hand.")
1433
+ print("#define MAP_W %d" % W)
1434
+ print("#define MAP_H %d" % H)
1435
+ print("#define MAP_STREAM_LEN %d" % len(stream))
1436
+ print("static const char map_rle[] = {")
1437
+ for i in range(0, len(stream), 16):
1438
+ print(" " + ", ".join("0x%02x" % b for b in stream[i:i + 16]) + ",")
1439
+ print("};")
1440
+ print("static const unsigned map_row_chk[MAP_H] = {")
1441
+ print(" " + ", ".join("0x%04x" % c for c in checks))
1442
+ print("};")
1443
+ print("#define JUMP_FRAMES %d" % len(table))
1444
+ print("static const int jump_vy[JUMP_FRAMES] = {")
1445
+ for i in range(0, len(table), 8):
1446
+ print(" " + ", ".join("%d" % v for v in table[i:i + 8]) + ",")
1447
+ print("};")
1448
+ print("#define N_PLATFORM_ROWS %d" % len(PLATFORM_ROWS))
1449
+ print("static const char platform_rows[N_PLATFORM_ROWS] = { %s };" % ", ".join(str(r) for r in PLATFORM_ROWS))
1450
+ import sys
1451
+ print("// map bytes %d -> rle %d bytes; jump apex %.1f px over %d frames" % (
1452
+ W * H, len(stream), sum(-v for v in table if v < 0) / 256.0, len(table)), file=sys.stderr)
1453
+ ```
1454
+
1455
+ ## Where each part came from
1456
+
1457
+ The build this page promotes logged every KB call it made. The parts and
1458
+ their sources: tile map from `tile-map-render.md`; collision from the
1459
+ `tile_grid_collision` prose, now with `tile-grid-collision.md` beside it;
1460
+ jump from `fixed-point-jump.md`; input from `joystick-input.md`; pool
1461
+ and waves from `object-pool.md`; random from `lfsr-random.md`; frame loop
1462
+ and bar from `frame-sync-loop.md`; decimal from the recipe listings,
1463
+ rewritten to subtract; sound from `sfx-engine.md`; disk from
1464
+ `high-score-persist.md` and `save-load-seq-file.md`; verdict from
1465
+ `headless-verify.md` and `runtime/vice-reference.md`, "Verifying a run
1466
+ without a human". The autopilot had no source in the KB and is this
1467
+ page's own.