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,1686 @@
1
+ ---
2
+ category: banking
3
+ chip: 6510
4
+ ---
5
+
6
+ <!-- doc-type: technique-reference -->
7
+
8
+ # Memory Banking Techniques
9
+
10
+ The Commodore 64 is a machine built on layered illusions. Its 6510 CPU sees a flat
11
+ 16-bit address space, but that 64 KB window simultaneously looks onto 64 KB of
12
+ dynamic RAM, 8 KB of BASIC ROM, 8 KB of KERNAL ROM, 4 KB of character generator
13
+ ROM, the VIC-II register set, the SID, two CIA chips, and a slab of Color RAM.
14
+ None of those components occupy distinct address ranges — they all occupy the same
15
+ ranges, bank-switched in and out by hardware. The programmer's job is to control
16
+ which physical device answers a given address at any given moment, and to keep
17
+ the VIC-II's separate 16 KB view coherent with whatever CPU-visible layout has
18
+ been selected.
19
+
20
+ Banking on the C64 is not a special mode you enable for advanced work. It is the
21
+ default state of the machine from the first instruction after reset. Every write to
22
+ $D011 silently depends on the I/O chip being visible at $D000. Every IRQ relies on
23
+ KERNAL ROM or a RAM replacement being readable at $FFFA-$FFFF. Understanding the
24
+ banking system is a prerequisite for any serious C64 development.
25
+
26
+ This document covers the CPU I/O port that selects which ROMs are visible, the
27
+ CIA2 register that defines VIC's 16 KB working window, the relationship between
28
+ VIC banking and character ROM visibility, $D018's role in positioning screen RAM
29
+ and bitmaps within the VIC bank, the EasyFlash cartridge's per-bank mechanism,
30
+ and the technique of hiding working data under KERNAL ROM.
31
+
32
+ ---
33
+
34
+ ## cpu_io_port_bank — $00/$01 port banking
35
+
36
+ **Complexity:** medium
37
+ **Region:** both
38
+
39
+ ### Why
40
+
41
+ Every C64 program eventually outgrows the default memory layout. BASIC ROM at
42
+ $A000-$BFFF eats 8 KB that could hold graphics data, music, or game code.
43
+ KERNAL ROM at $E000-$FFFF eats another 8 KB. If a program needs to address the
44
+ character ROM to copy its bitmap patterns into RAM, the I/O chips must be
45
+ temporarily replaced by the character ROM window. The CPU I/O port is the
46
+ single switch that controls all of this.
47
+
48
+ ### How
49
+
50
+ The 6510 CPU has six on-chip general-purpose I/O pins that are accessible
51
+ through two memory locations that exist below the normal RAM map:
52
+
53
+ - **$0000 — Data Direction Register (DDR).** Each bit set to 1 makes the
54
+ corresponding $0001 bit an output; each bit set to 0 makes it an input.
55
+ The DDR must be configured before writing $0001 has any effect on output pins.
56
+ The KERNAL sets the DDR to $2F at reset (bits 0-3 and 5 are outputs; bit 4,
57
+ the cassette sense line, is an input).
58
+
59
+ - **$0001 — Data register.** Bits 0-2 are the three banking control lines.
60
+ Writing a new value here changes which ROMs the PLA presents to the CPU.
61
+
62
+ The three relevant bits are:
63
+
64
+ | Bit | Name | 0 means | 1 means |
65
+ |-----|--------|--------------------------|--------------------------------------|
66
+ | 0 | LORAM | $A000-$BFFF is RAM | $A000-$BFFF can be BASIC ROM |
67
+ | 1 | HIRAM | $E000-$FFFF is RAM | $E000-$FFFF can be KERNAL ROM |
68
+ | 2 | CHAREN | $D000-$DFFF is char ROM | $D000-$DFFF is I/O (VIC/SID/CIA) |
69
+
70
+ BASIC ROM appears only when both LORAM and HIRAM are 1. KERNAL ROM appears
71
+ when HIRAM is 1 (regardless of LORAM). Character ROM appears at $D000-$DFFF
72
+ when CHAREN is 0 and at least one of LORAM or HIRAM is 1.
73
+
74
+ The commonly-used configurations are:
75
+
76
+ | $01 value | Name | $A000-$BFFF | $D000-$DFFF | $E000-$FFFF |
77
+ |-----------|-------------------------------|-------------|-------------|-------------|
78
+ | $37 (55) | Default (BASIC + KERNAL + I/O)| BASIC ROM | I/O | KERNAL ROM |
79
+ | $36 (54) | KERNAL + I/O (no BASIC) | RAM | I/O | KERNAL ROM |
80
+ | $35 (53) | I/O only (no ROM) | RAM | I/O | RAM |
81
+ | $34 (52) | All RAM (no ROM, no I/O) | RAM | RAM | RAM |
82
+ | $33 (51) | BASIC + KERNAL + char ROM | BASIC ROM | Char ROM | KERNAL ROM |
83
+
84
+ Mode $35 is the most common alternative to the default for demos and games: all
85
+ ROM is banked out, I/O remains accessible at $D000-$DFFF so the program can still
86
+ talk to VIC-II, SID, and CIA. Mode $34 replaces I/O with RAM as well, giving a
87
+ flat 64 KB of RAM at the cost of losing direct register access.
88
+
89
+ Mode $33 is used when a program needs to read the built-in character ROM at
90
+ $D000-$DFFF — for example to copy the ROM font into a custom RAM location for
91
+ modification.
92
+
93
+ ### Why it works
94
+
95
+ The PLA chip (906114) sits between the CPU's address and data buses and all the
96
+ peripheral devices. It monitors the three processor-port bits from the 6510 along
97
+ with the GAME and EXROM lines from the cartridge port. For any given combination
98
+ of those five inputs it asserts or deasserts the chip-select lines for each ROM
99
+ and the I/O devices. The RAM is always physically present; the PLA merely disables
100
+ the RAM's CAS line in a specific address range when it wants a ROM or I/O device
101
+ to win that range on reads. Writes to a ROM-mapped range always go to the
102
+ underlying RAM — the PLA routes a write cycle at $A000-$BFFF or $E000-$FFFF to
103
+ RAM whatever $01 says, and the same holds for $D000-$DFFF while character ROM is
104
+ mapped there (CHAREN = 0 with LORAM or HIRAM set). It is NOT true of $D000-$DFFF
105
+ while I/O is mapped ($35/$36/$37): there the write goes to the VIC/SID/CIA/colour-RAM
106
+ register and the RAM beneath is untouched (measured in VICE x64sc: RAM under $D000
107
+ seeded $1D in mode $34; $2D written to $D000 with $01 = $37 read back as $2D — the
108
+ sprite-0 X register — and the RAM under it still read $1D once I/O was banked out;
109
+ the same test with $E000 put the byte in RAM). An earlier version of this page
110
+ said writes go to RAM in every banking state. So you can write code or data into
111
+ $E000-$FFFF even while KERNAL ROM is banked in — the bytes land in RAM and become
112
+ visible once KERNAL is banked out; to put data under I/O, bank it out first ($34,
113
+ or $33 if char ROM is acceptable) with interrupts disabled.
114
+
115
+ ### Variations
116
+
117
+ **Mode $35 with custom IRQ vectors.** When HIRAM goes to 0, the CPU's hardware
118
+ IRQ vector at $FFFE/$FFFF and NMI vector at $FFFA/$FFFB are no longer in ROM —
119
+ they read from RAM. Before switching to $35, disable interrupts with SEI, write
120
+ your IRQ and NMI handler addresses to the RAM at $FFFE/$FFFB (these are in the
121
+ underlying RAM even while KERNAL ROM covers them — just write directly), then
122
+ write $35 to $01 and re-enable with CLI. The KERNAL-provided interrupt chain at
123
+ $EA31 is gone; the program owns all interrupts.
124
+
125
+ **Temporarily banking in char ROM.** To copy the ROM font to a custom location:
126
+ disable IRQs (SEI), write $33 to $01 to swap char ROM into $D000-$DFFF, perform
127
+ the copy loop, then write $37 back to restore normal layout, then CLI. The copy
128
+ must be complete before re-enabling the I/O chips.
129
+
130
+ **Preserving bits 3-5.** Bits 3 and 5 of $01 are datasette outputs — bit 3 the
131
+ write line, bit 5 the motor (0 = motor ON) — and bit 4 is the tape-button sense
132
+ input (DDR $2F leaves it an input, so writes to it do nothing). The constants
133
+ above ($33-$37) all keep bit 5 set, so writing them directly does not start the
134
+ motor; a value with bit 5 clear does — `LDA #$05 / STA $01` banks out the ROMs
135
+ and switches the motor on, and under mode $35 nothing turns it off again, because
136
+ the interlock that does so ($EA61) is part of the KERNAL IRQ you have just
137
+ removed. Prefer read-modify-write when changing only the banking bits so that
138
+ bits 3 and 5 are left as the tape code set them: read $01, AND #$F8, OR in the
139
+ new banking value, write back. (An earlier version of this paragraph called bit 4
140
+ an output that controls the datasette; it is the switch-sense input, as the DDR
141
+ note above says.)
142
+
143
+ ### Cycle budget
144
+
145
+ Banking switches take effect on the very next bus cycle after the write to $01
146
+ completes. There is no pipeline delay or setup time. The PLA combinatorially
147
+ decodes the processor-port lines on every cycle, so a STA $01 is followed
148
+ immediately by the new memory layout being visible on the next instruction fetch.
149
+
150
+ ### Recipes
151
+
152
+ No Phase 3 recipes target this technique directly. Banking is a supporting
153
+ infrastructure technique — recipes for specific effects (stable IRQ, raster bars,
154
+ custom charsets) use mode $35 or $37 as their starting point. Recipes land in
155
+ Phase 4+.
156
+
157
+ ---
158
+
159
+ ## vic_bank_select — CIA2 VIC bank select
160
+
161
+ **Complexity:** low
162
+ **Region:** both
163
+ **Uses registers:** DD00
164
+ **Claims:** cia2_vic_bank (owns)
165
+ **Claims basis:** estimated
166
+
167
+ ### Why
168
+
169
+ The VIC-II chip has a 14-bit internal address bus: it can independently access
170
+ any of 16,384 bytes (16 KB) of the C64's RAM. But the C64 has 64 KB of RAM.
171
+ To allow VIC to reach graphics data anywhere in the address space, CIA2 port A
172
+ bits 0-1 act as a two-bit extension that shifts VIC's 16 KB window to one of
173
+ four positions. Choosing the right VIC bank is the first decision in any
174
+ memory layout — everything else (screen RAM position, char set or bitmap position,
175
+ sprite data, sprite pointer table) is relative to whichever 16 KB bank VIC sees.
176
+
177
+ ### How
178
+
179
+ CIA2 port A, the register at $DD00, multiplexes the VIC bank selection onto its
180
+ lowest two bits alongside the IEC serial bus and RS-232 lines:
181
+
182
+ | $DD00 bits 1-0 | VIC bank | Address range in CPU view |
183
+ |----------------|----------|---------------------------|
184
+ | 11 | 0 | $0000-$3FFF |
185
+ | 10 | 1 | $4000-$7FFF |
186
+ | 01 | 2 | $8000-$BFFF |
187
+ | 00 | 3 | $C000-$FFFF |
188
+
189
+ After reset the KERNAL sets $DD02 (CIA2 port A DDR) to $3F (bits 0-5 are
190
+ outputs). Bits 0-1 of $DD00 reset to 11, placing VIC in bank 0 — which is why
191
+ the default screen at $0400 is visible to VIC without any setup.
192
+
193
+ To change the bank, always read-modify-write $DD00: read the current value, mask
194
+ off bits 0-1 with AND #$FC, then OR in the desired two-bit bank code, then write
195
+ back. This preserves the IEC bus state and prevents glitches on the serial port.
196
+
197
+ ### Why it works
198
+
199
+ Between CIA2 port A pins PA0-PA1 and the VIC-II's address-bus extension inputs
200
+ VA14-VA15 there is a pair of inverting buffers on the C64 motherboard. The two
201
+ bit-patterns are therefore inverted with respect to each other: when CIA2 drives
202
+ both pins high (the all-ones reset state), the VIC receives low on VA14-VA15,
203
+ which it interprets as bank 0. The counter-intuitive mapping — that bit pattern
204
+ 11 means bank 0 — comes from this inversion. The design choice is pragmatic:
205
+ TTL open-collector bus lines default to a pulled-high state, so the safe reset
206
+ state (everything high) naturally delivers VIC bank 0 and screen RAM at $0400
207
+ without any software configuration.
208
+
209
+ After changing $DD00, the new bank address extension takes effect immediately on
210
+ the next VIC fetch cycle. If the raster is currently rendering and VIC is
211
+ mid-fetch, the visible result will change partway through the current line.
212
+ Production code always makes bank changes during the vertical blank or in a
213
+ raster interrupt timed to occur in the overscan region.
214
+
215
+ ### Variations
216
+
217
+ **Banks 1 and 3 for full custom layouts.** VIC banks 1 ($4000-$7FFF) and 3
218
+ ($C000-$FFFF) contain no char ROM shadow; the VIC sees only RAM there. This is
219
+ the preferred layout for demos that use fully custom graphics and want maximum
220
+ clarity: all graphics live in one 16 KB bank, the CPU never accidentally reads
221
+ char ROM data, and the mapping is unambiguous.
222
+
223
+ **Multi-bank sprite tricks.** Sprite data must be addressable within VIC's current
224
+ bank, but the CPU can freely write into any bank's RAM while VIC sees only its
225
+ own bank. Double-buffering sprite data in the off-bank (writing to the other
226
+ 16 KB while VIC reads this 16 KB) is a clean way to animate sprites without
227
+ tearing, by flipping $DD00 once per frame rather than per-sprite.
228
+
229
+ ### Cycle budget
230
+
231
+ No critical cycle constraints. The $DD00 write is a single STA instruction;
232
+ the bank switch is instantaneous from VIC's perspective on the next cycle.
233
+
234
+ ### Recipes
235
+
236
+ No Phase 3 recipes. VIC bank selection is a layout prerequisite used by all
237
+ graphics recipes. Recipes land in Phase 4+.
238
+
239
+ ---
240
+
241
+ ## char_rom_under_vic — Map char ROM into VIC's bank
242
+
243
+ **Complexity:** medium
244
+ **Region:** both
245
+ **Uses registers:** DD00, D018
246
+
247
+ ### Why
248
+
249
+ The C64 ships with a 4 KB character generator ROM containing the default
250
+ uppercase/graphics and lower-case/uppercase font data. Most programs need a
251
+ custom font, custom symbols, or a modified character set at some point. Before
252
+ any custom character data can be used, the developer must either place their
253
+ custom data at the address VIC expects to find characters, or understand how
254
+ the ROM characters are shadowed into VIC banks 0 and 2 so that the default
255
+ font remains available without consuming RAM.
256
+
257
+ ### How
258
+
259
+ The character ROM resides at $D000-$DFFF from the CPU's perspective (when CHAREN
260
+ is 0 and at least LORAM or HIRAM is 1). But VIC-II does not share the CPU's
261
+ address space — VIC has its own 16 KB window determined by $DD00. The character
262
+ ROM hardware includes a second set of decode logic that makes it appear inside
263
+ VIC banks 0 and 2 at specific offsets:
264
+
265
+ | VIC bank | CPU range | Char ROM visible to VIC at | VIC-relative address |
266
+ |----------|---------------|---------------------------|----------------------|
267
+ | 0 | $0000-$3FFF | Yes | $1000-$1FFF (CPU $1000-$1FFF) |
268
+ | 1 | $4000-$7FFF | No — VIC sees RAM only | n/a |
269
+ | 2 | $8000-$BFFF | Yes | $1000-$1FFF (CPU $9000-$9FFF) |
270
+ | 3 | $C000-$FFFF | No — VIC sees RAM only | n/a |
271
+
272
+ In VIC bank 0, addresses $1000-$1FFF in VIC's view return char ROM data even
273
+ though those CPU addresses ($1000-$1FFF) contain normal RAM. The KERNAL defaults
274
+ take advantage of this: the default $D018 value of $14 points the character
275
+ generator base to $1000 within bank 0, which silently reads the char ROM without
276
+ occupying any RAM.
277
+
278
+ To place a custom character set at a specific address, choose a VIC bank and
279
+ pick a character base address that does not conflict with the char ROM shadow.
280
+ Common choices:
281
+
282
+ - Bank 0, char base at $2000 (VIC-relative): no char ROM interference.
283
+ CPU loads the charset into RAM at $2000-$27FF.
284
+ $D018 CB field = %100 (= 4), giving $D018 = $18 for VM at $0400 + CB at $2000.
285
+ (An earlier version said $14; $14 is CB = %010 = $1000, the char ROM shadow,
286
+ i.e. the KERNAL default.)
287
+
288
+ - Bank 1 (any address): no char ROM shadow in bank 1 at all.
289
+ CPU loads the charset anywhere in $4000-$7FFF.
290
+
291
+ - Bank 2, char base at VIC-relative $0000 (CPU $8000), $D018 CB field = %000:
292
+ clear of the char ROM shadow, which in bank 2 sits at VIC-relative $1000-$1FFF
293
+ (CPU $9000-$9FFF). CPU loads the charset into RAM at $8000-$87FF. (An earlier
294
+ version of this bullet and of the table above put CPU addresses in the
295
+ VIC-relative slots for bank 2; VIC-relative addresses only run $0000-$3FFF.
296
+ Measured in VICE x64sc: bank 2, CB=0 reads CPU $8000; bank 2, CB=2 reads char
297
+ ROM although CPU $9000 RAM is zero.)
298
+
299
+ ### Why it works
300
+
301
+ Inside the C64 motherboard, the character ROM's chip-select line responds not
302
+ only to the CPU-side address decoder but also to a separate signal derived from
303
+ VIC's address bus via the same PLA. When VIC is in bank 0 or bank 2, and VIC
304
+ generates an internal address in the $1000-$1FFF window — CPU $1000-$1FFF in
305
+ bank 0, CPU $9000-$9FFF in bank 2 — the PLA asserts char ROM's chip-select instead of RAM's CAS
306
+ line. The CPU never knows this is happening — from the CPU's side those are
307
+ ordinary RAM locations whose content is readable and writable. The char ROM
308
+ shadow is VIC-only hardware behavior, not a side-effect of CPU banking.
309
+
310
+ ### Variations
311
+
312
+ **Dual-charset switching.** Because custom charsets live in RAM and the char
313
+ ROM shadow is a hardware overlay, a program can maintain two charsets in RAM
314
+ at different $D018 CB offsets and switch fonts by writing $D018 on a per-raster-
315
+ line basis in an IRQ. The VIC applies the new $D018 value from the next character
316
+ fetch onward, enabling per-line font changes.
317
+
318
+ **Charset placement at $3800 in bank 0.** Advanced demos sometimes place a
319
+ charset near the end of bank 0 ($3800-$3FFF is 2 KB). This allows sprite pointers
320
+ and the sprite pool to coexist in the same 16 KB bank with no gaps.
321
+
322
+ **Copying the ROM font.** To modify the built-in font, copy it first: disable
323
+ IRQs, set $01 to $33 (char ROM in $D000-$DFFF), copy 2 KB from $D000-$D7FF
324
+ into target RAM, restore $01 to $37. Then point $D018 at the RAM copy.
325
+
326
+ ### Cycle budget
327
+
328
+ No cycle budget for the bank configuration itself. If $D018 is changed inside a
329
+ raster IRQ, the timing of where the new char base takes effect matters — see the
330
+ raster techniques doc.
331
+
332
+ ### Recipes
333
+
334
+ No Phase 3 recipes. Custom-charset setup is infrastructure shared by many
335
+ techniques and will be demonstrated in Phase 4+ recipe docs.
336
+
337
+ ---
338
+
339
+ ## charset_copy_rom_to_ram — Copy the character ROM into RAM
340
+
341
+ **Complexity:** low
342
+ **Region:** both
343
+ **Uses registers:** D018
344
+ **Requires:** cpu_io_port_bank
345
+ **Cost:** bytes_code=70, bytes_data=2048
346
+ **Cost basis:** derived-listing
347
+ **Cost measured on:** kickassembler-charset-copy-rom-to-ram (one-off copy)
348
+
349
+ ### Why
350
+
351
+ The built-in font lives in ROM, so it cannot be edited in place, and
352
+ the VIC only sees it in banks 0 and 2 (`char_rom_under_vic`). A program
353
+ that wants the stock glyphs plus a few of its own, or the stock font in
354
+ VIC bank 1 or 3, needs a RAM copy. The copy is cheap and runs once, but
355
+ it is the one moment in most programs when the I/O chips leave the
356
+ address map, and that moment has a failure mode that stops the machine.
357
+
358
+ ### How
359
+
360
+ 1. `SEI`.
361
+ 2. Read `$01`, clear bits 0-2, set `$33` (CHAREN low, HIRAM and LORAM
362
+ high): `$D000-$DFFF` now reads the character ROM. Keep bits 3-5 as
363
+ found (`cpu_io_port_bank`, "Preserving bits 3-5").
364
+ 3. Copy 2 KB (`$D000-$D7FF`, the upper-case set) or 4 KB (both sets)
365
+ to a RAM address the VIC can reach and that is not the ROM shadow.
366
+ In bank 0 that means anything but `$1000-$1FFF`.
367
+ 4. Read `$01`, clear bits 0-2, set `$37`: I/O is back.
368
+ 5. `CLI`.
369
+ 6. Point `$D018`'s character-base field at the copy. Only then edit
370
+ glyphs, or edit them first and switch afterwards; either order works
371
+ because the VIC is not reading the copy until the switch.
372
+
373
+ ### Why it works
374
+
375
+ `$01` bit 2 (CHAREN) with HIRAM or LORAM set selects the character ROM
376
+ in the `$D000` window instead of the VIC, SID, CIAs and colour RAM. The
377
+ PLA switches on the next bus cycle. Writes in that window go to the RAM
378
+ underneath, not to the ROM and not to the I/O chips.
379
+
380
+ The interrupt flag is the whole safety of the technique. The KERNAL's
381
+ IRQ handler acknowledges CIA1 by reading `$DC0D`, scans the keyboard
382
+ through `$DC00`/`$DC01`, and a raster handler acknowledges the VIC by
383
+ writing `$D019`. While the ROM is mapped every one of those addresses
384
+ is a font byte. An acknowledge that never happens leaves the interrupt
385
+ line asserted, so the handler re-enters as soon as it returns, and the
386
+ copy loop never gets another cycle. Measured in VICE x64sc with the
387
+ recipe below: the copy done with interrupts enabled managed 30 of 256
388
+ loop iterations on PAL and 7 on NTSC before the first KERNAL IRQ, and
389
+ never advanced again; the handler ran 26 times in the 61,000 cycles a
390
+ watchdog NMI allowed it, and its keyboard scan queued one phantom key.
391
+ Pitfall `irq_during_charen_window` has the detail. A program that has
392
+ replaced the KERNAL IRQ with its own handler is in the same position if
393
+ that handler touches any I/O register, which every raster handler does.
394
+
395
+ ### Variations
396
+
397
+ **Copy one set only.** The upper-case/graphics set is `$D000-$D7FF`,
398
+ the lower-case set `$D800-$DFFF`. Most programs want one; eight
399
+ load/store pairs in the loop instead of sixteen.
400
+
401
+ **Copy with the display blanked.** Every badline the copy spans costs
402
+ about 40 cycles. Clear `$D011` bit 4, wait for a frame past line `$30`
403
+ so the VIC samples DEN off, copy, restore. Or do the copy before the
404
+ display is switched on at all, which is where a loader or a title
405
+ screen usually has it anyway.
406
+
407
+ **Copy under a raster interrupt.** If a raster IRQ is already running,
408
+ `SEI` alone is not enough for a long copy: the interrupt is only
409
+ deferred, and the frame it was meant to split is torn. Stop the raster
410
+ IRQ (clear `$D01A` bit 0, acknowledge `$D019`), copy, restart it. The
411
+ copy is under 40,000 cycles, two PAL frames, so the interruption is one
412
+ or two frames of the default picture.
413
+
414
+ **Pointer loop.** In C, `memcpy(dst, (const char *)0xd000, 2048)`
415
+ between the two `$01` writes does the same job; the compiler's loop is
416
+ slower than the unrolled indexed one but the difference is one frame.
417
+
418
+ ### Cycle budget
419
+
420
+ Measured in VICE x64sc with the CIA2 timers, the 17-cycle timing window
421
+ subtracted, in the recipe below: the 2 KB copy costs 19,733 cycles with
422
+ the display blanked on both models, which is exactly the instruction
423
+ table (eight `LDA abs,X` / `STA abs,X` pairs, `INX`, `BNE`, 256 times,
424
+ plus 22 for the two `$01` switches). With the display on it is 20,784
425
+ on PAL and 21,150 on NTSC, the difference being the badlines the window
426
+ spans. The 4 KB copy with the display on is 39,580 PAL, 40,267 NTSC;
427
+ arithmetic puts it at 38,165 with the display blanked, not measured
428
+ here. Two figures from earlier builds are worth knowing: a copy routine
429
+ that holds `SEI` and `CLI` inside the timed window reads about 470
430
+ cycles high, because the KERNAL IRQ that fell due during the copy runs
431
+ at the `CLI`; and a loop whose `BNE` crosses a page boundary reads 255
432
+ high (pitfall `branch_page_cross_extra_cycle`). The Cost line above is
433
+ the built 2 KB routine's code and the RAM the copy occupies; the copy
434
+ runs once, so it carries no per-frame figure.
435
+
436
+ ### Recipes
437
+
438
+ - `recipes/kickassembler/charset-copy-rom-to-ram.md`: the copy,
439
+ checked against a host checksum of the ROM, timed three ways, the
440
+ VIC pointed at it, and then done once without `SEI` under a watchdog
441
+ NMI so the hang is measured rather than described.
442
+ - `recipes/kickassembler/big-font-scroller.md` and
443
+ `recipes/oscar64/load-asset-runtime.md` do the copy inline as a step
444
+ of something else.
445
+
446
+ ---
447
+
448
+ ## screen_ram_relocation — Move screen RAM via $D018 hi-nibble
449
+
450
+ **Complexity:** low
451
+ **Region:** both
452
+ **Uses registers:** D018
453
+
454
+ ### Why
455
+
456
+ The default screen RAM at $0400-$07E7 (1000 bytes for 40×25 characters) sits
457
+ inside VIC bank 0 but is also in the middle of the general-purpose RAM range.
458
+ Programs that want to use $0400-$07FF for code, sprite data, or other graphics
459
+ must relocate screen RAM. The mechanism is in $D018: the upper nibble (bits 4-7)
460
+ selects any 1 KB boundary inside the current VIC bank as the new screen RAM base.
461
+
462
+ ### How
463
+
464
+ Bits 7-4 of $D018 (the VM field) hold a 4-bit value that selects the screen RAM
465
+ offset within the 16 KB VIC bank:
466
+
467
+ ```
468
+ screen_ram_address_in_vic_bank = VM_bits * $0400
469
+ ```
470
+
471
+ With VM_bits ranging from 0 to 15, the 16 possible positions are:
472
+
473
+ | VM bits (7-4) | Offset in VIC bank | CPU address in bank 0 |
474
+ |---------------|--------------------|-----------------------|
475
+ | 0000 | $0000 | $0000 |
476
+ | 0001 | $0400 | $0400 (default) |
477
+ | 0010 | $0800 | $0800 |
478
+ | 0011 | $0C00 | $0C00 |
479
+ | ... | ... | ... |
480
+ | 1111 | $3C00 | $3C00 |
481
+
482
+ The default KERNAL value of $D018 is $14 (binary 0001 0100), giving VM = %0001
483
+ = 1, which places screen RAM at offset $0400 within bank 0, i.e. CPU address
484
+ $0400. This is the "READY." screen you see at boot. ($14 is the value the KERNAL
485
+ writes; the register reads back as $15 because bit 0 is unused and reads 1 —
486
+ measured in VICE x64sc.)
487
+
488
+ Sprite pointers always follow the screen RAM: the 8 bytes at screen_base + $3F8
489
+ (screen_base + 1016) hold the sprite data-block pointers. When screen RAM moves,
490
+ sprite pointers move with it automatically — they are at a fixed offset from the
491
+ screen base, not at a fixed CPU address.
492
+
493
+ To move screen RAM, write a new value to $D018 keeping the CB bits unchanged:
494
+ read the register, mask bits 3-0 to preserve the char base, OR in the new VM
495
+ nibble shifted to bits 7-4, write back.
496
+
497
+ If KERNAL is still active (mode $37), the KERNAL's shadow of screen RAM location
498
+ at $0288 (HIBASE) should be updated to reflect the new screen RAM page. KERNAL
499
+ routines including CHROUT use $0288 to find the screen; if it is stale, KERNAL
500
+ output will write to the old location.
501
+
502
+ ### Why it works
503
+
504
+ The VIC-II uses the VM bits to generate the high bits of its 14-bit address bus
505
+ during the video matrix fetch phase. During cycles 15-54 of each badline the VIC
506
+ fetches the 40 bytes for that character row from video_matrix_base + row*40 +
507
+ column into an internal latch, and reuses the latch for the row's remaining
508
+ seven lines; sprite pointers at video_matrix_base + $3F8 are fetched once per
509
+ raster line. (An earlier version said one byte was fetched on each character
510
+ clock of the visible area, which is not how the video matrix is read.)
511
+ Only the upper bits of that address come from VM; the lower 10 bits are the
512
+ running character clock counter. The effect is that moving VM simply shifts the
513
+ entire screen fetch window by multiples of 1 KB within the VIC bank — a pure
514
+ hardware address-offset operation with no software overhead per character.
515
+
516
+ ### Variations
517
+
518
+ **Screen at $3C00.** Placing screen RAM at the top of VIC bank 0 ($3C00-$3FE7,
519
+ the 1 KB block running to $3FFF; an earlier version said $3FEF, which is 1008 bytes)
520
+ frees the lower 15 KB for code and graphics. The 8 bytes of sprite pointers at
521
+ $3FF8-$3FFF are conveniently at the very top of the bank. This is a common
522
+ layout for demos that use a full custom layout in bank 0.
523
+
524
+ **Double-buffered screen RAM.** (Worked through, with the sprite pointer
525
+ mirror and a measured recipe, as `screen_double_buffer_d018` below.) Two
526
+ screen buffers can live at different VM
527
+ offsets (e.g., $0000 and $0400 within the bank). The visible buffer flips by
528
+ changing VM bits; the invisible buffer is updated by the CPU. This avoids all
529
+ screen-tearing artifacts on text-mode displays; the flip lands at the next
530
+ badline, so write $D018 during the border or vertical blank for a whole-frame
531
+ swap.
532
+
533
+ ### Cycle budget
534
+
535
+ A VM change is not seen until the next badline: the VIC fetches the 40
536
+ video-matrix bytes only during cycles 15-54 of a badline and reuses that latch
537
+ for the remaining seven lines of the character row, so a $D018 write mid-row
538
+ leaves the current row on the old base and moves the display from the next row
539
+ down (measured in VICE x64sc: written on line 54, effective from line 59).
540
+ Sprite pointers are the exception — they are fetched every line and follow the
541
+ new base from the next line. There is no cycle overhead beyond the write. (An
542
+ earlier version said the write took effect at the next video matrix fetch, which
543
+ read as "immediately".)
544
+
545
+ ### Recipes
546
+
547
+ No Phase 3 recipes. This technique is infrastructure used by other Phase 4+
548
+ recipe docs.
549
+
550
+ ---
551
+
552
+ ## bitmap_relocation — Move bitmap base via $D018 lo-nibble
553
+
554
+ **Complexity:** low
555
+ **Region:** both
556
+ **Uses registers:** D018
557
+
558
+ ### Why
559
+
560
+ Standard C64 bitmap modes (hires and multicolor) require an 8 KB block of RAM
561
+ holding the pixel data. With the VIC bank being only 16 KB wide, there are only
562
+ two valid positions for the bitmap within the bank. Programs need to know which
563
+ positions are available and how to select between them.
564
+
565
+ ### How
566
+
567
+ In bitmap mode, bits 3-1 of $D018 (the CB field) serve a reduced role: only bit 3
568
+ (CB2, the high bit of the three-bit CB field) is meaningful. It selects between
569
+ two possible bitmap base addresses within the VIC bank:
570
+
571
+ | $D018 bit 3 | Bitmap base offset in VIC bank |
572
+ |-------------|-------------------------------|
573
+ | 0 | $0000 |
574
+ | 1 | $2000 |
575
+
576
+ The lower two CB bits (bits 2-1) are ignored in bitmap mode; they still affect
577
+ character base selection in text mode but have no effect on the bitmap address.
578
+
579
+ For VIC bank 0, the two legal bitmap positions in CPU address space are:
580
+ - Bit 3 = 0: bitmap at $0000-$1FFF
581
+ - Bit 3 = 1: bitmap at $2000-$3FFF
582
+
583
+ Screen RAM (the color/nybble data in standard bitmap mode) is positioned
584
+ independently via the VM bits (bits 7-4), as described in screen_ram_relocation.
585
+ A common layout for hires bitmap in bank 0 is bitmap at $2000 (bit 3 = 1) with
586
+ screen RAM at $0400 (VM = 1). The $D018 value for this is $18 (bitmap bit set,
587
+ VM = 1): binary 0001 1000.
588
+
589
+ ### Why it works
590
+
591
+ In bitmap mode the VIC-II generates pixel addresses differently from text mode.
592
+ Instead of a separate character fetch to look up a bitmap address, the VIC
593
+ directly computes: bitmap_base + (character_row * 320) + (character_column * 8)
594
+ + scan_line_within_row. The bitmap base is the one degree of freedom in this
595
+ computation, and it is encoded in a single bit of $D018 because only powers of
596
+ two from 0 to 8192 are legal — an 8 KB block must land on an 8 KB boundary.
597
+
598
+ ### Variations
599
+
600
+ **FLI (Flexible Line Interpretation).** FLI forces a badline on every raster
601
+ line by writing $D011 (YSCROLL = line & 7) so the badline condition becomes true
602
+ on cycle 15; each forced badline re-fetches the 40 video-matrix bytes
603
+ (c-accesses, cycles 15-54) from whatever page the VM nibble of $D018 selects, so
604
+ with $D018 changed once per line (before the $D011 write) each 8×1 strip gets its
605
+ own colour bytes. The cycle-exact write is the $D011 one; $D018 only has to be
606
+ in place before it. The bitmap base is not changed. See fli_image in
607
+ bitmap-modes.md and recipes/kickassembler/fli-image.md. (An earlier version of
608
+ this paragraph described FLI as a cycle-exact $D018 write during an idle fetch
609
+ and did not mention $D011 or the forced badline.)
610
+
611
+ **Bitmap at $0000 and sprite multiplexing.** The $0000-$1FFF bitmap position
612
+ overlaps with zero page and the stack ($0000-$01FF). Sprites whose data blocks
613
+ land in $0000-$1FFF are valid as long as the sprite pointer value accounts for
614
+ the collision. In practice most demos use $2000 for the bitmap and leave
615
+ $0000-$1FFF for code, zero-page variables, and stack.
616
+
617
+ ### Cycle budget
618
+
619
+ No per-line cycle cost. The bitmap base is a one-time configuration write.
620
+ FLI timing is covered under `fli_image` in `docs/techniques/bitmap-modes.md`
621
+ (Cycle budget (PAL)) and in `docs/recipes/kickassembler/fli-image.md`. (An
622
+ earlier version pointed at the raster techniques doc, which declares FLI out of
623
+ scope.)
624
+
625
+ ### Recipes
626
+
627
+ No Phase 3 recipes. Bitmap setup will be demonstrated in Phase 4+ recipe docs.
628
+
629
+ ---
630
+
631
+ ## cartridge_bank_easyflash — EasyFlash cart bank switching
632
+
633
+ **Complexity:** medium
634
+ **Region:** both
635
+
636
+ ### Why
637
+
638
+ A stock C64 program is limited to 64 KB of addressable memory with roughly
639
+ 38-52 KB free for game or demo code and data, depending on which ROMs are banked
640
+ out. EasyFlash cartridges expand this by providing up to 1 MB of flash memory
641
+ organized as up to 64 banks of 16 KB each. The cartridge presents two 8 KB ROM
642
+ windows simultaneously (LOROM at $8000-$9FFF and HIROM at $A000-$BFFF), and
643
+ software selects which 16 KB bank to page in by writing to the cartridge's bank
644
+ register. The entire flash contents become accessible as a sequence of banked
645
+ 8 KB ROM windows, making EasyFlash the simplest way to distribute a large game
646
+ or demo that exceeds the 64 KB address space.
647
+
648
+ ### How
649
+
650
+ An EasyFlash does not power up in 16 KB mode with the KERNAL present, as an
651
+ earlier version of this section said. With boot enabled (the EasyFlash jumper in
652
+ its start position; VICE's default, `-easyflashjumper` disables it and the
653
+ machine boots to BASIC with the cartridge invisible) the cartridge comes up in
654
+ Ultimax mode: bank 0's HIROM is mapped at $E000-$FFFF (ROML at $8000 in Ultimax
655
+ per the memory-map reference; not measured here), the CPU fetches its reset
656
+ vector from $FFFC of that HIROM, and the KERNAL is not mapped. The startup code
657
+ therefore lives in bank 0 HIROM, not LOROM. It typically copies a stub to RAM
658
+ and writes $07 to $DE02 (MODE=1, EXROM=1, GAME=1 — bit set means the line is
659
+ asserted) to enter 16 KB mode, in which:
660
+
661
+ - $8000-$9FFF is LOROM (cartridge ROM, low bank)
662
+ - $A000-$BFFF is HIROM (cartridge ROM, high bank)
663
+ - $D000-$DFFF is I/O (as normal when CHAREN = 1)
664
+ - $E000-$FFFF is KERNAL ROM
665
+
666
+ Measured in VICE x64sc 3.10 with a two-bank type-$0020 .CRT: a CBM80 stub in
667
+ bank 0 LOROM is never executed at power-on (the same stub in a generic 16 KB
668
+ .CRT is), the HIROM reset vector is, and the CRT header's EXROM/GAME bytes do
669
+ not change this.
670
+
671
+ The EasyFlash hardware decodes two I/O addresses in expansion area 1:
672
+
673
+ | Register | Address | Function |
674
+ |-----------|---------|---------------------------------------------------|
675
+ | EF_BANK | $DE00 | Select the active 16 KB flash bank (0-63) |
676
+ | EF_CONTROL| $DE02 | Control: GAME (bit 0), EXROM (bit 1), MODE (bit 2), LED (bit 7) |
677
+
678
+ Writing a bank number (0-63) to $DE00 immediately pages in the corresponding
679
+ 16 KB chunk of flash. The low 8 KB of that chunk appears at $8000-$9FFF; the
680
+ high 8 KB appears at $A000-$BFFF. This happens on the next CPU cycle — the switch
681
+ is instantaneous.
682
+
683
+ Bank 0 is the entry bank: it is active when the cartridge powers on and its
684
+ HIROM (not LOROM, as an earlier version said — see above) contains the reset
685
+ vector and the startup and loader stub. The remaining banks hold game
686
+ chapters, level data, music, graphics, or further code segments. The cartridge
687
+ author decides how to partition and use the 64 banks.
688
+
689
+ $DE02 is the control register: bit 0 = GAME, bit 1 = EXROM (1 = line asserted),
690
+ bit 2 = MODE, bit 7 = LED. Bit 1 always drives EXROM, but bit 0 drives GAME only
691
+ while bit 2 is set; with bit 2 clear, GAME follows the cartridge's boot jumper
692
+ instead, so on a cartridge set to boot, writes of $00-$03 only ever give Ultimax
693
+ ($00, $01) or 16 KB ($02, $03) and neither 8 KB nor off can be selected. Always
694
+ write with bit 2 set: $07 = 16 KB, $06 = 8 KB, $05 = Ultimax, $04 = cartridge
695
+ off. Bit names from Oscar64's easyflash.h; mode table measured in VICE x64sc
696
+ (3.10) against its EasyFlash emulation, not against hardware. (An earlier
697
+ version described $DE02 as a two-bit GAME/EXROM register and omitted MODE.) For
698
+ standard EasyFlash operation during gameplay the register is left at $07.
699
+
700
+ ### Why it works
701
+
702
+ The C64 cartridge port exposes the full 16-bit address bus, the data bus, the
703
+ GAME and EXROM input lines, the phi-2 clock, and dedicated ROML/ROMH strobe
704
+ outputs. The PLA generates ROML and ROMH based on address range and the
705
+ GAME/EXROM configuration. EasyFlash decodes ROML and ROMH as chip-selects for
706
+ its flash memory array. A small CPLD or FPGA on the cartridge board intercepts
707
+ $DE00 writes and stores the bank number in a latch; on every subsequent ROML or
708
+ ROMH access the latch value is OR'd onto the higher address lines of the flash
709
+ chip, selecting which physical page of flash responds to that $8000 or $A000
710
+ access. The CPU address bus sees ROM at the same two fixed windows; only the
711
+ source flash page changes.
712
+
713
+ ### Variations
714
+
715
+ **Self-modifying cartridge ROM.** Because the C64 PLA disables RAM CAS on reads
716
+ in cartridge ROM ranges but writes still reach RAM, any write to $8000-$BFFF
717
+ (while cartridge ROM is mapped there) actually writes to the underlying RAM.
718
+ This allows a program to maintain RAM buffers at $8000-$BFFF and bank the
719
+ cartridge ROM out ($DE02 = $04) when the program needs to write
720
+ them back, then bank cartridge ROM in for read-only access. Advanced EasyFlash
721
+ programs use this technique for per-level score tables, save states, and
722
+ configuration data.
723
+
724
+ **EasyFlash 3 and extended bank space.** Successor cartridges (EF3) support
725
+ more than 64 banks through an additional high-bank register and provide USB
726
+ or SD card access, but the basic $DE00 bank-switch mechanism is the same.
727
+
728
+ ### Cycle budget
729
+
730
+ No cycle constraints for bank switching itself. The bank latch takes the value
731
+ on the write cycle of the STA $DE00 — the instruction's last cycle — so the STA
732
+ itself always completes correctly (its opcode and operand were fetched before the
733
+ write) and a switch can never fall inside an instruction. The hazard is the cycle
734
+ after it: the very next opcode fetch already comes from the new bank. Execute the
735
+ switching code from RAM, or from a region whose bytes are identical in every
736
+ bank, never from the $8000-$BFFF window being switched unless the code that
737
+ follows the STA is present at the same address in the target bank. The same
738
+ applies to $DE02 mode changes, which can swap $E000-$FFFF from HIROM to KERNAL
739
+ under the executing PC — which is why the EasyFlash start-up stub copies itself
740
+ to RAM before writing $DE02. (An earlier version advised aligning switches to
741
+ instruction boundaries against a mid-instruction switch, which cannot happen.)
742
+
743
+ ### Recipes
744
+
745
+ - `recipes/kickassembler/easyflash-save.md` builds an EasyFlash `.crt` from one
746
+ listing and saves to flash through the Am29F040 commands (technique
747
+ `cartridge_save` on the file I/O page). An earlier version of this section
748
+ said no recipe covered EasyFlash.
749
+
750
+ ---
751
+
752
+ ## ram_under_kernal — Hide RAM under KERNAL ROM
753
+ **Demands:** kernal_rom_out
754
+
755
+ **Complexity:** medium
756
+ **Region:** both
757
+ **Claims:** irq_vector_fffe (owns), nmi_vector_fffa (owns)
758
+ **Claims basis:** estimated
759
+
760
+ ### Why
761
+
762
+ With KERNAL ROM banked in ($01 bit 1 = 1), the 8 KB region from $E000 to $FFFF
763
+ reads as ROM. But the underlying RAM at those addresses is still physically
764
+ present and is still written by any STA into that range. Banking KERNAL out
765
+ (bit 1 = 0) exposes that RAM to the CPU, providing 8 KB of additional work RAM
766
+ above and beyond the normal ~38 KB free. This is used for large data buffers,
767
+ custom IRQ and NMI handlers that must live at $FFFA-$FFFF, KERNAL replacement,
768
+ and packing maximum data into a 64 KB build.
769
+
770
+ ### How
771
+
772
+ The HIRAM bit is bit 1 of $01. Setting it to 0 disables KERNAL ROM and exposes
773
+ the 8 KB RAM from $E000 to $FFFF. Bit 0 (LORAM) does not need to change:
774
+ KERNAL ROM visibility is controlled solely by HIRAM when no cartridge is present.
775
+
776
+ The standard sequence for switching to mode $35 (I/O visible, all ROM banked out):
777
+
778
+ 1. Disable IRQs with SEI (mandatory — KERNAL IRQ handler at $EA31 will be gone).
779
+ 2. Write the address of the new IRQ handler into RAM at $FFFE/$FFFF. The bytes
780
+ written land in RAM even while KERNAL is still visible on reads.
781
+ 3. Write the address of the new NMI handler into RAM at $FFFA/$FFFB.
782
+ 4. Write $35 to $01 (LORAM=1, HIRAM=0, CHAREN=1 — I/O visible, no ROM).
783
+ 5. Re-enable IRQs with CLI once the new handler is in place.
784
+
785
+ From this point the $E000-$FFFF range reads back the RAM values written in steps
786
+ 2 and 3. The CPU correctly fetches interrupt vectors from $FFFA-$FFFF and they
787
+ point to the program's custom handlers.
788
+
789
+ If the program needs to call any KERNAL routine (CHROUT, CHKIN, OPEN, etc.),
790
+ it must copy those routines into RAM before banking KERNAL out, or temporarily
791
+ bank KERNAL back in with interrupts disabled, call the routine, and bank it
792
+ out again.
793
+
794
+ ### Why it works
795
+
796
+ All ROM banking on the C64 is read-only from the PLA's perspective. The PLA
797
+ asserts or deasserts chip-select lines for ROM reads only; it never generates
798
+ a ROM chip-select for write cycles. Writes to $E000-$FFFF always reach the
799
+ underlying RAM regardless of the HIRAM bit. This is the same property that
800
+ applies to BASIC ROM at $A000-$BFFF and char ROM at $D000-$DFFF. The distinction
801
+ is that KERNAL ROM also contains the hardware interrupt vectors at $FFFA-$FFFF,
802
+ so when KERNAL is banked out and the CPU takes an IRQ or NMI, it reads those
803
+ vector addresses from RAM — which must already contain valid handler addresses
804
+ before the bank switch happens.
805
+
806
+ ### Variations
807
+
808
+ **KERNAL replacement.** Entire custom KERNAL images can be placed at
809
+ $E000-$FFFF by writing them in while HIRAM is still 1 (ROM wins on reads,
810
+ RAM accepts the writes), then clearing HIRAM to expose the RAM image. This
811
+ technique powers KERNAL replacement cartridges and some fastloader
812
+ implementations. The custom image must provide all jump-table entries at
813
+ $FF81-$FFF5 if any downstream code calls KERNAL via the standard jump table.
814
+
815
+ **RAM at $E000-$FFFF for music and graphics.** In a demo build where KERNAL is
816
+ never called and all interrupts are custom-managed, the full 64 KB becomes
817
+ available: mode $34 (no ROM, no I/O) gives $A000-$BFFF plus $E000-$FFFF plus
818
+ $D000-$DFFF as RAM, totaling 20 KB of previously-ROM space. I/O access in mode
819
+ $34 requires temporarily toggling CHAREN: write $35 to $01 for a VIC/SID/CIA
820
+ access, then write $34 back. Most demo engines instead use mode $35 (I/O always
821
+ visible) and accept that $D000-$DFFF is occupied by I/O rather than RAM.
822
+
823
+ **The stack cannot move under the KERNAL.** The stack cannot leave page 1: S is
824
+ 8 bits and the 6510 fixes stack accesses to $0100-$01FF, so RAM under the KERNAL
825
+ cannot host a hardware stack. Engines short of stack keep page 1 free of other
826
+ data and use a software stack (indexed store through a pointer) for bulk state.
827
+ Measured: TXS with X=$50 followed by PHA writes $0150 (VICE x64sc). (An earlier
828
+ version of this variation claimed a secondary stack could be relocated to $E1xx
829
+ via TXS/TSX; it cannot.)
830
+
831
+ ### Cycle budget
832
+
833
+ No special cycle budget. Switching HIRAM is a single STA $01. The KERNAL
834
+ is gone from the very next read after the write completes.
835
+
836
+ ### Recipes
837
+
838
+ No Phase 3 recipes. Custom-IRQ-with-RAM-KERNAL infrastructure is prerequisite
839
+ to the raster and sprite recipes landing in Phase 4+.
840
+
841
+ ---
842
+
843
+ ## screen_double_buffer_d018 — Screen double buffer via $D018
844
+
845
+ **Complexity:** medium
846
+ **Region:** both
847
+ **Uses registers:** D018, D011
848
+ **Requires:** screen_ram_relocation
849
+
850
+ ### Why
851
+
852
+ A program that redraws most of the text screen every frame cannot finish
853
+ before the VIC reads the cells it is still writing. The result is a frame
854
+ that is half old and half new. Two screen pages fix it: the CPU draws into
855
+ the page the VIC is not showing, then one write to $D018 swaps them. The
856
+ swap is atomic from the viewer's side, so a frame is always whole.
857
+
858
+ ### How
859
+
860
+ 1. Pick two 1 KB pages in the same VIC bank. Each starts on a 1 KB boundary
861
+ and is selected by the VM nibble (bits 7-4 of $D018), so the two flip
862
+ values differ only in their upper nibble. The pages must not overlap the
863
+ character set (the ROM font the VIC sees at $1000-$1FFF in banks 0 and
864
+ 2, or a 2 KB custom set) or a bitmap. The recipe below uses $2800 (VM 10)
865
+ and $2C00 (VM 11) in bank 0 with the ROM font, flip values $A4 and $B4.
866
+ 2. Keep an index `hidden`. Every draw of the frame goes to `page[hidden]`
867
+ and nowhere else.
868
+ 3. When the frame is drawn, wait for the vertical blank and write
869
+ `(vm[hidden] << 4) | charset_bits` to $D018. Then `hidden ^= 1`. The
870
+ toggle sits next to the write so no draw can land between them.
871
+ 4. Before the flip, make the hidden page's sprite pointer block (bytes
872
+ +$3F8 to +$3FF) equal to the visible page's. The VIC reads the pointers
873
+ from whichever page is on display.
874
+
875
+ Waiting for the blank: poll bit 7 of $D011 (RST8). It sets on line 256,
876
+ after the last display line (250 for 25 rows), and clears at line 0. Wait
877
+ for it to be clear and then set, not just set: a draw that finishes inside
878
+ the bottom border would otherwise see RST8 already high and flip twice in
879
+ one frame. Oscar64's `vic_waitFrame()` does exactly this (`vic.c`: while
880
+ RST8 set, then while clear).
881
+
882
+ ### Why it works
883
+
884
+ The VM nibble is only read when the VIC fetches the video matrix, on a
885
+ badline, and the 40 bytes are held in a latch for the rest of the character
886
+ row (`screen_ram_relocation`, measured there: written on line 54, effective
887
+ from line 59). A write in the blank is therefore complete before the first
888
+ badline of the next frame, and the whole frame comes from the new page.
889
+ The sprite pointers are the exception: they are fetched every raster line
890
+ from `visible_page + $3F8`, so a flip moves them on the next line. That is
891
+ why the hidden page's pointer block has to be right before the flip and
892
+ not after.
893
+
894
+ Colour RAM at $D800 is not selected by $D018. There is one 1,000-byte
895
+ colour map and both pages share it. A colour written while page A is shown
896
+ changes A's cell now and B's cell after the flip. Three ways to live with
897
+ that: keep the colour map fixed and change only characters (the recipe
898
+ sets all 1,000 cells white once); write colour changes in the blank, after
899
+ the flip, so the character and its colour arrive together; or confine
900
+ colour changes to cells whose character is the same on both pages. The
901
+ blank is short for a full colour rewrite. By arithmetic from 312 lines at
902
+ 63 cycles and 263 lines at 65: lines 251-311 plus 0-50 give 112 lines,
903
+ about 7,000 cycles on PAL; 251-262 plus 0-50 give 63 lines, about 4,100
904
+ on NTSC. A 1,000-byte fill does not fit the NTSC figure with a C loop.
905
+
906
+ Frame parity. The page being drawn holds what was on screen two frames
907
+ ago, not one. A full redraw does not care. A partial (dirty-cell) update
908
+ does: each change has to be applied to both pages, one frame apart, or the
909
+ page you flip to shows a cell two frames stale. Keep a change list and
910
+ apply it to `page[hidden]` on two consecutive frames, or redraw everything.
911
+
912
+ Sprite pointers and libraries. Anything that writes sprite pointers
913
+ through a single screen address assumes one page. Oscar64's `vspr_init(char
914
+ * screen)` stores `screen + 0x3f8` once and bakes the absolute address of
915
+ that block into its raster IRQ entries (`c64/sprites.c`); `spr_init` keeps
916
+ the same single pointer. `vspr_screen(char * screen)` re-points them. With
917
+ two pages, either keep both blocks identical by hand after every image
918
+ change, or call `vspr_screen()` with the page about to be shown before the
919
+ update that writes the pointers. The second path is not measured here.
920
+ The cheapest failure is a clear routine that works in whole 1 KB pages:
921
+ bytes 1000 to 1023 hold the pointer block, and a fill that runs to the end
922
+ of the page rewrites it every frame. The companion recipe shows the sprite
923
+ that results.
924
+
925
+ If the KERNAL prints to the screen (CHROUT), it prints to the page named by
926
+ HIBASE ($0288), which is one page. Set $0288 to the hidden page's high byte
927
+ before printing. Not measured here.
928
+
929
+ ### Variations
930
+
931
+ **Two bitmap pages.** A bitmap needs 8 KB and can only sit at offset $0000
932
+ or $2000 in a bank, so two full bitmaps plus their matrices do not fit one
933
+ bank; the usual layout is two banks and a $DD00 write alongside the $D018
934
+ one (`effects-vector-3d.md`, Double buffering; `bitmap-modes.md`,
935
+ Two-bitmap pages).
936
+
937
+ **Two pages, one charset, two fonts.** With two pages the CB bits can
938
+ differ between the two flip values as well, so each page can carry its own
939
+ 2 KB character set. The cost is 4 KB of charset instead of 2 KB and the
940
+ same 1 KB rule for the pages.
941
+
942
+ **Triple buffering.** A third page lets the draw run more than one frame
943
+ without stalling the flip. Three 1 KB pages plus the ROM font still fit
944
+ bank 0. Not built here.
945
+
946
+ ### Cycle budget
947
+
948
+ The flip is one store. The draw has one frame minus nothing: it runs during
949
+ the display, so badline and sprite DMA cycles come out of it. Measured in
950
+ VICE x64sc 3.10 with the recipe below at `-O2`: a whole-page fill from a
951
+ 256-byte template (`LDA (zp),y` plus four `STA abs,y`, 30 cycles per four
952
+ bytes) plus a 30-cell caption costs 12,598 cycles on PAL and 13,165 on
953
+ NTSC, the difference being the badlines and sprite fetches it overlapped.
954
+ Both are under a frame (19,656 PAL, 17,095 NTSC), and two runs one PAL
955
+ frame apart show consecutive frame numbers on opposite pages. The first
956
+ draft filled the page with `memcpy` and `memset` in 40-byte rows at 17,437
957
+ and 22,760 cycles; PAL still alternated on the short draw, and NTSC showed
958
+ the same frame number in two runs one frame apart, because neither draw
959
+ fit in 17,095 cycles. If the counter stops advancing once per frame, the
960
+ draw is too slow, not the flip.
961
+
962
+ ### Recipes
963
+
964
+ - `recipes/oscar64/double-buffer.md` — two pages, one sprite, flip every frame, pointer block mirrored
965
+ - `recipes/oscar64/double-buffer-nomirror.md` — the same listing without the mirror, and the sprite it shows
966
+
967
+ ## memory_layout_plan — Plan the memory map before the first build
968
+
969
+ **Complexity:** low
970
+ **Uses registers:** D018, DD00
971
+
972
+ **Why.** The toolchain places code where it likes; the VIC-II does not.
973
+ A music player expects `$1000`, a charset must start on a 2 KB boundary
974
+ inside the VIC's 16 KB bank, a screen on a 1 KB boundary, a sprite on 64
975
+ bytes, and none of them may overlap code that grows with the next
976
+ feature. A layout decided after the fact produces the collision the
977
+ `charset_blit_overruns_grown_code` pitfall describes: a build that boots
978
+ and shows garbage where the font was.
979
+
980
+ **How.** List every asset with a fixed address or alignment; choose the
981
+ VIC bank; place the aligned assets first, largest alignment first; then
982
+ let code and data fill what remains, and read the map file back after
983
+ every build to see that nothing moved into a reserved range. Write the
984
+ plan down as the toolchain's own words: Oscar64 regions and sections,
985
+ the cc65 linker configuration, KickAssembler `.pc` and segments. The
986
+ toolchain page `toolchains/memory-layout-planning.md` walks through it
987
+ for all three.
988
+
989
+ **Why it works.** The VIC reads through its own 16 KB window with fixed
990
+ alignments set by `$D018` and the bank bits of `$DD00`; the CPU sees the
991
+ whole 64 KB and does not care where anything is. Planning the VIC's
992
+ constraints first and letting the CPU's flexible material fill the gaps
993
+ means the constraints are met by construction, not by luck.
994
+
995
+ **Variations.** Two VIC banks with the assets split between them; data
996
+ under the ROMs for the CPU only (`ram_under_kernal`); a loader that owns
997
+ the top of RAM, which the plan leaves free.
998
+
999
+ **Cycle budget.** None: this is a build-time decision.
1000
+
1001
+ ### Recipes
1002
+
1003
+ - `recipes/oscar64/memory-layout.md` — stub, music, charset, sprite, screen and code at planned addresses, printing each symbol's address so the screen can be read against the map
1004
+
1005
+ ## irq_owns_processor_port — Interrupt handlers that save, set and restore $01
1006
+
1007
+ **Complexity:** medium
1008
+ **Region:** both
1009
+ **Uses registers:** D019, DD0D
1010
+ **Requires:** cpu_io_port_bank, ram_under_kernal
1011
+ **Demands:** kernal_rom_out
1012
+ **Cost:** cycles_per_frame=18
1013
+ **Cost basis:** measured-vice
1014
+ **Cost measured on:** kickassembler-irq-owns-port (per interrupt taken)
1015
+
1016
+ ### Why
1017
+
1018
+ The 4 KB of RAM under `$D000-$DFFF` is only reachable with I/O banked
1019
+ out (`$01` = `$34`, or `$30`). The rule elsewhere on this page and in
1020
+ `pitfalls/banking.md` is `SEI` first, because an interrupt handler run
1021
+ in that state writes its acknowledge and its register updates into RAM.
1022
+ That rule is fine for a short poke. It is not fine for a decruncher or
1023
+ a loader filling 4 KB of level data, which takes frames: with `SEI` the
1024
+ music stops and the raster splits fall apart for the whole depack. The
1025
+ answer games and loaders use is to make every interrupt handler put I/O
1026
+ back itself, so main code can leave `$01` at `$34` with interrupts on.
1027
+
1028
+ ### How
1029
+
1030
+ 1. Bank the KERNAL out for good. Write the IRQ handler's address to
1031
+ `$FFFE/$FFFF` and the NMI handler's to `$FFFA/$FFFB`; the writes
1032
+ reach the RAM under the ROM (`ram_under_rom_traps`). With HIRAM
1033
+ clear, as it is in both `$34` and `$35`, the CPU takes its vectors
1034
+ from that RAM. The KERNAL's `$0314`/`$0318` vectors are never read.
1035
+ 2. Every handler, IRQ and NMI alike, starts by saving `$01` and
1036
+ storing `#$35`:
1037
+ `PHA` / `LDA $01` / `PHA` / `LDA #$35` / `STA $01`.
1038
+ 3. The handler does its work: acknowledge (`$D019`, `$DC0D` or
1039
+ `$DD0D`), raster writes, the music player.
1040
+ 4. It ends by putting back exactly the value it found:
1041
+ `PLA` / `STA $01` / `PLA` / `RTI`.
1042
+ 5. Main code may now set `$01` = `$34` and copy, decrunch or load into
1043
+ `$D000-$DFFF` with interrupts enabled, and set `$35` again when it
1044
+ needs I/O itself.
1045
+
1046
+ ### Why it works
1047
+
1048
+ The PLA chip decodes the processor-port lines on every access, so the handler's store of `#$35`
1049
+ makes the VIC, SID and CIAs visible from its next instruction, and the
1050
+ restore hides them again before `RTI` returns to the interrupted copy.
1051
+ The copy never sees I/O, and the handler never sees RAM where it
1052
+ expects a register.
1053
+
1054
+ The restore must be the saved value, not a constant. The interrupted
1055
+ code may be mid-copy at `$34` or may itself be at `$35`; a handler that
1056
+ ends with `#$35` returns a `$34` copy into the I/O window, and the rest
1057
+ of the copy is written into the VIC, SID and CIA registers. Measured in
1058
+ VICE with the recipe below: the display turned to garbage and main code
1059
+ never finished its first pass.
1060
+
1061
+ The save goes on the stack, or in a byte of the handler's own. An NMI
1062
+ can land inside an IRQ handler's wrapper, between its `LDA $01` and its
1063
+ restore. With one shared save byte the NMI would overwrite the IRQ's
1064
+ saved `$34` with `$35`. With the stack each handler restores its own
1065
+ copy.
1066
+
1067
+ Store `#$35`, do not `ORA #$01`: from `$34` either gives `$35`, but
1068
+ from `$30` the `ORA` gives `$31`, which maps the character ROM, not I/O
1069
+ (`hardware/c64-registers-reference.md`, the `$01` bit description). `#$35` keeps bit 5 set, so the
1070
+ datasette motor stays off; it does overwrite bits 3-5 while the handler
1071
+ runs, and the restore puts back what the main code had.
1072
+
1073
+ The NMI needs the same treatment because `SEI` does not mask it: the
1074
+ RESTORE key and CIA2 can raise one at any time. An NMI handler without
1075
+ the wrapper reads `$DD0D` from RAM, CIA2's flag stays set, `/NMI` never
1076
+ rises, and no later NMI, from CIA2 or RESTORE, can happen (pitfall
1077
+ `kernal_nmi_handler_runs_stop_check`). The recipe's broken build showed
1078
+ exactly one NMI in 115 million cycles.
1079
+
1080
+ A handler without the wrapper under `$34` fails at once: its `$D019`
1081
+ write goes to RAM, the VIC's interrupt stays asserted, and the handler
1082
+ re-enters as soon as it returns. Measured in VICE: the recipe's two
1083
+ raster handlers re-entered each other for 115 million cycles (PAL; 100
1084
+ million NTSC) and main code never completed a pass. This is the same
1085
+ mechanism as `irq_during_charen_window`, with RAM in place of the
1086
+ character ROM. The handler's other writes land in the RAM copy
1087
+ (`$D020`, `$D012`, `$D400` were all found there).
1088
+
1089
+ ### Variations
1090
+
1091
+ **Save in zero page.** `LDA $01` / `STA save` / `LDA #$35` / `STA $01`
1092
+ and `LDA save` / `STA $01` cost 17 cycles, one fewer than the stack
1093
+ form (arithmetic from the instruction table). Each handler that can
1094
+ nest needs its own byte.
1095
+
1096
+ **Music code under I/O.** The wrapper assumes the handler and the
1097
+ player live outside `$D000-$DFFF`. A player placed under I/O cannot
1098
+ write the SID while its own code is visible. GoatTracker 2's
1099
+ "zeropage ghostregs" option has the player write to a zero-page copy
1100
+ of the SID registers, which a copy loop moves to `$D400` afterwards
1101
+ with I/O banked in (GoatTracker 2 readme, section 5.1, not measured
1102
+ here).
1103
+
1104
+ **Loaders that do it for you.** Sparkle's loader sets `$35` at each loader
1105
+ call and `$34` while a bundle is decrunched under I/O, and its manual requires
1106
+ every IRQ to save `$01`, set `$35` and restore it (Sparkle 3.4 manual,
1107
+ "Loading to the RAM under I/O registers", not measured here).
1108
+
1109
+ ### Cycle budget
1110
+
1111
+ Measured in VICE x64sc 3.10 with the recipe below, PAL and NTSC alike:
1112
+ the wrapper costs 18 cycles per interrupt taken (`LDA zp` 3, `PHA` 3,
1113
+ `LDA #` 2, `STA zp` 3, `PLA` 4, `STA zp` 3, which is what the
1114
+ instruction table gives). The recipe's line-120 handler is 61 cycles
1115
+ with it and 43 without, each plus the 7-cycle interrupt sequence. The
1116
+ Cost line is one interrupt; a frame pays 18 for every IRQ and NMI it
1117
+ takes: 36 for a two-split raster chain, 36 plus 18 per NMI with a CIA2
1118
+ timer running; the recipe's own frame, 2 IRQs and up to 4 NMIs, is
1119
+ 108 (arithmetic). The wrapper is 10 bytes per handler (arithmetic from
1120
+ the opcode sizes).
1121
+
1122
+ ### Recipes
1123
+
1124
+ - `recipes/kickassembler/irq-owns-port.md` — main code copies 4 KB
1125
+ under I/O at `$34` and checks it by sum 32 times while two raster
1126
+ IRQs, a SID player and a CIA2 NMI keep running; handler cycles with
1127
+ and without the wrapper; two broken builds described.
1128
+
1129
+ ### Sources
1130
+
1131
+ - Sparkle 3.4 user manual (Sparta), "Loading to the RAM under I/O
1132
+ registers ($D000-$DFFF)" and "Common issues" item 6:
1133
+ https://github.com/spartaomg/SparkleCPP
1134
+ - GoatTracker 2 readme, section 5.1 "Playroutine options", option
1135
+ "Use zeropage ghostregs":
1136
+ https://sourceforge.net/projects/goattracker2/
1137
+
1138
+ ## reu_dma — REU DMA transfers, fixed-address fills and the $FF00 trigger
1139
+
1140
+ **Complexity:** medium
1141
+ **Region:** both
1142
+ **Uses registers:** DF00, DF01, DF02, DF03, DF04, DF05, DF06, DF07, DF08, DF09, DF0A
1143
+
1144
+ ### Why
1145
+
1146
+ A 17xx RAM Expansion Unit (1700 128 KB, 1764 256 KB, 1750 512 KB, per
1147
+ codebase64) and its modern equivalents add banks of RAM the CPU cannot
1148
+ address, and a DMA controller that moves bytes between them and C64
1149
+ memory at one byte per cycle. A game uses it as a store for level data
1150
+ and graphics that would otherwise be loaded from disk, as a copier that
1151
+ beats any CPU loop (an unrolled `LDA abs` / `STA abs` is 8 cycles a
1152
+ byte, `speedcode_generation`), and as a filler: with one address held
1153
+ still, it fills a block from one byte or streams a block into one
1154
+ register. It is an optional extra. A program that uses it must detect
1155
+ it and must still run without it.
1156
+
1157
+ ### How
1158
+
1159
+ The registers sit at `$DF00-$DF0A`, in the cartridge I/O-2 page. Bit
1160
+ meanings are from codebase64 `base:reu_programming`. The ones marked
1161
+ "measured" were exercised by the recipe in VICE x64sc 3.10; the rest are
1162
+ from that source only.
1163
+
1164
+ | Register | Meaning |
1165
+ |---|---|
1166
+ | `$DF00` status, read | bit 7 interrupt pending; bit 6 end of block (measured); bit 5 verify fault (measured); bit 4 size, set for 256 KB chips, so on a 1764 or 1750 (measured set with `-reusize 512`); bits 3-0 version (measured 0). Reading clears the fault bit (codebase64; not measured here). |
1167
+ | `$DF01` command | bit 7 execute; bit 5 autoload, which restores the address and length registers after the transfer; bit 4 set = start now, clear = wait for a write to `$FF00` (both measured); bits 1-0 type: `00` stash C64 to REU, `01` fetch REU to C64, `10` swap, `11` verify (all measured). Bits 6, 3, 2 reserved. |
1168
+ | `$DF02/$DF03` | C64 address, low/high (measured) |
1169
+ | `$DF04/$DF05/$DF06` | REU address low/high and bank; bank bits 2-0 for 512 KB (measured: bits 7-3 read back as 1 in VICE with 512 KB) |
1170
+ | `$DF07/$DF08` | length, low/high; 0 = 65,536 (measured) |
1171
+ | `$DF09` interrupt mask | bit 7 enable, bit 6 on end of block, bit 5 on verify error; bits 4-0 unused |
1172
+ | `$DF0A` address control | bit 7 holds the C64 address still, bit 6 the REU address (both measured); bits 5-0 unused |
1173
+
1174
+ 1. Detect. Write a pattern to `$DF02-$DF08` and read it back. Compare
1175
+ `$DF06` on bits 2-0 only. With no REU the reads are open bus (the
1176
+ byte the VIC-II last fetched, `hardware/c64-registers-reference.md`)
1177
+ and the pattern does not come back: the recipe read all `$00` on PAL
1178
+ and `$00` with one `$FF` on NTSC. Another cartridge that decodes
1179
+ `$DF00` can pass this test (codebase64); knowing the cartridge is the
1180
+ caller's job.
1181
+ 2. Load the C64 address, REU address and bank, length and `$DF0A`.
1182
+ Without autoload the registers are left past the end, so load all of
1183
+ them before each transfer.
1184
+ 3. Write the command to `$DF01`: `$90` stash, `$91` fetch, `$92` swap,
1185
+ `$93` verify. The CPU is halted until the last byte has moved.
1186
+ 4. For a verify, read `$DF00` first to clear it, then test bit 5 after.
1187
+ On a mismatch the verify stops, bit 6 stays clear, and `$DF02/$DF03`
1188
+ point one past the differing byte (measured).
1189
+
1190
+ To reach the RAM under I/O, arm the transfer with bit 4 clear (`$81` for
1191
+ a fetch) while I/O is visible, set `$01` = `$34`, and write to `$FF00`.
1192
+ The write starts the DMA, which sees the memory configuration that is
1193
+ in force, so it reaches the RAM at `$D000-$DFFF`. `LDA $FF00` / `STA
1194
+ $FF00` keeps the byte already in the RAM under the KERNAL. Keep
1195
+ interrupts masked while `$01` is `$34`, or give the handlers the
1196
+ `irq_owns_processor_port` wrapper.
1197
+
1198
+ ### Why it works
1199
+
1200
+ The REU is a bus master. It pulls the CPU off the bus and drives the
1201
+ address and data lines itself, one access per cycle, so a stash, fetch
1202
+ or verify moves one byte per cycle and a swap, which reads and writes
1203
+ each C64 byte, one byte per two. The VIC-II keeps priority. On a badline
1204
+ and on a sprite's fetch cycles it takes the bus from the REU as it
1205
+ takes it from the CPU, so the transfer waits (pitfalls
1206
+ `badline_cycle_loss`, `vic_bus_takeover_on_dma`). The `$FF00` trigger
1207
+ exists because the store to `$DF01` needs I/O visible, while the
1208
+ transfer may need it hidden.
1209
+
1210
+ ### Variations
1211
+
1212
+ - **Fill.** Stash one byte once. Then fetch with `$DF0A` = `$40` (REU
1213
+ address held): every C64 byte in the block gets that value. The recipe
1214
+ fills 1,000 bytes of colour RAM this way in 1,000 cycles.
1215
+ - **Stream to a register.** Fetch with `$DF0A` = `$80` (C64 address
1216
+ held) to write every REU byte to one address, such as `$D418` for a
1217
+ digi. The DMA runs flat out, one byte per cycle, so the samples do not
1218
+ come out at a controlled rate unless the transfer is split into short
1219
+ lengths and timed; this is not measured here. The recipe uses the
1220
+ same bit the other way round, stashing 65,536 reads of one byte.
1221
+ - **Level store.** Fetch a level's map, charset and sprites at level
1222
+ start: 16 KB is about 16,400 cycles blanked, under one PAL frame of
1223
+ 19,656 (arithmetic from the measured one cycle per byte).
1224
+ - **Double buffer.** Fetch the next screen's 1,000 bytes in 1,008
1225
+ cycles; with the screen on, allow for the badlines it crosses.
1226
+
1227
+ ### Cycle budget
1228
+
1229
+ Measured in VICE x64sc 3.10 with the recipe, PAL and NTSC. The store to
1230
+ `$DF01` is part of the count; after it a stash, fetch, fill or verify of
1231
+ n bytes takes n cycles and a swap 2n, with the screen blanked. A 1-byte
1232
+ stash came to 9 cycles including the 8-cycle `LDA abs` / `STA abs`.
1233
+ With the text screen on, a 65,536-byte stash took 69,414 cycles on PAL
1234
+ (1.05 per byte) and 69,844 on NTSC (1.06). With eight sprites on 21
1235
+ lines as well, it took 71,134 on PAL and 71,439 on NTSC. The cost of a
1236
+ transfer depends on its length and where in the frame it runs, so this
1237
+ page carries no Cost line: budget one cycle per byte blanked, and about 6 %
1238
+ (PAL) and 7 % (NTSC) more with the screen on across whole frames. A transfer that must
1239
+ fit a raster window should be timed in that window.
1240
+
1241
+ ### Recipes
1242
+
1243
+ - `recipes/kickassembler/reu-dma.md` — detects the REU; stashes, fetches
1244
+ into screen RAM, fills colour RAM from one byte, swaps, verifies (and
1245
+ reads the fault bit on a changed byte), fetches under I/O with the
1246
+ `$FF00` trigger, and times each transfer blanked, with the screen on
1247
+ and with eight sprites on.
1248
+
1249
+ ### Sources
1250
+
1251
+ - codebase64, "REU programming" (Richard Hable, Marko Mäkelä), register
1252
+ bits, transfer speed, `$FF00` use, detection and model sizes:
1253
+ https://codebase64.net/doku.php?id=base:reu_programming
1254
+
1255
+ ## pucrunch_decruncher — Pucrunch: a small forward decruncher in the zero page, stack and input buffer
1256
+
1257
+ **Complexity:** low
1258
+ **Region:** both
1259
+ **Uses kernal:** (none)
1260
+ **Cost:** bytes_code=245, zp_bytes=11
1261
+ **Cost basis:** derived-listing
1262
+
1263
+ The Cost line is the default C64 decruncher's footprint as pucrunch prints
1264
+ it after a crunch (`$2d/$2e`, `$f7-$1b6` and `$200-$234`: 192 plus 53
1265
+ bytes of decruncher, and eleven bytes of zero page, nine of them `$F7` to
1266
+ `$FF` and two more at `$2D`/`$2E`, which the stub rewrites as BASIC's
1267
+ end-of-program pointer before it decrunches). The `-ffast` variant
1268
+ reports 268 bytes and the `-fshort` variant 225, and `-fshort` widens the
1269
+ low pair to `$2D` to `$30`. Decrunch time is in the cycle budget below; it
1270
+ is a one-off cost at start, so it is not on the line.
1271
+
1272
+ ### Why
1273
+
1274
+ Pucrunch is Pasi 'Albert' Ojala's LZ77 plus RLE cruncher, first published
1275
+ in 1997. The compressor is a single C file (`pucrunch.c` with its
1276
+ generated `pucrunch.h`) that builds with `cc` on any host; the source's
1277
+ version string reads 1.14, dated 22 November 2008. The author's page says
1278
+ that the compressor has been under the GNU LGPL since December 2005, and
1279
+ that the decompression code is under the wxWindows Library Licence, which
1280
+ in short lets the binary decruncher travel with the crunched data. That
1281
+ makes it one of the few crunchers whose licence a shipped game does not
1282
+ have to think about. The decruncher is small, sits in memory the KERNAL
1283
+ does not need at start-up, and expands forwards, so a file crunched with
1284
+ it can start as low as `$0258` and reach `$FFFF`. Exomizer is smaller on
1285
+ output and, on the two inputs measured here, faster to decrunch as well;
1286
+ the case for pucrunch is the licence, the C64-side footprint and the
1287
+ one-file build, not the ratio.
1288
+
1289
+ Everything below marked "measured" was run on 2026-09-23 with pucrunch
1290
+ 1.14 built here (`cc -O2 -o pucrunch pucrunch.c -lm`, Apple clang 21,
1291
+ macOS arm64; the `.h` must sit beside the `.c`) and Exomizer 3.1.3b0
1292
+ built the night before, in the windowless x64sc build of VICE 3.10, PAL.
1293
+
1294
+ ### How
1295
+
1296
+ The self-extracting form is the default. Give the machine and the PRG:
1297
+
1298
+ ```text
1299
+ pucrunch -c64 game.prg game-pu.prg # default decruncher
1300
+ pucrunch -c64 -ffast game.prg game-fast.prg # a longer, faster decruncher
1301
+ pucrunch -c64 -fshort game.prg game-tiny.prg # a shorter, slower one
1302
+ pucrunch -c64 -fdelta game.prg game-dl.prg # delta LZ77, helps ramps and tables
1303
+ pucrunch -flist # every decruncher it can emit
1304
+ ```
1305
+
1306
+ The output is a PRG at `$0801` with a one-line BASIC stub (`SYS 2061` in
1307
+ every run here) followed by the decruncher and the crunched stream. Run,
1308
+ the stub copies the decruncher into the zero page from `$F7` upward, the
1309
+ low part of the stack page and the system input buffer at `$0200`, moves
1310
+ the crunched stream up in memory so that its last byte sits a computed
1311
+ safety margin past the end of the original file, and expands the original
1312
+ from its own load address upwards, back over the memory the stub and the
1313
+ stream occupied. When it finishes it jumps to the execution address, which
1314
+ it takes from the input file's own SYS line; `x<addr>` overrides it,
1315
+ `l<addr>` overrides the load address, `i0` leaves interrupts off at the
1316
+ jump and `g<val>` sets the `$01` bank configuration the program starts
1317
+ under. `-fbasic` selects the decruncher meant for a BASIC program (not
1318
+ run here).
1319
+
1320
+ The raw form has no stub. `-c0` writes a stand-alone stream with a short
1321
+ header (18 bytes on the test file, load address and execution address
1322
+ inside it) for a decruncher you link yourself; `-d` marks the input as
1323
+ headerless data with no load address, and `-c64 -d` still emits a C64
1324
+ stub for it. The author's page publishes the decruncher source
1325
+ (`uncrunch.asm`, DASM-style conditional assembly with switches for the
1326
+ machine, the speed variant, delta and the wrap buffer), which is where a
1327
+ raw-stream caller starts; the page places the decompression code under
1328
+ the wxWindows licence. It is not reproduced here.
1329
+
1330
+ Two things the layout imposes. First, the span from `$00F7` to `$0258`
1331
+ is the decruncher's during expansion: zero-page variables from `$F7` up,
1332
+ the lower part of the stack page and the input buffer are overwritten,
1333
+ so a pointer kept in `$FB` to `$FE` across the SYS comes back changed.
1334
+ The stub also writes `$2D`/`$2E` (`$2D` to `$30` with `-fshort`) on its
1335
+ way in, so state kept there is lost as well.
1336
+ The author's page describes the stack use as the part BASIC is not using
1337
+ at the time; whether a return to BASIC survives was not measured here.
1338
+ Second, the original file's end plus the safety margin must fit below
1339
+ `$10000`; when it does not, the compressor switches to a wrap-buffer
1340
+ variant on its own, so a file that ends at `$FFFF` still crunches, and it
1341
+ says which memory the result uses on every run, in the line beginning
1342
+ `uses the memory`. Nothing below `$0258` can be the file's load address,
1343
+ and pucrunch refuses such a file with a message saying so.
1344
+
1345
+ ### Why it works
1346
+
1347
+ The stream mixes two codings. Run-length coding replaces a repeated byte
1348
+ with a count and one byte; a ranked table of the most common run bytes,
1349
+ built by the compressor and shrunk to the values actually used, lets the
1350
+ frequent ones cost less. LZ77 replaces a string that already appeared in
1351
+ the output with an offset back into it and a length. Anything neither
1352
+ covers is a literal, and here is the trick that keeps the decruncher
1353
+ small: a literal carries no flag bit of its own. Instead a few of its top
1354
+ bits are compared with a running escape code; a literal that happens to
1355
+ begin with the escape is written with an extra escape marker, and the
1356
+ compressor picks the number of escape bits per file so that this happens
1357
+ rarely (four bits on the test file, seven escaped literals). Lengths and
1358
+ offsets are Elias gamma codes, short for small values. All three kinds
1359
+ are decoded by one loop that writes forwards, which is why the stream can
1360
+ be expanded in place from its original address up, with only a small
1361
+ margin for the escaped literals.
1362
+
1363
+ ### Variations
1364
+
1365
+ **The raw decruncher in your own loader.** For a level file the game
1366
+ loads itself, crunch with `-c0` (or `-d` for data without a load
1367
+ address), put the stream where the loader leaves it and call the raw
1368
+ decruncher with the stream's address. The `$F7` to `$0258` span is then
1369
+ the decruncher's for the duration, as above, so the loader's own zero
1370
+ page must lie below `$F7` or be saved first. Not measured here: the raw
1371
+ decruncher was not assembled in this run, because the shipped source is
1372
+ DASM syntax and its licence forbids putting a copy on this page.
1373
+
1374
+ **Faster or smaller.** `-ffast` bought 16 % of decrunch time for 23
1375
+ bytes on the mixed test file; `-fshort` saved 24 bytes and cost 20 % more
1376
+ time. `-fdelta` halved the crunched size of the mixed file, whose ramp
1377
+ and sine table it suits, and gained nothing on the code file.
1378
+
1379
+ **Comparison with Exomizer, measured.** Two subjects, both KickAssembler
1380
+ PRGs at `$0801` with a SYS stub: a 4,519-byte file of code plus mixed
1381
+ filler (a 1 KB zero run, a byte ramp, a sine table, repeated text), and a
1382
+ 4,231-byte file whose filler is 4 KB of KERNAL ROM copied in as data, so
1383
+ that it looks like machine code. Each crunched PRG was run on its own to
1384
+ a green border with the marker at `$02FF` set, then again under a small
1385
+ loader that copied it to `$0801`, started a 32-bit CIA2 timer cascade and
1386
+ jumped to the stub's SYS address; the subject reads the timer as its first
1387
+ act. The figure is SYS to entry, decruncher setup included, and two runs
1388
+ gave the same figure to the cycle (the loader alone measures 271 cycles
1389
+ on the first subject and 228 on the second). One difference between the
1390
+ rows: pucrunch's C64 stub runs under SEI from its first instruction, while
1391
+ Exomizer's `sfx sys` stub reports interrupts enabled on entry, during and
1392
+ on exit, and the loader has interrupts on when it jumps. Exomizer's
1393
+ figures therefore include the KERNAL's IRQ service for the frames the
1394
+ decrunch takes, and pucrunch's do not. That works against Exomizer, so
1395
+ the ordering stands, but the two columns are not like for like.
1396
+
1397
+ | Cruncher | Mixed file: bytes | Mixed file: cycles | Code file: bytes | Code file: cycles |
1398
+ |---|---|---|---|---|
1399
+ | none | 4,519 | 271 | 4,231 | 228 |
1400
+ | pucrunch default | 1,084 | 349,505 | 3,918 | 1,008,259 |
1401
+ | pucrunch `-ffast` | 1,107 | 292,113 | 3,941 | 969,912 |
1402
+ | pucrunch `-fshort` | 1,060 | 418,487 | 3,894 | 1,314,757 |
1403
+ | pucrunch `-fdelta` | 598 | 253,598 | 3,919 | 1,024,326 |
1404
+ | exomizer `sfx sys` | 1,103 | 189,276 | 3,772 | 590,882 |
1405
+
1406
+ Exomizer's stream was smaller on the code file and its decruncher faster
1407
+ on both; pucrunch's default was 19 bytes smaller than Exomizer on the
1408
+ mixed file. Per output byte that is about 77 cycles for pucrunch's
1409
+ default and 42 for Exomizer on the mixed file, 238 and 140 on the code
1410
+ file. A claim that pucrunch decrunches faster than Exomizer was not borne
1411
+ out by either input here. Exomizer's own figures and its `-P` flag rules
1412
+ are in `loaders-packers.md`, `exomizer_basics`.
1413
+
1414
+ **What the decruncher leaves behind.** After each sfx run the sixteen
1415
+ bytes from `$0808` were printed: they matched the original file, BASIC
1416
+ line terminator and padding included, for pucrunch's default and
1417
+ `-fshort` decrunchers and for Exomizer. The host-side `pucrunch -u` on an
1418
+ sfx file gave a file of the right length that differed from the input at
1419
+ 1,067 of its 4,519 byte positions, the first at offset 11; 1,063 of them
1420
+ are a zero byte that came back as 1, the 1 KB zero run among them. `-u`
1421
+ on the `-c0`
1422
+ stream gave the input back byte for byte. The C64-side decruncher is the
1423
+ one that matters, and it was checked only at the sixteen bytes above. Not
1424
+ investigated further.
1425
+
1426
+ ### Cycle budget
1427
+
1428
+ Measured, PAL, SYS to entry: 349,505 cycles (0.35 s) for 4,517 bytes of
1429
+ mixed data and 1,008,259 cycles (1.02 s) for 4,229 bytes of code with the
1430
+ default decruncher; `-ffast` 292,113 and 969,912. Code that crunches
1431
+ badly is slow to decrunch as well as large, because most of its bytes go
1432
+ through the literal path. Budget by the output's nature, not its size,
1433
+ and measure a real level file before promising a load time.
1434
+
1435
+ ### Recipes
1436
+
1437
+ - No recipe yet. The recipe verifier assembles a page's listing and runs
1438
+ the PRG; it has no step for running a cruncher on the result, so a page
1439
+ whose pin is a crunched program cannot be verified as the gate stands.
1440
+
1441
+ ### Sources
1442
+
1443
+ - Pasi 'Albert' Ojala, "Pucrunch: An Optimizing Hybrid LZ77 RLE Data
1444
+ Compression Program" (the author's page, with `pucrunch.c`,
1445
+ `pucrunch.h` and `uncrunch.asm`), read for the memory layout, the
1446
+ escape scheme, the licence statements and the flag meanings:
1447
+ https://a1bert.kapsi.fi/Dev/pucrunch/
1448
+ - `pucrunch.c` version string `pucrunch 1.14 22-Nov-2008`; usage text
1449
+ from `pucrunch -h` run here.
1450
+
1451
+ ## zx0_lzsa_decrunchers — ZX0, Dali, ZX02 and LZSA: modern crunchers with tiny decrunchers
1452
+
1453
+ **Complexity:** low
1454
+ **Region:** both
1455
+ **Uses kernal:** (none)
1456
+ **Requires:** ram_under_kernal
1457
+ **Cost:** bytes_code=257, zp_bytes=236
1458
+ **Cost basis:** arithmetic
1459
+
1460
+ The Cost line is Dali 0.3.5's standard self-extractor: 257 bytes of
1461
+ copier and decruncher, worked as the sfx file's size less its two-byte
1462
+ load address, its twelve-byte BASIC stub and the 770-byte stream the raw
1463
+ mode writes for the same input (269 with the stub, which the pucrunch
1464
+ section's 245 also leaves out), and the 236 bytes of zero page from `$01`
1465
+ upward that the copier fills (the `LDY #$EC` in the stub's own bytes). Dali saves that zero page
1466
+ on the stack and puts it back, so the figure is the space the decruncher
1467
+ borrows, not what it destroys. The `--small` self-extractor is 210 bytes
1468
+ and takes 183 bytes of zero page without saving them, and bitfire's own
1469
+ ZX0 self-extractor is 263 bytes over 212 bytes of zero page, also without
1470
+ saving. Decrunch times are in the cycle budget; they are a one-off cost at
1471
+ start, so they are not on the line.
1472
+
1473
+ ### Why
1474
+
1475
+ Exomizer and pucrunch trade decruncher size for ratio and speed. A newer
1476
+ family of formats takes the other end of the trade: a decruncher of one
1477
+ to two hundred bytes, a handful of zero-page bytes, a decrunch loop with
1478
+ no tables to build, and a ratio that on the mixed test file below beats
1479
+ both of the older tools. Four of them have C64 or generic 6502
1480
+ decrunchers with a permissive licence, so a shipped game can carry the
1481
+ decruncher without thinking about it:
1482
+
1483
+ - **ZX0** by Einar Saukas, an optimal LZ77 cruncher whose repository
1484
+ holds the compressor and Z80 decrunchers, under the BSD 3-clause
1485
+ licence. It has no 6502 decruncher of its own; the README lists two
1486
+ 6502 ports, one of them the copy inside bitfire.
1487
+ - **Dali** by Tobias Bindhammer (Bitbreaker), a C64 tool that re-encodes
1488
+ ZX0 output into its own bit layout and writes a C64 self-extracting
1489
+ PRG. Its compressor is Emmanuel Marty's Salvador, which produces
1490
+ ZX0-compatible streams. The tarball's assembly sources carry a BSD
1491
+ 3-clause header; Salvador's own licence file is zlib with a CC0 match
1492
+ finder. The `dali.c` file has no licence header of its own; a shipped
1493
+ product should take the assembly headers as the statement.
1494
+ - **ZX02** by Daniel Serpell (DMSC), a ZX0 variant reworked for the 6502
1495
+ and not stream-compatible with ZX0, under the MIT licence. Its README
1496
+ names four 6502 decrunchers of 108 to 166 bytes, all using eight bytes
1497
+ of zero page.
1498
+ - **LZSA1 and LZSA2** by Emmanuel Marty, a byte-aligned format designed
1499
+ for 8-bit decoders, under the zlib licence with a CC0 match finder.
1500
+ The repository carries six generic 6502 decrunchers; the faster v1 and
1501
+ v2 sources state their own sizes (165 and 191 bytes for LZSA1, 241 and
1502
+ 256 for LZSA2) and their zero-page use (the last seven bytes of the
1503
+ zero page for v1, the last eleven for v2); the fast sources state
1504
+ neither, and the small v2 source uses one zero-page byte, `$FC`.
1505
+
1506
+ The C64 self-extractors measured here were built from bitfire (commit
1507
+ `5a3964b`, 2026-09-10) and Dali 0.3.5 (the CSDb tarball). The 6502
1508
+ decrunchers for ZX02 and LZSA were read, not assembled; they are written
1509
+ for other assemblers, and their decrunch times were not measured here.
1510
+
1511
+ Everything below marked "measured" was run on 2026-09-23 in the
1512
+ windowless x64sc build of VICE 3.10, PAL, with the same two inputs, the
1513
+ same loader and the same method as the pucrunch section above.
1514
+
1515
+ ### How
1516
+
1517
+ Each tool has a raw mode and, for two of them, a C64 self-extracting mode.
1518
+ The commands as used here:
1519
+
1520
+ ```text
1521
+ zx0 game.bin game.zx0 # ZX0 v2.2, raw stream, no load address
1522
+ zx02 game.bin game.zx02 # ZX02, raw stream
1523
+ lzsa -f 1 -r game.bin game.lzsa1 # LZSA1 raw block
1524
+ lzsa -f 2 -r game.bin game.lzsa2 # LZSA2 raw block
1525
+ dali --sfx 0x0810 -o game-dali.prg game.prg # Dali C64 self-extractor, entry $0810
1526
+ dali --sfx 0x0810 --small -o game-tiny.prg game.prg
1527
+ dali -o game.dali game.prg # Dali raw stream for bitfire's decruncher
1528
+ zx0 --sfx 0x0810 -o game-bf.prg game.prg # bitfire's own ZX0 packer, same shape
1529
+ ```
1530
+
1531
+ ZX0, ZX02 and LZSA read a headerless file and write a headerless stream;
1532
+ the caller's own decruncher knows where it goes. Dali and bitfire's
1533
+ packer read a PRG, keep its load address as the decrunch target, and
1534
+ report the original and packed spans on every run. Their `--sfx` takes
1535
+ the entry address as a number after the flag; give it, or the flag eats
1536
+ the next argument. Dali adds `--01` to set the processor port after
1537
+ decrunching, `--cli` to leave with interrupts on (the default is off),
1538
+ `--effect` for a border effect while it runs, `--no-inplace`,
1539
+ `--binfile`, `--from`/`--to` for a slice, `--prefix-file` for a
1540
+ dictionary already in memory, and `--relocate-sfx` for a stub without a
1541
+ BASIC line. bitfire's packer shares `--sfx`, `--no-inplace`,
1542
+ `--binfile`, `--from`/`--to` and the relocate flags, adds `-f` and `-q`,
1543
+ and uses `--use-prefix` in place of the prefix-file options; it has no
1544
+ `--01`, `--cli`, `--small` or `--effect`.
1545
+
1546
+ The self-extractor's layout, read from the two `sfx.asm` sources and
1547
+ checked against the bytes of the output: a one-line BASIC stub (`SYS
1548
+ 2061` in every run here) followed by a copier, the decruncher and the
1549
+ stream. Run, the copier moves the decruncher into the zero page, counting
1550
+ down from `$EC` (Dali) or `$D4` (bitfire) to `$01`; the byte that lands
1551
+ at `$01` is the processor port's new value, `$34`, which banks the ROMs
1552
+ out so the whole 64 KB is writable. It then copies
1553
+ the crunched stream to the top of memory, ending at `$FFFF` under the
1554
+ KERNAL ROM, and decrunches forwards from the original load address,
1555
+ which is why both files here landed with their own BASIC stub back at
1556
+ `$0801` byte for byte. Dali's standard decruncher pushes the zero page
1557
+ onto the stack before overwriting it and pops it back on exit, with `$37`
1558
+ in the port unless `--01` says otherwise. Its `--small` decruncher and
1559
+ bitfire's do not save anything: measured here, the subject's KERNAL
1560
+ print path did not survive either. Under bitfire's self-extractor the
1561
+ subject reached its green border but printed nothing, and its timer
1562
+ bytes had to be read from memory with a monitor breakpoint; under Dali's
1563
+ `--small` it never reached the border at all, in a stand-alone run and
1564
+ under the loader alike. Which zero-page byte kills it was not traced;
1565
+ the KERNAL's own variables live in the span both overwrite. Code that
1566
+ follows a `--small` or bitfire decrunch must not call the KERNAL until it
1567
+ has reset what it needs, or must be a program that owns the machine.
1568
+
1569
+ The raw decrunchers ask for less. bitfire's `dzx0` uses five zero-page
1570
+ bytes at `$F8` to `$FC` and keeps its source pointer in its own operands;
1571
+ its header warns that it reads the unmodified ZX0 stream only, not the
1572
+ Dali one, which has its own `dzx0_dali.asm` with six bytes at `$F0`.
1573
+ ZX02's four decrunchers take eight bytes from `$80` by default and are
1574
+ ROM-able; the README names in-place decrunching with the compressor's
1575
+ reported `delta` (at worst 12 bytes per KB) as the safety margin. LZSA's
1576
+ small v1 decruncher keeps every pointer in self-modified operands and
1577
+ uses no zero page at all as written; small v2 uses `$FC`. None of the
1578
+ three formats carries a header that says which variant wrote it, so, as
1579
+ with Exomizer's `-P` bits, a stream and its decruncher must come from the
1580
+ same tool: ZX02 does not read ZX0, bitfire's decruncher does not read
1581
+ Dali, and LZSA1 and LZSA2 are different formats.
1582
+
1583
+ ### Why it works
1584
+
1585
+ All four are LZ77: the output is built from literal bytes and from copies
1586
+ of what was already written, named by a distance back and a length. They
1587
+ differ in how those are coded. ZX0 keeps three kinds of block, literal
1588
+ run, match at the previous offset and match at a new offset, with lengths
1589
+ and offsets in interleaved Elias gamma codes, and the compressor picks
1590
+ the block sequence that is optimal for the whole file, which is where its
1591
+ ratio comes from. ZX02 caps the gamma codes at eight bits, stores offsets
1592
+ as positive values minus one and lets a match be one byte long, all so
1593
+ that an 8-bit register holds every quantity the decoder handles; that
1594
+ costs a little on long runs and gains on code. LZSA gives up the bit
1595
+ stream altogether: each token is a byte whose fields hold a literal count
1596
+ and a match length, with longer values in following bytes and the offset
1597
+ as one or two whole bytes, so the decoder never shifts a bit reservoir.
1598
+ LZSA2 adds nibble-sized fields and a repeat-offset match, which is why it
1599
+ beats LZSA1 on both inputs below. Dali re-encodes ZX0's blocks into the
1600
+ bit order bitfire's decruncher wants; the blocks themselves are ZX0's.
1601
+
1602
+ ### Variations
1603
+
1604
+ **In-place decrunching.** Every tool here supports it. The stream is
1605
+ placed so that its end sits a small margin past the end of the output,
1606
+ and the decoder writes forwards without ever overtaking the input it has
1607
+ not read. ZX0 and ZX02 print the margin as `delta` when they crunch
1608
+ (3 bytes on the mixed file, 3 on the code file for ZX0); Dali and bitfire
1609
+ assume it unless `--no-inplace` is given. The C64 self-extractors go one
1610
+ further and copy the stream to the top of RAM first, so the margin is not
1611
+ the caller's problem.
1612
+
1613
+ **Streaming from disk.** bitfire is a disk loader whose files are all
1614
+ Dali-crunched, and it decrunches as sectors arrive; that path was not
1615
+ run here and its figures are bitfire's, not this page's. LZSA's raw block
1616
+ form and its stated small-decruncher sizes are the reason the format was
1617
+ built for that use on other 8-bit machines.
1618
+
1619
+ **Comparison, measured.** The same two subjects and loader as the
1620
+ pucrunch section: a 4,519-byte PRG of code plus mixed filler and a
1621
+ 4,231-byte PRG whose filler is 4 KB of KERNAL ROM bytes. Sizes for the
1622
+ raw modes are the stream alone from a headerless input two bytes shorter;
1623
+ Dali's raw mode reads the PRG and writes a PRG, so its row is the stream
1624
+ after the two-byte load address it keeps (772 and 3,523 as files); sizes
1625
+ for the sfx modes are the whole PRG. Cycles are SYS to entry with
1626
+ the loader's own 271 or 228 cycles included, two runs each, identical to
1627
+ the cycle. The pucrunch and Exomizer rows are quoted from the pucrunch
1628
+ section above. One difference between the rows, as there: Dali's
1629
+ self-extractor runs under SEI from its first instruction; so does
1630
+ bitfire's.
1631
+
1632
+ | Cruncher | Mixed file: bytes | Mixed file: cycles | Code file: bytes | Code file: cycles |
1633
+ |---|---|---|---|---|
1634
+ | none | 4,519 | 271 | 4,231 | 228 |
1635
+ | dali `--sfx` | 1,041 | 114,369 | 3,792 | 311,234 |
1636
+ | dali `--sfx --small` | 982 | not measured here | 3,733 | not measured here |
1637
+ | bitfire zx0 `--sfx` | 1,035 | 99,422 | 3,786 | 295,612 |
1638
+ | zx0 v2.2 raw | 771 | not measured here | 3,522 | not measured here |
1639
+ | dali raw (stream) | 770 | not measured here | 3,521 | not measured here |
1640
+ | zx02 raw | 797 | not measured here | 3,522 | not measured here |
1641
+ | lzsa1 raw | 785 | not measured here | 3,852 | not measured here |
1642
+ | lzsa2 raw | 781 | not measured here | 3,682 | not measured here |
1643
+ | pucrunch default (pucrunch section) | 1,084 | 349,505 | 3,918 | 1,008,259 |
1644
+ | exomizer `sfx sys` (pucrunch section) | 1,103 | 189,276 | 3,772 | 590,882 |
1645
+
1646
+ Both ZX0 self-extractors are smaller than pucrunch's and Exomizer's on
1647
+ the mixed file and decrunch it in a third of pucrunch's time and well
1648
+ under Exomizer's; on the code file they are 20 bytes larger than
1649
+ Exomizer's sfx and about half its time. Per output byte that is about
1650
+ 25 cycles for Dali and 22 for bitfire on the mixed file, 74 and 70 on the
1651
+ code file. The `--small` variant saved 59 bytes of PRG; its time is not
1652
+ on the table because the subject did not run to its entry under it.
1653
+ Raw-mode times are not measured here: the ZX02 and LZSA 6502 sources are
1654
+ written for other assemblers and were not ported for this run.
1655
+
1656
+ ### Cycle budget
1657
+
1658
+ Measured, PAL, SYS to entry: 114,369 cycles (0.12 s) for 4,517 bytes of
1659
+ mixed data and 311,234 cycles (0.32 s) for 4,229 bytes of code with
1660
+ Dali's standard self-extractor; bitfire's 99,422 and 295,612. As with the
1661
+ older tools, code that crunches badly costs more per byte to decrunch:
1662
+ about three times as much per byte on both families, from a base a
1663
+ third as high here. Measure a real level file before promising a load
1664
+ time.
1665
+
1666
+ ### Recipes
1667
+
1668
+ - No recipe yet. The recipe verifier assembles a page's listing and runs
1669
+ the PRG; it has no step for running a cruncher on the result, so a page
1670
+ whose pin is a crunched program cannot be verified as the gate stands.
1671
+
1672
+ ### Sources
1673
+
1674
+ - Einar Saukas, ZX0 repository (`src/zx0.c` banner `ZX0 v2.2`, README's
1675
+ list of 6502 ports, `LICENSE`): https://github.com/einar-saukas/ZX0
1676
+ - Tobias Bindhammer, bitfire repository, `packer/zx0/` (`zx0.c` usage,
1677
+ `sfx.asm`, `6502/dzx0_v2.asm`, `LICENSE`): https://github.com/bboxy/bitfire
1678
+ - Tobias Bindhammer, Dali 0.3.5 (`dali035.tar.gz` from CSDb release
1679
+ 247483: `dali.c` usage, `sfx.asm`, `dzx0_dali.asm`, `Makefile`,
1680
+ `salvador/README.md` and licence files): https://csdb.dk/release/?id=247483
1681
+ - Daniel Serpell, ZX02 repository (README's decruncher list and format
1682
+ notes, `6502/zx02-small.asm` and `zx02-optim.asm` headers, `LICENSE`):
1683
+ https://github.com/dmsc/zx02
1684
+ - Emmanuel Marty, LZSA repository (`src/lzsa.c` version string 1.4.1,
1685
+ README's licence section, `asm/6502/` headers): https://github.com/emmanuel-marty/lzsa
1686
+ - Usage text from each tool run here without arguments.