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,1292 @@
1
+ ---
2
+ category: scroll
3
+ chip: VIC-II
4
+ ---
5
+
6
+ <!-- doc-type: technique-reference -->
7
+
8
+ # Scroll Techniques
9
+
10
+ The VIC-II provides two hardware scroll registers that shift the visible
11
+ display area up to 7 pixels in either axis without touching screen RAM.
12
+ Used alone, each register gives only eight positions — seven pixels of
13
+ travel — before it wraps (an earlier version said "one pixel of range per
14
+ frame", conflating the range with the 1 px/frame step rate). The real power comes from combining the hardware offset with
15
+ timed screen-RAM rotation: the hardware register handles sub-character
16
+ granularity while a CPU-side copy updates the coarser character grid. The
17
+ techniques in this document cover the full range from a single-axis
18
+ 1-pixel-per-frame scroller to parallax depth effects and bitmap-mode
19
+ horizontal panning.
20
+
21
+ ---
22
+
23
+ ## soft_scroll_h — Hardware horizontal soft-scroll
24
+
25
+ **Complexity:** low
26
+ **Region:** both
27
+ **Uses registers:** D016
28
+ **Uses kernal:** (none)
29
+ **Cost:** cycles_per_frame=74041
30
+ **Cost basis:** measured-vice
31
+ **Cost measured on:** oscar64-soft-scroll-h (carry frame: a 25-row memmove of screen and colour RAM)
32
+ **Cost includes:** char_scroll_buffer_h
33
+
34
+ ### Why
35
+
36
+ Games and demos frequently need to shift the entire display left or right
37
+ by one pixel at a time — a news ticker, a side-scrolling landscape, a
38
+ credit scroll. Doing this by rewriting every byte of screen RAM each frame
39
+ is prohibitively expensive. The VIC-II's $D016 XSCROLL field solves this
40
+ by shifting the pixel output pipeline before it reaches the border logic,
41
+ so the chip does the work in hardware at zero CPU cost per pixel column.
42
+
43
+ ### How
44
+
45
+ $D016 bits 2-0 (XSCROLL) hold a three-bit fine-scroll offset. Each frame,
46
+ increment (or decrement) the stored XSCROLL value and write the new three
47
+ bits into $D016, preserving the CSEL and MCM bits in bits 3 and 4. The
48
+ display shifts by the number of pixels indicated without any change to
49
+ screen RAM or color RAM.
50
+
51
+ XSCROLL = n places the display n pixels to the RIGHT of the character grid
52
+ (measured in VICE x64sc: a block at column 0 sat at x 32-39 with XSCROLL=0
53
+ and at x 36-43 with XSCROLL=4). An earlier version of the next two
54
+ paragraphs had the directions reversed.
55
+
56
+ To move the display content to the right, increment XSCROLL each frame:
57
+ 0 → 1 → 2 → ... → 7, then roll to 0 and simultaneously shift the
58
+ screen-RAM columns one character to the right.
59
+
60
+ To move the display content to the left (the usual "scrolling" direction
61
+ for a ticker), decrement XSCROLL: 7 → 6 → ... → 0, then roll to 7 and
62
+ shift screen-RAM one column to the left.
63
+
64
+ The XSCROLL = 0 case produces no visual shift relative to the character
65
+ grid. XSCROLL = 7 shifts seven pixels, placing the display one pixel
66
+ short of a full character-width offset.
67
+
68
+ ### Why it works
69
+
70
+ The VIC-II's horizontal pixel sequencer ordinarily starts drawing at a
71
+ fixed position aligned to the character grid. XSCROLL delays the start of
72
+ the visible area within each 8-pixel character cell by the specified number
73
+ of clock cycles (one clock per pixel). Because the delay applies globally
74
+ to the entire display, every column shifts uniformly. The border logic and
75
+ the display-window start position are unaffected, so the column count
76
+ remains 40 and the visible width is unchanged.
77
+
78
+ ### Variations
79
+
80
+ - **Single-pixel tick:** Change XSCROLL by 1 each frame for 1 px/frame.
81
+ - **Multi-pixel skip:** Advance XSCROLL by 2 or 3 per frame for faster
82
+ scrolling; just handle the boundary rollover at the correct modulus.
83
+ - **Bidirectional toggle:** Store scroll direction in a flag and negate
84
+ the increment to reverse at runtime.
85
+
86
+ ### Cycle budget
87
+
88
+ Writing $D016 costs 6 cycles (LDA #imm 2 + STA abs 4; an earlier version
89
+ said 4). Preserving CSEL, MCM and RES from a shadow byte — LDA shadow /
90
+ AND #$F8 / ORA new / STA $D016 — is 11-14 cycles depending on whether the
91
+ shadow and new value are immediate, zero-page or absolute (cycle counts
92
+ from `docs/hardware/6510-cpu-reference.md`). This technique has no
93
+ raster-critical timing requirement.
94
+
95
+ The Cost line's 74,041 cycles is not the register write. It is the carry
96
+ frame of `recipes/oscar64/soft-scroll-h.md`, measured there in VICE: that
97
+ recipe shifts all 25 rows of screen and colour RAM with `memmove`, which
98
+ takes 3.8 PAL frames. The figure belongs to that implementation and
99
+ exceeds a frame; issue #18 tracks rewriting the move to fit the vertical
100
+ blank.
101
+
102
+ ### Recipes
103
+
104
+ - `recipes/oscar64/soft-scroll-h.md`
105
+
106
+ ---
107
+
108
+ ## soft_scroll_v — Hardware vertical soft-scroll
109
+
110
+ **Complexity:** low
111
+ **Region:** both
112
+ **Uses registers:** D011
113
+ **Uses kernal:** (none)
114
+
115
+ ### Why
116
+
117
+ Vertical soft-scroll is the companion to horizontal: shift the entire
118
+ displayed raster up or down by up to 7 pixels without rewriting screen
119
+ RAM. Commonly used for vertical credits, FLD split-screen effects, and
120
+ as one axis of a two-dimensional smooth-scroller.
121
+
122
+ ### How
123
+
124
+ $D011 bits 2-0 (YSCROLL) hold a three-bit fine-scroll offset. Each frame,
125
+ write the new YSCROLL value into $D011, preserving the DEN, RSEL, RST8,
126
+ ECM, and BMM bits. The visible window shifts by the specified number of
127
+ raster lines.
128
+
129
+ The default YSCROLL value after KERNAL initialisation is 3. Decrementing
130
+ YSCROLL moves the display upward; incrementing moves it downward (the
131
+ reference point is the top of the character row, so larger values push
132
+ content down).
133
+
134
+ When YSCROLL reaches 0 and a further upward shift is needed, reset
135
+ YSCROLL to 7 and shift the screen-RAM rows one row upward (see
136
+ `char_scroll_buffer_v`). For downward scrolling, the symmetrical
137
+ procedure applies.
138
+
139
+ ### Why it works
140
+
141
+ The VIC-II's vertical display window start is fixed relative to the
142
+ badline condition. A badline fires when the bottom three bits of the
143
+ current raster line match YSCROLL (and the line is within the active
144
+ region). By decrementing YSCROLL, you retard the point at which the chip
145
+ believes the first row of each character tile begins, which slides the
146
+ displayed image upward by one raster line. The effect is a pure hardware
147
+ shift with no pixel-by-pixel CPU work.
148
+
149
+ **Critical side effect:** changing YSCROLL shifts the entire set of
150
+ badlines for the current frame. Because a badline costs the CPU 40 to 43
151
+ of the line's 63 cycles (the VIC holds the bus for cycles 15-54, and BA
152
+ drops three cycles earlier at cycle 12, where the CPU halts on its first
153
+ read; ordinary code therefore keeps only 20 cycles on a badline — an
154
+ earlier version said "up to 40"), smooth-scrolling code that changes YSCROLL must
155
+ ensure any time-sensitive raster IRQ code is written to tolerate the
156
+ resulting change in badline positions. This is most relevant when
157
+ combining vertical scroll with raster split bars.
158
+
159
+ ### Variations
160
+
161
+ - **FLD (Flexible Line Distance):** Change YSCROLL mid-frame inside a
162
+ raster IRQ to open or close extra blank lines between rows, stretching
163
+ the picture vertically. This technique is built on top of soft_scroll_v.
164
+ - **Row hold:** Set YSCROLL to 0 and hold it to keep the display
165
+ "bottom-aligned" within each character row, which shifts the apparent
166
+ top of the screen upward 3 pixels from the KERNAL default.
167
+
168
+ ### Cycle budget
169
+
170
+ Writing $D011 requires a careful read-modify-write to preserve the mode
171
+ bits. The safest pattern is: LDA yscroll_shadow, AND #$F8, ORA new_yscroll,
172
+ STA $D011 — 12-14 cycles (3 + 2 + 3 + 4 with zero-page operands, 14 with
173
+ absolute ones; an earlier version said 10-12). Because changing YSCROLL inside a visible
174
+ raster can produce glitches, the write should happen during the vertical
175
+ blank or in a stable raster window above line $30.
176
+
177
+ ### Recipes
178
+
179
+ - `recipes/kickassembler/scroll-panel-split.md` scrolls a playfield vertically through all eight YSCROLL phases above a fixed panel; `recipes/oscar64/soft-scroll-h.md` is the horizontal counterpart.
180
+
181
+ ---
182
+
183
+ ## char_scroll_buffer_h — Char-mode horizontal scroll with screen-RAM buffer rotation
184
+
185
+ **Complexity:** medium
186
+ **Region:** both
187
+ **Uses registers:** D016
188
+ **Uses kernal:** (none)
189
+
190
+ ### Why
191
+
192
+ The hardware XSCROLL register only provides a 0-7 pixel range. Scrolling
193
+ a character-mode display continuously requires advancing the content by
194
+ one full character column (8 pixels) at the moment XSCROLL would overflow.
195
+ This technique handles that overflow by rotating screen RAM and color RAM
196
+ in memory, effectively carrying new character data onto the visible edge
197
+ while resetting XSCROLL to maintain seamless motion.
198
+
199
+ ### How
200
+
201
+ The display is backed by a 40×25 text screen (1000 bytes) and a parallel
202
+ 40×25 color RAM at $D800 (1000 nibbles). To scroll left by one character
203
+ column:
204
+
205
+ 1. Copy columns 1-39 of each row to columns 0-38 (memmove of 39 bytes
206
+ per row, or equivalently shift the entire 1000-byte screen window left
207
+ by one byte taking care at the row boundary).
208
+ 2. Write fresh data into column 39 (the new rightmost column) from an
209
+ off-screen content buffer.
210
+ 3. Repeat the same move on color RAM at $D800.
211
+ 4. Reset XSCROLL to 7 in $D016 (it has just wrapped from 0; the column
212
+ move cancels the 8-pixel snap). An earlier version of this step said
213
+ "reset to 0", which produces an 8-pixel jump every eighth frame.
214
+
215
+ The net result: the visible content has shifted one full character to the
216
+ left, and XSCROLL is back at 7 ready for the next seven single-pixel steps
217
+ down to 0.
218
+
219
+ For scrolling right, mirror the process: copy columns 0-38 to columns
220
+ 1-39, write fresh data into column 0, reset XSCROLL to 0 (it has just
221
+ wrapped from 7). This matches the `if (xscroll == 0) { shift; xscroll = 7; }`
222
+ form in `recipes/oscar64/soft-scroll-h.md`.
223
+
224
+ ### Why it works
225
+
226
+ The hardware XSCROLL shift and the software screen-RAM shift are
227
+ complementary. The hardware provides fractional (sub-character) precision;
228
+ the software provides whole-character carries. When the two are reset
229
+ atomically in the same frame, the viewer sees a seamless stream of 1-pixel
230
+ steps even though the underlying mechanism alternates between a hardware
231
+ shift and a memory copy.
232
+
233
+ ### Variations
234
+
235
+ - **Double-buffered screen RAM:** Maintain two screen-RAM pages and
236
+ alternate which one $D018 points to, avoiding tearing on fast machines.
237
+ - **Unrolled move:** On stock C64 there is no DMA. Unrolling the copy
238
+ into straight LDA abs / STA abs pairs brings it to 8 cycles per byte
239
+ (8,000 cycles for 40×25), which still exceeds the off-screen span on
240
+ both PAL (~7,056 cycles) and NTSC (~4,095); unrolling reduces the cost,
241
+ it does not make the move fit in the blank. An earlier version of this
242
+ item claimed the unrolled move "can complete inside the vertical blank".
243
+ - **Wide content ring buffer:** Keep the source content in a ring buffer
244
+ wider than 40 columns. Advance the ring pointer each time a column shift
245
+ fires instead of precomputing content on demand.
246
+
247
+ ### Cycle budget
248
+
249
+ A naive byte-by-byte shift of 1000 bytes at roughly 10 cycles per
250
+ byte costs ~10,000 cycles. PAL has 63 × 312 = 19,656 cycles per frame
251
+ minus ~25 × 43 = 1,075 badline-stolen cycles for a CPU budget of ~18,581
252
+ cycles per frame. The screen shift alone therefore consumes about 54% of
253
+ the frame budget. The color RAM shift doubles that cost to ~108%. This
254
+ means a brute-force shift must be overlapped across multiple frames or
255
+ replaced with a DEC-and-pointer approach. An unrolled inner loop using
256
+ indexed addressing and/or a 2-byte-per-iteration pattern roughly halves
257
+ the cycle count.
258
+
259
+ ### Recipes
260
+
261
+ - `recipes/oscar64/soft-scroll-h.md`
262
+
263
+ ---
264
+
265
+ ## char_scroll_buffer_v — Char-mode vertical scroll
266
+
267
+ **Complexity:** medium
268
+ **Region:** both
269
+ **Uses registers:** D011
270
+ **Uses kernal:** (none)
271
+
272
+ ### Why
273
+
274
+ The hardware YSCROLL register provides only a 0-7 raster-line range.
275
+ Continuous vertical scrolling requires a software carry to advance the
276
+ content by one full character row (8 raster lines) when YSCROLL overflows.
277
+ This technique handles that carry via a screen-RAM row shift, mirroring
278
+ `char_scroll_buffer_h` in the vertical axis.
279
+
280
+ ### How
281
+
282
+ To scroll upward by one row (content moves up, new row appears at the
283
+ bottom):
284
+
285
+ 1. Copy rows 1-24 of screen RAM to rows 0-23. Because a row is 40 bytes,
286
+ this is a memmove of 40 × 24 = 960 bytes from offset 40 to offset 0.
287
+ 2. Write a fresh 40-byte row into row 24 from the content source.
288
+ 3. Perform the same move on color RAM at $D800.
289
+ 4. Reset YSCROLL to 7 in $D011 (or to the value it had before it
290
+ decremented to -1).
291
+
292
+ For scrolling downward, copy rows 0-23 to rows 1-24, write row 0 from
293
+ the source, and reset YSCROLL to 0.
294
+
295
+ The row shift should be performed while the VIC is not fetching display
296
+ data, or spread across raster interrupts, to avoid visible tearing. The
297
+ row shift does not fit in the blanking period (an earlier version said it
298
+ did). Even fully unrolled as LDA abs / STA abs it costs 8 cycles per byte
299
+ — 960 × 8 = 7,680 cycles for screen RAM alone, doubled again for colour
300
+ RAM — against an off-screen span of only 112 lines × 63 = 7,056 cycles on
301
+ PAL (lines 251-311 and 0-50) and 63 × 65 = 4,095 on NTSC; the hardware
302
+ vertical blank proper (PAL lines 300-15) is far smaller still. Production
303
+ scrollers therefore spread the move across the frames between carries,
304
+ shift only the rows that scroll, or write the shifted copy into a second
305
+ screen page during the active frame and flip $D018 in the border.
306
+
307
+ ### Why it works
308
+
309
+ Same principle as the horizontal case. The hardware YSCROLL field provides
310
+ fine alignment within the current character row. The software row shift
311
+ provides the coarser row-level carry. Together they produce continuous
312
+ pixel-level motion.
313
+
314
+ ### Variations
315
+
316
+ - **FLD combined scroll:** Use soft_scroll_v during most of the frame
317
+ and trigger the screen-RAM row shift only when the YSCROLL carry fires.
318
+ FLD effects (flexible line distance) can be applied to individual rows
319
+ during the same IRQ pass.
320
+ - **Map streamer:** Instead of pre-constructing content in a buffer,
321
+ decode map data on the fly into the newly exposed row. This is the
322
+ approach used by most C64 platformers.
323
+
324
+ ### Cycle budget
325
+
326
+ A 960-byte row shift at ~10 cycles per byte costs ~9,600 cycles (7,680
327
+ fully unrolled). Between the last display line (250, RSEL=1) and the
328
+ first badline of the next frame (48 + YSCROLL) there are no badlines and
329
+ no character/bitmap fetches: with the default YSCROLL=3 that is lines
330
+ 251-311 and 0-50, 112 raster lines = 7,056 cycles on PAL; because this
331
+ technique itself drives YSCROLL through 0-7, the span guaranteed at every
332
+ scroll position is lines 251-311 and 0-47, 109 lines = 6,867 cycles
333
+ (VICE shows the first badline at 48, 51 and 55 for YSCROLL 0, 3 and 7;
334
+ window and badline bounds from `docs/hardware/vic-ii-reference.md`). Only
335
+ lines 300-15 are vertical blanking in the video sense — 28 lines, 1,764
336
+ cycles (`docs/hardware/pal-ntsc-reference.md`); the rest of the span is
337
+ visible border, which is equally free of display DMA. Enabled sprites
338
+ still take their DMA in these lines. An earlier version of this paragraph
339
+ offered a 3,780-cycle "vertical blank" of lines 300-311 + 0-47 "of which
340
+ many are non-badline"; no line in that span is a badline. Since the shift
341
+ does not fit in the off-screen span, spread it across several frames or
342
+ use a double-buffer scheme where row 24 is pre-populated during the next
343
+ frame's active display period.
344
+
345
+ ### Recipes
346
+
347
+ - `recipes/kickassembler/scroll-panel-split.md` shifts the rows on the carry frame and scrolls through all eight YSCROLL phases above a fixed panel.
348
+
349
+ ---
350
+
351
+ ## scroll_panel_split — Vertically scrolled playfield over a fixed score panel
352
+
353
+ **Complexity:** medium
354
+ **Region:** both
355
+ **Uses registers:** D011, D012, D016, D018, D021
356
+ **Uses kernal:** (none)
357
+ **Requires:** soft_scroll_v
358
+ **Demands:** midframe_raster_irqs
359
+ **Cost:** irq_slots=2, lines_active=5, cycles_per_frame=413
360
+ **Cost basis:** measured-vice
361
+ **Cost measured on:** kickassembler-scroll-panel-split (two IRQs, screen on; not the carry frame)
362
+ **Claims:** vic_raster_irq (owns)
363
+ **Claims basis:** derived-listing
364
+
365
+ ### Why
366
+
367
+ A game whose playfield scrolls vertically still needs a score panel that
368
+ stays still. The playfield's YSCROLL changes every frame; the panel's must
369
+ not. A raster split has to change `$D011`, and usually `$D016`, `$D018` and a
370
+ colour, between the playfield's last line and the panel's first. A split at
371
+ one fixed line with one fixed delay works at seven YSCROLL phases and breaks
372
+ at the eighth (pitfall `scroll_phase_breaks_panel_split`).
373
+
374
+ ### How
375
+
376
+ 1. Put the panel's first line on a line that is 7 mod 8, 48 + 8k + 7, and give
377
+ the panel YSCROLL 7. The playfield then ends on the line before, at every
378
+ phase.
379
+ 2. Let the playfield scroll with `soft_scroll_v` and `char_scroll_buffer_v`.
380
+ The last playfield row is cut short by the panel's badline and shows
381
+ 7 − YSCROLL lines.
382
+ 3. Take a raster IRQ two lines before the playfield's last line. Load every
383
+ register value, poll `$D012` for the last line, then wait for a delay read
384
+ from an eight-entry table indexed by the playfield's YSCROLL.
385
+ 4. Store the panel's `$D016`, `$D011` and `$D018` in the right border of the
386
+ last playfield line.
387
+ 5. Poll for the panel's first line and let its badline stall the CPU; store
388
+ the panel's background colour after the stall.
389
+ 6. Below the panel, restore the playfield's registers and write the next
390
+ frame's YSCROLL before line 48.
391
+
392
+ ### Why it works
393
+
394
+ A character row is fetched again from its start until it has shown all eight
395
+ lines. A playfield row cut short by the panel's badline therefore does not
396
+ count, and the panel's first row is the screen row after the last complete
397
+ playfield row. With the panel's first badline on 48 + 8k + 7, exactly k
398
+ playfield rows complete at every YSCROLL, so the panel always reads the same
399
+ screen row. Measured in VICE x64sc 3.10 (`recipes/kickassembler/scroll-panel-split.md`):
400
+ with the panel on line 216 at YSCROLL 0 instead, the panel read screen row 21
401
+ at playfield YSCROLL 0 and row 20 at YSCROLL 3. The cut-short playfield row
402
+ and the panel's first row are the same screen row, so the panel needs its own
403
+ screen matrix, selected through `$D018`. They also share one colour RAM row.
404
+
405
+ The split line L is a badline at exactly one playfield phase, YSCROLL = L mod
406
+ 8. At that phase the VIC holds the CPU from cycle 12 to cycle 54 while the
407
+ split code waits (`badline_cycle_loss`), and a full delay lands the stores
408
+ about 40 cycles late, inside the panel's first line. The table entry for that
409
+ phase is 0: the stall is the wait. c64gameframework's panel IRQ uses the same
410
+ scheme, a delay table indexed by `$D011` (source:
411
+ https://github.com/cadaver/c64gameframework, `raster.s` and `aligneddata.s`,
412
+ not run here). Its panel `$D011` value `$57` has YSCROLL 7, which matches the
413
+ row rule above.
414
+
415
+ The panel's first line is always a badline once the split has run. Any code
416
+ that reads memory across cycle 12 of that line resumes at cycle 55 at every
417
+ phase, so a store after it has no poll jitter. The recipe puts `$D021` there.
418
+
419
+ ### Variations
420
+
421
+ - **Panel at the top:** the split sets the playfield's YSCROLL below the
422
+ panel instead. The row-count rule above was measured only for a panel at
423
+ the bottom.
424
+ - **Horizontal scrolling too:** the split resets XSCROLL in `$D016` and
425
+ usually switches 38 columns to 40, as the recipe does.
426
+ - **Colour RAM:** a colour-RAM shift for the playfield must not touch the
427
+ panel's rows; it is usually run while the beam is in the panel or the
428
+ border (Cadaver, https://cadaver.github.io/rants/scroll.html, not measured
429
+ here).
430
+
431
+ ### Cycle budget
432
+
433
+ The measured write window is narrow. At YSCROLL 3 the delay loop (`dec` on an
434
+ absolute counter and `bpl`, 9 cycles a pass) is clean at 4 and 5 passes on
435
+ PAL and NTSC; 3 writes `$D016` before line 214's right border and 6 misses the
436
+ start of line 215. Two consecutive pass counts are clean, so the margin is
437
+ between one and three passes (9-27 cycles) beyond the poll's 0-6 cycle jitter
438
+ (arithmetic; the poll loop is `cmp` absolute 4 + `bcs` taken 3 = 7 cycles). The delay
439
+ counter is at an absolute address (`DEC` opcode `$CE` in the assembled PRG),
440
+ so a pass is 9 cycles.
441
+
442
+ Two IRQs a frame: the split and the one below the panel. Measured in VICE
443
+ x64sc 3.10 with CIA 2 timer A free-running, read at entry and exit of both
444
+ handlers in a separate build of the recipe, 32 consecutive frames on PAL and
445
+ NTSC, from the IRQ sequence to the end of `rti`: the split takes 267-310
446
+ cycles depending on the phase (267 at YSCROLL 4, when line 212 is a badline)
447
+ and spans five raster lines; the bottom handler takes 103. That is at most 413
448
+ cycles a frame, the same on both models. On the one frame in eight that
449
+ carries, the bottom handler also shifts twenty rows (20 × 560 cycles,
450
+ arithmetic) and took 13,262 cycles on PAL, 13,519 on NTSC; that cost belongs
451
+ to `char_scroll_buffer_v`, not to the split.
452
+
453
+ ### Recipes
454
+
455
+ - `recipes/kickassembler/scroll-panel-split.md`: playfield scrolling up through all eight phases over a five-row panel, with the per-phase naive-versus-table measurement on PAL and NTSC.
456
+
457
+ ---
458
+
459
+ ## infinite_scroll_h — Combine soft + buffer for continuous horizontal scroll
460
+
461
+ **Complexity:** medium
462
+ **Region:** both
463
+ **Uses registers:** D016
464
+ **Uses kernal:** (none)
465
+ **Requires:** soft_scroll_h, char_scroll_buffer_h
466
+
467
+ ### Why
468
+
469
+ `soft_scroll_h` alone stops after 7 pixels. `char_scroll_buffer_h` alone
470
+ produces only character-column-resolution jumps. Combining both into a
471
+ unified scroll engine yields genuinely seamless 1-pixel-per-frame
472
+ continuous horizontal scrolling, which is the backbone of virtually every
473
+ C64 side-scroller and horizontal text scroller.
474
+
475
+ ### How
476
+
477
+ Maintain two state variables: a pixel-level scroll counter (0-7) and a
478
+ character-level content pointer into an off-screen map buffer.
479
+
480
+ Each frame, inside a vertical-blank or raster IRQ:
481
+
482
+ 1. Decrement (or increment) the pixel counter.
483
+ 2. Write the pixel counter into XSCROLL in $D016.
484
+ 3. If the pixel counter has rolled over (crossed 0 for leftward scroll,
485
+ or crossed 7 for rightward):
486
+ a. Shift screen RAM one column in the scroll direction.
487
+ b. Populate the newly exposed column from the map buffer at the
488
+ current content pointer.
489
+ c. Advance the content pointer by one column.
490
+ d. Advance the color RAM column the same way.
491
+ 4. Increment the frame counter for timing.
492
+
493
+ The pixel counter and the screen-RAM shift are always in sync. The viewer
494
+ sees a smooth stream of 1-pixel advances — the hardware register handles
495
+ sub-character motion, the software carry handles character-boundary
496
+ transitions.
497
+
498
+ ### Why it works
499
+
500
+ The VIC-II draws each frame from the fixed content of screen RAM, offset
501
+ by XSCROLL. Changing XSCROLL by 1 between frames produces a 1-pixel shift.
502
+ When XSCROLL wraps, the 1-pixel offset becomes 0 (aligned to the grid
503
+ again). Without the screen-RAM shift, the content would snap back 8 pixels
504
+ at the wrap. With the shift, the screen RAM has already advanced by one
505
+ column, exactly canceling the wrap reset. The visual result is a perfectly
506
+ continuous sub-pixel stream.
507
+
508
+ ### Variations
509
+
510
+ - **Variable speed:** Increment XSCROLL by more than 1 per frame (2, 3,
511
+ or 4) to double, triple, or quadruple scroll speed. At 8 px/frame the
512
+ software and hardware components become decoupled — just shift the screen
513
+ each frame and skip the fractional register entirely.
514
+ - **Reversed direction:** All the same logic applies rightward; the only
515
+ difference is the direction of the XSCROLL ramp (0→7 instead of 7→0)
516
+ and the direction of the screen-RAM column copy.
517
+ - **Speed modulation:** For smooth acceleration and deceleration
518
+ (ease-in / ease-out), store the current speed as a fixed-point number
519
+ and accumulate it into the pixel counter, firing a carry whenever it
520
+ crosses a character boundary.
521
+
522
+ ### Cycle budget
523
+
524
+ The per-frame cost is dominated by the occasional screen-RAM column shift,
525
+ which fires once every 8 frames at 1 px/frame. Amortized over 8 frames
526
+ on PAL (50 Hz), the average cost per frame is approximately 1,000 / 8 ×
527
+ 10 cycles = ~1,250 cycles amortized from screen copy, plus ~10 cycles per
528
+ frame for the XSCROLL write. This is comfortably within budget for a
529
+ game that can afford ~18,000 CPU cycles per frame.
530
+
531
+ ### Recipes
532
+
533
+ - `recipes/oscar64/soft-scroll-h.md`
534
+
535
+ ---
536
+
537
+ ## parallax_dual_layer — Char + sprite layered scroll at different speeds
538
+
539
+ **Complexity:** high
540
+ **Region:** both
541
+ **Uses registers:** D016, D000-D00F, D010
542
+ **Uses kernal:** (none)
543
+ **Requires:** infinite_scroll_h
544
+
545
+ ### Why
546
+
547
+ A single-plane scroller looks flat. Parallax — different screen layers
548
+ moving at different speeds — creates a strong illusion of depth. The C64
549
+ achieves this by running two conceptually separate scroll systems in the
550
+ same frame: the character-mode background scrolls at one rate, while
551
+ sprite-rendered foreground objects move at a different (usually faster)
552
+ rate. The viewer perceives the foreground as being closer because it moves
553
+ more quickly across the visual field.
554
+
555
+ ### How
556
+
557
+ Divide the scene into two logical layers.
558
+
559
+ **Background layer:** the character-mode screen scrolled via `infinite_scroll_h`
560
+ (or the vertical equivalent). Each frame, advance XSCROLL by the background
561
+ speed and carry to the screen-RAM column shift when needed.
562
+
563
+ **Foreground layer:** hardware sprites. Each frame, update each sprite's
564
+ X position by the foreground speed. For a leftward-scrolling foreground
565
+ faster than the background, decrement each sprite's X position by more
566
+ pixels per frame than the background moves (sprite X grows to the right;
567
+ an earlier version said "increment"). When a sprite's X coordinate drops
568
+ below the left edge, wrap it to the right edge and update its content
569
+ pointer to the next foreground object.
570
+
571
+ Sprite X positions are set via $D000 (sprite 0 X low byte), $D002, $D004,
572
+ $D006, $D008, $D00A, $D00C, $D00E for sprites 0-7 respectively. Because
573
+ the X coordinate is 9 bits, the MSB for all eight sprites is packed into
574
+ $D010 (MSIGX), one bit per sprite. When a sprite's X coordinate exceeds
575
+ 255, set its bit in $D010; when it drops below 256, clear it.
576
+
577
+ Typical speed ratios are 2:1 (foreground at 2 px/frame, background at 1
578
+ px/frame) or 4:1. The larger the ratio, the stronger the depth cue.
579
+
580
+ ### Why it works
581
+
582
+ The VIC-II renders sprites on top of (or behind, if the sprite priority
583
+ bit is clear) the character background in the same frame. Because the chip
584
+ reads sprite data independently of character data, there is no coupling
585
+ between the two position systems. The background XSCROLL affects only the
586
+ character rendering pipeline; sprite positions are absolute screen
587
+ coordinates and are unaffected by XSCROLL. Advancing them at a different
588
+ rate than the background produces the parallax effect entirely at the
589
+ hardware level.
590
+
591
+ ### Variations
592
+
593
+ - **Three-layer parallax:** Add a second character plane using a split-screen
594
+ raster IRQ — the top half of the screen uses one $D018 character base,
595
+ the bottom half uses another, with independent scroll variables for
596
+ each half.
597
+ - **Sprite foreground at 4x speed:** At 4 px/frame a foreground object
598
+ crosses the 320-px screen in 80 frames — 1.6 s on PAL — against 6.4 s
599
+ for a 1 px/frame background, a 4:1 depth cue that convincingly simulates
600
+ fast-moving near objects (bullets, sparks, foreground pillars). An
601
+ earlier version claimed "25 full-screen traversals per second", which
602
+ would need 160 px per frame.
603
+ - **Sprite-multiplexed deep parallax:** Combine a sprite multiplexer
604
+ (`sprite_multiplex_8`, or `sprite_multiplex_24` for larger counts — see
605
+ `docs/techniques/sprite.md`) with parallax to field more than 8 visible
606
+ foreground objects at different parallax depths.
607
+
608
+ ### Cycle budget
609
+
610
+ Per-frame sprite update cost: for N sprites, update the X low byte, check
611
+ the MSB threshold, and conditionally flip the $D010 bit. Roughly
612
+ 16 cycles per sprite for the conditional MSB path. Eight sprites: ~128
613
+ cycles. Background scroll update: ~10 cycles for the XSCROLL write plus
614
+ the amortized ~1,250-cycle column shift (once per 8 frames). A
615
+ two-layer scene at 2:1 ratio costs approximately 150 cycles per frame
616
+ in positional math plus the occasional carry. Well within PAL budget.
617
+
618
+ ### Recipes
619
+
620
+ - No recipe yet for parallax layers.
621
+
622
+ ---
623
+
624
+ ## charset_parallax — Parallax inside the character layer by rolling reserved glyphs
625
+
626
+ **Complexity:** medium
627
+ **Region:** both
628
+ **Uses registers:** D018
629
+ **Uses kernal:** (none)
630
+ **Requires:** infinite_scroll_h
631
+ **Cost:** cycles_per_frame=378, bytes_code=26
632
+ **Cost basis:** derived-listing
633
+ **Cost measured on:** oscar64-charset-parallax (roll frame, every second frame, in the vertical blank)
634
+
635
+ ### Why
636
+
637
+ `parallax_dual_layer` gets its second layer from sprites, and a raster
638
+ band split gets it from a second scroll value per band, so its layers
639
+ cannot overlap. Character parallax needs neither. The background is a
640
+ repeating pattern drawn with a few reserved glyphs, and the glyph bytes
641
+ are shifted so the pattern moves at a different speed from the
642
+ foreground tiles that share the screen. It costs a few hundred cycles a
643
+ frame, no sprites and no raster interrupt.
644
+
645
+ ### How
646
+
647
+ 1. **Reserve the background glyphs.** Pick a tile of glyphs, 2x2 is
648
+ usual (A B over C D, a 16x16 pattern), in a RAM charset. Fill every
649
+ background cell with A, B, C or D by world column and row parity:
650
+ `code = base + (world_col & 1) + 2 * (row & 1)`. Foreground tiles use
651
+ other codes, never these four.
652
+ 2. **Scroll the whole screen as the foreground.** `$D016` XSCROLL each
653
+ frame and a one-column shift of screen RAM on the carry
654
+ (`infinite_scroll_h`). Background cells ride along with it, so
655
+ without step 3 the pattern moves at the foreground's speed.
656
+ 3. **Shift the glyph bytes against the scroll.** Each pixel row of the
657
+ tile is a 16-bit word, left glyph's byte high. One pixel right is
658
+ `LDA right,X / LSR / ROR left,X / ROR right,X`: LSR drops the right
659
+ byte's last pixel into carry, the first ROR takes it in at the left
660
+ edge and drops the left byte's last pixel into carry, the second ROR
661
+ takes that in. That is 20 cycles a pixel row, 320 for the 16 rows of
662
+ a 2x2 tile. One pixel left is the mirror: `LDA left,X / ASL / ROL
663
+ right,X / ROL left,X`.
664
+ 4. **Pick the rate.** The pattern's speed on screen is the foreground's
665
+ speed minus the glyph shift speed. A foreground at 1 px a frame
666
+ leftward with a one-pixel right roll every second frame puts the
667
+ background at 1/2 px a frame leftward. A roll every frame holds it
668
+ still; a roll in the same direction as the scroll makes it faster
669
+ than the foreground.
670
+ 5. **Do it in the blank.** The glyph bytes are read on every raster
671
+ line of every background row, so the roll must finish before the
672
+ first background row is fetched, or that frame shows old rows above
673
+ the write and new rows below it.
674
+
675
+ Vertical is byte rotation, not bit rotation: to move the tile down one
676
+ pixel, each 16-byte column (A over C, B over D) moves every byte to the
677
+ next row's address, and the last row's byte wraps to the first. Unrolled, that
678
+ is a load and a store per byte, about 128 cycles a column (arithmetic
679
+ from 4-cycle absolute loads and stores, not built here).
680
+
681
+ ### Why it works
682
+
683
+ The VIC-II has no copy of a glyph. It reads eight bytes from the
684
+ charset for each cell's code on every line of the row, so changing the
685
+ tile's 32 bytes changes every background cell on the screen at once,
686
+ however many there are. The screen RAM is untouched; the scroll code
687
+ still moves the cells, and the cell grid and the glyph content add. In
688
+ the recipe the foreground moved 203 px in 203 frames and the pattern
689
+ 101 px against it, so the pattern moved 102 px, which the exit
690
+ screenshot confirms to the pixel.
691
+
692
+ ### Variations
693
+
694
+ - **Pre-shifted copies instead of a roll.** Keep every phase of the
695
+ tile in a table (16 phases of 32 bytes for a 2x2 hires tile, 512
696
+ bytes) and copy this frame's phase in. The copy does not accumulate
697
+ error and can jump phases, but in the recipe it cost 560 cycles,
698
+ including the pointer set-up for the phase, against 378 for the roll.
699
+ The roll is cheaper because it touches each byte in place.
700
+ - **One charset per phase.** Store each phase in its own charset and
701
+ flip `$D018` (Hawkeye used four charsets switched continuously, per
702
+ C64-Wiki; not measured here). One store a frame, 2 KB of RAM per
703
+ phase, and every other glyph copied into every charset.
704
+ - **Multicolour.** A multicolour pixel is two bits, so one pixel step is
705
+ two rolls, twice the cycles (codebase64; not built here).
706
+ - **Shared cells.** Where a foreground tile and the background share a
707
+ cell, that cell needs its own glyph, rebuilt after each roll as
708
+ `(background AND NOT mask) OR foreground` per byte, the merge
709
+ `char_bullets` does for bullets. The recipe forbids shared cells
710
+ instead: foreground tiles are whole cells, so a tile's edge is a cell
711
+ edge.
712
+ - **Animated glyphs.** Rewriting a glyph's bytes in place is
713
+ `charset_animation`; this technique is that method with the new bytes
714
+ computed from the old ones by a shift.
715
+
716
+ ### Cycle budget
717
+
718
+ The roll of a 2x2 hires tile is 378 cycles measured with CIA1 timer B,
719
+ which includes about five cycles of the timer's stop; the instruction
720
+ count is 373 with `JSR` and `RTS`. Measured in VICE x64sc 3.10 on PAL and NTSC alike,
721
+ because it runs in the vertical blank where no cycles are stolen. It
722
+ runs every second frame at half speed, so the typical frame is 378 or 0.
723
+ The worst frame is a roll frame; the `**Cost:**` line states it, and
724
+ the 26-byte routine from the Oscar64 map. A larger tile scales the roll
725
+ linearly: 20 cycles per pixel row per glyph pair. The recipe's screen
726
+ shift on the carry frame, 12,321 cycles on PAL and 12,537 on NTSC, is
727
+ `infinite_scroll_h`'s cost, not this technique's.
728
+
729
+ ### Recipes
730
+
731
+ - `recipes/oscar64/charset-parallax.md` — a 2x2 background tile rolled
732
+ one pixel every second frame under a foreground scrolled at 1 px a
733
+ frame; the program checks the glyph bytes against a model and every
734
+ cell against the map after 203 frames, and prints both methods'
735
+ cycles; PAL and NTSC
736
+
737
+ ### Sources
738
+
739
+ - https://codebase.c64.org/doku.php?id=base:simple_parallax_shifting
740
+ (2x2 tile, ASL/ROL and LSR/ROR across the glyph pair, byte moves for
741
+ vertical, a call every second frame for a slower layer, twice per
742
+ frame in multicolour). Read for facts; no code is taken from it.
743
+ - https://www.c64-wiki.com/wiki/Parallax_Scrolling (bits "rolled
744
+ (horizontally) or copied (all directions) within one or several
745
+ chars"; X-Out, Snare and Parallax named; Hawkeye's four charsets;
746
+ raster-band layers "cannot overlap"). Not measured here.
747
+
748
+ ---
749
+
750
+ ## bitmap_scroll — Bitmap-mode scroll via $D016 + bitmap shuffling
751
+
752
+ **Complexity:** high
753
+ **Region:** both
754
+ **Uses registers:** D011, D016, D018
755
+ **Uses kernal:** (none)
756
+
757
+ ### Why
758
+
759
+ Bitmap mode gives pixel-level control over every dot on the screen —
760
+ 320×200 in standard single-color mode, 160×200 in multicolor mode. Some
761
+ demos and games want to scroll a pixel-accurate drawn scene rather than
762
+ a character-based one. This is fundamentally harder than character-mode
763
+ scrolling because there is no sparse screen-RAM grid to rotate; the entire
764
+ 8000-byte bitmap must be shifted, or an expensive double-buffer strategy
765
+ must be used.
766
+
767
+ ### How
768
+
769
+ There are two viable approaches. Both use $D016 XSCROLL for fine-scroll
770
+ and $D011 YSCROLL for fine vertical scroll, exactly as in character mode.
771
+ The difference is how the coarser carry is handled.
772
+
773
+ **Approach 1 — memshift per frame.**
774
+
775
+ When the hardware XSCROLL register wraps (after 8 sub-pixel steps), shift
776
+ the entire 8000-byte bitmap left by one cell. The standard bitmap is 8000
777
+ bytes laid out as 25 bands (one per character row) × 40 cells × 8 bytes;
778
+ the 8 bytes of a cell are its 8 scanlines, so cell (col,row) starts at
779
+ (row*40+col)*8 and the cell to its right is 8 bytes further on (layout
780
+ from `docs/techniques/bitmap-modes.md`; an earlier version of this
781
+ paragraph gave "40 columns × 200 rows × 8 bytes", which is 64,000 bytes,
782
+ and "one byte per 8-row band"). Shifting the picture left by one cell
783
+ therefore moves every 8-byte cell to the cell before it — a memmove of the
784
+ whole 8000-byte bitmap down by 8 bytes — after which the last cell of each
785
+ of the 25 bands (40 × 8 = 320 bytes per band) is redrawn from source; the
786
+ 1000-byte screen RAM (and Color RAM in multicolor mode) shifts by one byte
787
+ in step.
788
+
789
+ For vertical scrolling by one character row, the coarse carry is one
790
+ band: move the bitmap up by 320 bytes (40 cells × 8 bytes = one row of
791
+ characters, 8 raster lines) — a 7,680-byte memmove — and refill the
792
+ bottom 320 bytes. An earlier version said "40 bytes", which shifts by
793
+ five cells horizontally within the same band, not by one row. The
794
+ screen-RAM attribute matrix (1000 bytes) moves by 40 bytes at the same
795
+ time, and in multicolor bitmap mode colour RAM at $D800 moves by 40 bytes
796
+ as well. Note that a vertical shift of fewer than 8 lines is not a
797
+ memmove at all — each scanline byte moves within its own cell, and line 0
798
+ of a cell takes line 7 of the cell in the band above — which is why
799
+ bitmap scrollers carry vertically by whole bands and use YSCROLL for the
800
+ intermediate lines.
801
+
802
+ Cycle cost: an 8000-byte shift at ~10 cycles per byte = ~80,000 cycles.
803
+ PAL provides ~18,500 CPU cycles per frame (after badlines). An 8000-byte
804
+ shift is approximately 4.3 frames of CPU time at full speed. This approach
805
+ is not viable at 50 Hz for a full-screen scrolling bitmap without
806
+ significant compromises.
807
+
808
+ **Approach 2 — double-buffer with $D018 page flip.**
809
+
810
+ Maintain two 8000-byte bitmap buffers at offsets $0000 and $2000 of the
811
+ VIC bank. Use bank 1 ($4000-$7FFF) or bank 3 ($C000-$FFFF): in banks 0
812
+ and 2 the VIC sees character ROM at bank offset $1000-$1FFF (CPU $1000 /
813
+ $9000), which lands inside the low bitmap page
814
+ (`docs/hardware/vic-ii-reference.md`, character ROM shadowing). In bank 3
815
+ the low page spans $D000-$DFFF, which the CPU sees as I/O, so the CPU
816
+ must bank I/O out via $01 (e.g. $34) while writing that page. An earlier
817
+ version of this section named $8000, which is one of the banks that does
818
+ not work. Each frame, render
819
+ (draw and scroll) into the invisible back buffer, then toggle $D018 to
820
+ flip the two buffers at the start of vertical blank. This prevents tearing
821
+ and decouples the rendering time from the frame deadline — as long as
822
+ rendering completes before the vertical blank of the target frame.
823
+
824
+ $D018 bits 7-4 select the 1 KB video matrix within the VIC bank (screen
825
+ RAM in text mode; in bitmap mode it holds the per-cell
826
+ foreground/background colour nibbles). Bits 3-1 select the 2 KB character
827
+ generator in text mode; in bitmap mode only bit 3 matters: 0 puts the
828
+ 8000-byte bitmap at bank offset $0000, 1 at $2000, and bits 2-1 are
829
+ ignored. Toggling bit 3 of $D018 therefore switches between the two
830
+ bitmap pages. (An earlier version of this paragraph described the video
831
+ matrix as "the character generator pointer in text mode" and never said
832
+ what it holds in bitmap mode.)
833
+
834
+ Even with double buffering, filling 8000 bytes per frame is expensive.
835
+ Practical scrolling bitmap scenes either reduce the scrolling area to a
836
+ sub-screen viewport, use hardware XSCROLL + YSCROLL to cover most frames
837
+ with no pixel work, and only do the 8000-byte shift once every 8 frames
838
+ when the coarse carry fires.
839
+
840
+ ### Why it works
841
+
842
+ The VIC-II treats the bitmap as a contiguous region of video memory. XSCROLL
843
+ and YSCROLL shift the rendering pipeline's start position within each
844
+ 8×8 cell exactly as in character mode. The fine-scroll mechanism is
845
+ identical. The cost difference is in the carry: where character mode
846
+ carries by rotating 1000 bytes of screen RAM, bitmap mode must carry by
847
+ rotating 8000 bytes of raw pixel data. The $D018 page-flip trick avoids
848
+ moving pixel data altogether by pointing the VIC at a different memory
849
+ region, leaving the shifting work to the back-buffer renderer which runs
850
+ across multiple cycles asynchronously.
851
+
852
+ ### Variations
853
+
854
+ - **Reduced viewport:** Apply bitmap scrolling only to a horizontal strip
855
+ (e.g., the bottom 100 rows). Use a raster IRQ to switch $D018 or the
856
+ display mode at the viewport edge. This halves the bitmap size and
857
+ memory requirement.
858
+ - **Sprite overlay on bitmap:** Hardware sprites work the same way in
859
+ bitmap mode. Combine with `parallax_dual_layer` — sprite foreground
860
+ objects at a faster rate over a slow-scrolling bitmap background.
861
+ - **Vertical bitmap scroll only:** Shifting 8000 bytes vertically is the
862
+ same cost as horizontally, but the double-buffer flip approach works
863
+ equally well. Vertical-only bitmap scroll is somewhat more common in
864
+ intros because vertical motion on a bitmap preserves horizontal
865
+ compositional alignment better than horizontal motion.
866
+
867
+ ### Cycle budget
868
+
869
+ Fine-scroll step (XSCROLL or YSCROLL write only, no carry): ~10 cycles.
870
+
871
+ Coarse carry, memshift approach (fires once per 8 frames at 1 px/frame):
872
+ 8000 bytes × 10 cycles/byte = 80,000 cycles. Spread across 8 frames:
873
+ 10,000 cycles/frame average, or ~54% of the PAL per-frame budget.
874
+ The colour carry must move in lockstep with the pixel carry: the
875
+ 1000-byte video matrix (each byte holds the cell's foreground and
876
+ background nibbles), plus the 1000 nibbles at $D800 in multicolour bitmap
877
+ mode — a further ~10,000-20,000 cycles per coarse step, about an eighth
878
+ to a quarter of the bitmap shift. (An earlier version spoke of a
879
+ "4000-byte" colour array; there are 40 × 25 = 1000 cells and no
880
+ 4000-entry structure anywhere in the VIC.) Combined, the overhead is
881
+ practical only for slow-scrolling backgrounds or sub-screen viewports.
882
+
883
+ Double-buffer approach: the page flip itself costs ~10 cycles ($D018
884
+ write). The rendering work (clearing and redrawing the 8000-byte back
885
+ buffer) is the real budget item and is scene-specific.
886
+
887
+ ### Recipes
888
+
889
+ - No recipe yet for bitmap scrolling.
890
+
891
+ ---
892
+
893
+ ## tile_map_render — Metatile map decode to screen and colour RAM
894
+
895
+ **Complexity:** medium
896
+ **Region:** both
897
+ **Uses registers:** (none)
898
+ **Uses kernal:** (none)
899
+ **Cost:** cycles_per_frame=268
900
+ **Cost basis:** arithmetic
901
+ **Cost measured on:** oscar64-tile-map-render (one column edge, 11 metatiles)
902
+
903
+ ### Why
904
+
905
+ A scrolling game level is far larger than the 1000 cells of one text
906
+ screen, and a level stored as raw screen codes plus colour costs two bytes
907
+ a cell. Storing the level as a grid of metatiles (here 2x2 characters plus
908
+ one colour) divides that by eight, and run-length coding the metatile
909
+ rows takes it down further: the recipe's 20 x 11 map is 135 stream bytes
910
+ for 220 metatiles, which expand to 880 screen bytes and 880 colour
911
+ nibbles. The decoder is also the thing that feeds `char_scroll_buffer_h`
912
+ and `char_scroll_buffer_v`: both say "write fresh data into column 39"
913
+ or "into the new row" from an off-screen source, and this technique is
914
+ that source.
915
+
916
+ ### How
917
+
918
+ Three tables and two decoders.
919
+
920
+ 1. **Metatile table.** One entry per metatile: four screen codes
921
+ (top-left, top-right, bottom-left, bottom-right) and one colour. A
922
+ per-character colour variant stores four colour bytes instead of one;
923
+ the write count is the same, the table is three bytes larger per
924
+ metatile.
925
+ 2. **Map.** One byte per metatile, MAP_W wide by MAP_H high, decoded once
926
+ into RAM at level start. It is indexed as `map[my * MAP_W + mx]`. For
927
+ a level wider than the screen MAP_W is the level width, not 20.
928
+ 3. **RLE row streams.** Each map row is its own stream of control bytes.
929
+ In the recipe's format bit 7 set means a run (the next byte repeated
930
+ `c & 0x7f` times), bit 7 clear means `c` literal bytes follow, and zero
931
+ ends the row. The decoder returns the address after the terminator, so
932
+ the rows are walked in sequence with no offset table.
933
+ 4. **Row decode.** Draw the top or bottom character row of one map row
934
+ into one screen row: for each metatile write two screen codes and two
935
+ colours. This is the new-row source for `char_scroll_buffer_v` and
936
+ `soft_scroll_v`: a vertical scroll steps one character row at a time,
937
+ so it asks for half a metatile row per step and alternates `half`
938
+ between 0 and 1.
939
+ 5. **Column decode.** Draw the left or right character column of one
940
+ map column into screen column 0 or 39, all rows: for each metatile row
941
+ write one screen code and one colour at `s[0]` and again at `s[40]`,
942
+ then step 80 bytes. This is the new-column source for
943
+ `char_scroll_buffer_h` and `soft_scroll_h`, and again `half` alternates
944
+ because a metatile is two columns wide.
945
+
946
+ The two edge decoders, as built in scratch with Oscar64 build 2026-05-19
947
+ at `-O2` to confirm they compile (they are not the recipe's listing and
948
+ their cost is not measured here):
949
+
950
+ ```c
951
+ struct Metatile { char c[4]; char col; };
952
+ extern const struct Metatile tiles[];
953
+ extern char map[MAP_W * MAP_H]; // one byte per metatile, decoded once
954
+
955
+ // Left (half 0) or right (half 1) character column of map column mx,
956
+ // into screen column sx (0 or 39), every metatile row.
957
+ void decode_column(char mx, char half, char sx)
958
+ {
959
+ char *s = Screen + MAP_ROW * 40 + sx;
960
+ char *k = Color + MAP_ROW * 40 + sx;
961
+ const char *m = map + mx;
962
+ for (char y = 0; y < MAP_H; y++) {
963
+ const struct Metatile *t = tiles + *m;
964
+ s[0] = t->c[half]; s[40] = t->c[half + 2];
965
+ k[0] = t->col; k[40] = t->col;
966
+ s += 80; k += 80; m += MAP_W;
967
+ }
968
+ }
969
+
970
+ // Top (half 0) or bottom (half 1) character row of map row my, starting
971
+ // at map column mx, into screen row sy.
972
+ void decode_row(char mx, char my, char half, char sy)
973
+ {
974
+ char *s = Screen + sy * 40;
975
+ char *k = Color + sy * 40;
976
+ const char *m = map + my * MAP_W + mx;
977
+ for (char x = 0; x < 20; x++) {
978
+ const struct Metatile *t = tiles + m[x];
979
+ s[0] = t->c[2 * half]; s[1] = t->c[2 * half + 1];
980
+ k[0] = t->col; k[1] = t->col;
981
+ s += 2; k += 2;
982
+ }
983
+ }
984
+ ```
985
+
986
+ ### Why it works
987
+
988
+ Screen RAM holds screen codes and colour RAM at `$D800` holds one nibble
989
+ per cell, so a metatile is nothing more than a fixed pattern of writes to
990
+ both. The VIC-II reads the two arrays every badline; nothing in the chip
991
+ knows about metatiles, which is why the decoder can write at any time
992
+ the cell is off-screen or about to be overwritten anyway. Keeping the map
993
+ as one byte per metatile in RAM, rather than decoding the RLE on demand,
994
+ is what makes the column decode cheap: a column of a run-length coded row
995
+ cannot be reached without decoding the row up to it, but an unpacked map
996
+ is a stride-MAP_W walk.
997
+
998
+ ### Variations
999
+
1000
+ - **Per-character colour.** `char col[4]` in the metatile instead of one
1001
+ byte. Same write count; use it when a metatile mixes, say, a green tree
1002
+ top over a brown trunk.
1003
+ - **Larger metatiles.** 4x4 characters with a 16-byte pattern divides the
1004
+ map size by a further four. The edge decoders then alternate `half`
1005
+ over four values.
1006
+ - **CharPad import.** CharPad's `.ctm` (version 8) already holds
1007
+ characters, tiles, attributes and a map, and Oscar64's `#embed` extracts
1008
+ each channel directly: `ctm_chars`, `ctm_tiles8` / `ctm_tiles16`,
1009
+ `ctm_map8` / `ctm_map16`, `ctm_attr1` / `ctm_attr2`
1010
+ (`docs/toolchains/oscar64-reference.md`, "Embedding" section, the
1011
+ `#embed ctm_chars` paragraph; the Oscar64 manual `oscar64.md`,
1012
+ "Embedding sprite and graphics data", is the source). The directive
1013
+ must stand alone on its own line inside the initialiser braces. The
1014
+ `.ctm` layout is summarised in `docs/art/asset-pipelines.md` under
1015
+ "Charsets (.ctm from CharPad)". A CharPad tile maps onto the
1016
+ `Metatile` struct here as its character indices plus its attribute
1017
+ byte; the RLE streams are then whatever the build produces, or
1018
+ `#embed ... rle` for a plain run-length pass. No page in this KB ships
1019
+ a `.ctm`, and the recipe below needs none.
1020
+ - **RLE variants.** The recipe's format caps a run at 127 and has no
1021
+ escape for a single repeated pair. A two-byte `(count, value)` format
1022
+ with no literal mode is smaller code and worse on noisy rows.
1023
+
1024
+ ### Cycle budget
1025
+
1026
+ Measured in VICE x64sc 3.10 with a CIA1 timer B harness, display blanked
1027
+ and interrupts masked, Oscar64 `-O2`, the recipe's listing (rung 1; the
1028
+ figures are identical on PAL and NTSC because they count CPU cycles
1029
+ only):
1030
+
1031
+ | Step | Cycles | Per unit |
1032
+ |---|---|---|
1033
+ | RLE decode, 11 rows, 135 stream bytes to 220 map bytes | 8,828 | 40.1 per decoded byte |
1034
+ | Expand 220 metatiles (880 screen + 880 colour writes) | 10,731 | 48.8 per metatile |
1035
+
1036
+ The edge decoders above were not timed. One column decode touches 11
1037
+ metatiles and writes four bytes for each, half of what `expand_row`
1038
+ writes per metatile, so a figure in the low hundreds of cycles is
1039
+ arithmetic from the expand figure (rung 3), not a measurement: half of
1040
+ 48.8 is 24.4 per metatile, and eleven metatiles come to about 268
1041
+ cycles, which is the figure on the Cost line above. A full-map expand at 10,731
1042
+ cycles is about 55% of the ~19,656-cycle PAL frame, so it belongs at level
1043
+ start, not inside the scroll loop; the scroll loop does one edge decode
1044
+ per character step.
1045
+
1046
+ ### Recipes
1047
+
1048
+ - `recipes/oscar64/tile-map-render.md`
1049
+ - `recipes/oscar64/level-rle-decoder.md` (the same RLE format on three 40 x 22 rooms: ratio, decode cycles and decoder size measured, in C and by hand)
1050
+ - `recipes/oscar64/tile-grid-collision.md` (tests a sprite against the decoded map array this technique fills; `tile_grid_collision` in `logic.md`)
1051
+
1052
+ ---
1053
+
1054
+ ## dycp_scroller — DYCP: each text column at its own pixel Y, drawn through the charset
1055
+
1056
+ **Complexity:** medium
1057
+ **Region:** both
1058
+ **Uses registers:** D012, D016, D018
1059
+ **Uses kernal:** (none)
1060
+ **Requires:** frame_sync_loop
1061
+ **Cost:** cycles_per_frame=5343, bytes_code=4117, bytes_data=1090, zp_bytes=2, irq_slots=1
1062
+ **Cost basis:** derived-listing
1063
+ **Cost measured on:** kickassembler-dycp-scroller (worst frame, 39 columns, in the vertical blank)
1064
+
1065
+ ### Why
1066
+
1067
+ A sine scroller that moves whole characters between rows
1068
+ (`recipes/kickassembler/sine-scroller.md`) steps eight pixels at a time
1069
+ vertically. DYCP, Different Y Character Position, puts every column at
1070
+ its own pixel height. The trick is to stop moving characters around the
1071
+ screen at all. Each column of the band is a fixed vertical strip of
1072
+ character cells that never changes; what moves is the glyph inside the
1073
+ strip, copied every frame into a custom charset at the pixel row the wave
1074
+ gives. The VIC-II reads the charset afresh on every raster line, so a
1075
+ glyph written at byte offset `y` of a strip appears `y` pixels down it.
1076
+
1077
+ ### How
1078
+
1079
+ 1. **Lay the strips once.** Give the band N screen rows. Column `c`,
1080
+ cell `k` holds charset slot `base + c*N + k`. Because a slot is eight
1081
+ consecutive bytes, the N cells of one column are `8N` consecutive
1082
+ charset bytes, and pixel row `y` of the strip is the single byte
1083
+ `strip + y`. Set `$D018` to point at the charset and never write the
1084
+ band's screen RAM again. Everything outside the band shows a blank
1085
+ slot from the same charset, and any text elsewhere on the screen has
1086
+ to be drawn from slots the strips do not use.
1087
+ 2. **Each frame, per column:** blank the eight bytes the glyph occupied
1088
+ last frame; look the column's new `Y` up (`sine[phase + c*STEP]`);
1089
+ copy the glyph's eight rows to `strip + Y .. strip + Y + 7`. Seen as
1090
+ cells, rows `0 .. 7 - (Y & 7)` of the glyph land in cell `Y >> 3` at
1091
+ row offset `Y & 7` and the rest in the cell below; the address form
1092
+ does that split for nothing. `Y` runs from 0 to `8N - 8`, so a strip
1093
+ N cells tall gives `8N - 8` pixels of travel: six cells, 40 pixels.
1094
+ 3. **Keep the glyph source indexable.** A copy of the glyphs the message
1095
+ uses, 32 of them, in 256 bytes: `glyph * 8` then fits a byte and one
1096
+ `lda font+r,y` reaches any row of any glyph. Screen codes for space
1097
+ and punctuation lie above 31, so the message is remapped onto the 32
1098
+ slots when it is assembled or when it is fed into the ring buffer.
1099
+ 4. **Scroll horizontally as usual.** `$D016` XSCROLL a pixel a frame and
1100
+ a ring-buffer shift every eighth frame (`soft_scroll_h` and
1101
+ `char_scroll_buffer_h` above); the shift changes which glyph a column
1102
+ copies, nothing else.
1103
+
1104
+ ### Why it works
1105
+
1106
+ The character generator reads eight bytes per glyph at charset base plus
1107
+ code times eight, one byte per raster line of the row. Consecutive slots
1108
+ are consecutive in memory, so a column of consecutive slots is one
1109
+ unbroken run of bytes and a glyph can be placed at any byte offset in
1110
+ it. Where the old and new positions overlap, the write after the clear
1111
+ wins, so clearing the previous eight rows and then writing the new eight
1112
+ is correct for any move up to eight pixels a frame. The screen RAM never
1113
+ changes, so nothing is redrawn on the character grid and no part of the
1114
+ effect costs screen or colour writes. `$D016` still applies: the fine
1115
+ scroll shifts every row including any caption outside the band.
1116
+
1117
+ ### Variations
1118
+
1119
+ - **Two charsets, `$D018` flip.** Copy into the charset the VIC is not
1120
+ showing and swap at the frame sync. Buys the freedom to run the copy
1121
+ inside the display at 2 KB per charset and one register write; not
1122
+ needed when the copy is budgeted to finish above the band, which the
1123
+ recipe measures.
1124
+ - **Cheaper clear.** When the per-frame move is at most one pixel, two
1125
+ zero stores (the row above and the row below the new glyph) replace
1126
+ the eight-store clear, six stores a column fewer. Ties the copy to the
1127
+ wave's speed.
1128
+ - **1x2 letters.** Sixteen-row glyphs in strips two cells wider apart:
1129
+ twice the copy per column, half the columns for the same slot budget.
1130
+ - **Wave tables.** Separate speed and amplitude tables indexed by frame
1131
+ give a wave that breathes; a second sine added to the first gives a
1132
+ compound wave. Amplitude is bounded by `8N - 8` less the glyph height.
1133
+
1134
+ ### Cycle budget
1135
+
1136
+ Per column per frame: 16 stores and 8 loads, plus the table lookups.
1137
+ Unrolled with absolute-indexed addressing, the recipe's copy is 136
1138
+ cycles a column: 39 columns in 5,305 to 5,343 cycles over 304 PAL frames,
1139
+ measured with CIA2 timer A in VICE x64sc 3.10 (the spread is the page
1140
+ crossing of the sine lookup in some columns). The `**Cost:**` line above
1141
+ states that worst frame, the code segment (`$0900-$1914`, of which the
1142
+ unrolled copy is most) and the table segment (`$2000-$2441`) from
1143
+ KickAssembler's memory map; the 2 KB charset the copy writes is cleared
1144
+ at run time and is in neither segment.
1145
+
1146
+ Where in the frame the copy runs is the whole budget. A column's strip
1147
+ bytes are read by the VIC on every raster line of the band, so the copy
1148
+ must finish before the band's first line, or run into a charset the VIC
1149
+ is not showing. From an interrupt at line 250 the recipe's copy ends by
1150
+ line 35 on PAL and by line 84 on NTSC, against a band starting at line
1151
+ 122. The NTSC figure is 49 lines later for the same CPU work because the
1152
+ 263-line frame leaves 13 lines of blank after 250, so the copy runs over
1153
+ the top of the display and crosses three to five badlines, each 40 to 43
1154
+ cycles: the timer reads 5,433 to 5,556 on NTSC against 5,305 to 5,343 on
1155
+ PAL. Each extra column costs 136 cycles and about two lines; each extra
1156
+ strip cell costs eight slots and nothing per frame. The slot budget
1157
+ binds first: with 22 slots kept for a blank and a caption, 39 columns
1158
+ of six cells is 234, and a seventh cell would allow only 33 columns.
1159
+
1160
+ ### Recipes
1161
+
1162
+ - `recipes/kickassembler/dycp-scroller.md`
1163
+
1164
+ ---
1165
+
1166
+ ## eight_way_scroll_double_buffer — Eight-way tile scroll over two screen matrices
1167
+
1168
+ **Complexity:** high
1169
+ **Region:** both
1170
+ **Uses registers:** D011, D012, D016, D018
1171
+ **Uses kernal:** (none)
1172
+ **Requires:** screen_double_buffer_d018, soft_scroll_h, soft_scroll_v
1173
+ **Cost:** cycles_per_frame=13152
1174
+ **Cost basis:** measured-vice
1175
+
1176
+ ### Why
1177
+
1178
+ `soft_scroll_h` and `soft_scroll_v` together move the display by up to
1179
+ seven pixels in each axis. Past that the character grid has to move, and
1180
+ in eight directions that means the whole matrix, not an edge: a diagonal
1181
+ step changes every cell's contents, so the column shift that serves
1182
+ `char_scroll_buffer_h` has nothing to shift into.
1183
+
1184
+ A full 1,000-byte matrix rewrite does not fit the vertical blank on
1185
+ either model, and written into the live matrix it tears: the beam crosses
1186
+ the rewrite and shows the top of the old world above the bottom of the
1187
+ new. Two matrices in the same VIC bank solve it. The redraw goes into the
1188
+ one that is not on display, over as many fields as it needs, and the
1189
+ `$D018` VM nibble swaps them in the blank when it is finished.
1190
+
1191
+ ### How
1192
+
1193
+ 1. **Camera in world pixels.** Tile origin is `camx >> 3` and `camy >> 3`.
1194
+ XSCROLL moves the display right, so a rightward camera needs
1195
+ `7 - (camx & 7)` in `$D016` bits 0 to 2 and `7 - (camy & 7)` in `$D011`
1196
+ bits 0 to 2. Write both registers whole from a shadow rather than
1197
+ read-modify-write, or the store takes CSEL and MCM with it
1198
+ (`d016_unmasked_rmw_clobbers_csel_mcm`).
1199
+ 2. **Both windows narrow.** RSEL 0 and CSEL 0 hide the partial row and
1200
+ column at each edge, so a 40 x 25 matrix covers the display at every
1201
+ fine scroll value.
1202
+ 3. **Draw for the origin after next.** The spare matrix cannot be drawn
1203
+ for the origin the camera has now, because the camera moves while it is
1204
+ being drawn. Work out which axis crosses a tile boundary first, from
1205
+ `8 - (cam & 7)` when it is increasing and `(cam & 7) + 1` when it is
1206
+ decreasing, and draw for the origin that crossing will produce. On a
1207
+ diagonal the axes cross on different fields and one spare matrix cannot
1208
+ serve two origins, so only the nearer crossing is targeted.
1209
+ 4. **Flip on a match, never on a hope.** The flip is taken only if the
1210
+ spare matrix already holds exactly the origin wanted. Count the
1211
+ refusals; a non-zero count is the scroll stuttering.
1212
+ 5. **Colour RAM in four calls.** Colour RAM is not paged. Both halves are
1213
+ written in `irqColB` at raster 4, at most `BAND_MAX` rows per call.
1214
+ After a flip, the first top-half call uses `BAND_FIRST` rows (fewer
1215
+ than `BAND_MAX`) so it finishes before the first top rows' badlines even
1216
+ when `irqColB` starts late on NTSC. Four calls cover all 25 rows over
1217
+ four fields, producing three displayed fields of stale colour per
1218
+ crossing before all rows are updated.
1219
+ 6. **Cap every band of the redraw.** Spread the matrix over the fields
1220
+ available, and put a ceiling on every field, not only the ones doing
1221
+ colour work.
1222
+
1223
+ ### Why it works
1224
+
1225
+ The VIC reads the matrix at the address the VM nibble names, on the
1226
+ badline of each character row. Changing that nibble between fields
1227
+ changes which 1,000 bytes the next field reads, and nothing else: the
1228
+ fine scroll, the charset base and the bank are untouched. So a matrix
1229
+ drawn over several fields is invisible until the field it first appears
1230
+ in, and appears whole.
1231
+
1232
+ Sub-tile and whole-tile motion are the same motion split at the eight
1233
+ pixel boundary. `camx` is the only state; the fine scroll is its low three
1234
+ bits inverted and the origin its high bits, so they can never disagree.
1235
+
1236
+ Colour RAM is the part that cannot be double buffered, and it is why the
1237
+ technique is a raster problem rather than a blank-interval one. All 25
1238
+ rows are written in four calls of `irqColB`, which fires at raster 4 each
1239
+ field. The first call is limited to `BAND_FIRST` rows so it finishes
1240
+ before row 6's badline, which is what the verdict checks; rows 0 to 4 can
1241
+ still show old colour for one field on NTSC when `irqColB` starts late.
1242
+ The matrix appears whole on the flip field; the colour
1243
+ takes three more displayed fields to catch up, producing a transient
1244
+ mismatch of about 60 ms PAL and 50 ms NTSC per crossing.
1245
+
1246
+ ### Variations
1247
+
1248
+ - **AGSP.** Rewriting `$D011` and `$D016` per raster line, and the VM
1249
+ nibble mid-frame, gives a hardware-scrolled playfield without any matrix
1250
+ redraw at all, at the price of a per-line interrupt and a much harder
1251
+ stability problem. It is the standard answer when the whole screen
1252
+ scrolls and nothing else needs the CPU.
1253
+ - **A panel that does not scroll.** A world of 25 rows plus a status area
1254
+ wants the split raster to become a mode change rather than a colour
1255
+ deadline; see `scroll_panel_split`.
1256
+ - **Colour in one page.** If the world is one colour per screen, or the
1257
+ colour changes only on a flip that also changes the palette, the two
1258
+ halves collapse to nothing and the budget roughly halves.
1259
+ - **Smaller worlds.** A world of 40 x 25 tiles or less needs no redraw at
1260
+ all, only the fine scroll and a wrap.
1261
+
1262
+ ### Cycle budget
1263
+
1264
+ Measured in VICE x64sc 3.10 over runs of 20,946,000 cycles on each model,
1265
+ with CIA 1 timer B read on entry to and exit from each of the three
1266
+ handlers and the per-field totals compared, so badline stalls are
1267
+ included:
1268
+
1269
+ | Model | Field | Worst field measured | Headroom |
1270
+ |---|---|---|---|
1271
+ | PAL | 19,656 | 13,111 | 6,545 |
1272
+ | NTSC | 17,095 | 13,152 | 3,943 |
1273
+
1274
+ The worst field is the one that writes seven colour rows in `irqColB` and
1275
+ seven matrix rows in `irqPrep`. A copy loop of `lda abs,x` / `sta abs,x`
1276
+ / `dex` / `bpl` is 14 cycles a byte in instruction terms and measures
1277
+ 17.6 to 17.8 with badlines (measured-vice, 25-row draw, 1,000 bytes, CIA
1278
+ timer), so a 40-byte row costs around 710 cycles.
1279
+
1280
+ `BAND_MAX = 7` keeps every call within the available window: the prep
1281
+ handler has 92 rasters from 152 + YSCROLL to 251, and the colour handler
1282
+ has from raster 4 to 152 + YSCROLL. The first colour call uses
1283
+ `BAND_FIRST = 5` rows instead of `BAND_MAX` to fit within NTSC's late
1284
+ start. At half speed (one pixel every two fields), tile crossings are
1285
+ sixteen fields apart on straight legs. The requirement is five fields: four
1286
+ matrix preps plus four colour calls finish in the same four-field span, and
1287
+ a flip is possible from the fifth field. The six-field minimum gap leaves
1288
+ one field of slack.
1289
+
1290
+ ### Recipes
1291
+
1292
+ - `recipes/kickassembler/eight-way-scroll.md`