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,743 @@
1
+ ---
2
+ recipe: game-tree-search
3
+ toolchain: oscar64
4
+ output_format: PRG
5
+ region: both
6
+ techniques: [game_tree_search]
7
+ file_formats: [PRG]
8
+ uses_registers: [D011, D012, D019, D01A, D020, D021, DC04, DC05, DC06, DC07, DC0E, DC0F]
9
+ uses_kernal: []
10
+ ---
11
+
12
+ <!-- doc-type: recipe -->
13
+
14
+ # Oscar64 Game Tree Search: Connect Four by negamax with alpha-beta
15
+
16
+ ## Synopsis
17
+
18
+ The machine plays Connect Four (7 columns, 6 rows) against itself: a
19
+ negamax search with alpha-beta pruning, five plies deep, columns tried
20
+ centre first, leaves scored by an incremental cell-weight sum. The search
21
+ is a loop over an explicit per-ply record, not a recursive function, so
22
+ it can stop after any node and go on in a later frame. The game runs to a
23
+ full board (a draw at this depth) three times: blocking, with each search
24
+ in one call; one node a call, to time the worst single node; and sliced,
25
+ four nodes a frame while a per-frame counter keeps running. Every column
26
+ chosen and the node count of every one of the 42 searches in each game is
27
+ compared with a Python model of the same search, compiled in. The program
28
+ prints PASS or FAIL, nodes per second and cycles per node from a CIA1
29
+ timer, frames per move from a raster IRQ counter, the worst single call
30
+ and the worst slice. It implements `game_tree_search`
31
+ (`techniques/logic.md`). Build `-dRECURSIVE=1` for the blocking game with
32
+ the search written as a recursive function.
33
+
34
+ ## Source
35
+
36
+ ```c
37
+ // game-tree-search.c
38
+ // Connect Four (7 columns, 6 rows) played by the machine against itself:
39
+ // negamax with alpha-beta to a fixed depth, centre-first move order and an
40
+ // incremental cell-weight evaluation. The search is iterative, with one
41
+ // record per ply in static arrays, so it can stop after any node and go on
42
+ // later: search_step(n) runs at most n nodes. The game is played three
43
+ // times: blocking (each search in one call), one node a call (to time the
44
+ // worst single node), and sliced (SLICE nodes a frame while a per-frame
45
+ // counter keeps running). Every chosen column and node count of each game
46
+ // is checked against a Python model of the same search.
47
+ // Build -dRECURSIVE=1 for the blocking game with a recursive search.
48
+ #include <c64/vic.h>
49
+ #include <c64/cia.h>
50
+ #include <c64/rasterirq.h>
51
+
52
+ #ifndef DEPTH
53
+ #define DEPTH 5 // plies searched from each position
54
+ #endif
55
+ #ifndef SLICE
56
+ #define SLICE 4 // nodes a frame in the sliced game
57
+ #endif
58
+ #ifndef RECURSIVE
59
+ #define RECURSIVE 0 // 1: blocking game only, recursive search
60
+ #endif
61
+
62
+ #define SCREEN ((char *)0x0400)
63
+ #define COLOUR ((char *)0xd800)
64
+ #define EMPTY 0
65
+ #define EDGE 3 // sentinel: never equal to a player
66
+ #define WIN 1000 // a win found at ply p scores WIN - p
67
+ #define INF 30000
68
+ #define MAXMOVES 42
69
+
70
+ // The Python model's game at DEPTH 5, a draw on a full board: the column
71
+ // chosen and the nodes searched for each of the 42 moves.
72
+ #define NEXP 42
73
+ static const char exp_col[NEXP] = {3, 3, 2, 4, 3, 3, 3, 3, 2, 2, 2, 4, 2, 1,
74
+ 1, 4, 4, 4, 2, 4, 5, 1, 1, 1, 1, 0, 0, 0, 5, 5, 0, 0, 0, 6, 5, 5, 5, 6, 6,
75
+ 6, 6, 6};
76
+ static const unsigned exp_nodes[NEXP] = {755, 563, 1484, 941, 2283, 702, 2360,
77
+ 908, 1342, 341, 1177, 746, 1024, 637, 658, 525, 347, 494, 415, 177, 124,
78
+ 164, 80, 172, 142, 72, 119, 72, 105, 53, 107, 64, 58, 27, 23, 9, 7, 5, 4,
79
+ 3, 2, 1};
80
+
81
+ // Board: 8 rows of 8 bytes. Rows 1..6 are the play rows (row 1 at the
82
+ // bottom), columns 0..6. Row 0, row 7 and column 7 hold EDGE, so a run
83
+ // along any of the four directions stops at the edge without a bounds test.
84
+ static char board[64];
85
+ static char height[7]; // pieces in each column
86
+ static int score; // sum of weights: player 1 plus, player 2 minus
87
+ static char nmoves; // pieces on the board
88
+ static unsigned nodes; // positions made by the current search
89
+
90
+ // Number of four-in-a-row windows through each cell, row 1 first.
91
+ static const char weight[64] = {
92
+ 0, 0, 0, 0, 0, 0, 0, 0,
93
+ 3, 4, 5, 7, 5, 4, 3, 0,
94
+ 4, 6, 8,10, 8, 6, 4, 0,
95
+ 5, 8,11,13,11, 8, 5, 0,
96
+ 5, 8,11,13,11, 8, 5, 0,
97
+ 4, 6, 8,10, 8, 6, 4, 0,
98
+ 3, 4, 5, 7, 5, 4, 3, 0,
99
+ 0, 0, 0, 0, 0, 0, 0, 0 };
100
+ static const char order[7] = { 3, 2, 4, 1, 5, 0, 6 }; // centre first
101
+ static const char dirs[4] = { 1, 8, 9, 7 };
102
+
103
+ // Does the piece just placed at cell i complete four for player p?
104
+ static char wins(char i, char p)
105
+ {
106
+ for (char k = 0; k < 4; k++)
107
+ {
108
+ char d = dirs[k], n = 1, j = i + d;
109
+ while (board[j] == p) { n++; j += d; }
110
+ j = i - d;
111
+ while (board[j] == p) { n++; j -= d; }
112
+ if (n >= 4) return 1;
113
+ }
114
+ return 0;
115
+ }
116
+
117
+ // Returns the cell index. The new height goes through a variable: written
118
+ // as i = ((height[c] + 1) << 3) + c; board[i] = side; height[c]++;
119
+ // Oscar64 1.32.271 stores side + 1 into height[c] (see "Why this works").
120
+ static char make(char c, char side)
121
+ {
122
+ char h = height[c] + 1;
123
+ height[c] = h;
124
+ char i = (h << 3) + c;
125
+ board[i] = side; nmoves++;
126
+ if (side == 1) score += weight[i]; else score -= weight[i];
127
+ return i;
128
+ }
129
+
130
+ static void unmake(char c, char i, char side)
131
+ {
132
+ board[i] = EMPTY; height[c]--; nmoves--;
133
+ if (side == 1) score -= weight[i]; else score += weight[i];
134
+ }
135
+
136
+ // The value of the move just made, for the side that made it, when the
137
+ // search stops here: a win, a full board, or the depth limit. Otherwise
138
+ // returns 1 in *go and the caller searches below.
139
+ static int leaf(char i, char side, char ply, char *go)
140
+ {
141
+ *go = 0;
142
+ if (wins(i, side)) return WIN - ply;
143
+ if (nmoves == MAXMOVES) return 0;
144
+ if (ply + 1 == DEPTH) return side == 1 ? score : -score;
145
+ *go = 1;
146
+ return 0;
147
+ }
148
+
149
+ static char bestcol; // the root's choice
150
+
151
+ #if RECURSIVE
152
+ static int negamax(char ply, int alpha, int beta, char side)
153
+ {
154
+ int best = -INF;
155
+ for (char k = 0; k < 7; k++)
156
+ {
157
+ char c = order[k];
158
+ if (height[c] == 6) continue;
159
+ char i = make(c, side), go;
160
+ nodes++;
161
+ int v = leaf(i, side, ply, &go);
162
+ if (go) v = -negamax(ply + 1, -beta, -alpha, 3 - side);
163
+ unmake(c, i, side);
164
+ if (v > best)
165
+ {
166
+ best = v;
167
+ if (ply == 0) bestcol = c;
168
+ if (v > alpha) { alpha = v; if (alpha >= beta) break; }
169
+ }
170
+ }
171
+ return best;
172
+ }
173
+ static void search_begin(char side) { nodes = 0; negamax(0, -INF, INF, side); }
174
+ static char search_step(unsigned budget) { return 1; }
175
+ #else
176
+ // One record per ply: the window, the best value so far, the next entry
177
+ // of order[] to try, and the move made from this ply (column and cell).
178
+ // With the ply and side to move, this is the whole state of a search.
179
+ static int p_alpha[DEPTH], p_beta[DEPTH], p_best[DEPTH];
180
+ static char p_next[DEPTH], p_col[DEPTH], p_cell[DEPTH];
181
+ static char s_ply, s_side;
182
+
183
+ static void search_begin(char side)
184
+ {
185
+ nodes = 0; s_ply = 0; s_side = side;
186
+ p_alpha[0] = -INF; p_beta[0] = INF; p_best[0] = -INF; p_next[0] = 0;
187
+ }
188
+
189
+ // Runs the search for at most budget nodes. Returns 1 when it has
190
+ // finished (bestcol holds the move), 0 when it stopped to go on later.
191
+ static char search_step(unsigned budget)
192
+ {
193
+ char ply = s_ply, side = s_side;
194
+ for (;;)
195
+ {
196
+ int v;
197
+ if (p_next[ply] == 7)
198
+ {
199
+ // Every child of this ply is done: return its value to the parent.
200
+ if (ply == 0) return 1;
201
+ v = -p_best[ply];
202
+ ply--; side = 3 - side;
203
+ unmake(p_col[ply], p_cell[ply], side);
204
+ }
205
+ else
206
+ {
207
+ char c = order[p_next[ply]];
208
+ if (height[c] == 6) { p_next[ply]++; continue; }
209
+ if (budget == 0) { s_ply = ply; s_side = side; return 0; }
210
+ budget--;
211
+ p_next[ply]++;
212
+ char i = make(c, side), go;
213
+ p_col[ply] = c; p_cell[ply] = i;
214
+ nodes++;
215
+ v = leaf(i, side, ply, &go);
216
+ if (go)
217
+ {
218
+ // Descend: the child's window is the parent's, negated and swapped.
219
+ ply++; side = 3 - side;
220
+ p_alpha[ply] = -p_beta[ply - 1]; p_beta[ply] = -p_alpha[ply - 1];
221
+ p_best[ply] = -INF; p_next[ply] = 0;
222
+ continue;
223
+ }
224
+ unmake(c, i, side);
225
+ }
226
+ // v is the value of the move p_col[ply] for the side to move at ply.
227
+ if (v > p_best[ply])
228
+ {
229
+ p_best[ply] = v;
230
+ if (ply == 0) bestcol = p_col[0];
231
+ if (v > p_alpha[ply])
232
+ {
233
+ p_alpha[ply] = v;
234
+ if (v >= p_beta[ply]) p_next[ply] = 7; // cut: skip the rest
235
+ }
236
+ }
237
+ }
238
+ }
239
+ #endif
240
+
241
+ // --- the demonstration: play, check, time, draw ---------------------------
242
+ static void put_text(char row, char col, const char *s)
243
+ {
244
+ char *p = SCREEN + 40 * row + col;
245
+ while (*s)
246
+ {
247
+ char c = *s++;
248
+ if (c >= 'A' && c <= 'Z') c -= 64;
249
+ *p++ = c;
250
+ }
251
+ }
252
+ // v as n decimal digits (n = 1..8), leading zeros shown.
253
+ static void put_dec(char row, char col, unsigned long v, char n)
254
+ {
255
+ static const unsigned long pw[8] = { 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
256
+ char *p = SCREEN + 40 * row + col;
257
+ for (char i = 8 - n; i < 8; i++)
258
+ {
259
+ char d = 0x30;
260
+ while (v >= pw[i]) { v -= pw[i]; d++; }
261
+ *p++ = d;
262
+ }
263
+ }
264
+ // CIA1 timer A counts phi2 from $FFFF; timer B counts A's underflows.
265
+ static void t_start(void)
266
+ {
267
+ cia1.cra = 0; cia1.crb = 0;
268
+ cia1.ta = 0xffff; cia1.tb = 0xffff;
269
+ cia1.crb = 0x51; // force load, start, count A underflows
270
+ cia1.cra = 0x11; // force load, start, count phi2
271
+ }
272
+ static unsigned long t_stop(void)
273
+ {
274
+ cia1.cra = 0; cia1.crb = 0;
275
+ return ((unsigned long)(0xffff - cia1.tb) << 16) + (0xffff - cia1.ta);
276
+ }
277
+ static unsigned long c_null; // t_start plus t_stop with nothing between
278
+
279
+ static void put_cell(char i) // board cell i to the screen
280
+ {
281
+ char r = (i >> 3) - 1, c = i & 7, b = board[i];
282
+ unsigned o = 40 * (6 - r) + 2 * c + 1; // row 1 at screen row 6
283
+ SCREEN[o] = b == EMPTY ? 0x2e : 0x51;
284
+ COLOUR[o] = b == 1 ? VCOL_YELLOW : b == 2 ? VCOL_RED : VCOL_MED_GREY;
285
+ }
286
+ static void reset(void)
287
+ {
288
+ for (char i = 0; i < 64; i++) board[i] = EDGE;
289
+ for (char r = 1; r < 7; r++)
290
+ for (char c = 0; c < 7; c++) board[(r << 3) + c] = EMPTY;
291
+ for (char c = 0; c < 7; c++) height[c] = 0;
292
+ score = 0; nmoves = 0;
293
+ }
294
+
295
+ RIRQCode frame_irq;
296
+ volatile unsigned ticks;
297
+ __interrupt void on_frame(void) { ticks++; }
298
+
299
+ // The result of one game.
300
+ static char played, winner, okcol, oknodes;
301
+ // Called when the search for move 'played' has finished: check it against
302
+ // the model, play it, and say whether the game is over.
303
+ static char finish_move(char side, char draw)
304
+ {
305
+ if (played < NEXP)
306
+ {
307
+ if (bestcol == exp_col[played]) okcol++;
308
+ if (nodes == exp_nodes[played]) oknodes++;
309
+ }
310
+ char i = make(bestcol, side);
311
+ if (draw)
312
+ {
313
+ put_cell(i);
314
+ // the columns played, 21 to a row, on rows 9 and 10
315
+ SCREEN[40 * (9 + played / 21) + 4 + played % 21] = 0x30 + bestcol;
316
+ }
317
+ played++;
318
+ if (wins(i, side)) { winner = side; return 1; }
319
+ return played == MAXMOVES;
320
+ }
321
+ static void new_game(void) { reset(); played = 0; winner = 0; okcol = 0; oknodes = 0; }
322
+ static void put_check(char row)
323
+ {
324
+ put_text(row, 0, " COLS / NODES /");
325
+ put_dec(row, 8, okcol, 2); put_dec(row, 11, NEXP, 2);
326
+ put_dec(row, 20, oknodes, 2); put_dec(row, 23, NEXP, 2);
327
+ }
328
+ static char game_ok(void)
329
+ {
330
+ return played == NEXP && okcol == NEXP && oknodes == NEXP && winner == 0;
331
+ }
332
+
333
+ int main(void)
334
+ {
335
+ __asm { sei }
336
+ vic.color_border = VCOL_BLACK;
337
+ vic.color_back = VCOL_BLACK;
338
+ for (unsigned i = 0; i < 1000; i++) { SCREEN[i] = 0x20; COLOUR[i] = VCOL_WHITE; }
339
+
340
+ // PAL or NTSC: the highest raster line reached past 255.
341
+ while (!(vic.ctrl1 & 0x80)) ;
342
+ char top = 0;
343
+ while (vic.ctrl1 & 0x80) { char r = vic.raster; if (r > top) top = r; }
344
+ char pal = top > 0x20; // PAL reaches $137, NTSC $106
345
+
346
+ t_start(); c_null = t_stop();
347
+
348
+ rirq_init(true);
349
+ rirq_build(&frame_irq, 1);
350
+ rirq_call(&frame_irq, 0, on_frame);
351
+ rirq_set(0, 250, &frame_irq);
352
+ rirq_sort();
353
+ rirq_start();
354
+
355
+ // Game 1, blocking: each search runs to the end in one call.
356
+ unsigned long allnodes = 0, allcyc = 0, cmax = 0;
357
+ unsigned fmin = 0xffff, fmax = 0, total = 0;
358
+ char side = 1, pass;
359
+ new_game();
360
+ for (;;)
361
+ {
362
+ unsigned f0 = ticks;
363
+ t_start();
364
+ search_begin(side);
365
+ search_step(0xffff);
366
+ unsigned long cyc = t_stop() - c_null;
367
+ unsigned fr = ticks - f0;
368
+ allnodes += nodes; allcyc += cyc; total += fr;
369
+ if (cyc > cmax) cmax = cyc;
370
+ if (fr < fmin) fmin = fr;
371
+ if (fr > fmax) fmax = fr;
372
+ if (finish_move(side, RECURSIVE)) break;
373
+ side = 3 - side;
374
+ }
375
+ pass = game_ok();
376
+ put_text(11, 0, "BLOCKING"); put_check(12);
377
+ put_text(13, 0, " NODES CYCLES");
378
+ put_dec(13, 9, allnodes, 6); put_dec(13, 24, allcyc, 8);
379
+ // nodes per second = nodes * clock / cycles, scaled to stay in 32 bits
380
+ unsigned long clk100 = pal ? 9852 : 10227; // phi2 / 100: PAL 985,248 Hz, NTSC 1,022,727 Hz
381
+ unsigned long nps = allnodes * clk100 / (allcyc / 100);
382
+ put_text(14, 0, " NODES/S CYC/NODE");
383
+ put_dec(14, 11, nps, 5); put_dec(14, 26, allcyc / allnodes, 5);
384
+ put_text(15, 0, " FRAMES/MOVE MIN MAX ALL");
385
+ put_dec(15, 19, fmin, 3); put_dec(15, 27, fmax, 3); put_dec(15, 35, total, 4);
386
+ put_text(16, 0, " SLOWEST MOVE CYCLES");
387
+ put_dec(16, 16, cmax, 7);
388
+
389
+ #if !RECURSIVE
390
+ // Game 2: one node a call, each call timed: the worst single step is
391
+ // one node plus any returns up the plies before it.
392
+ unsigned long worst1 = 0;
393
+ side = 1;
394
+ new_game();
395
+ for (;;)
396
+ {
397
+ search_begin(side);
398
+ char done;
399
+ do
400
+ {
401
+ t_start();
402
+ done = search_step(1);
403
+ unsigned long c = t_stop() - c_null;
404
+ if (c > worst1) worst1 = c;
405
+ } while (!done);
406
+ if (finish_move(side, 0)) break;
407
+ side = 3 - side;
408
+ }
409
+ pass &= game_ok();
410
+ put_text(17, 0, "ONE NODE A CALL"); put_check(18);
411
+ put_text(19, 0, " WORST CALL CYCLES");
412
+ put_dec(19, 14, worst1, 5);
413
+
414
+ // Game 3, sliced: SLICE nodes a frame. Each frame the loop waits for
415
+ // the raster IRQ, advances a spinner and frame count, and runs one
416
+ // slice; the frame that follows a finished search plays the move.
417
+ static const char spin[4] = { 0x2d, 0x4e, 0x5d, 0x4d }; // - / | \ in screen codes
418
+ unsigned long worstn = 0;
419
+ unsigned frames = 0, missed = 0, mf = 0, mfmax = 0;
420
+ for (char i = 0; i < 64; i++) if (board[i] != EDGE) { board[i] = EMPTY; put_cell(i); }
421
+ side = 1;
422
+ new_game();
423
+ search_begin(side);
424
+ char searching = 1;
425
+ put_text(22, 0, "FRAME");
426
+ unsigned last = ticks;
427
+ for (;;)
428
+ {
429
+ while (ticks == last) ;
430
+ unsigned now = ticks;
431
+ missed += now - last - 1;
432
+ last = now;
433
+ frames++; mf++;
434
+ SCREEN[40 * 22 + 12] = spin[frames & 3];
435
+ put_dec(22, 6, frames, 5);
436
+ if (searching)
437
+ {
438
+ t_start();
439
+ char done = search_step(SLICE);
440
+ unsigned long c = t_stop() - c_null;
441
+ if (c > worstn) worstn = c;
442
+ if (done) searching = 0;
443
+ }
444
+ else
445
+ {
446
+ if (mf > mfmax) mfmax = mf;
447
+ mf = 0;
448
+ if (finish_move(side, 1)) break;
449
+ side = 3 - side;
450
+ search_begin(side);
451
+ searching = 1;
452
+ }
453
+ }
454
+ pass &= game_ok();
455
+ put_text(20, 0, "SLICED NODES A FRAME"); put_dec(20, 7, SLICE, 1);
456
+ put_check(21);
457
+ put_text(22, 14, "MISSED MOST/MOVE");
458
+ put_dec(22, 21, missed, 5); put_dec(22, 37, mfmax, 3);
459
+ put_text(23, 0, " WORST SLICE CYCLES");
460
+ put_dec(23, 15, worstn, 5);
461
+ #endif
462
+
463
+ put_text(8, 0, "DEPTH MOVES WINNER");
464
+ put_dec(8, 6, DEPTH, 1); put_dec(8, 14, played, 2);
465
+ put_text(8, 24, winner == 1 ? "YELLOW" : winner == 2 ? "RED" : "NONE");
466
+ put_text(9, 0, "SEQ");
467
+ put_text(24, 0, pal ? "PAL" : "NTSC");
468
+ put_text(24, 5, RECURSIVE ? "RECURSIVE" : "ITERATIVE");
469
+ put_text(24, 16, pass ? "PASS" : "FAIL");
470
+ vic.color_border = pass ? VCOL_GREEN : VCOL_RED;
471
+ for (;;) ;
472
+ return 0;
473
+ }
474
+ ```
475
+
476
+ ## Build
477
+
478
+ ```bash
479
+ oscar64 -tm=c64 -O2 -o=game-tree-search.prg game-tree-search.c
480
+ ```
481
+
482
+ Run headless (PAL; add `-model ntsc` for NTSC):
483
+
484
+ ```bash
485
+ timeout 180 x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 -limitcycles 160000000 -exitscreenshot game-tree-search.png -autostart game-tree-search.prg
486
+ ```
487
+
488
+ The report is on screen between 135,000,000 and 140,000,000 cycles on
489
+ PAL and between 125,000,000 and 130,000,000 on NTSC (measured by
490
+ bisecting `-limitcycles`); 160,000,000 leaves a margin. The sliced game
491
+ takes most of that: 4,880 frames.
492
+
493
+ ## Expected output
494
+
495
+ `screenshots/game-tree-search.png` (PAL) and
496
+ `screenshots/game-tree-search-ntsc.png` (NTSC). Black screen, green
497
+ border. Screen rows 1 to 6 hold the full board, top row first, one
498
+ filled circle (screen code $51) every second column: yellow for the side
499
+ that moved first, red for the other. Top row first, the board reads:
500
+
501
+ ```text
502
+ YYYRRYR
503
+ RRYYRRY
504
+ YYYRYYR
505
+ RRRYRRY
506
+ YYYRRYR
507
+ RRYYRYR
508
+ ```
509
+
510
+ Rows 8 to 24, PAL:
511
+
512
+ ```text
513
+ DEPTH 5 MOVES 42 WINNER NONE
514
+ SEQ 332433332224211444245
515
+ 111100055000655566666
516
+ BLOCKING
517
+ COLS 42/42 NODES 42/42
518
+ NODES 019292 CYCLES 17397672
519
+ NODES/S 01092 CYC/NODE 00901
520
+ FRAMES/MOVE MIN 000 MAX 103 ALL 0885
521
+ SLOWEST MOVE 2019814 CYCLES
522
+ ONE NODE A CALL
523
+ COLS 42/42 NODES 42/42
524
+ WORST CALL 02301 CYCLES
525
+ SLICED 4 NODES A FRAME
526
+ COLS 42/42 NODES 42/42
527
+ FRAME 04880 - MISSED 00000 MOST/MOVE 591
528
+ WORST SLICE 05024 CYCLES
529
+ PAL ITERATIVE PASS
530
+ ```
531
+
532
+ NTSC reads `CYCLES 17601946`, `NODES/S 01120 CYC/NODE 00912`,
533
+ `FRAMES/MOVE MIN 000 MAX 120 ALL 1028`, `SLOWEST MOVE 2043608`,
534
+ `WORST CALL 02329`, `WORST SLICE 05325` and `NTSC`; the rest is the same.
535
+
536
+ - `SEQ` is the column (0 to 6, left to right) of each of the 42 moves of
537
+ the sliced game.
538
+ - `COLS` and `NODES` count the moves whose column, and whose node count,
539
+ equal the model's, once per game. A node is one position made by the
540
+ search, the root's children included.
541
+ - `CYCLES` is the sum over the 42 blocking searches, CIA1 timer A
542
+ cascaded into timer B, display on, with the frame counter's raster IRQ
543
+ running (one interrupt a frame, inside the figure). `NODES/S` is nodes
544
+ times the phi2 clock (985,248 Hz PAL, 1,022,727 Hz NTSC) over cycles;
545
+ NTSC has the faster clock, so it searches more nodes a second while
546
+ spending more cycles on each (a shorter frame and so a larger share of
547
+ badlines and interrupts, not separated here).
548
+ - `FRAMES/MOVE` is the fewest and most display frames one blocking
549
+ search took and the total, from the raster IRQ counter on line 250.
550
+ - `SLOWEST MOVE` is the most cycles one blocking search took: move 7,
551
+ 2,360 nodes, about two seconds on either model.
552
+ - `WORST CALL` is the most cycles one `search_step(1)` call took over the
553
+ whole game: one node, any returns up the plies before it, and the
554
+ call itself, timed like `CYCLES`, so it can include badlines and the
555
+ frame interrupt.
556
+ - `FRAME` is the frame count of the sliced game and the character after
557
+ it a spinner, both advanced by the main loop once a frame. `MISSED`
558
+ counts frames in which the loop did not run: the IRQ counter moved on by
559
+ more than one while the loop was busy. `MOST/MOVE` is the most frames
560
+ one sliced move took, the frame that plays it included. `WORST SLICE`
561
+ is the most cycles one `search_step(4)` call took.
562
+
563
+ The model: the same search in Python. Its column list, node list and
564
+ final board are the values compiled into the listing and quoted above.
565
+
566
+ ```python
567
+ EMPTY, EDGE, WIN, INF, DEPTH = 0, 3, 1000, 30000, 5
568
+ ROW = [[3, 4, 5, 7, 5, 4, 3], [4, 6, 8, 10, 8, 6, 4], [5, 8, 11, 13, 11, 8, 5]]
569
+ WEIGHT = [0] * 64 # windows of four through each cell
570
+ for r, w in enumerate(ROW + ROW[::-1]):
571
+ WEIGHT[(r + 1) * 8:(r + 1) * 8 + 7] = w
572
+ ORDER, DIRS = [3, 2, 4, 1, 5, 0, 6], [1, 8, 9, 7]
573
+ board = [EDGE] * 64
574
+ for r in range(1, 7):
575
+ board[r * 8:r * 8 + 7] = [EMPTY] * 7
576
+ height, st = [0] * 7, {'score': 0, 'moves': 0, 'nodes': 0}
577
+
578
+ def wins(i, p):
579
+ for d in DIRS:
580
+ n, j = 1, i + d
581
+ while board[j] == p: n, j = n + 1, j + d
582
+ j = i - d
583
+ while board[j] == p: n, j = n + 1, j - d
584
+ if n >= 4: return True
585
+ return False
586
+
587
+ def make(c, side):
588
+ i = (height[c] + 1) * 8 + c
589
+ board[i] = side; height[c] += 1; st['moves'] += 1
590
+ st['score'] += WEIGHT[i] if side == 1 else -WEIGHT[i]
591
+ return i
592
+
593
+ def unmake(c, i, side):
594
+ board[i] = EMPTY; height[c] -= 1; st['moves'] -= 1
595
+ st['score'] -= WEIGHT[i] if side == 1 else -WEIGHT[i]
596
+
597
+ def negamax(ply, alpha, beta, side):
598
+ best, bestcol = -INF, None
599
+ for c in ORDER:
600
+ if height[c] == 6: continue
601
+ i = make(c, side); st['nodes'] += 1
602
+ if wins(i, side): v = WIN - ply
603
+ elif st['moves'] == 42: v = 0
604
+ elif ply + 1 == DEPTH: v = st['score'] if side == 1 else -st['score']
605
+ else: v = -negamax(ply + 1, -beta, -alpha, 3 - side)[0]
606
+ unmake(c, i, side)
607
+ if v > best: # strictly better: the first best column stays
608
+ best, bestcol = v, c
609
+ if v > alpha:
610
+ alpha = v
611
+ if alpha >= beta: break
612
+ return best, bestcol
613
+
614
+ side, cols, nodes = 1, [], []
615
+ while st['moves'] < 42:
616
+ st['nodes'] = 0
617
+ col = negamax(0, -INF, INF, side)[1]
618
+ cols.append(col); nodes.append(st['nodes'])
619
+ if wins(make(col, side), side): break
620
+ side = 3 - side
621
+ print(''.join(map(str, cols)), sum(nodes))
622
+ print(nodes)
623
+ for r in range(6, 0, -1): # top row first, as on screen
624
+ print(''.join('.YR'[board[r * 8 + c]] for c in range(7)))
625
+ ```
626
+
627
+ ## Why this works
628
+
629
+ The board is 64 bytes: eight rows of eight, the six play rows in the
630
+ middle and a sentinel byte (`EDGE`) in row 0, row 7 and column 7. A cell
631
+ is `(row << 3) + column`, and the four line directions are +1, +8, +9
632
+ and +7. `wins()` counts equal pieces outwards from the new piece both
633
+ ways along each direction; a run stops at the first byte that is not the
634
+ player's, and a sentinel never is, so no bounds test is needed. Column 7
635
+ serves both edges of a row: stepping left from column 0 lands on
636
+ column 7 of the row below. A move is a column: `height[c]` gives the
637
+ cell, so move generation is seven tests of `height[c] == 6`.
638
+
639
+ The evaluation is a sum kept up to date by `make()` and `unmake()`: each
640
+ cell's weight is the number of four-in-a-row windows through it (3 at a
641
+ corner, 13 at the centre of the middle rows), added for the first player
642
+ and subtracted for the second. A leaf then costs one load and a negate,
643
+ not a scan of the board. A win scores `WIN - ply`, so a quicker win
644
+ scores higher; a full board scores 0.
645
+
646
+ `search_step()` is negamax: every value is from the view of the side to
647
+ move, and a child's value is negated on the way up. Each ply keeps its
648
+ window (`p_alpha`, `p_beta`), its best value, the index of the next
649
+ column to try and the move it made. Descending sets the child's window to
650
+ the parent's negated and swapped; returning unmakes the parent's move
651
+ and hands up the child's best, negated. A value at least `p_beta` ends
652
+ the ply at once (`p_next = 7`): the opponent above already has a better
653
+ choice and will not allow this line. Ties keep the first column
654
+ (strictly greater replaces), which is why the model must try columns in
655
+ the same order to match node for node.
656
+
657
+ **Time slicing.** `search_step(n)` makes at most `n` nodes. When the
658
+ budget runs out it stores the ply and the side to move in `s_ply` and
659
+ `s_side` and returns 0; the per-ply records already hold the rest of the
660
+ search, so the next call goes on from the same place. The budget is
661
+ tested only before a node is made, so the returns up the plies after a
662
+ slice's last node run at the start of the next slice. The sliced game
663
+ made the same 42 moves with the same node counts as the blocking game and
664
+ the model. Its 4,880 frames match the model exactly: a search of `n`
665
+ nodes takes ceil(n / 4) slices, 4,838 over the game, and each move one
666
+ more frame to play; the slowest move is ceil(2,360 / 4) + 1 = 591.
667
+ `MISSED 00000` on both models shows the main loop ran in every frame, so
668
+ the spinner and the counter never stopped. The cost is time: the slowest
669
+ move takes 591 frames, about 11.8 s on PAL and 9.9 s on NTSC
670
+ (arithmetic), against 103 and 120 frames blocking.
671
+
672
+ **Why four nodes a frame.** The worst single call was 2,301 cycles on
673
+ PAL and 2,329 on NTSC. Four of the NTSC figure, 9,316 cycles, fit in
674
+ half a PAL frame of 9,828 (arithmetic), leaving the other half for the
675
+ game. The bound is loose: the worst slice measured was 5,024 cycles on
676
+ PAL and 5,325 on NTSC, because a slice of four rarely meets four worst
677
+ nodes, and it counts the call overhead four times. A game that needs a
678
+ guarantee plans on the bound.
679
+
680
+ **Iterative or recursive.** The `-dRECURSIVE=1` build plays the same 42
681
+ blocking moves with the same node counts. Oscar64 gives `negamax()` an
682
+ 18-byte frame in its software stack region at $9000-$9FFF (the `.asm`
683
+ listing's `SBC #$12` on `SP`), because its static frame allocation cannot
684
+ cover a function that calls itself. Measured with the pinned command
685
+ (not pinned itself), it took 18,941,070 cycles on PAL (981 a node, 109
686
+ frames for the slowest move) against 17,397,672 (901) for the loop, and
687
+ 19,164,989 (993) against 17,601,946 (912) on NTSC: about 9 % slower. A
688
+ recursive search also cannot be sliced without keeping its whole call
689
+ chain alive between frames.
690
+
691
+ **Optimisation level.** The whole program, all three games, passes at
692
+ `-O0`, `-O1`, `-O2`, `-O3` and `-Os`. The blocking game's cycles a node
693
+ on PAL were 1,125 at `-O0`, 918 at `-O1`, 901 at `-O2`, 836 at `-Os` and
694
+ 780 at `-O3`, the last with a PRG of 10,073 bytes against 4,985 at `-O2`.
695
+
696
+ **An Oscar64 fault met on the way.** A first `make()` wrote
697
+ `char i = ((height[c] + 1) << 3) + c; board[i] = side; height[c]++;` and
698
+ returned `i`. Oscar64 1.32.271 (local build c1270bc, see #25) compiled
699
+ the increment as `LDA side; STA board,y; ADC #$01; STA height,x`: the
700
+ 6502 back-end drops the reload of `height[c]` and the `CLC`, so
701
+ `height[c]` gets `side + 1` plus the carry. The intermediate code is
702
+ correct. The full listing with that `make()` played `242424...` and
703
+ matched 10 of 42 columns and no node counts at `-O0`, `-O1`, `-O2` and
704
+ `-Os`; at `-O3` it passed, only because inlining changed the code. A
705
+ short test, `make(3, 2)` through volatile arguments with the returned
706
+ cell stored and then a check that `height[3]` is 1, turns the border red
707
+ at every level from `-O0` to `-Os`. Reading the height into a variable
708
+ first, as the listing does, gives the right result at every level. There
709
+ is no diagnostic. Upstream Oscar64 has later optimiser fixes; this recipe
710
+ was not built with upstream HEAD, and `toolchains/oscar64-reference.md`
711
+ records how HEAD handles the pattern (#30).
712
+
713
+ **Why five plies.** The model's node totals for the whole self-play game
714
+ are 4,670 at depth 4, 19,292 at 5, 59,145 at 6 and 132,100 at 7. At the
715
+ measured 901 cycles a node, depth 5 costs about 17.6 PAL seconds for 42
716
+ blocking moves (arithmetic), and the slowest move two seconds. Depth 7's
717
+ first move alone is 6,062 nodes, about 5.5 s blocking (arithmetic).
718
+
719
+ ## Verification
720
+
721
+ VICE x64sc 3.10, windowless build, Oscar64 1.32.271 (local build
722
+ c1270bc), 2026-09-23.
723
+
724
+ **Board and text against the model.** A script read the 42 board cells
725
+ of both pictures, matched each 8x8 cell against `chargen-901225-01.bin`
726
+ and its colour against VICE's palette triples for the model
727
+ (`runtime/vice-reference.md`, "The default palette"): yellow (255, 255,
728
+ 70) PAL and (255, 248, 141) NTSC, red (175, 60, 88) and (169, 71, 100).
729
+ 0 mismatches with the model's final board on PAL and on NTSC. The `SEQ`
730
+ rows decoded to the model's 42 columns. The text rows were decoded the
731
+ same way and read as quoted above; the border is (98, 213, 50) on PAL
732
+ and (114, 189, 103) on NTSC, colour 5 on each.
733
+
734
+ **Reproducibility.** The pinned command was run twice per model; the PNG
735
+ bytes were identical each time.
736
+
737
+ ## Sources
738
+
739
+ - `cave-scan.md` and `print-number.md`: the CIA1 cascaded timer and the
740
+ raster IRQ frame counter, reused in shape.
741
+ - `toolchains/oscar64-reference.md`, "Avoid recursion and function
742
+ pointers": the static call graph and recursion; the cost of the
743
+ recursive form was measured here.